lumber

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 9, 2026 License: MIT Imports: 23 Imported by: 0

README

lumber-go

Go client for Lumber: cloud logging with end-to-end encryption. Every log is encrypted with AES-256-GCM before it leaves your process, so Lumber's servers store only ciphertext plus each log's level (kept readable for filtering and push alerts). You read your logs in the Lumber app, which decrypts them on your device.

No dependencies beyond the standard library. Logs are sent from a background goroutine, so logging never waits on the network, and it plugs into log/slog.

Installation

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. Call Close before your program exits, or the logs still queued are lost; give its context a deadline if exiting must not wait long.

Getting your API key

Create an app in the Lumber app and it takes you straight to adding an API key. The key is shown only once, so copy it right away. Any device holding the app's key can add more from the app's Secrets screen, one per deployment if you like.

An API key is two values joined by a dot: a token, then the app's encryption key. The client sends only the token to Lumber and encrypts every log with the rest, so the encryption key never leaves your process. Keep the API key in an environment variable or your secrets manager, never in source control.

With log/slog

client.Handler is a slog.Handler, so code that already logs with slog sends to Lumber:

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, except three 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

Method Level
Debug(msg, args...) DEBUG
Info(msg, args...) INFO
Warn(msg, args...) WARN
Error(msg, args...) ERROR
Fatal(msg, args...) FATAL. It doesn't exit, unlike log.Fatal
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, 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.

Development

go test -race ./...

testdata/contract holds Lumber-Kit's contract at the version in testdata/contract/VERSION: a JSON Schema of the wire format and known answers for API keys, encryption, payloads and delivery, which Lumber's Node and Python clients are tested against too. scripts/sync-contract.sh 0.8.0 moves it to a new kit version.

License

MIT. See LICENSE.

Documentation

Overview

Package lumber sends logs to Lumber, cloud logging with end-to-end encryption. Every log is encrypted with AES-256-GCM before it leaves the process, so Lumber's servers store only ciphertext plus each log's level.

client, err := lumber.New(os.Getenv("LUMBER_API_KEY"))
if err != nil {
	log.Fatal(err)
}
defer client.Close(context.Background())

client.Info("Server started", "port", 8080)
slog.SetDefault(slog.New(client.Handler(nil)))

Logging never blocks: logs are queued and sent from a background goroutine. Close sends what's queued before the program exits.

Index

Constants

View Source
const DefaultEndpoint = "https://api.lumber.infinitetoken.com"

DefaultEndpoint is the Lumber API logs go to unless New is given WithEndpoint.

View Source
const LevelFatal = slog.Level(12)

LevelFatal is the slog level Lumber sends as FATAL: logger.Log(ctx, lumber.LevelFatal, "db down"). Any level from slog.LevelError up to it is ERROR.

View Source
const Version = "1.0.0"

Version is this package's version, sent in the User-Agent.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client encrypts logs with an app's key and sends them to Lumber. It's safe for concurrent use.

func New

func New(apiKey string, opts ...Option) (*Client, error)

New makes a client from an app's API key, `<token>.<encryptionKey>`, from the app's Secrets screen in Lumber. Only the token is sent to Lumber; the encryption key encrypts every log and never leaves this process.

func (*Client) Close

func (c *Client) Close(ctx context.Context) error

Close sends what's queued, waiting until ctx is done at most, and stops. Logs after this are dropped.

func (*Client) Debug

func (c *Client) Debug(msg string, args ...any)

Debug logs at DEBUG. Its arguments are slog's: alternating keys and values, or slog.Attr. See Handler for the keys that mean something to Lumber.

func (*Client) Error

func (c *Client) Error(msg string, args ...any)

Error logs at ERROR. See Debug for its arguments.

func (*Client) Fatal

func (c *Client) Fatal(msg string, args ...any)

Fatal logs at FATAL. Unlike log.Fatal it doesn't exit: call Close first if the program is about to.

func (*Client) Flush

func (c *Client) Flush(ctx context.Context) error

Flush waits until every queued log is sent (or dropped), or ctx is done.

func (*Client) Handler

func (c *Client) Handler(opts *HandlerOptions) slog.Handler

Handler is a slog.Handler that sends records to Lumber:

logger := slog.New(client.Handler(nil))
logger.Info("Server started", "port", 8080)

Attributes go into the log's metadata, groups as nested objects, except three 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 value is sent as its message.

func (*Client) Info

func (c *Client) Info(msg string, args ...any)

Info logs at INFO. See Debug for its arguments.

func (*Client) Warn

func (c *Client) Warn(msg string, args ...any)

Warn logs at WARN. See Debug for its arguments.

type HandlerOptions

type HandlerOptions struct {
	// Level is the lowest level sent. Nil means slog.LevelInfo.
	Level slog.Leveler
	// AddSource sends the file, line and function that logged in each log's context.
	AddSource bool
}

HandlerOptions configure Client.Handler.

type Option

type Option func(*options)

Option configures New.

func WithCaller

func WithCaller(depth int) Option

WithCaller sends the file, line and function that logged with each of the client's own log calls, skipping depth more frames, one for each wrapper around the client. A Handler sends them with HandlerOptions.AddSource instead.

func WithEndpoint

func WithEndpoint(url string) Option

WithEndpoint sends logs to another Lumber API, for testing against another server.

func WithEnvironment

func WithEnvironment(env string) Option

WithEnvironment sends env (production, staging) with every log, as `env` in its context.

func WithHTTPClient

func WithHTTPClient(client *http.Client) Option

WithHTTPClient sends logs with your own http.Client (a proxy, custom TLS). Its Timeout applies, not WithTimeout's.

func WithMaxQueue

func WithMaxQueue(n int) Option

WithMaxQueue is how many logs can wait to send, while Lumber can't be reached, before new ones are dropped (10,000 unless set).

func WithTimeout

func WithTimeout(timeout time.Duration) Option

WithTimeout is how long to wait for Lumber to answer one log (10 seconds unless set).

func WithWarnings

func WithWarnings(warn func(message string)) Option

WithWarnings receives the client's warnings (a log refused, Lumber unreachable) in place of the standard error output. Pass a function that does nothing to silence them.

func WithoutAutoContext

func WithoutAutoContext() Option

WithoutAutoContext stops sending the process id and hostname with every log.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL