Documentation
¶
Overview ¶
Package logging is structured logging for Tern apps, on log/slog. A logger from New adds the request's ID to every record logged in a request, redacts passwords, tokens and other secrets, can sample floods of the same message, and ships records to collectors and log services through sinks, such as github.com/Unfield/Tern/logging/otlp, in the background:
logger := logging.New(logging.Options{
Sinks: []logging.Sink{otlp.New(otlp.Options{})},
})
slog.SetDefault(logger)
server, err := ternApp(kit.WithLogger(logger))
kit flushes the sinks when the server shuts down. Under tern dev, records also appear in the dev server's log view, at /__tern/dev/logs.
The defaults come from the environment: TERN_LOG_LEVEL (debug, info, warn, error) and TERN_LOG_FORMAT (text, json). Without TERN_LOG_FORMAT, output is text on a terminal and under tern dev, JSON otherwise.
Index ¶
- Constants
- Variables
- func Attrs(ctx context.Context) []slog.Attr
- func AttrsMap(attrs []slog.Attr) map[string]any
- func Close(ctx context.Context, l *slog.Logger) error
- func Flush(ctx context.Context, l *slog.Logger) error
- func IsPermanent(err error) bool
- func New(opts Options) *slog.Logger
- func NewHandler(opts Options) slog.Handler
- func Permanent(err error) error
- func RedactURL(u *url.URL) string
- func RequestID(ctx context.Context) string
- func RetryAfter(err error, d time.Duration) error
- func ValueOf(v slog.Value) any
- func With(ctx context.Context, attrs ...slog.Attr) context.Context
- func Wrap(h slog.Handler) slog.Handler
- type Batch
- type Entry
- type JSONEntry
- type Options
- type Sampling
- type Secret
- type Sink
- type SinkFunc
- type Stats
Constants ¶
const ( // RequestIDKey is the key of the request's ID. RequestIDKey = "request_id" // TraceIDKey and SpanIDKey are the W3C trace context's IDs, from a // traceparent header the app trusts. The OTLP sink sends them as the // record's trace and span. TraceIDKey = "trace_id" SpanIDKey = "span_id" )
The keys kit uses for a request's attributes.
const Redacted = "[redacted]"
Redacted is what a redacted value is replaced with.
Variables ¶
var DefaultRedact = []string{
"password", "passwd", "new_password", "current_password",
"secret", "client_secret",
"token", "access_token", "refresh_token", "id_token", "bearer",
"api_key", "apikey", "private_key", "secret_key", "signing_key",
"authorization", "proxy_authorization",
"cookie", "set_cookie",
"session", "session_id", "sessionid",
"csrf", "csrf_token", "xsrf_token",
"otp", "totp", "recovery_code", "recovery_codes",
"credit_card", "card_number", "cvv", "cvc",
}
DefaultRedact is the list of keys whose values are redacted unless Options.Keep names them. Keys match without regard to case, with "-" and "_" the same, and so do keys ending in "_token", "_secret", "_password" or "_key" after one of these words: "access_token" and "stripe_secret" are redacted, "cache_key" is not.
Functions ¶
func AttrsMap ¶
AttrsMap returns attrs as a map for encoding/json: groups as nested maps, times in RFC 3339, durations as strings, errors as their messages. A key that appears twice keeps its last value.
func Close ¶
Close flushes l's sinks and stops them; records logged after it are written to the output but not shipped.
func Flush ¶
Flush waits until the records l logged so far have been sent to its sinks, or ctx ends. l is a logger from New or Wrap; for others it does nothing. kit calls it when the server shuts down.
func IsPermanent ¶
IsPermanent reports whether err was marked with Permanent.
func NewHandler ¶
NewHandler returns the handler of a logger from New, for use with handlers of other packages.
func Permanent ¶
Permanent marks an error from Export as one retrying won't fix, such as a rejected request: the batch is dropped.
func RedactURL ¶
RedactURL returns u as a string with its password and the values of query parameters DefaultRedact names replaced, as log records have URLs.
func RetryAfter ¶
RetryAfter marks an error from Export as one to retry after d.
func With ¶
With returns a copy of ctx that carries attrs: every record logged with that context, through a logger from New or a handler from Wrap, includes them. kit adds the request's ID this way.
ctx = logging.With(ctx, slog.String("user", id))
slog.InfoContext(ctx, "invoice sent") // ... user=… request_id=…
Attributes with the same key as one already carried replace it.
func Wrap ¶
Wrap makes h add the attributes of the context a record is logged with, as handlers from NewHandler do, and under tern dev send records to the dev server's log view. A handler from NewHandler is returned as it is. kit wraps the handler of the logger it is given with it.
Types ¶
type Batch ¶
type Batch struct {
Size int // default 512
Interval time.Duration // default 1s
Queue int // default 16384
MaxRetries int // default 5; -1 for none
// Timeout bounds each Export call. Default 15s.
Timeout time.Duration
}
Batch says how entries are sent to sinks. Entries wait in a queue; a batch goes out when Size have gathered or Interval has passed. A batch that fails is retried MaxRetries times, pausing 1s, 2s, 4s… up to 30s between tries; meanwhile new entries queue. When the queue holds Queue entries, new ones are dropped and counted (see Stats).
type Entry ¶
type Entry struct {
Time time.Time
Level slog.Level
Message string
// Attrs are the record's attributes. Groups are slog.KindGroup values.
Attrs []slog.Attr
// Source is where the record was logged, when [Options.AddSource] is
// set.
Source *slog.Source
}
Entry is a log record as sinks get it: its attributes resolved, redacted and nested in their groups, those of the context first.
type JSONEntry ¶
type JSONEntry struct {
Time time.Time `json:"time"`
Level string `json:"level"`
Message string `json:"msg"`
Attrs map[string]any `json:"attrs,omitempty"`
Source *slog.Source `json:"source,omitempty"`
}
JSONEntry is an entry as JSON: the shape tern dev's log view reads, and a plain one for sinks that send JSON objects.
func EncodeJSON ¶
EncodeJSON returns entries as JSONEntry values.
type Options ¶
type Options struct {
// Level is the lowest level logged. Pass a *slog.LevelVar to change it
// while the app runs. The default is TERN_LOG_LEVEL's, or Info.
Level slog.Leveler
// Format is "text" or "json". See the package documentation for the
// default.
Format string
// Output is where formatted records go. The default is os.Stderr; use
// io.Discard to only ship records to sinks.
Output io.Writer
// AddSource adds the file and line that logged each record.
AddSource bool
// Redact adds keys to [DefaultRedact]; Keep takes keys out of it.
// NoDefaultRedact leaves only Redact.
Redact []string
Keep []string
NoDefaultRedact bool
// Sampling, when set, samples floods of records with the same message.
Sampling *Sampling
// Sinks get every record at or above Level (or [MinLevel]'s level), in
// batches, from a goroutine each.
Sinks []Sink
// Batch says how records are batched and retried for sinks.
Batch Batch
}
Options configures a logger.
type Sampling ¶
type Sampling struct {
First int
Thereafter int // 0 keeps none after the first First
Tick time.Duration
// MaxLevel is the highest level sampled. The zero value is Info.
MaxLevel slog.Level
}
Sampling keeps a burst of log records from drowning the rest and from costing what they cost to ship. Within each Tick, the first First records with the same level and message are kept, then every Thereafter-th one. Records above MaxLevel are always kept.
The default, when Options.Sampling is set with zero fields: 100 a second, then 1 in 100, for Info and below; warnings and errors are never sampled.
type Secret ¶
type Secret string
Secret is a string that never shows in logs: its value logs as Redacted, whatever its key and whatever the handler.
logger.Info("calling the API", "key", logging.Secret(key))
type Sink ¶
A Sink sends log entries somewhere: a collector, a log service, a file. Its Export gets them in batches from one goroutine, in the order they were logged; see Options.Batch for how batches are made and retried.
Export returns an error to have the batch sent again, after a growing pause, or Permanent to drop it. RetryAfter asks for a pause of its own, as a service's Retry-After header does.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package datadog is a logging sink that sends records to Datadog's log intake (HTTP API v2):
|
Package datadog is a logging sink that sends records to Datadog's log intake (HTTP API v2): |
|
Package file is a logging sink that writes records to a file as JSON lines, rotating it by size:
|
Package file is a logging sink that writes records to a file as JSON lines, rotating it by size: |
|
internal
|
|
|
push
Package push sends a sink's batch over HTTP, the way the sinks that talk to services share: gzip, status codes mapped to retries, and Retry-After.
|
Package push sends a sink's batch over HTTP, the way the sinks that talk to services share: gzip, status codes mapped to retries, and Retry-After. |
|
sinktest
Package sinktest has helpers for the sinks' tests.
|
Package sinktest has helpers for the sinks' tests. |
|
Package loki is a logging sink that pushes records to Grafana Loki:
|
Package loki is a logging sink that pushes records to Grafana Loki: |
|
Package otlp is a logging sink that sends records to an OpenTelemetry collector, or any service that takes OTLP over HTTP (Grafana Cloud, Honeycomb, New Relic, SigNoz, Axiom…), as OTLP/HTTP JSON:
|
Package otlp is a logging sink that sends records to an OpenTelemetry collector, or any service that takes OTLP over HTTP (Grafana Cloud, Honeycomb, New Relic, SigNoz, Axiom…), as OTLP/HTTP JSON: |