logkit

package module
v1.0.0 Latest Latest
Warning

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

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

README

logkit

High-performance, structured, and secure Go logging library built on top of log/slog (standard library, Go 1.21+). Designed for low overhead, request-context propagation, and defense-in-depth safety.


⚡ Fitur Utama

  • Zero-Allocation Context Propagation: Propagasi request ID, correlation ID, trace/span ID, dan fields kustom via context.Context tanpa alokasi heap di hot-path.
  • High-Performance Async Logging: slogx.AsyncWriter untuk buffering log secara asinkron (mengurangi overhead syscall I/O seperti Pino).
  • Advanced Pino-like Redaction: Konfigurasi RedactConfig untuk penyaringan data sensitif tingkat lanjut menggunakan dot-notation path, wildcards, custom censor, atau penghapusan key (Remove: true).
  • Dynamic Caller Stack Skipping: Dukungan WithCallerSkip(n) untuk menyesuaikan kedalaman stack trace saat membungkus logger pada helper/wrapper function.
  • Wide Events & Tail Sampling: Akumulasi log dalam satu lifecycle request (canonical log line) dan penentuan sampling keputusan di akhir request via TailSampler.
  • Secure by Construction: Pemisahan tegas pesan error publik dengan internal (secure.SafeError) serta proteksi data sensitif di level tipe (secure.Secret[T]).

📂 Struktur Package

logkit/
├── logkit.go, levels.go   # Logger core (slog wrapper) & Custom levels (Trace, Fatal)
├── logctx/                # Context-propagation (request/correlation ID)
├── event/                 # Wide event / Canonical log line builder
├── secure/                # SafeError & Secret[T] (redaksi otomatis level tipe)
├── sampler/               # Tail sampling untuk wide event
├── slogx/                 # AsyncWriter, JSON/Console handlers, & Advanced Redactor
└── middleware/
    ├── httpmw/            # HTTP Middleware (chi, standard mux, etc.)
    └── grpcmw/            # gRPC Interceptors (Go module terpisah)

🚀 Quick Start

1. Inisialisasi Logger (dengan Async Writer & Redaction)
package main

import (
	"context"
	"log/slog"
	"os"
	"time"

	"github.com/semmidev/logkit"
	"github.com/semmidev/logkit/logctx"
	"github.com/semmidev/logkit/slogx"
)

func main() {
	// 1. Setup Async Writer (High-Performance/Pino-like)
	asyncWriter := slogx.NewAsyncWriter(os.Stdout, 8192, 100*time.Millisecond)
	defer asyncWriter.Close()

	// 2. Setup Logger dengan Advanced Path Redaction
	logger := logkit.New(logkit.Config{
		Format:      logkit.FormatJSON,
		Writer:      asyncWriter,
		Level:       slog.LevelDebug,
		ServiceName: "user-service",
		Version:     "1.0.0",
		Environment: "production",
		Redact: &logkit.RedactConfig{
			Paths: []string{
				"password",                  // Redaksi key "password" di level mana pun
				"req.headers.authorization", // Redaksi nested path spesifik
				"card.number",               // Redaksi credit card di dalam group card
			},
			Censor: "[SENSITIF]",
		},
	})
	defer logger.Sync() // Pastikan semua log tertulis sebelum app exit

	ctx := context.Background()
	logger.Info(ctx, "Server started successfully")
}
2. Context Propagation & Enrichment
// Stamp IDs di middleware
ctx = logctx.WithRequestID(ctx, logctx.NewID())
ctx = logctx.WithCorrelationID(ctx, logctx.NewID())
ctx = logctx.WithFields(ctx)

// Tambah context bisnis di sub-function
logctx.Enrich(ctx, "user_id", "u_999", "tier", "premium")

logger.Info(ctx, "User purchased item")
// Hasil output log otomatis menyertakan: request_id, correlation_id, user_id, dan tier.
3. Custom Caller Skipping (Helper/Wrapper)
func logErrorHelper(ctx context.Context, logger *logkit.Logger, err error) {
    // Lewati 1 frame tambahan agar file:line menunjuk ke pemanggil logErrorHelper
    logger.WithCallerSkip(1).Error(ctx, "database failed", "error", err)
}

⚙️ Panduan Ekstensi

