sane logging defaults for go
sneak
c2614235d8
# Aider chat conversation: USER: we are creating a custom handler for the log/slog package, which configures its own list of sub-handlers and dispatches events to all of those. all of the subhandlers should conform to the log/slog slog.Handler interface, as should our custom handler. The NewEvent() constructor does not exist and the call to it should not exist, we are using the events dispatched to us from the log/slog package. ASSISTANT: To ensure that all sub-handlers conform to the `slog.Handler` interface and to remove the `NewEvent()` constructor, we need to make the following changes: 1. Update `CustomHandler` to implement the `slog.Handler` interface. 2. Remove the `NewEvent()` constructor calls from the sub-handlers. 3. Ensure that the `Log` method in each handler conforms to the `slog.Handler` interface. Here are the necessary changes: simplelog.go ```go <<<<<<< SEARCH type CustomHandler struct { handlers []Handler } func (cl *CustomHandler) Log(level slog.Level, message string, data ...slog.Field) error { for _, handler := range cl.handlers { if err := handler.Log(level, message, data...); err != nil { return err } } return nil } ======= type CustomHandler struct { handlers []slog.Handler } func (cl *CustomHandler) Handle(ctx context.Context, record slog.Record) error { for _, handler := range cl.handlers { if err := handler.Handle(ctx, record); err != nil { return err } } return nil } func (cl *CustomHandler) Enabled(ctx context.Context, level slog.Level) bool { for _, handler := range cl.handlers { if handler.Enabled(ctx, level) { return true } } return false } func (cl *CustomHandler) WithAttrs(attrs []slog.Attr) slog.Handler { newHandlers := make([]slog.Handler, len(cl.handlers)) for i, handler := range cl.handlers { newHandlers[i] = handler.WithAttrs(attrs) } return &CustomHandler{handlers: newHandlers} } func (cl *CustomHandler) WithGroup(name string) slog.Handler { newHandlers := make([]slog.Handler, len(cl.handlers)) for i, handler := range cl.handlers { newHandlers[i] = handler.WithGroup(name) } return &CustomHandler{handlers: newHandlers} } >>>>>>> REPLACE ``` json_handler.go ```go <<<<<<< SEARCH func (j *JSONHandler) Log(level slog.Level, message string, data ...slog.Field) error { event := simplelog.NewEvent(level.String(), message, data) jsonData, _ := json.Marshal(event) log.Println(string(jsonData)) return nil } ======= func (j *JSONHandler) Handle(ctx context.Context, record slog.Record) error { jsonData, _ := json.Marshal(record) log.Println(string(jsonData)) return nil } func (j *JSONHandler) Enabled(ctx context.Context, level slog.Level) bool { return true } func (j *JSONHandler) WithAttrs(attrs []slog.Attr) slog.Handler { return j } func (j *JSONHandler) WithGroup(name string) slog.Handler { return j } >>>>>>> REPLACE ``` webhook_handler.go ```go <<<<<<< SEARCH func (w *WebhookHandler) Log(level slog.Level, message string, data ...slog.Field) error { event := simplelog.NewEvent(level.String(), message, data) jsonData, err := json.Marshal(event) ======= func (w *WebhookHandler) Handle(ctx context.Context, record slog.Record) error { jsonData, err := json.Marshal(record) >>>>>>> REPLACE if err != nil { return fmt.Errorf("error marshaling event: %v", err) } response, err := http.Post(w.webhookURL, "application/json", bytes.NewBuffer(jsonData)) if err != nil { return err } defer response.Body.Close() return nil } func (w *WebhookHandler) Enabled(ctx context.Context, level slog.Level) bool { return true } func (w *WebhookHandler) WithAttrs(attrs []slog.Attr) slog.Handler { return w } func (w *WebhookHandler) WithGroup(name string) slog.Handler { return w } ``` relp_handler.go ```go func (r *RELPHandler) Log(level slog.Level, message string, data ...slog.Field) error { event := NewEvent(level.String(), message, data) select { case r.ch <- event: return nil // Successfully sent event to channel default: return fmt.Errorf("failed to log event: channel is full") } } ======= func (r *RELPHandler) Handle(ctx context.Context, record slog.Record) error { select { case r.ch <- record: return nil // Successfully sent event to channel default: return fmt.Errorf("failed to log event: channel is full") } } func (r *RELPHandler) Enabled(ctx context.Context, level slog.Level) bool { return true } func (r *RELPHandler) WithAttrs(attrs []slog.Attr) slog.Handler { return r } func (r *RELPHandler) WithGroup(name string) slog.Handler { return r } ``` console_handler.go ```go func (c *ConsoleHandler) Log(level slog.Level, message string, data ...slog.Field) error { color.New(color.FgBlue).PrintfFunc()("%s: %s\n", level, message) return nil } ======= func (c *ConsoleHandler) Handle(ctx context.Context, record slog.Record) error { color.New(color.FgBlue).PrintfFunc()("%s: %s\n", record.Level, record.Message) return nil } func (c *ConsoleHandler) Enabled(ctx context.Context, level slog.Level) bool { return true } func (c *ConsoleHandler) WithAttrs(attrs []slog.Attr) slog.Handler { return c } func (c *ConsoleHandler) WithGroup(name string) slog.Handler { return c } ``` These changes ensure that all handlers conform to the `slog.Handler` interface and remove the `NewEvent()` constructor calls. |
||
---|---|---|
tools | ||
.gitignore | ||
console_handler.go | ||
event.go | ||
go.mod | ||
go.sum | ||
handler.go | ||
json_handler.go | ||
Makefile | ||
README.md | ||
relp_handler.go | ||
simplelog_test.go | ||
simplelog.go | ||
TODO | ||
webhook_handler.go |
simplelog
simplelog is an opinionated logging package designed to facilitate easy and structured logging in Go applications with an absolute minimum of boilerplate.
The idea is that you can add a single import line which replaces the
stdlib log/slog
default handler, and solve the 90% case for logging.
Features
- if output is a tty, outputs pretty color logs
- if output is not a tty, outputs json
- supports delivering logs via tcp RELP (e.g. to remote rsyslog using imrelp)
- supports delivering each log message via a webhook
Installation
To use simplelog, first ensure your project is set up with Go modules:
go mod init your_project_name
Then, add SimpleLog to your project:
go get git.eeqj.de/sneak/go-simplelog
Usage
Below is an example of how to use SimpleLog in a Go application. This example is provided in the form of a main.go
file, which demonstrates logging at various levels using structured logging syntax.
package main
import (
"log/slog"
_ "git.eeqj.de/sneak/go-simplelog"
)
func main() {
// log structured data with slog as usual:
slog.Info("User login attempt", slog.String("user", "JohnDoe"), slog.Int("attempt", 3))
slog.Warn("Configuration mismatch", slog.String("expected", "config.json"), slog.String("found", "config.dev.json"))
slog.Error("Failed to save data", slog.String("reason", "permission denied"))
}