srog

package module
v1.2.0 Latest Latest
Warning

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

Go to latest
Published: Jul 19, 2026 License: MIT Imports: 19 Imported by: 0

README

srog

Structured logging for Go with Serilog-style message templates — powered by zerolog.

Write the message once. Get a human-readable line and typed structured fields — for free.

CI Go Reference Go Report Card Go Built on zerolog Templates Hot path License

log := srog.MustNew(srog.WithConsole())
log.Information("User {Username} logged in from {IP}", "neo", "10.0.0.1")
Console — clean, for humansJSON — structured, for machines
13:04:05 INF User neo logged in from 10.0.0.1
{"level":"info","Username":"neo","IP":"10.0.0.1",
 "@mt":"User {Username} logged in from {IP}",
 "message":"User neo logged in from 10.0.0.1"}

The named holes {Username} and {IP} become typed structured fields, and @mt preserves the raw template so log pipelines can group events by their template identity — exactly like Serilog.


✨ Features

  • 🧩 Serilog message templates{Name}, destructuring {@obj}, stringify {$obj}, alignment, formats, positional holes.
  • Fast — template parsing is cached; field binding uses a type switch, not reflection. 0 allocations on the structured hot path.
  • 🪵 Multi-sink fan-out — pretty console and rotated JSON files at once, each with its own format and level.
  • ♻️ Log rotation — by size, by time (hourly/daily), with age/backup retention and gzip.
  • 📦 Ships anywhere — NDJSON output drops straight into Fluent Bit / Elasticsearch / OpenSearch / Loki.
  • 🧵 Request-scoped logging — carry a RequestId & service name through context.Context.
  • 🌐 Batteries includednet/http middleware and gRPC interceptors.
  • 🔎 Readable stack traces — captured on Error/Fatal, pretty in console, single indexable string in JSON.

📑 Table of contents

📥 Install

go get github.com/dvislobokov/srog

Requires Go 1.23+ (the srog/srogotel module requires Go 1.25+, dictated by its OpenTelemetry dependencies). The core package and srog/sroghttp depend only on zerolog and lumberjack; srog/sroggrpc adds google.golang.org/grpc.

🚀 Quick start

package main

import "github.com/dvislobokov/srog"

func main() {
    log := srog.NewConsole() // colorized console at Debug, stack traces on

    log.Information("listening on {Host}:{Port}", "0.0.0.0", 8080)
    log.Warning("cache miss rate {Rate:0.0}%", 12.5)

    if err := connect(); err != nil {
        log.Error(err, "failed to reach {Service}", "postgres")
    }
}

🎚 Log levels

Serilog names map onto zerolog levels:

srog zerolog notes
Verbose trace
Debug debug
Information info Info is a shorthand alias
Warning warn
Error(err, …) error err attached as error field
Fatal(err, …) fatal calls os.Exit(1)

🧩 Message templates

Form Meaning
{Name} Scalar property — bound as a typed field (string/int/float/…)
{@Name} Destructure — serialize the value as a structured object
{$Name} Stringify — force the value to its string form
{Name:format} Format specifier ({T:HH:mm:ss}, {N:x}, …)
{Name,10} Right-align to width 10; negative width left-aligns
{0} {1} Positional holes bound by argument index
{{ / }} Literal { / }

Surplus arguments beyond the holes are attached as extra_N fields, so data is never silently dropped. Missing arguments leave the hole text in the message.

🏷 Enrichment

reqLog := log.ForContext("RequestId", "req-7")
reqLog.Information("handling {Path}", "/checkout")   // every event carries RequestId

multi := log.ForContextValues(map[string]any{"service": "api", "version": 3})
svc   := log.Named("billing")                        // sugar for service=billing

🪵 Sinks & output

A logger fans out to any number of sinks, each with its own format and minimum level. The classic layout — pretty console for humans, rotated JSON files for shipping — is one constructor call:

log, err := srog.New(
    srog.WithLevel(srog.DebugLevel),       // default level for sinks
    srog.WithStackTrace(true),             // stacks on Error/Fatal

    // colorized, human-readable console (parameters omitted)
    srog.WithConsole(srog.MinLevel(srog.DebugLevel)),

    // machine-readable JSON to a rotating file
    srog.WithFile("/var/log/app/app.log",
        srog.MinLevel(srog.InformationLevel),
        srog.Rotate(srog.Rotation{
            MaxSizeMB:  100,          // rotate past 100 MB
            MaxBackups: 10,           // keep 10 old files
            MaxAgeDays: 30,           // delete files older than 30 days
            Compress:   true,         // gzip rotated files
            Every:      srog.Daily,   // also rotate at midnight (or srog.Hourly)
        }),
    ),
)
if err != nil {
    panic(err)
}
defer log.Close()                          // flush & close file sinks
Constructor Default format Destination
WithConsole(...) console os.Stdout
WithFile(path, ...) JSON file at path
WithWriter(w, ...) JSON any io.Writer

Per-sink options: MinLevel(l) · AsJSON() · AsConsole() · AsECS() · AsOTel() · AsTemplate("...") · NoColor() · Rotate(Rotation{...}) · Async(n)

Output templates. AsTemplate renders a sink through a Serilog-style output template — compose the line from placeholders, each supporting the same ,alignment and :format specifiers as message templates:

srog.WithConsole(srog.AsTemplate(
    "[{Timestamp:15:04:05} {Level:u3}] {Message}{NewLine}{Exception}"))
// 	[13:04:05 WRN] cache miss on user:42

srog.WithFile("app.log", srog.AsTemplate(
    "{Timestamp:rfc3339} level={Level:w} msg=\"{Message}\" {Properties}")) // logfmt-ish
Placeholder Renders
{Timestamp[:layout]} Event time — Go layout, friendly name (rfc3339, datetime, …), or .NET-style (HH:mm:ss); bare prints the field as written
{Level[:u3|w3|u|w]} u3/w3INF/inf; u/wINFORMATION/information; bare → info
{Message} / {MessageTemplate} Rendered message / raw @mt
{Exception} Error text, stack trace on the next line; empty when no error
{Caller} file:line (with WithCaller(true))
{NewLine} \n
{Properties[:j]} Every field not otherwise consumed, as k=v pairs (:j → one JSON object)
{AnyField} Any event field by name — {RequestId}, {Amount,10:.2f}, …

