Custom handlers
Implement a handler for custom log destinations
Use a custom handler when built-in console, file, or syslog output is not the right destination. Common cases include audit streams, internal observability pipelines, test collectors, message queues, or platform-specific log transports.
Custom handlers receive a Record, decide whether it should be written, and
send it to the destination they own.
Handler interface
type Handler interface {
Handle(record Record) error
SetLevel(level Level)
Level() Level
SetEnabled(enabled bool)
IsEnabled() bool
IsLoggable(record Record) bool
}Handle writes a single record. SetLevel and Level control the minimum level
for that handler. SetEnabled and IsEnabled allow configuration to turn the
handler on or off. IsLoggable is the guard that combines enabled state and
level checks before expensive work happens.
The record contains the values a handler needs to format and route the message:
type Record struct {
Time time.Time
Level Level
Message string
Context context.Context
LoggerName string
StackTrace string
Error error
Caller Caller
}Implement a handler
Keep the implementation small and push destination-specific details behind the handler. This example stores records in memory, which is useful for tests or for adapting the same shape to another sink.
type AuditHandler struct {
enabled bool
level logy.Level
records []logy.Record
}
func NewAuditHandler() *AuditHandler {
return &AuditHandler{
enabled: true,
level: logy.LevelInfo,
}
}
func (h *AuditHandler) Handle(record logy.Record) error {
if !h.IsLoggable(record) {
return nil
}
h.records = append(h.records, record)
return nil
}
func (h *AuditHandler) SetLevel(level logy.Level) {
h.level = level
}
func (h *AuditHandler) Level() logy.Level {
return h.level
}
func (h *AuditHandler) SetEnabled(enabled bool) {
h.enabled = enabled
}
func (h *AuditHandler) IsEnabled() bool {
return h.enabled
}
func (h *AuditHandler) IsLoggable(record logy.Record) bool {
return h.enabled && record.Level >= h.level
}Register the handler
Register the handler before configuration loads. The built-in names console,
file, and syslog are reserved, so choose an application-specific name.
func init() {
logy.Register("audit", NewAuditHandler())
}After registration, reference the handler by name.
err := logy.LoadConfig(&logy.Config{
Handlers: logy.Handlers{"console", "audit"},
})
if err != nil {
panic(err)
}Custom handlers should keep Handle fast and predictable. If the destination is
slow, buffer or queue work outside the logging call path.
