Logging
Log to see what is going on in your application.
Introduction
Arrower uses slog.Logger
as an interface for the application.
It provides its own implementation of slog.Handler
, to add interesting extra functionalities.
logger := alog.New()
logger = alog.NewDevelopment(pgx)
logger = alog.Test(t)
Environment | Constructor | Key Features |
---|---|---|
production | New |
|
development | NewDevelopment |
|
testing | Test |
|
Available Handlers
A logger can take one or multiple handlers that it writes too simultaneously. You can bring and set your own handler(s). This gives you more control over your logging needs, compared to one of the default loggers from above.
logger := alog.New(
alog.WithHandler(h0),
alog.WithHandler(h1),
)
Please note, that the level of a given handler is ignored and the level of the logger is used for all handlers instead.
Name | Description |
---|---|
slog.NewTextHandler | The standard libraries handler |
slog.NewJSONHandler | The standard libraries handler |
NewLokiHandler | Sends all logs to a loki instance. Use this for local development only! For production log to Stderr and use docker, kubernetes, or other drivers to ship the logs. |
NewPostgresHandler | Sends all logs to postgres. Use this for local development only! |
Runtime Configuration
To conveniently debug issues, the logger supports changing some properties at runtime.
Change The Log Level
The log level will be changed for all handlers, independent of their specific configuration.
alog.Unwrap(logger).SetLevel(slog.LevelDebug)
Writing Log Messages
The Go community has struggled for some time to find good interfaces, that applies to loggers as well. Check out Dave Cheney's post Let's talk about logging where he makes a compelling argument to only log two things:
- Things that developers care about when they are developing or debugging software. =>
DEBUG
- Things that users care about when using your software. =>
INFO
One important consideration though: It is recommended to give the context to the methods,
so use Log()
, LogAttrs()
, or InfoCtx()
over Info()
.
The context is carrying information to correlate the logs with traces.
slog
Logger Interface
Arrower returns always an slog.Logger
for logging. So you can use the known API and all the available methods
that Go is offering.
alog
Logger Interface
Arrower recommends you the use the slog.Logger
interface.
You probably don't want to bind your code to our logger interface.
That said, the project itself uses a more restricted subset of the slog
interface, that:
- encourages the use of methods taking context.Context, so that tracing information can be correlated
- encourages the use of the levels
DEBUG
andINFO
, without preventing the others
type Logger interface {
Log(ctx context.Context, level slog.Level, msg string, args ...any)
LogAttrs(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr)
DebugCtx(ctx context.Context, msg string, args ...any)
InfoCtx(ctx context.Context, msg string, args ...any)
}
Log Level
Arrower works with the standard slog levels. That also means you can define your own log levels. Arrower uses the following two log levels internally, leaving you some space to define your own in between, if desired.
const (
// LevelInfo is used to see what is going on inside Arrower.
LevelInfo = slog.Level(-8)
// LevelDebug is used by Arrower developers, if you really want to know what is going on.
LevelDebug = slog.Level(-12)
)
Testing
Correlate With Tracing
As Arrower encourages web applications running in the cloud, they might span multiple machines.
To make it easy to trace down a "request", arrower adds the traceID
and spanID
(if present)
automatically to each output.
Screenshot how logs have IDs
In return each log is also recorded as an event in the span, to make it easier to debug potentially issues.
Screenshot how traces have logs with all attributes
For more on tracing, see traces.