Alignment pads ({Level,-5:u3}ERR ), {{/}} escape braces, and a field absent from the event renders as empty. Fields not referenced by the template are omitted unless {Properties} is present — like a console sink, it is a presentation layer.

With no sink option, the logger defaults to JSON on stdout. New returns an error if a file cannot be opened; MustNew panics instead; NewConsole() is a zero-config colorized console for local dev.

Logger-wide options
srog.WithLevel(srog.DebugLevel)
srog.WithRenderedMessage(false)       // structured-only: 0 allocations/event
srog.WithCaller(true)                 // reports the real call site, not srog internals
srog.WithTimestamp(true)
srog.WithStackTrace(true)
srog.WithTimeFormat(srog.TimeRFC3339Nano) // or srog.TimeUnix for epoch

// Reliability & throughput
srog.WithErrorHandler(func(err error) { /* count / alert / fall back */ })
srog.WithSampling(srog.BurstLimit(100, time.Second, srog.EveryN(100))) // flood control
// ...and per file sink: srog.WithFile(path, srog.Async(4096)) to move I/O off the request path
Context-scoped logging & trace correlation
// Middleware stores a request-scoped logger; handlers pull it back out:
srog.Ctx(ctx).Information("processing {OrderId}", id)
srog.InfoCtx(ctx, "charged {Amount}", 999) // package-level shorthand

// Register once so every Ctx/*Ctx log carries fields from the context
// (the srogotel module ships an OpenTelemetry trace_id/span_id extractor):
srog.AddContextField(func(ctx context.Context) []srog.Field { ... })

🗂 Configuration from a file

Everything above can also be declared in a srog.Config and loaded from JSON (or YAML — the struct carries yaml tags, so gopkg.in/yaml.v3 decodes it without srog itself depending on a YAML parser):

{
  "level": "information",
  "caller": true,
  "stackTrace": true,
  "timeFormat": "rfc3339nano",
  "sinks": [
    { "type": "console", "target": "stderr", "level": "debug" },
    { "type": "file", "path": "/var/log/app.log", "level": "warning",
      "rotation": { "maxSizeMB": 100, "maxBackups": 10, "compress": true, "every": "daily" } }
  ]
}
log, err := srog.NewFromConfigFile("logging.json")
// or, to compose with programmatic options:
cfg, _ := srog.LoadConfigFile("logging.json")
opts, _ := cfg.Options()
log, _ := srog.New(append(opts, srog.WithWriter(buf))...)

level/format/every/timeFormat accept the same friendly names shown throughout this README (case-insensitive); an unknown timeFormat is treated as a raw Go layout. Invalid values fail fast with an error from Build.

Configuration reference

Top-level fields (each maps 1:1 to a With* option; omit a field to keep its default):

Field Type Values Default Meaning
level string verbose/trace, debug, information/info, warning/warn, error, fatal information Logger-wide minimum level for sinks that don't set their own.
render bool true / false true Render the human-readable message field. Turn off for max throughput when you only consume structured fields; console sinks need it on.
caller bool true / false false Annotate each event with the calling file:line.
timestamp bool true / false true Add a timestamp to each event.
stackTrace bool true / false false Capture a call stack whenever an error is logged via Error/Fatal.
timeFormat string rfc3339, rfc3339nano, datetime, dateonly, timeonly, kitchen, unix, unixms, unixmicro, unixnano, or a raw Go layout rfc3339 Timestamp layout. The unix* names emit epoch numbers; the rest emit strings.
sinks array see below one JSON sink on stdout Output destinations; each event fans out to every sink that admits its level.

Sink fields (each entry in sinks):

Field Type Values Default Meaning
type string console, file, stdout, stderr, or any name registered via srog.RegisterSinkType (e.g. otlp once srog/srogotel is imported) — (required) Destination kind. stdout/stderr write to the standard streams; console is a stream sink that defaults to the colorized console format.
target string stdout, stderr stdout Which stream a console sink writes to. Ignored for other types.
path string any file path — (required for file) File to write. The parent directory must already exist.
level string same names as top-level level inherits logger level Per-sink minimum level, so one sink can show debug while another keeps only warning+.
format string json, console/text, ecs, otel/opentelemetry/otlp, template console for type: console, otherwise json Serialization. ecs = Elastic Common Schema field names; otel = OpenTelemetry OTLP/JSON log records; template = Serilog-style output template (requires template).
template string output template Serilog-style output template, e.g. "[{Timestamp:15:04:05} {Level:u3}] {Message}{NewLine}{Exception}". Setting it implies format: template.
noColor bool true / false false Disable ANSI colors (applies to the console format only).
rotation object see below none Size/time/age rotation. file sinks only.
options object type-specific none Settings for a sink type registered via RegisterSinkType; built-in types ignore it. See the otlp options under OpenTelemetry logs.

Rotation fields (rotation object on a file sink):

Field Type Values Default Meaning
maxSizeMB int ≥ 0 0 (no size trigger) Roll over once the file exceeds this many megabytes.
maxBackups int ≥ 0 0 (keep all) Maximum number of rotated files to retain.
maxAgeDays int ≥ 0 0 (no age limit) Delete rotated files older than this many days.
compress bool true / false false Gzip rotated files.
localTime bool true / false false Timestamp backup names in local time instead of UTC.
every string none/"", hourly, daily none Time-based rotation cadence, combined with the size trigger (first to fire wins).

See examples/formats for one logging.json that exercises every sink type and format at once.

♻️ Rotation

Rotate(srog.Rotation{...}) on a file sink combines size, time, and age triggers (size/age/backup/compress via lumberjack; Every adds hourly or daily rotation). A file rolls over when either the size or time trigger fires.

  • Size only → set just MaxSizeMB.
  • Time only → set just Every (srog.Hourly / srog.Daily).
  • Both → set both; the first to fire wins.

🎨 Console vs JSON

A console sink is purely a presentation layer for local development. It prints one clean line — timestamp, level, rendered message (and error text) — and omits every structured parameter. The values still appear inside the rendered message; the separate Username=…, @mt=… fields do not.

