# simplelog ## Summary simplelog is an opinionated logging package designed to facilitate easy and structured logging in Go applications with an absolute minimum of boilerplate. The idea is that you can add a single import line which replaces the stdlib `log/slog` default handler, and solve the 90% case for logging. ## Current Status Released v1.0.0 2024-06-14. Works as intended. No known bugs. ## Features - if output is a tty, outputs pretty color logs - if output is not a tty, outputs json - supports delivering each log message via a webhook - emits every `slog` attribute: those passed to a log call, those accumulated with `WithAttrs`, and those qualified by `WithGroup`. `slog.Group` values nest, and `slog.LogValuer` values are resolved. In json output attributes are object fields (groups become nested objects); in console output they are appended as `key=value` pairs, with grouped keys written as `group.key=value`. See [Attribute output](#attribute-output) for the details worth knowing ## Planned Features - supports delivering logs via tcp RELP (e.g. to remote rsyslog using imrelp) ## Installation To use simplelog, first ensure your project is set up with Go modules: ```bash go mod init your_project_name ``` Then, add SimpleLog to your project: ```bash go get sneak.berlin/go/simplelog ``` ## Usage Below is an example of how to use SimpleLog in a Go application. This example is provided in the form of a `main.go` file, which demonstrates logging at various levels using structured logging syntax. ```go package main import ( "log/slog" _ "sneak.berlin/go/simplelog" ) func main() { // log structured data with slog as usual: slog.Info("User login attempt", slog.String("user", "JohnDoe"), slog.Int("attempt", 3)) slog.Warn("Configuration mismatch", slog.String("expected", "config.json"), slog.String("found", "config.dev.json")) slog.Error("Failed to save data", slog.String("reason", "permission denied")) } ``` ## Attribute output Attributes reach every handler: the ones passed to the log call, the ones accumulated with `WithAttrs`, and the ones qualified by the groups open at the time they were attached. `slog.Group` values nest, and `slog.LogValuer` values are resolved to the value they stand for. A few behaviours are worth knowing before you rely on them. **Your values are never modified.** Whatever you log is read and rendered, never written to. A map or a slice you pass to `slog.Any` comes back from the logger exactly as you handed it over, even when a group later uses the same key. **Durations are nanoseconds in json, and readable on the console.** The json and webhook payloads emit a `slog.Duration` as a number of nanoseconds, matching `slog.NewJSONHandler`, so a consumer can compare and aggregate the field without parsing it. The console line emits the same duration as `3s`, matching `slog.NewTextHandler`, because a person reads it. **The json payload is an object, with the consequences an object has.** The record's own fields are named `Time`, `Level`, `Message` and `PC`, and they own those names: an attribute keyed after one of them is dropped from the json and webhook output. A key logged more than once keeps its last value there for the same reason. Neither applies to the console output, which is a line of text: both pairs appear, in order. If you need a field called `message`, pick a key that does not collide - the collision is silent. **Empty things follow the `slog.Handler` contract.** An empty `Attr` is ignored, an empty group is elided along with its key, a group with an empty key is inlined into its parent, and `WithGroup("")` returns the handler unchanged. ## License [WTFPL](./LICENSE)