filter

package module
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Sep 2, 2025 License: MIT Imports: 7 Imported by: 4

README

filter

Go Reference Go Report Card codecov Socket Badge

Filtering and transformation handler for darvaza.org/slog. Wraps any slog.Logger to provide level-based filtering and custom log entry transformation.

Architecture

The filter handler implements a two-tier architecture:

  • Logger: Factory and configuration layer that creates LogEntry instances. Print methods are no-ops. WithField/WithStack create LogEntry instances.
  • LogEntry: Working logger that handles actual logging operations with immutable field chains. Only logs when a level is set and within threshold.

Installation

go get darvaza.org/slog/handlers/filter

Key Behaviors

Logger vs LogEntry
  • Logger.Print()/Printf()/Println(): No-ops that don't log anything.

  • Logger.WithField()/WithStack(): Create LogEntry with fields (parentless don't collect).

  • Logger.Debug()/Info()/etc: Create LogEntry with specified level.

  • LogEntry without level: NOT enabled, collects fields speculatively.

  • LogEntry with level: Enabled if level ≤ threshold, fields only collected when enabled.

Quick Start

import (
    "strings"

    "darvaza.org/slog"
    "darvaza.org/slog/handlers/filter"
)

// Create a filter that only passes Info and above
baseLogger := getSomeLogger() // Any slog.Logger
filtered := filter.New(baseLogger, slog.Info)

// Debug and below are filtered out
filtered.Debug().Print("This won't appear")
filtered.Info().Print("This will appear")
Proper Usage Patterns
// CORRECT: Set level at the point of logging
logger.WithField("user", "alice").Info().Print("login successful")
logger.WithField("error", err).Error().Print("operation failed")

// INCORRECT: Don't change levels after setting
logger.Debug().Error().Print("confusing")  // Wrong: level set twice

// CORRECT: Branch for different levels
base := logger.WithField("request_id", "123")
base.Debug().Print("processing started")
base.Info().Print("processing complete")
base.Error().Print("processing failed")

Advanced Configuration

Custom Filter Configuration
filterLogger := &filter.Logger{
    Parent:    baseLogger,
    Threshold: slog.Debug,
    FieldFilter: func(key string, val any) (string, any, bool) {
        // Redact sensitive fields
        if key == "password" {
            return key, "[REDACTED]", true
        }
        // Remove internal fields
        if strings.HasPrefix(key, "_") {
            return "", nil, false
        }
        return key, val, true
    },
    FieldsFilter: func(fields map[string]any) (map[string]any, bool) {
        // Add common prefix to all fields
        result := make(map[string]any, len(fields))
        for k, v := range fields {
            result["app_"+k] = v
        }
        return result, true
    },
    MessageFilter: func(msg string) (string, bool) {
        // Filter out health check logs
        if strings.Contains(msg, "/health") {
            return "", false // Drop this entry
        }
        return "[APP] " + msg, true // Add prefix and keep
    },
}

Filter Hierarchy

The filter handler applies transformations using a specific hierarchy to ensure predictable behaviour:

For WithField() (Single Field Operations)
  1. FieldFilter (most specific) - Designed for single field transformation.
  2. FieldsFilter (fallback) - Handle as {key: value}map[string]any.
  3. No filter - Store field as-is in the loglet.
For WithFields() (Multiple Field Operations)
  1. FieldsFilter (most specific) - Designed for map transformation.
  2. FieldFilter (fallback) - Apply to each field individually.
  3. No filter - Store fields as-is in the loglet.

Features

Level-Based Filtering
  • Configurable minimum level threshold.
  • Parent logger consulted for final enablement decision.
  • Special handling for Fatal/Panic levels in parentless configurations.
Field Transformation
  • FieldFilter: Transform individual fields with key/value pairs.
  • FieldsFilter: Transform entire field maps for bulk operations.
  • Rejection: Return false to drop fields entirely.
Message Transformation
  • MessageFilter: Modify or filter log messages before output.
  • Conditional logging: Return false to drop entire log entries.
Design Principles
  • Immutable logger instances: Ensure thread-safe field management.
  • Selective field collection: Fields only collected when potentially enabled.
  • Parent delegation: Final output handled by wrapped parent logger.
  • Filter application: Applied at field attachment time for efficiency.
  • Level-less entries: Can accumulate fields speculatively but are NOT enabled for logging.
  • Level is terminal: Once a level is set (via .Debug(), .Info(), etc.), it should not be changed. The level is intended to be set at the point of logging, not as an intermediate transformation.
  • Logger.Print() methods: No-ops that do not log without an explicit level.

Special Behaviours

Parentless Loggers

Created with filter.NewNoop() or filter.New(nil, threshold):

  • Only Fatal and Panic levels are enabled (for termination).
  • Bypass parent delegation and use standard library logging.
  • Fields are NOT collected (nowhere to forward them).
  • Used primarily for termination-only behaviour.
Filter Chaining

Multiple filter loggers can be chained together:

filter1 := filter.New(baseLogger, slog.Error)  // Only Error and above
filter2 := filter.New(filter1, slog.Warn)     // Further restricted
filter3 := filter.New(filter2, slog.Info)     // Most restrictive wins

The most restrictive threshold in the chain determines final behaviour.

Performance Considerations

  • Efficient field collection: Only occurs when entry is potentially enabled.
  • Optimised disabled entries: Fields NOT collected when level exceeds threshold.
  • Immutable design: Safe for concurrent use without synchronisation.
  • Filter overhead: Transformation functions called during field attachment.
  • Speculative collection: Level-less entries collect fields that will be used when a level is eventually set.

Examples

Security Field Filtering
secureLogger := &filter.Logger{
    Parent:    baseLogger,
    Threshold: slog.Info,
    FieldFilter: func(key string, val any) (string, any, bool) {
        // Redact sensitive data
        sensitive := []string{"password", "token", "secret", "key"}
        for _, s := range sensitive {
            if strings.Contains(strings.ToLower(key), s) {
                return key, "[REDACTED]", true
            }
        }
        return key, val, true
    },
}
Development vs Production Filtering
func createLogger(isDevelopment bool) slog.Logger {
    baseLogger := getSomeLogger()

    if isDevelopment {
        // Development: Allow all levels, no filtering
        return filter.New(baseLogger, slog.Debug)
    }

    // Production: Filter out debug, redact sensitive fields
    return &filter.Logger{
        Parent:    baseLogger,
        Threshold: slog.Info,
        FieldFilter: func(key string, val any) (string, any, bool) {
            if strings.HasPrefix(key, "debug_") {
                return "", nil, false // Remove debug fields
            }
            return key, val, true
        },
    }
}

Documentation

Documentation

Overview

Package filter is a Logger that only allows entries of a given level.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type LogEntry

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

LogEntry implements a level-filtered logger.

func (*LogEntry) Debug

func (l *LogEntry) Debug() slog.Logger

Debug creates a new filtered logger on level slog.Debug.

func (*LogEntry) Enabled

func (l *LogEntry) Enabled() bool

Enabled tells if this logger would record logs.

func (*LogEntry) Error

func (l *LogEntry) Error() slog.Logger

Error creates a new filtered logger on level slog.Error.

func (*LogEntry) Fatal

func (l *LogEntry) Fatal() slog.Logger

Fatal creates a new filtered logger on level slog.Fatal.

func (*LogEntry) Info

func (l *LogEntry) Info() slog.Logger

Info creates a new filtered logger on level slog.Info.

func (*LogEntry) Level added in v0.7.0

func (l *LogEntry) Level() slog.LogLevel

Level returns the current log level. Exposed for testing only.

func (*LogEntry) Panic

func (l *LogEntry) Panic() slog.Logger

Panic creates a new filtered logger on level slog.Panic.

func (*LogEntry) Print

func (l *LogEntry) Print(args ...any)

Print would, if conditions are met, add a log entry with the arguments in the manner of fmt.Print.

func (*LogEntry) Printf

func (l *LogEntry) Printf(format string, args ...any)

Printf would, if conditions are met, add a log entry with the arguments in the manner of fmt.Printf.

func (*LogEntry) Println

func (l *LogEntry) Println(args ...any)

Println would, if conditions are met, add a log entry with the arguments in the manner of fmt.Println.

func (*LogEntry) Warn

func (l *LogEntry) Warn() slog.Logger

Warn creates a new filtered logger on level slog.Warn.

func (*LogEntry) WithEnabled

func (l *LogEntry) WithEnabled() (slog.Logger, bool)

WithEnabled returns itself and whether it's enabled.

func (*LogEntry) WithField

func (l *LogEntry) WithField(label string, value any) slog.Logger

WithField would, if conditions are met, attach a field to the log entry. This field could be altered if a FieldFilter is used.

func (*LogEntry) WithFields

func (l *LogEntry) WithFields(fields map[string]any) slog.Logger

WithFields would, if conditions are met, attach fields to the log entry. These fields could be altered if a FieldFilter is used.

func (*LogEntry) WithLevel

func (l *LogEntry) WithLevel(level slog.LogLevel) slog.Logger

WithLevel creates a new filtered logger on the given level.

func (*LogEntry) WithStack

func (l *LogEntry) WithStack(skip int) slog.Logger

WithStack would, if conditions are met, attach a call stack to the log entry.

type Logger

type Logger struct {

	// Parent is the Logger to use as backend when conditions are met.
	Parent slog.Logger

	// Threshold is the minimum level to be logged.
	Threshold slog.LogLevel

	// FieldFilter allows us to modify single fields before passing them
	// to the Parent logger.
	FieldFilter func(key string, val any) (string, any, bool)

	// FieldsFilter allows us to modify field maps before passing them
	// to the Parent logger. Returns filtered fields and whether to continue.
	FieldsFilter func(fields slog.Fields) (slog.Fields, bool)

	// MessageFilter allows us to modify Print() messages before passing
	// them to the Parent logger, or completely discard the entry.
	MessageFilter func(msg string) (string, bool)
	// contains filtered or unexported fields
}

Logger implements a factory for level-filtered loggers.

func New

func New(parent slog.Logger, threshold slog.LogLevel) *Logger

New creates a new filtered log factory at a given level. Logger can be manually initialised as well. Defaults filter entries at level slog.Error or higher. Parentless is treated as `noop`, with Fatal implemented like log.Fatal.

func NewNoop

func NewNoop() *Logger

NewNoop creates a new filtered log factory that only implements Fatal().Print().

func (*Logger) Debug

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

Debug returns a filtered logger on level slog.Debug.

func (*Logger) Enabled

func (*Logger) Enabled() bool

Enabled tells this logger doesn't log anything, but WithLevel() might.

func (*Logger) Error

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

Error returns a filtered logger on level slog.Error.

func (*Logger) Fatal

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

Fatal returns a filtered logger on level slog.Fatal.

func (*Logger) Info

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

Info returns a filtered logger on level slog.Info.

func (*Logger) Panic

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

Panic returns a filtered logger on level slog.Panic.

func (*Logger) Print

func (*Logger) Print(args ...any)

Print is a no-op on Logger - entries without a level are not enabled.

func (*Logger) Printf

func (*Logger) Printf(_ string, args ...any)

Printf is a no-op on Logger - entries without a level are not enabled.

func (*Logger) Println

func (*Logger) Println(args ...any)

Println is a no-op on Logger - entries without a level are not enabled.

func (*Logger) Warn

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

Warn returns a filtered logger on level slog.Warn.

func (*Logger) WithEnabled

func (l *Logger) WithEnabled() (slog.Logger, bool)

WithEnabled tells this logger doesn't log anything, but WithLevel() might.

func (*Logger) WithField

func (l *Logger) WithField(label string, value any) slog.Logger

WithField creates a LogEntry with the field.

func (*Logger) WithFields

func (l *Logger) WithFields(fields map[string]any) slog.Logger

WithFields creates a LogEntry with the fields.

func (*Logger) WithLevel

func (l *Logger) WithLevel(level slog.LogLevel) slog.Logger

WithLevel returns a filtered logger set to the given level.

func (*Logger) WithStack

func (l *Logger) WithStack(skip int) slog.Logger

WithStack creates a LogEntry with stack information.

Jump to

Keyboard shortcuts

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