2026-06-15T13:04:25+03:00 ERR startup failed at config connection refused
    main.startup
        /app/cmd/main.go:16
    main.main
        /app/cmd/main.go:24

The same event in JSON keeps the full structured payload:

{"level":"error","error":"connection refused",
 "stack":"main.startup\n\t/app/cmd/main.go:16\nmain.main\n\t/app/cmd/main.go:24",
 "@mt":"startup failed at {Stage}","Stage":"config","message":"startup failed at config"}

WithStackTrace(true) captures the call stack at the log site on Error/Fatal. In JSON it is a single multi-line stack string — one field that indexes and renders cleanly in Elasticsearch/OpenSearch — and in console it is pretty-printed beneath the message.

📦 Fluent Bit / ELK

JSON sinks emit newline-delimited JSON (NDJSON) with stable time, level, and message keys, so Fluent Bit ingests them with a plain tail input:

[INPUT]
    Name        tail
    Path        /var/log/app/*.log
    Parser      json

[PARSER]
    Name        json
    Format      json
    Time_Key    time
    Time_Format %Y-%m-%dT%H:%M:%S%z   # RFC3339 (srog default)

For epoch timestamps, set srog.WithTimeFormat(srog.TimeUnixMs) and let Fluent Bit handle the numeric time field. Console sinks are for humans and are not meant to be shipped.

ECS (Elastic Common Schema) — ELK out of the box

For a zero-mapping path into Elasticsearch/Kibana, use the ECS sink format. It renames fields to the schema Kibana expects (@timestamp, log.level, error.message, error.stack_trace, log.origin.file.*) and injects ecs.version, so events index into a standard ES index with no Logstash rules:

srog.WithFile("/var/log/app.log", srog.AsECS())   // or "format": "ecs" in Config
{"@timestamp":"2026-06-30T21:00:00Z","log.level":"error","message":"failed save",
 "error.message":"boom","message_template.text":"failed {Op}","Op":"save","ecs.version":"8.11.0"}

The recommended shipping path stays logger → NDJSON/ECS file → Filebeat/Fluent Bit → ES; core srog does not open network connections itself.

OpenTelemetry logs — OTLP/JSON out of the box

For an OpenTelemetry logs pipeline, use the OTel sink format. Each event is written as a single OTLP/JSON log record (one LogRecord per line), mapped onto the OpenTelemetry Logs Data Model: timetimeUnixNano, levelseverityNumber/severityText, messagebody, trace_id/span_idtraceId/spanId, and every remaining field becomes a typed attributes entry (errorexception.message, stackexception.stacktrace, callercode.filepath/code.lineno). This is the form the Collector's otlpjson receiver and file exporter read, so events feed straight into any OTel logs backend (Loki, Elastic, …):

srog.WithFile("/var/log/app.log", srog.AsOTel())   // or "format": "otel" in Config
{"timeUnixNano":"1751317200000000000","severityNumber":17,"severityText":"ERROR",
 "body":{"stringValue":"failed save"},"attributes":[{"key":"Op","value":{"stringValue":"save"}},
 {"key":"exception.message","value":{"stringValue":"boom"}}]}

Pair it with srog/srogotel (srogotel.Install()) so context-scoped logs carry the active span's trace_id/span_id — the OTel writer then promotes them into traceId/spanId, joining logs to traces in your backend.

To skip the file/shipper hop entirely, the srog/srogotel module also ships logs straight to the Collector over OTLP via the OTel Logs Bridge API. With a zero config it reuses the process-global LoggerProvider — the one you already configured next to your tracer/meter providers — so logs inherit the same exporter, resource (service.name, …), and batching:

// Reuse the already-configured global LoggerProvider (traces/metrics setup):
opt, sink, err := srogotel.WithLogs(ctx, srogotel.Config{})

// ...or a specific provider: srogotel.Config{Provider: loggerProvider}

// ...or build a private OTLP exporter with explicit parameters:
opt, sink, err = srogotel.WithLogs(ctx, srogotel.Config{
    Endpoint: "collector:4317",        // Protocol: "http" for OTLP/HTTP (4318)
    Insecure: true,
    Headers:  map[string]string{"authorization": "Bearer ..."},
})

if err != nil { /* ... */ }
defer sink.Close() // flushes an owned provider on shutdown
log := srog.MustNew(srog.WithConsole(), opt)

Each event is mapped onto the Logs Data Model exactly like the OTel writer above, and trace_id/span_id (from srogotel.Install()) become the record's trace context, so logs land in the Collector already joined to their traces. Batching, retries, and delivery are handled by the OTel SDK's BatchProcessor, off the logging hot path. See examples/otel-logs for a runnable end-to-end setup.

The same sink is available from the declarative JSON config: importing srog/srogotel (a blank import works) registers the otlp sink type. An empty options object reuses the global LoggerProvider; setting endpoint builds a private exporter:

import _ "github.com/dvislobokov/srog/srogotel" // registers "type": "otlp"
{"sinks": [
  {"type": "otlp"},
  {"type": "otlp", "level": "warning", "options": {
      "endpoint": "collector:4317", "protocol": "grpc", "insecure": true,
      "headers": {"authorization": "Bearer ..."}, "timeout": "10s",
      "scopeName": "my-service",
      "attributes": {"data_stream.dataset": "billing"}}}
]}

The optional attributes map (also Config.Attributes in code) is stamped onto every record — use it for routing hints the Collector reads, such as data_stream.dataset or elasticsearch.index for the elasticsearch exporter's dynamic index. An event field with the same name wins over the static value.

The sink parses srog's JSON events, so leave the entry's format at its default. Third-party sinks can plug into the config the same way — register a factory with srog.RegisterSinkType(name, factory) and read type-specific settings from the entry's options via SinkSpec.DecodeOptions; a writer that implements io.Closer is closed by Logger.Close.

If you cannot run a shipper, the opt-in srog/srogelastic module writes directly to Elasticsearch's _bulk API — fully asynchronous, so it never blocks the application (Write only enqueues; a background worker batches, retries with backoff, and drops on a full queue). It depends only on the standard library:

