-
Notifications
You must be signed in to change notification settings - Fork 2
Logging
The public github.com/reeflective/team/log package is the console logging system shared by
teamclients and teamservers. It is built entirely on the standard library's log/slog — no
third-party logging dependency. Applications embedding a team core can reuse and restyle the
exact same logging the core uses, or replace it wholesale with their own slog.Handler.
- How the core logs by default
- Choosing a format (console / text / json)
- Restyling the console (keep the core loggers)
- Injecting your own slog.Handler (replace the backend)
- Logging in your own code with the team style
- Named loggers and the package column
- Runtime controls: level and output
- Custom levels
- The audit log
- In-memory logging
When you don't configure logging, server.New / client.New build a log.Logger that fans
out every record to two destinations, each with its own independently adjustable level:
- Console — colored, aligned, human-readable. Info/Debug/Trace go to stdout, Warn/Error/Fatal/Panic go to stderr. Default console level: Warn.
-
File — plain text (no color), with source
[file:line]. Default file level: the server config'sLog.Level(Info by default) on the server; Info on the client. Default paths:~/.<app>/teamserver/logs/<app>.teamserver.logand~/.<app>/teamclient/logs/<app>.teamclient.log.
The console/stderr split means normal operation stays quiet on stdout while warnings and errors surface immediately, and the file keeps a fuller record.
The console/stdout stream can be rendered in three formats (the file logger always stays plain text):
log.Format |
What it is | Use for |
|---|---|---|
log.FormatConsole (default) |
aligned, colored, human-readable | interactive use |
log.FormatText |
slog TextHandler, uncolored key=value
|
grep/awk, log shippers |
log.FormatJSON |
slog JSONHandler, structured records |
Loki / ELK / CloudWatch |
From the CLI — the --log-format persistent flag on both trees:
myapp teamserver daemon --log-format json
myapp teamserver client users --log-format textFrom code — the option, or at runtime:
teamserver, _ := server.New("myapp", server.WithLogFormat(log.FormatJSON))
// or later:
teamserver.SetLogFormat(log.FormatText)Helpers: log.Formats() (stable-ordered list, drives completion), Format.Describe(),
Format.Valid(), Format.String().
Use WithConsoleOptions when you want the team look with small changes — different level
markers, colors, column widths, timestamp — while keeping the default console+file loggers
and their runtime level control. The callback receives *log.ConsoleOptions pre-filled with
the library defaults; tweak only what you want.
import "github.com/reeflective/team/log"
teamserver, _ := server.New("myapp",
server.WithConsoleOptions(func(o *log.ConsoleOptions) {
// Terse bracket markers instead of the default words.
o.Levels = map[slog.Level]log.LevelStyle{
slog.LevelInfo: {Label: "[*]", Color: "blue"},
slog.LevelWarn: {Label: "[!]", Color: "yellow"},
slog.LevelError: {Label: "[x]", Color: "red"},
} // missing levels fall back to DefaultLevelStyles()
o.ShowTimestamp = true
o.TimestampFormat = "15:04:05.000"
o.PackageWidth = 14 // widen the package column
o.MessageColor = "bright-white"
}),
)ConsoleOptions is fully documented in the package; the restyleable fields are Levels,
PackageWidth, LevelWidth, PackageColor, TimeColor, MessageColor, DisableColors,
ShowTimestamp, TimestampFormat, AddSource. Colors are
carapace/style names.
Copy log.DefaultLevelStyles() and edit the entries you care about.
Both server and client expose WithConsoleOptions.
Use WithLogger when you already have a fully set-up slog.Handler (multiple destinations,
your house format, a log shipper...). It becomes the core's sole logging backend, in
place of the console+file split.
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})
teamserver, _ := server.New("myapp", server.WithLogger(handler))
teamclient, _ := client.New("myapp", client.WithLogger(handler), client.WithDialer(d))Because the handler fully controls formatting and routing, the core's runtime level/output
knobs do not apply to it: SetLogLevel, SetLogWriter and SetLogFormat become no-ops
for a custom-handler logger. (On the server, the independent audit log is unaffected.)
Pick your tool:
| Goal | Use |
|---|---|
| Team look, minor tweaks | WithConsoleOptions |
| Team look, different stream format | WithLogFormat |
| Completely your own logging backend | WithLogger |
You don't have to embed a core to use the look. The package gives standalone constructors:
import "github.com/reeflective/team/log"
// A ready *slog.Logger with the team console style.
logger := log.NewConsole(log.ConsoleOptions{Level: someLevelVar, ShowTimestamp: true})
// Tag it so the aligned package column renders.
logger = log.Named(logger, "compiler", "build")
logger.Info("starting build", "target", "linux/amd64")Other constructors:
-
log.NewStdio(level)— console-only logger (stdout/stderr split), no file. -
log.New(logFile, stdioLevel, fileLevel, style)— the full console+file logger the core uses;logFileis anyio.Writer(nil ⇒ console only). -
log.NewFromHandler(handler)— wrap an existingslog.Handler. -
log.NewJSON(w, level)— JSON logger to a writer. -
log.NewFormatHandler(format, w, level, style)— a standalone handler for a chosen format. -
log.NewConsoleHandler(opts)— the rawConsoleHandler(anslog.Handler).
The ConsoleHandler renders lines of the form:
[HH:MM:SS] <level-marker> <package> <message> [key=value ...]
The <package> column is populated from two special attributes:
-
log.PackageKey("teamserver_pkg") — the package/domain. -
log.StreamKey("stream") — a finer flow/stream (kept by JSON/audit handlers, not shown on the console text).
Named(logger, pkg, stream) is sugar that attaches both. On a core:
log := teamserver.NamedLogger("transport", "mTLS")
log.Info("serving gRPC teamserver", "addr", ln.Addr())On the cores (no-ops for a custom-handler logger):
teamserver.SetLogLevel(int(slog.LevelDebug)) // adjust console + file levels together
teamserver.SetLogFormat(log.FormatText)
teamclient.SetLogLevel(int(slog.LevelDebug))
teamclient.SetLogWriter(cmd.OutOrStdout(), cmd.ErrOrStderr()) // redirect console streamsSetLogWriter is how the generated command runners point the console at a cobra command's
own stdout/stderr; it uses an internal swap-writer so streams can be redirected without
rebuilding handlers.
From the CLI, the -v/--verbosity count flag raises verbosity (e.g. -vvv) and
--log-format picks the stream format. See CLI Reference.
slog only defines Debug/Info/Warn/Error. The package adds three:
log.LevelTrace = slog.LevelDebug - 4 // -8 (below Debug)
log.LevelFatal = slog.LevelError + 4 // 12 (aborts the program)
log.LevelPanic = slog.LevelError + 8 // 16Helpers: log.Trace(logger, msg, args...) logs at Trace; log.Fatal(logger, msg, args...)
logs at Fatal and exits with status 1 (reserved for unrecoverable failures such as the
certificate infrastructure). log.LevelFrom(int) clamps an int to [Trace, Panic].
The teamserver keeps a separate JSON audit log (default
~/.<app>/teamserver/logs/audit.json), independent of the console/file loggers and their
level knobs. It logs at Debug so every request is recorded. Transport middleware is where you
use it:
audit, err := teamserver.AuditLogger() // *slog.Logger writing JSON to audit.json
audit.Info("request", "method", info.FullMethod, "user", user.Name)log.NewAudit(w) builds one over any writer if you want your own destination.
Because the core's filesystem is an abstracted (afero-backed) FS returned by
Server.Filesystem() / Client.Filesystem(), a core running with WithInMemory() keeps its
log files in memory. If you need your own ephemeral logger against that same FS, open a file
on it and pass the writer to log.New(...). See
Testing & In-Memory Use.
Related: Configuration → · Writing a Transport →