Go

github.com/infinitetoken/lumber-go is Lumber's Go client. It encrypts every log with AES-256-GCM before it leaves your process, has no dependencies beyond the standard library, and plugs into log/slog, so code that already logs with slog can send to Lumber.

Install

go get github.com/infinitetoken/lumber-go

Go 1.22 or later.

Quick start

import lumber "github.com/infinitetoken/lumber-go"

client, err := lumber.New(os.Getenv("LUMBER_API_KEY"))
if err != nil {
    log.Fatal(err)
}
defer client.Close(context.Background()) // sends what's still queued

client.Info("Server started", "port", 8080)
client.Warn("Rate limit approaching", "request_id", "req-123", "user_id", 456)
client.Error("Payment failed", "trace_id", "trace-abc", "order_id", 789, "err", err)

The arguments after the message are slog's: alternating keys and values, or slog.Attr. Logs are sent from a background goroutine, so logging never waits on the network. Call Close before your program exits, or the logs still queued are lost; give its context a deadline if exiting must not wait long.

The API key comes from the Lumber app (see Getting Started). Keep it in an environment variable or your secrets manager, never in source control, and never in an app or game you ship to people (why).

With log/slog

client.Handler is a slog.Handler:

slog.SetDefault(slog.New(client.Handler(&lumber.HandlerOptions{Level: slog.LevelInfo, AddSource: true})))

slog.Info("Report ready", "request_id", "req-123", "rows", 1500, "took", 2400*time.Millisecond)

logger := slog.Default().With("service", "billing").WithGroup("job")
logger.Error("Job failed", "id", 42, "err", err)

logger.Log(ctx, lumber.LevelFatal, "Database unreachable")

Each record becomes a log at the matching level: from slog.LevelError up is ERROR, and from lumber.LevelFatal (12) up is FATAL.

  • Attributes go into the log's metadata, with groups as nested objects.
  • Three are special at the top level: request_id and trace_id (or requestId and traceId) are the log's own fields, and a context group or map goes into its context.
  • An error is sent as its message.
  • AddSource sends the file, line and function that logged in the context. Level defaults to slog.LevelInfo.

Log methods

MethodLevel
Debug(msg, args...)DEBUG
Info(msg, args...)INFO
Warn(msg, args...)WARN
Error(msg, args...)ERROR
Fatal(msg, args...)FATAL. Unlike log.Fatal, it doesn't exit
Flush(ctx)Waits for queued logs to send, or for ctx
Close(ctx)Sends what's queued, waiting for ctx at most, and stops

Options

client, err := lumber.New(os.Getenv("LUMBER_API_KEY"),
    lumber.WithEnvironment("production"), // sent as `env` in every log's context
    lumber.WithCaller(0),                 // send the file, line and function of the client's own calls; 1 skips a wrapper
    lumber.WithoutAutoContext(),          // don't send the process id and hostname
    lumber.WithTimeout(10*time.Second),   // how long to wait for Lumber to answer one log
    lumber.WithMaxQueue(10_000),          // logs waiting to send before new ones are dropped
    lumber.WithHTTPClient(httpClient),    // your own http.Client, for a proxy or custom TLS
    lumber.WithWarnings(func(msg string) { /* your logger */ }),
)

Delivery

Logs are sent in order, one POST /logs per log, over a connection kept open between logs.

  • A network error or a 5xx counts as a failed attempt. Each log gets 5 attempts, waiting 1, 2, 4, then 8 seconds between them, and is then dropped with a warning.
  • A log Lumber refuses (a 4xx, such as an invalid API key) is dropped at once with a warning, since sending it again won't help.
  • When your team has sent its plan's logs for the month, Lumber answers 429: the client warns once and drops logs until the month turns.
  • While Lumber can't be reached, up to WithMaxQueue logs wait; past that, new ones are dropped.

Warnings go to standard error unless you pass WithWarnings.

Source

The client is open source: lumber-go on GitHub, with its reference on pkg.go.dev. It's tested against the same known answers for encryption and the wire format as Lumber's other clients.