opt, sink, err := srogelastic.WithElasticsearch(srogelastic.Config{
    Addresses: []string{"http://localhost:9200"},
    Index:     "app-logs-%{2006.01.02}", // %{…} = Go time layout, resolved per batch (UTC)
    Gzip:      true,                     // compress bulk bodies
    OnError:   func(err error) { /* metrics / alert */ },
})
if err != nil { /* ... */ }
defer sink.Close() // flushes the queue on shutdown
log := srog.MustNew(srog.WithConsole(), opt) // opt ships events as ECS

Delivery is resilient by default: network errors, 429 and 5xx responses are retried with exponential backoff, and when a _bulk response reports partial failures only the rejected documents are resent — an accepted document is never duplicated and one poisoned document cannot sink a batch. Set DataStream: true to target a data stream (bulk actions switch to create).

The module also registers itself with the declarative config, so importing it (blank import works) enables:

{"sinks": [{
    "type": "elasticsearch",
    "options": {
        "addresses": ["http://es:9200"],
        "index": "app-logs-%{2006.01.02}",
        "gzip": true,
        "dataStream": false,
        "username": "elastic", "password": "secret",
        "batchSize": 500, "flushInterval": "5s", "timeout": "30s"
    }
}]}

Events default to ECS formatting; set the entry's format to override. Logger.Close flushes and closes the sink.

🧵 Request-scoped logging

Enrich a logger once and stash it in the context.Context. Downstream code — including services that know nothing about HTTP or gRPC — pulls it back out with srog.FromContext, which never returns nil (it falls back to the default logger).

// at the edge: derive a request-scoped logger and put it in the context
rl := log.ForContext("RequestId", srog.NewID())
ctx = srog.NewContext(ctx, rl)        // or: rl.IntoContext(ctx)

// deep inside a service: retrieve it; tag it with the service name
func (s *Billing) Charge(ctx context.Context, cents int) {
    log := srog.FromContext(ctx).Named("billing") // adds service=billing
    log.Information("charging {Amount} cents", cents)
}

Every line then shares the same RequestId, so a single query pulls the whole request:

{"level":"info","RequestId":"f93e…401f","message":"handling checkout"}
{"level":"info","RequestId":"f93e…401f","service":"billing","Amount":999,"message":"charging 999 cents"}
{"level":"info","RequestId":"f93e…401f","status":200,"duration_ms":1.2,"Method":"GET","Path":"/checkout","message":"GET /checkout -> 200"}

Integration modules. The core library depends only on zerolog and lumberjack. Framework integrations live in separate modules so their dependencies never reach core: srog/sroghttp (stdlib, in-tree), srog/sroggrpc (gRPC), srog/srogecho (Echo), srog/srogotel (OpenTelemetry trace correlation + OTLP log export), and srog/srogelastic (direct Elasticsearch _bulk sink, stdlib-only). Import only what you use.

HTTP middleware (srog/sroghttp)

Standard-library net/http middleware — no extra dependencies. It reuses an incoming X-Request-Id or generates one, echoes it on the response, injects the request-scoped logger into the context, and logs completion with method, path, status, byte count, remote address, and duration. The level is chosen by status: 5xx → Error, 4xx → Warning, else Information.

mw := sroghttp.Middleware(log,
    sroghttp.WithSkip(func(r *http.Request) bool { return r.URL.Path == "/healthz" }),
    sroghttp.WithStartLog(true), // also log when the request begins
)
http.ListenAndServe(":8080", mw(router))

// in a handler:
func handler(w http.ResponseWriter, r *http.Request) {
    srog.FromContext(r.Context()).Information("handling checkout")
}

Options: WithHeader · WithField · WithIDGenerator · WithSkip · WithStartLog

gRPC interceptors (srog/sroggrpc)

Server interceptors that mirror the HTTP behavior using gRPC metadata (x-request-id), logging completion at a level chosen from the gRPC status code:

srv := grpc.NewServer(
    grpc.UnaryInterceptor(sroggrpc.UnaryServerInterceptor(log)),
    grpc.StreamInterceptor(sroggrpc.StreamServerInterceptor(log)),
)
// handlers use srog.FromContext(ctx) exactly as in HTTP

Note: srog/sroggrpc pulls in google.golang.org/grpc. The core logger and srog/sroghttp have no gRPC dependency — split this subpackage into its own module if you want to keep the core dependency-light.

🌍 Global logger

A package-level default mirrors Serilog's static Log facade:

srog.SetDefault(srog.NewConsole())
srog.Information("started on {Port}", 8080)
srog.Error(err, "boom in {Op}", "flush")

⚡ Performance

Template parsing is cached (string literals hit the cache ~100% of the time), and value→field binding uses a type switch instead of reflection for common types. Measured on a Ryzen 5 8400F:

Benchmark ns/op B/op allocs/op
Rendered ~196 48 1
StructuredOnly ~125 0 0
ParseCached ~12 0 0

The single allocation on the rendered path is the message string handed to zerolog. Disable rendering with WithRenderedMessage(false) for a fully zero-allocation hot path when you only consume structured fields plus @mt.

go test ./...                       # run the suite
go test -bench . -benchmem ./...    # run benchmarks

📄 License

MIT — see LICENSE.

Built with ❤️ on top of zerolog.

Documentation

Overview

Package srog is a structured logger built on zerolog that speaks Serilog's message-template language. Named holes in a template become typed structured fields, while an optional human-readable message is rendered alongside them:

log := srog.MustNew(srog.WithConsole())
log.Information("User {Username} logged in from {IP}", "neo", "10.0.0.1")

A logger fans out to any number of sinks, each with its own format and level — for example pretty console plus rotated JSON files that Fluent Bit can tail:

log, err := srog.New(
    srog.WithConsole(srog.MinLevel(srog.DebugLevel)),
    srog.WithFile("/var/log/app.log",
        srog.MinLevel(srog.InformationLevel),
        srog.Rotate(srog.Rotation{MaxSizeMB: 100, MaxBackups: 10, Compress: true, Every: srog.Daily}),
    ),
)
defer log.Close()

The API mirrors Serilog (Verbose/Debug/Information/Warning/Error/Fatal and ForContext) while keeping zerolog's zero-allocation event model underneath.

