backupy-agent/apps/agent/internal/logging/logging.go
TronoSfera 8b0c978337 feat(initial): Backupy agent + backupy-decrypt CLI
Source ports from the TronoSfera/backupy-cloud monorepo:
- apps/agent/        — Go agent (WSS client, persistent queue, Docker
                       discovery, 5 DB drivers: PG/MySQL/Mongo/Redis/SQLite,
                       pre/post hooks, Prometheus metrics)
- apps/backupy-decrypt/ — standalone CLI for client-side decryption
- packages/proto/    — protobuf wire format (generated .pb.go committed
                       so the repo builds without protoc)
- docs/              — agent spec + wire-protocol contract

Apache-2.0 license. Image published to ghcr.io/tronosfera/backupy-agent
on every v* tag via .github/workflows/release.yml (multi-arch amd64+arm64).
2026-05-17 20:22:35 +03:00

62 lines
1.9 KiB
Go

// Package logging configures the agent's structured logger built on
// the standard library's log/slog package.
//
// Output is always JSON on stdout (per docs/03-agent-spec.md — agent logs
// are eventually streamed to the server, so structured form is mandatory).
// The dev profile lowers verbosity by disabling source positions.
//
// BACKUP_AGENT_KEY is never logged — see config.Config which tags it
// `json:"-"` and the redactKey helper here for defence-in-depth.
package logging
import (
"io"
"log/slog"
"os"
"strings"
)
// New returns a configured *slog.Logger writing JSON to stdout.
func New(level string) *slog.Logger {
return NewWithWriter(os.Stdout, level)
}
// NewWithWriter is the same as New but writes to a caller-supplied io.Writer.
// Useful for tests.
func NewWithWriter(w io.Writer, level string) *slog.Logger {
opts := &slog.HandlerOptions{
Level: parseLevel(level),
ReplaceAttr: redactSecrets,
}
return slog.New(slog.NewJSONHandler(w, opts))
}
// parseLevel converts a textual log level to slog.Level. Unknown levels
// default to info so a typo never silently silences the logger.
func parseLevel(s string) slog.Level {
switch strings.ToLower(strings.TrimSpace(s)) {
case "trace", "debug":
return slog.LevelDebug
case "info", "":
return slog.LevelInfo
case "warn", "warning":
return slog.LevelWarn
case "error":
return slog.LevelError
default:
return slog.LevelInfo
}
}
// redactSecrets is a slog.HandlerOptions.ReplaceAttr hook that masks any
// attribute named like a secret. Defence-in-depth — keys should never be
// passed into logs in the first place, but if a caller slips up the value
// is replaced before serialisation.
func redactSecrets(_ []string, a slog.Attr) slog.Attr {
key := strings.ToLower(a.Key)
switch key {
case "agent_key", "backup_agent_key", "password", "secret", "token", "authorization":
return slog.String(a.Key, "***")
}
return a
}