Skip to content

Hook System

Hooks let you inject custom logic at key points of the log lifecycle, such as before/after log writes, file rotation, and error occurrences.

Hook Events

DD provides 6 lifecycle hook events:

EventWhen TriggeredTypical Use
HookBeforeLogBefore a log is formatted (fields already filtered)Conditional skip, sampling
HookAfterLogAfter a log write completesUpdate metrics, send notifications
HookOnFilterWhen a field value is redacted (message-text redaction does not trigger this; the hook receives only the field key, not the original value)Record redaction events, audit
HookOnRotateAfter file rotation completesNotify ops, upload old files
HookOnCloseWhen the Logger closesClean up resources, send final reports
HookOnErrorWhen a write error occursAlerting, graceful degradation

Quick Start

Using HooksConfig

go
hooks := dd.NewHooksFromConfig(dd.HooksConfig{
    BeforeLog: []dd.Hook{func(ctx context.Context, hCtx *dd.HookContext) error {
        fmt.Printf("about to write: %s\n", hCtx.Message)
        return nil
    }},
    AfterLog: []dd.Hook{func(ctx context.Context, hCtx *dd.HookContext) error {
        metrics.LogCount.Inc()
        return nil
    }},
})

logger, err := dd.New(dd.Config{
    Hooks: hooks,
})
if err != nil {
    log.Fatal(err)
}
defer logger.Close()

Using HookRegistry

go
registry := dd.NewHookRegistry()

// Register a BeforeLog hook
registry.Add(dd.HookBeforeLog, func(ctx context.Context, hCtx *dd.HookContext) error {
    // Skip some processing for debug-level logs
    if hCtx.Level == dd.LevelDebug {
        return nil
    }
    return nil
})

// Register an OnRotate hook
registry.Add(dd.HookOnRotate, func(ctx context.Context, hCtx *dd.HookContext) error {
    fmt.Printf("file rotated: %s\n", hCtx.Metadata)
    return nil
})

logger, err := dd.New(dd.Config{
    Hooks: registry,
})
if err != nil {
    log.Fatal(err)
}
defer logger.Close()

HookContext

Each hook receives a HookContext containing complete information about the current log:

go
type HookContext struct {
    Event          HookEvent    // Triggered event type
    Level          LogLevel     // Log level
    Message        string       // Log message
    Fields         []Field      // Processed fields
    OriginalFields []Field      // Original fields (before filtering)
    Error          error        // Related error (OnError)
    Timestamp      time.Time    // Timestamp
    Writer         io.Writer    // Target Writer
    Metadata       map[string]any // Attached metadata
}

Common Scenarios

Metrics Collection

go
var (
    logCounter   atomic.Int64
    errorCounter atomic.Int64
)

registry := dd.NewHookRegistry()

registry.Add(dd.HookAfterLog, func(ctx context.Context, hCtx *dd.HookContext) error {
    logCounter.Add(1)
    if hCtx.Level >= dd.LevelError {
        errorCounter.Add(1)
    }
    return nil
})

logger, err := dd.New(dd.Config{Hooks: registry})
if err != nil {
    log.Fatal(err)
}
defer logger.Close()

Log Sampling

go
var requestCount atomic.Int64

registry := dd.NewHookRegistry()
registry.Add(dd.HookBeforeLog, func(ctx context.Context, hCtx *dd.HookContext) error {
    if hCtx.Level == dd.LevelInfo {
        count := requestCount.Add(1)
        // Keep 1 of every 100
        if count%100 != 0 {
            return fmt.Errorf("sampled out") // Returning an error prevents the log from being written
        }
    }
    return nil
})

File-Rotation Notification

go
registry.Add(dd.HookOnRotate, func(ctx context.Context, hCtx *dd.HookContext) error {
    // Notify the monitoring system
    monitoring.Alert("log_rotated", map[string]any{
        "path": hCtx.Metadata["path"],
    })
    return nil
})

Error Alerting

go
registry.Add(dd.HookOnError, func(ctx context.Context, hCtx *dd.HookContext) error {
    // Send an alert
    alerting.Send(fmt.Sprintf("log write failed: %v", hCtx.Error))
    return nil
})

Error Handling

Global Error Handler

go
hooks := dd.NewHooksFromConfig(dd.HooksConfig{
    BeforeLog: []dd.Hook{func(ctx context.Context, hCtx *dd.HookContext) error {
        // May return an error
        return someOperation()
    }},
    ErrorHandler: func(event dd.HookEvent, hCtx *dd.HookContext, err error) {
        log.Printf("hook %s failed: %v", event, err)
    },
})

BeforeLog Aborting a Log

When a BeforeLog hook returns an error, the log entry is not written:

go
registry.Add(dd.HookBeforeLog, func(ctx context.Context, hCtx *dd.HookContext) error {
    // Check a condition; skip if not met
    if shouldSkip(hCtx.Message) {
        return fmt.Errorf("skipped") // Prevents writing
    }
    return nil // Allow writing
})

Panics in Hooks

If a hook function panics, DD recovers automatically without affecting the main flow. The panic value is passed to the ErrorHandler.

Dynamic Registration

go
// Register a new hook at runtime
registry.Add(dd.HookAfterLog, newHookFunc)

// Remove at runtime (via HookRegistry methods)

registry cloning

When creating a Logger, the passed-in registry is cloned (after dd.New(dd.Config{Hooks: registry}), the Logger holds a copy); subsequent modifications to the original registry do not affect the already-created Logger. To change hooks of an already-created Logger at runtime, use logger.AddHook(event, hook) (internal Clone-Modify-Store).

Next Steps