Index

Constants

View Source
const (
	// TimeRFC3339 is ISO 8601 with second precision — the default layout.
	TimeRFC3339 = time.RFC3339
	// TimeRFC3339Nano is ISO 8601 with nanosecond precision.
	TimeRFC3339Nano = time.RFC3339Nano
	// TimeDateTime is "2006-01-02 15:04:05" — a human-friendly datetime.
	TimeDateTime = time.DateTime
	// TimeDateOnly is "2006-01-02".
	TimeDateOnly = time.DateOnly
	// TimeOnly is "15:04:05".
	TimeOnly = time.TimeOnly
	// TimeKitchen is "3:04PM".
	TimeKitchen = time.Kitchen

	// TimeUnix emits epoch seconds as a JSON number.
	TimeUnix = zerolog.TimeFormatUnix
	// TimeUnixMs emits epoch milliseconds as a JSON number.
	TimeUnixMs = zerolog.TimeFormatUnixMs
	// TimeUnixMicro emits epoch microseconds as a JSON number.
	TimeUnixMicro = zerolog.TimeFormatUnixMicro
	// TimeUnixNano emits epoch nanoseconds as a JSON number.
	TimeUnixNano = zerolog.TimeFormatUnixNano
)

Common timestamp layouts for WithTimeFormat. The first group are ISO/Go time layouts rendered as JSON strings; the epoch group re-exports zerolog's sentinels (emitted as JSON numbers) so callers need not import zerolog.

View Source
const StackFieldName = stackFieldName

StackFieldName is the exported name of the field under which srog stores a captured stack trace, and which console sinks pretty-print. Integrations that capture their own stack (for example panic recovery, where the useful frames only exist at recover time) should attach it under this key.

Variables

This section is empty.

Functions

func AddContextField

func AddContextField(fn ContextFieldFunc)

AddContextField registers fn so that Ctx and the *Ctx package helpers enrich every context-scoped log with the fields fn extracts. Call it once at startup (it is safe for concurrent use). Integrations provide ready-made extractors — e.g. the srogotel module registers OpenTelemetry trace_id/span_id.

func Debug

func Debug(tmpl string, args ...any)

func DebugCtx

func DebugCtx(ctx context.Context, tmpl string, args ...any)

func Error

func Error(err error, tmpl string, args ...any)

func ErrorCtx

func ErrorCtx(ctx context.Context, err error, tmpl string, args ...any)

func Fatal

func Fatal(err error, tmpl string, args ...any)

func FatalCtx

func FatalCtx(ctx context.Context, err error, tmpl string, args ...any)

func Info

func Info(tmpl string, args ...any)

func InfoCtx

func InfoCtx(ctx context.Context, tmpl string, args ...any)

InfoCtx is a shorthand alias for InformationCtx.

func Information

func Information(tmpl string, args ...any)

func InformationCtx

func InformationCtx(ctx context.Context, tmpl string, args ...any)

func NewContext

func NewContext(ctx context.Context, l *Logger) context.Context

NewContext returns a copy of ctx carrying l. Middleware and interceptors use it to propagate a request-scoped logger (already enriched with a request ID, service name, etc.) down the call chain.

func NewID

func NewID() string

NewID returns a random 128-bit identifier as a 32-character hex string, suitable as a request/correlation ID. It is safe for concurrent use.

func RegisterSinkType added in v1.1.0

func RegisterSinkType(name string, factory SinkFactory)

RegisterSinkType makes factory available to Config under the given type name (case-insensitive), letting external modules plug their sinks into the declarative JSON/YAML config. The built-in names (console, file, stdout, stderr) always win and cannot be overridden. Modules typically register in init, so importing e.g. srog/srogotel enables `"type": "otlp"`. Safe for concurrent use; a repeated name replaces the earlier factory.

func SetDefault

func SetDefault(l *Logger)

SetDefault replaces the package-level logger used by the top-level functions.

func Verbose

func Verbose(tmpl string, args ...any)

func VerboseCtx

func VerboseCtx(ctx context.Context, tmpl string, args ...any)

func Warning

func Warning(tmpl string, args ...any)

func WarningCtx

func WarningCtx(ctx context.Context, tmpl string, args ...any)

Types

type Config

type Config struct {
	// Level is the default minimum level: one of verbose, debug, information
	// (or info), warning (or warn), error, fatal. Empty means Information.
	Level string `json:"level,omitempty" yaml:"level,omitempty"`
	// Render toggles the human-readable message. Pointer so that an explicit
	// false is distinguishable from "unset" (which defaults to true).
	Render *bool `json:"render,omitempty" yaml:"render,omitempty"`
	// Caller annotates each event with the calling file and line.
	Caller bool `json:"caller,omitempty" yaml:"caller,omitempty"`
	// Timestamp adds a timestamp to each event. Pointer so an explicit false is
	// distinguishable from "unset" (which defaults to true).
	Timestamp *bool `json:"timestamp,omitempty" yaml:"timestamp,omitempty"`
	// StackTrace captures a stack when an error is logged.
	StackTrace bool `json:"stackTrace,omitempty" yaml:"stackTrace,omitempty"`
	// TimeFormat is a friendly name (rfc3339, rfc3339nano, datetime, dateonly,
	// timeonly, kitchen, unix, unixms, unixmicro, unixnano) or a raw Go layout.
	// Empty leaves the default (RFC3339).
	TimeFormat string `json:"timeFormat,omitempty" yaml:"timeFormat,omitempty"`
	// Sinks lists the output destinations. Empty yields New's default (JSON to
	// stdout).
	Sinks []SinkSpec `json:"sinks,omitempty" yaml:"sinks,omitempty"`
}

Config is a declarative, serializable description of a Logger, suitable for loading from a JSON or YAML file (or any source that unmarshals into it). It mirrors the functional Option API: every field maps to one With* option, and an empty/zero field leaves that option at its default. Build it programmatically or decode it, then call Build:

c, err := srog.LoadConfigFile("logging.json")
if err != nil { ... }
log, err := c.Build()

The struct carries both `json` and `yaml` tags, so it decodes with the standard library or with gopkg.in/yaml.v3 without srog itself depending on a YAML parser.

