Frequently Asked Questions
Basic Usage
What is the difference between the global logger and a custom logger?
The global logger is used directly via package-level functions such as dd.Info(), suitable for simple scenarios. A custom logger is created via dd.New(), supporting independent configuration and lifecycle management.
// Global logger
dd.Info("global log")
// Custom logger
logger, _ := dd.New(dd.JSONConfig())
logger.Info("independent log")How do I initialize the global logger at program startup?
func init() {
err := dd.InitDefault(dd.JSONConfig())
if err != nil {
log.Fatal(err)
}
}Or via SetDefault:
logger, _ := dd.New(dd.Config{
Format: dd.FormatJSON,
Targets: []dd.OutputTarget{
dd.ConsoleOutput(),
dd.FileOutput("logs/app.json"),
},
})
dd.SetDefault(logger)What happens with Fatal-level logs?
Fatal / Fatalf / FatalWith call os.Exit(1) after emitting the log (deferred functions will not run; internally it first tries to Close() to flush pending logs, waiting up to 5 seconds). You can customize the exit behavior via FatalHandler; for resource cleanup, use ErrorWith + an explicit Shutdown(ctx) instead.
Configuration
How do I output to both console and file?
logger, _ := dd.New(dd.Config{
Targets: []dd.OutputTarget{
dd.ConsoleOutput(),
dd.FileOutput("logs/app.log"),
},
})
// Or JSON format
logger, _ := dd.New(dd.Config{
Format: dd.FormatJSON,
Targets: []dd.OutputTarget{
dd.ConsoleOutput(),
dd.FileOutput("logs/app.json"),
},
})How do I dynamically change the log level?
_ = logger.SetLevel(dd.LevelDebug) // Modify at runtime (returns an error)
_ = dd.SetLevel(dd.LevelDebug) // Modify the global logger's levelHow do I configure the file-rotation strategy?
Configure via FileWriter:
fw, _ := dd.NewFileWriter("logs/app.log",
dd.DefaultFileWriterConfig(), // 100MB, 30 days, 10 backups
)Performance
Will logging affect program performance?
DD is designed for high performance:
- Low-allocation optimization on hot paths
- Atomic level checks, lock-free
- Sensitive-data filtering for large inputs (>=10KB) runs in a separate goroutine with timeout protection; small inputs are processed synchronously
- Optional buffered writes to reduce I/O
How do I optimize for high-throughput scenarios?
- Use
BufferedWriterto reduce I/O - Check the level before building fields
- Consider enabling log sampling
- Avoid
Anyfields on hot paths
See Performance Tuning.
Security
How does sensitive-data filtering work?
SensitiveDataFilter uses regex pattern matching to automatically replace matching sensitive values with [REDACTED] before the log is written. Small inputs are processed synchronously; large inputs run in a separate goroutine with timeout protection, without blocking log writes.
How do I customize sensitive-data patterns?
filter, _ := dd.NewCustomSensitiveDataFilter(
`(?i)my_secret_field\s*[:=]\s*\S+`,
)How do I ensure logs are not tampered with?
Use IntegritySigner to HMAC-sign logs:
cfg, _ := dd.DefaultIntegrityConfigSafe()
signer, _ := dd.NewIntegritySigner(cfg)
sig := signer.Sign(logMessage)
// Verify: signer.Verify(signedEntry)Error Handling
Why does AddWriter return an error?
Possible reasons:
ErrNilWriter-- a nil Writer was passedErrLoggerClosed-- the logger is closedErrMaxWritersExceeded-- the writer count exceeds the limit
How do I handle write failures?
logger.SetWriteErrorHandler(func(w io.Writer, err error) {
// Custom handling
metrics.WriteErrors.Inc()
})Testing
How do I capture logs in tests?
Use LoggerRecorder:
rec := dd.NewLoggerRecorder()
logger, _ := rec.NewLogger()
logger.Info("test")
if !rec.ContainsMessage("test") {
t.Error("expected log not found")
}See Test Helper.
Next Steps
- Quick Start -- Getting started guide
- API Reference -- Complete API
- Production Checklist -- Pre-launch checks