Tujuan Ekstensi Metode
Mengirim log ke loket pihak ketiga (Loki, Datadog) Implementasi standard slog.Handler lalu berikan ke Config.Handler
Mengganti algoritma generator ID Override logctx.NewID lewat config GenerateID di middleware
Kriteria sampling kustom Implementasi sampler.Decider
Filter/Ubah data sebelum ditulis Berikan fungsi kustom pada slogx.HandlerOptions.ReplaceAttr

🛠️ Testing & Running

# Menjalankan unit tests & benchmark performa
go test ./... -v
go test -bench=. -benchmem ./...

# Menjalankan contoh aplikasi
go run ./examples/basicapp
go run ./examples/httpapp

Documentation

Overview

Package logkit is an opinionated, extensible layer on top of the standard library's log/slog, built around three ideas:

  1. Context propagation. Request/correlation/trace ids and ad-hoc business fields travel on context.Context (see the logctx sub-package) so any logkit.Logger call anywhere in the call graph picks them up automatically -- no need to thread a *Logger through every function signature.
  2. Wide events / canonical log lines. Instead of many scattered, low-context log statements per request, the event sub-package lets you accumulate one rich event and emit it once (see https://loggingsucks.com/ for the rationale).
  3. Secure by construction. The secure sub-package gives you SafeError (public/internal message separation) and Secret[T] (type-level redaction), following the JetBrains guide to secure Go error handling.

Every extension point is a plain interface from the standard library or from this package: slog.Handler for output destinations, slog.LogValuer for redaction/custom formatting, sampler.Decider for sampling policy. Nothing here is closed for extension.

Index

Constants

View Source
const (
	// LevelTrace is for extremely verbose, per-iteration detail that is
	// normally too noisy even for Debug (e.g. raw wire bytes, tight loop
	// state). Sits below Debug.
	LevelTrace slog.Level = slog.LevelDebug - 4 // -8

	// LevelFatal is for errors that are about to terminate the process.
	// Logging at this level does NOT call os.Exit by itself -- that
	// decision belongs to the caller (see Logger.Fatal), so that the
	// logger stays testable and side-effect free.
	LevelFatal slog.Level = slog.LevelError + 4 // 12
)

Custom levels that extend the four built-in slog levels (Debug=-4, Info=0, Warn=4, Error=8).

slog.Level is just an int, so adding new levels is a matter of picking values that sort correctly relative to the built-ins. This is the officially recommended way to extend slog's leveling (see log/slog package docs, "Levels").

Variables

This section is empty.

Functions

func LevelString

func LevelString(lvl slog.Level) string

LevelString renders lvl using the extended name table, falling back to slog's default String() for anything it doesn't recognize (including levels offset from the named ones, e.g. "INFO+2").

func ParseLevel

func ParseLevel(s string) (slog.Level, bool)

ParseLevel parses a case-insensitive level name ("trace", "debug", "info", "warn"/"warning", "error", "fatal") into a slog.Level. This is the counterpart to LevelString and is handy for reading the level out of an env var or config file.

Types

type Config

type Config struct {
	// Format picks a built-in handler. Ignored if Handler is set.
	Format Format
	// Writer is where the built-in handler writes to. Defaults to
	// os.Stderr. Ignored if Handler is set.
	Writer io.Writer
	// Level sets the initial minimum level. Defaults to slog.LevelInfo.
	// You can change it later at runtime via Logger.SetLevel --
	// New always wraps this in an internal *slog.LevelVar regardless of
	// what's passed here.
	Level slog.Level
	// AddSource includes file:line on every record. Ignored if Handler
	// is set (configure it directly on your handler instead).
	AddSource bool
	// SensitiveKeys is a defense-in-depth redaction list forwarded to
	// the built-in handler (see slogx.HandlerOptions.SensitiveKeys).
	// Ignored if Handler is set.
	SensitiveKeys []string
	// Redact configures advanced, Pino-like redaction behavior. If set,
	// it takes precedence over SensitiveKeys. Ignored if Handler is set.
	Redact *RedactConfig

	// ServiceName, Version, Environment, if non-empty, are attached as
	// base attributes to every log line -- the minimum "which process
	// emitted this" context a wide event / canonical log line needs.
	ServiceName string
	Version     string
	Environment string

	// Handler, if set, is used as-is instead of building one from
	// Format/Writer/AddSource/SensitiveKeys. This is the escape hatch:
	// wrap your own slog.Handler (e.g. one that also ships to OTel, or a
	// third-party handler) and logkit's context-aware methods,
	// SafeError, Secret[T], and event/sampler machinery all keep
	// working on top of it unchanged.
	Handler slog.Handler
}

Config configures a Logger built with New. All fields are optional; the zero value produces a reasonable development-friendly logger (console format, info level, stderr).

type Format

type Format int

Format selects one of the built-in output formats for New. Use Config.Handler instead if you need something these don't cover -- Format/Writer is a convenience, not the only way in.

const (
	// FormatJSON writes newline-delimited JSON. Use this in production.
	FormatJSON Format = iota
	// FormatConsole writes colorized, human-readable lines. Use this for
	// local development.
	FormatConsole
)

type Logger

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

Logger wraps a slog.Handler with context-aware logging methods and a runtime-adjustable level. It is cheap to copy and safe for concurrent use (same guarantees as *slog.Logger, which it wraps).

func New

func New(cfg Config) *Logger

New builds a Logger from cfg.

func NewDefault

func NewDefault() *Logger

NewDefault returns a ready-to-use development Logger: colorized console output on stderr, info level. Equivalent to New(Config{Format: FormatConsole}).

func NewNop

func NewNop() *Logger

NewNop returns a Logger that discards everything. Useful as a default in code that takes a *Logger but shouldn't panic if the caller (e.g. a test) doesn't provide one.

func (*Logger) Debug

func (l *Logger) Debug(ctx context.Context, msg string, args ...any)

Debug logs at slog.LevelDebug: development/diagnostic detail not normally enabled in production.

func (*Logger) Error

func (l *Logger) Error(ctx context.Context, msg string, args ...any)

Error logs at slog.LevelError. If args contains an error under the key "error" whose value implements slog.LogValuer (e.g. *secure.SafeError), it's expanded/redacted automatically -- no special handling needed at the call site beyond secure.Wrap/secure.New producing that kind of error in the first place.

func (*Logger) Fatal

func (l *Logger) Fatal(ctx context.Context, msg string, args ...any)

Fatal logs at LevelFatal and then terminates the process via os.Exit(1). Use sparingly and only at the outermost level (e.g. main(), or a supervisor goroutine deciding the process can't continue) -- never in library code, since it removes the caller's ability to handle the failure. Prefer returning an error everywhere else.

func (*Logger) Info

func (l *Logger) Info(ctx context.Context, msg string, args ...any)

Info logs at slog.LevelInfo: normal operational messages and wide events / canonical log lines.

func (*Logger) Leveler

func (l *Logger) Leveler() *slog.LevelVar

Leveler returns the *slog.LevelVar backing this logger, so callers can wire it up to, say, an admin HTTP endpoint or a SIGHUP handler to change verbosity without restarting the process.

func (*Logger) SetLevel

func (l *Logger) SetLevel(level slog.Level)

SetLevel changes the minimum level for this logger (and everything derived from it via With/WithGroup, since they share the same *slog.LevelVar) at runtime.

func (*Logger) Slog

func (l *Logger) Slog() *slog.Logger

Slog returns the underlying *slog.Logger, for interoperability with libraries that expect one directly (e.g. http.Server.ErrorLog via slog.NewLogLogger, or third-party packages that accept a *slog.Logger for their own logging).

func (*Logger) Sync

func (l *Logger) Sync() error

Sync flushes any buffered log entries. Call this before the program exits (for example, via defer logger.Sync()) to ensure all logs are written.

func (*Logger) Trace

func (l *Logger) Trace(ctx context.Context, msg string, args ...any)

Trace logs at LevelTrace: the most verbose level, for detail too noisy even for Debug.

func (*Logger) Warn

func (l *Logger) Warn(ctx context.Context, msg string, args ...any)

Warn logs at slog.LevelWarn: recoverable problems worth a human's attention but not an immediate page.

func (*Logger) With

func (l *Logger) With(args ...any) *Logger

With returns a new Logger with args attached to every subsequent log call, exactly like slog.Logger.With. Cheap; does not affect l.

func (*Logger) WithCallerSkip

func (l *Logger) WithCallerSkip(n int) *Logger

WithCallerSkip returns a new Logger that skips an additional n frames when resolving the file:line of a log call. This is useful when writing helper functions or logging middleware wrappers.

func (*Logger) WithError

func (l *Logger) WithError(err error) *Logger

WithError is a small convenience for the extremely common `logger.Error(ctx, "...", "error", err)` call, saving four characters and a moment's thought about the key name so it stays consistent across a codebase: logger.WithError(err).Error(ctx, "operation failed").

func (*Logger) WithGroup

func (l *Logger) WithGroup(name string) *Logger

WithGroup returns a new Logger that nests subsequent attributes under name, exactly like slog.Logger.WithGroup.

type RedactConfig

type RedactConfig = slogx.RedactConfig

RedactConfig configures advanced, path-based log redaction.

Directories

Path Synopsis
Package event implements the "wide event" / "canonical log line" pattern from https://loggingsucks.com/: instead of scattering 15 vague log statements across a request's lifecycle, accumulate a single, rich, structured event as the request flows through your code, then emit it exactly once when the request finishes.
Package event implements the "wide event" / "canonical log line" pattern from https://loggingsucks.com/: instead of scattering 15 vague log statements across a request's lifecycle, accumulate a single, rich, structured event as the request flows through your code, then emit it exactly once when the request finishes.
examples
basicapp command
Example: basic usage of logkit outside of any HTTP/gRPC framework -- levels, context propagation, secret redaction, and safe errors.
Example: basic usage of logkit outside of any HTTP/gRPC framework -- levels, context propagation, secret redaction, and safe errors.
httpapp command
Example: an HTTP server using httpmw to get one canonical log line per request, automatic request-id propagation, panic recovery, and secure error responses -- all pluggable via a plain func(http.Handler) http.Handler, so this drops straight into chi (r.Use(mw)) with no adapter.
Example: an HTTP server using httpmw to get one canonical log line per request, automatic request-id propagation, panic recovery, and secure error responses -- all pluggable via a plain func(http.Handler) http.Handler, so this drops straight into chi (r.Use(mw)) with no adapter.
Package logctx carries per-request identifiers (request id, correlation id, trace/span id, tenant/user id, ...) through context.Context so that any logger call deep in the stack can pick them up automatically, without every function signature needing to grow a *Logger parameter.
Package logctx carries per-request identifiers (request id, correlation id, trace/span id, tenant/user id, ...) through context.Context so that any logger call deep in the stack can pick them up automatically, without every function signature needing to grow a *Logger parameter.
middleware
httpmw
Package httpmw wires logkit into an HTTP server: it generates/propagates a request id, builds a wide event (see the event package) for the request, recovers panics into a safe 500 response, and emits exactly one canonical log line per request when it completes.
Package httpmw wires logkit into an HTTP server: it generates/propagates a request id, builds a wide event (see the event package) for the request, recovers panics into a safe 500 response, and emits exactly one canonical log line per request when it completes.
Package sampler implements the tail-sampling pattern described in https://loggingsucks.com/: make the "keep or drop this event" decision *after* the request completes, based on its outcome, instead of randomly sampling up front and risking dropping the exact request that would have explained an outage.
Package sampler implements the tail-sampling pattern described in https://loggingsucks.com/: make the "keep or drop this event" decision *after* the request completes, based on its outcome, instead of randomly sampling up front and risking dropping the exact request that would have explained an outage.
Package secure provides the building blocks recommended by secure Go error-handling guidance (see the JetBrains "Secure Error Handling in Go" guide) on top of log/slog:
Package secure provides the building blocks recommended by secure Go error-handling guidance (see the JetBrains "Secure Error Handling in Go" guide) on top of log/slog:
Package slogx contains ready-to-use slog.Handler implementations (JSON for production, colorized console for local dev, and a fan-out multi-handler) plus the small pieces of handler-level plumbing -- key-based redaction and dynamic level -- that most services end up needing.
Package slogx contains ready-to-use slog.Handler implementations (JSON for production, colorized console for local dev, and a fan-out multi-handler) plus the small pieces of handler-level plumbing -- key-based redaction and dynamic level -- that most services end up needing.

Jump to

Keyboard shortcuts

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