func LoadConfig

func LoadConfig(r io.Reader) (Config, error)

LoadConfig decodes a JSON Config from r.

func LoadConfigFile

func LoadConfigFile(path string) (Config, error)

LoadConfigFile reads and decodes a JSON Config from path.

func (Config) Build

func (c Config) Build() (*Logger, error)

Build constructs a Logger from the Config, returning an error if any field is invalid or a file sink cannot be opened.

func (Config) Options

func (c Config) Options() ([]Option, error)

Options translates the Config into the equivalent slice of functional Options, in the same order the fields are declared. Useful for composing a config with additional programmatic options:

opts, _ := cfg.Options()
log, _ := srog.New(append(opts, srog.WithWriter(buf))...)

type ContextFieldFunc

type ContextFieldFunc func(ctx context.Context) []Field

ContextFieldFunc pulls zero or more structured fields out of a context. It is how correlation data that lives in the context — OpenTelemetry trace/span IDs, a tenant, a deadline — is attached to logs without srog depending on those packages. Register implementations with AddContextField.

type Field

type Field struct {
	Name  string
	Value any
}

Field is a name/value pair extracted from a context by a ContextFieldFunc.

type Format

type Format uint8

Format selects how a sink serializes events.

const (
	// FormatJSON writes newline-delimited JSON (NDJSON) — the machine-readable
	// form consumed by log shippers such as Fluent Bit.
	FormatJSON Format = iota
	// FormatConsole writes colorized, human-friendly lines with structured
	// parameters omitted (they remain available via a JSON sink).
	FormatConsole
	// FormatECS writes NDJSON using Elastic Common Schema field names
	// (@timestamp, log.level, error.message, ...), so events index cleanly into
	// Elasticsearch and render in Kibana without a Logstash mapping.
	FormatECS
	// FormatOTel writes each event as a single OpenTelemetry log record encoded
	// as OTLP/JSON (one LogRecord per line), so events feed straight into an
	// OpenTelemetry logs pipeline (Collector -> Loki/Elastic/...).
	FormatOTel
	// FormatTemplate renders each event through a Serilog-style output template
	// supplied with AsTemplate — literal text plus placeholders like
	// {Timestamp:15:04:05}, {Level:u3}, {Message}, {Properties}, or any event
	// field by name, each honoring ",alignment" and ":format" specifiers.
	FormatTemplate
)

type Interval

type Interval uint8

Interval selects time-based rotation cadence for a file sink. It composes with size-based rotation: a file rolls over when either trigger fires.

const (
	// NoInterval disables time-based rotation (size-based only, if configured).
	NoInterval Interval = iota
	// Hourly rotates at the top of every hour.
	Hourly
	// Daily rotates at midnight.
	Daily
)

type Level

type Level int8

Level mirrors Serilog's severity ladder. Values map onto zerolog levels.

const (
	VerboseLevel     Level = Level(zerolog.TraceLevel)
	DebugLevel       Level = Level(zerolog.DebugLevel)
	InformationLevel Level = Level(zerolog.InfoLevel)
	WarningLevel     Level = Level(zerolog.WarnLevel)
	ErrorLevel       Level = Level(zerolog.ErrorLevel)
	FatalLevel       Level = Level(zerolog.FatalLevel)
)

func ParseLevel

func ParseLevel(s string) (Level, error)

ParseLevel resolves a Serilog-style level name (case-insensitive) to a Level.

type Logger

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

Logger is an immutable, concurrency-safe logger. Derive enriched loggers with ForContext; the zero value is not usable — construct one with New/MustNew.

func Ctx

func Ctx(ctx context.Context) *Logger

Ctx returns the logger for ctx, enriched with any fields produced by the registered ContextFieldFuncs (see AddContextField). It resolves the base logger with FromContext (falling back to Default), so it never returns nil and is the idiomatic entry point for context-scoped logging:

srog.Ctx(ctx).Information("processing {OrderId}", id) // carries trace_id, etc.

With no extractors registered it costs no more than FromContext.

func Default

func Default() *Logger

Default returns the current package-level logger.

func ForContext

func ForContext(name string, value any) *Logger

ForContext derives an enriched logger from the package default.

func FromContext

func FromContext(ctx context.Context) *Logger

FromContext returns the Logger stored in ctx, or the package Default logger when none is present. It never returns nil, so callers can log unconditionally:

srog.FromContext(ctx).Information("processing {OrderId}", id)

func MustNew

func MustNew(opts ...Option) *Logger

MustNew is like New but panics on error. Use it for configurations that cannot fail (no file sinks) or when a startup failure should abort the process.

func New

func New(opts ...Option) (*Logger, error)

New constructs a Logger from the given options. With no sink option it defaults to a single JSON sink on os.Stdout at Information level. It returns an error if a file sink cannot be opened.

func NewConsole

func NewConsole() *Logger

NewConsole is a convenience constructor for development: colorized console output at Debug level with pretty stack traces on errors.

func NewFromConfig

func NewFromConfig(c Config) (*Logger, error)

NewFromConfig builds a Logger from an already-parsed Config. It is shorthand for Config.Build.

func NewFromConfigFile

func NewFromConfigFile(path string) (*Logger, error)

NewFromConfigFile loads a JSON Config from path and builds a Logger from it.

func (*Logger) Close

func (l *Logger) Close() error

Close releases any file sinks held by the logger. Call it once on the root logger during shutdown; derived (ForContext) loggers share the same sinks.

func (*Logger) Debug

func (l *Logger) Debug(tmpl string, args ...any)

Debug logs diagnostic detail useful during development.

func (*Logger) Enabled

func (l *Logger) Enabled(level Level) bool

Enabled reports whether events at level would be emitted by this logger.

func (*Logger) Error

func (l *Logger) Error(err error, tmpl string, args ...any)

Error logs a failure. Pass the triggering error as err; it is attached under the standard "error" field. Use nil when there is no associated error.

func (*Logger) Fatal

func (l *Logger) Fatal(err error, tmpl string, args ...any)

Fatal logs a failure, flushes all sinks, and then calls os.Exit(1). The flush ensures the final event reaches file/async sinks before the process dies.

func (*Logger) ForContext

func (l *Logger) ForContext(name string, value any) *Logger

ForContext returns a child logger that includes the given property on every event, equivalent to Serilog's ForContext. The receiver is left unchanged.

func (*Logger) ForContextValues

func (l *Logger) ForContextValues(fields map[string]any) *Logger

ForContextValues returns a child logger enriched with several properties.

func (*Logger) Info

func (l *Logger) Info(tmpl string, args ...any)

Info is a shorthand alias for Information.

func (*Logger) Information

func (l *Logger) Information(tmpl string, args ...any)

Information logs a normal, expected event.

func (*Logger) IntoContext

func (l *Logger) IntoContext(ctx context.Context) context.Context

IntoContext stores the receiver in ctx and returns the derived context. It is the fluent counterpart of NewContext:

ctx = log.ForContext("RequestId", id).IntoContext(ctx)

func (*Logger) Named

func (l *Logger) Named(service string) *Logger

Named returns a child logger tagged with a "service" property, the idiomatic way to identify which component or service emitted an event:

svcLog := log.Named("billing")
svcLog.Information("charged {Amount}", 999) // every event carries service=billing

func (*Logger) Verbose

func (l *Logger) Verbose(tmpl string, args ...any)

Verbose logs at the lowest severity (maps to zerolog Trace).

func (*Logger) Warning

func (l *Logger) Warning(tmpl string, args ...any)

Warning logs a recoverable concern.

func (*Logger) WithLevel

func (l *Logger) WithLevel(level Level) *Logger

WithLevel returns a child logger with a different minimum level.

func (*Logger) WithStackTrace

func (l *Logger) WithStackTrace(on bool) *Logger

WithStackTrace returns a child logger that captures (or stops capturing) a stack trace on logged errors. It is the per-logger counterpart of the WithStackTrace construction option — useful when a caller has already captured a stack (e.g. a panic recovery) and wants to attach it under the "stack" field itself without srog adding a second, less useful one from the recovery site.

type Option

type Option func(*config)

Option customizes a Logger at construction time.

func WithCaller

func WithCaller(on bool) Option

WithCaller annotates each event with the calling file and line.

func WithConsole

func WithConsole(opts ...SinkOption) Option

WithConsole adds a colorized console sink writing to os.Stdout. Use Target to redirect (e.g. os.Stderr) and the sink options above to tune it.

func WithErrorHandler

func WithErrorHandler(fn func(error)) Option

WithErrorHandler installs fn to be called when a sink's underlying Write fails. By default such errors are dropped (zerolog's behavior); a handler lets you count them, alert, or fall back to stderr. fn must be safe for concurrent use and should not itself log through the same logger.

func WithFile

func WithFile(path string, opts ...SinkOption) Option

WithFile adds a JSON file sink at path. Combine with Rotate for rotation and retention. The parent directory must already exist.

func WithLevel

func WithLevel(l Level) Option

WithLevel sets the default minimum level emitted by sinks that do not specify their own via MinLevel.

func WithRenderedMessage

func WithRenderedMessage(on bool) Option

WithRenderedMessage toggles rendering of the human-readable message into the "message" field. Disable it for maximum throughput when you only consume the structured fields plus the raw template. Enabled by default. Console sinks rely on the rendered message, so keep it on when using them.

func WithSampling

func WithSampling(s Sampler) Option

WithSampling applies s to every event before it reaches the sinks. Sampling is evaluated after level filtering. Combine samplers to taste; see EveryN and BurstLimit.

func WithStackTrace

func WithStackTrace(on bool) Option

WithStackTrace captures a call stack whenever an error is logged via Error or Fatal. The stack is stored in the structured "stack" field (always present in JSON output) and pretty-printed by console sinks.

func WithTimeFormat

func WithTimeFormat(layout string) Option

WithTimeFormat sets the timestamp layout for this logger's JSON output. Pass a Go time layout (e.g. time.RFC3339Nano) or one of the package's Time* constants (TimeRFC3339Nano, TimeDateTime, TimeUnix, TimeUnixMs, ...) for ISO or epoch timestamps without importing zerolog. The format is applied per-logger via a hook, so unlike setting zerolog.TimeFieldFormat directly it does not leak into other loggers in the process. The default, RFC3339, is ISO 8601 and parses cleanly in Fluent Bit with `Time_Key time`.

func WithTimestamp

func WithTimestamp(on bool) Option

WithTimestamp adds a timestamp to each event. Enabled by default.

func WithWriter

func WithWriter(w io.Writer, opts ...SinkOption) Option

WithWriter adds a sink writing to an arbitrary io.Writer. It defaults to JSON; pass AsConsole to format it for human reading instead.

type Rotation

type Rotation struct {
	// MaxSizeMB rotates the file once it grows beyond this many megabytes.
	// Zero means no size-based trigger.
	MaxSizeMB int
	// MaxBackups caps how many rotated files are retained (0 = keep all).
	MaxBackups int
	// MaxAgeDays deletes rotated files older than this many days (0 = no limit).
	MaxAgeDays int
	// Compress gzips rotated files.
	Compress bool
	// LocalTime uses local time (instead of UTC) in backup timestamps and for
	// computing rotation boundaries.
	LocalTime bool
	// Every selects an additional time-based rotation cadence.
	Every Interval
}

Rotation configures rotation and retention for a file sink. The zero value performs no rotation. Size-based rotation triggers when the active file exceeds MaxSizeMB; time-based rotation triggers when Every elapses.

type RotationSpec

type RotationSpec struct {
	MaxSizeMB  int    `json:"maxSizeMB,omitempty" yaml:"maxSizeMB,omitempty"`
	MaxBackups int    `json:"maxBackups,omitempty" yaml:"maxBackups,omitempty"`
	MaxAgeDays int    `json:"maxAgeDays,omitempty" yaml:"maxAgeDays,omitempty"`
	Compress   bool   `json:"compress,omitempty" yaml:"compress,omitempty"`
	LocalTime  bool   `json:"localTime,omitempty" yaml:"localTime,omitempty"`
	Every      string `json:"every,omitempty" yaml:"every,omitempty"`
}

RotationSpec is the serializable form of Rotation. Every is a friendly cadence name ("", "none", "hourly", "daily") instead of the Interval enum.

type Sampler

type Sampler = zerolog.Sampler

Sampler decides which events are emitted, for flood control. It aliases zerolog's Sampler so the built-in samplers below (and any custom one) compose.

func BurstLimit

func BurstLimit(burst uint32, period time.Duration, next Sampler) Sampler

BurstLimit emits up to burst events per period, then defers to next for the overflow (pass nil to drop everything past the burst). Use it to cap a hot log site without losing the first events of each window:

srog.WithSampling(srog.BurstLimit(100, time.Second, srog.EveryN(100)))

func EveryN

func EveryN(n uint32) Sampler

EveryN returns a sampler that emits one of every n events, regardless of level. n<=1 emits everything.

type SinkFactory added in v1.1.0

type SinkFactory func(cfg Config, spec SinkSpec) (w io.Writer, format Format, err error)

SinkFactory builds the destination writer for an externally registered sink type. cfg is the full Config being built, so a factory can inherit logger-wide settings (such as TimeFormat); spec is the sink's own entry, with its type-specific settings in spec.Options. The returned writer receives events serialized in format (unless spec.Format overrides it); if the writer also implements io.Closer it is closed by Logger.Close.

type SinkOption

type SinkOption func(*sinkConfig)

SinkOption customizes a single sink (console, file, or writer).

func AsConsole

func AsConsole() SinkOption

AsConsole forces colorized console output for this sink.

func AsECS

func AsECS() SinkOption

AsECS forces Elastic Common Schema NDJSON output for this sink (see FormatECS).

func AsJSON

func AsJSON() SinkOption

AsJSON forces NDJSON output for this sink.

func AsOTel added in v1.1.0

func AsOTel() SinkOption

AsOTel forces OpenTelemetry OTLP/JSON log-record output for this sink (see FormatOTel).

func AsTemplate added in v1.2.0

func AsTemplate(tmpl string) SinkOption

AsTemplate renders this sink through a Serilog-style output template (see FormatTemplate). The classic console layout, for example:

srog.WithConsole(srog.AsTemplate("[{Timestamp:15:04:05} {Level:u3}] {Message}{NewLine}{Exception}"))

Built-in placeholders: {Timestamp[:layout]}, {Level[:u3|w3|u|w]}, {Message}, {MessageTemplate}, {Exception}, {Caller}, {NewLine}, {Properties[:j]}; any other name prints that event field. All support ",alignment" (e.g. {Level,-7}) and use "{{"/"}}" for literal braces. Malformed holes render as literal text, matching message-template behavior. Structured fields not referenced by the template are omitted unless {Properties} is present.

func Async

func Async(bufferSize int) SinkOption

Async offloads this sink's writes to a background goroutine backed by a queue of bufferSize events (a non-positive size uses a default). It keeps a slow destination — a file on a busy disk, a network stream — off the request path. If the queue fills the sink drops events rather than block the caller, and reports the total dropped through the error handler (see WithErrorHandler) on Close. Always Close the logger so the queue drains before exit.

func MinLevel

func MinLevel(l Level) SinkOption

MinLevel sets the minimum level this sink emits, overriding the logger-wide level for this destination only. This is what lets a console sink show Debug while a file sink keeps only Warning and above.

func NoColor

func NoColor() SinkOption

NoColor disables ANSI colors for a console sink.

func Rotate

func Rotate(r Rotation) SinkOption

Rotate enables size/time/age-based rotation for a file sink. It has no effect on non-file sinks.

type SinkSpec

type SinkSpec struct {
	// Type is "console", "file", "stdout", "stderr", or any name registered via
	// RegisterSinkType (e.g. "otlp" once srog/srogotel is imported). Required.
	Type string `json:"type" yaml:"type"`
	// Target selects the stream for a "console" sink: "stdout" (default) or
	// "stderr". Ignored for other types.
	Target string `json:"target,omitempty" yaml:"target,omitempty"`
	// Path is the file path for a "file" sink. Required for that type.
	Path string `json:"path,omitempty" yaml:"path,omitempty"`
	// Level overrides the logger-wide minimum level for this sink only.
	Level string `json:"level,omitempty" yaml:"level,omitempty"`
	// Format overrides the sink's default serialization: "json", "console",
	// "ecs", "otel" (OpenTelemetry OTLP/JSON log records), or "template"
	// (Serilog-style output template; requires Template).
	Format string `json:"format,omitempty" yaml:"format,omitempty"`
	// Template is a Serilog-style output template (see AsTemplate), e.g.
	// "[{Timestamp:15:04:05} {Level:u3}] {Message}{NewLine}{Exception}".
	// Setting it implies Format "template".
	Template string `json:"template,omitempty" yaml:"template,omitempty"`
	// NoColor disables ANSI colors for a console sink.
	NoColor bool `json:"noColor,omitempty" yaml:"noColor,omitempty"`
	// Rotation configures rotation/retention for a "file" sink.
	Rotation *RotationSpec `json:"rotation,omitempty" yaml:"rotation,omitempty"`
	// Options carries type-specific settings for sinks registered via
	// RegisterSinkType; built-in types ignore it. Factories typically read it
	// through DecodeOptions.
	Options map[string]any `json:"options,omitempty" yaml:"options,omitempty"`
}

SinkSpec is the serializable form of one sink.

func (SinkSpec) DecodeOptions added in v1.1.0

func (s SinkSpec) DecodeOptions(v any) error

DecodeOptions re-marshals the sink's Options map into v (a pointer to a struct with json tags), so a SinkFactory can parse its type-specific settings without touching the raw map. A nil Options leaves v unchanged.

Directories

Path Synopsis
srogecho module
srogelastic module
sroggrpc module
Package sroghttp provides net/http middleware that attaches a request-scoped srog logger to each request: it assigns a request ID, propagates the logger through the request context, and logs request completion with structured fields.
Package sroghttp provides net/http middleware that attaches a request-scoped srog logger to each request: it assigns a request ID, propagates the logger through the request context, and logs request completion with structured fields.
srogotel module

Jump to

Keyboard shortcuts

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