sane logging defaults for go
Go to file
sneak c2614235d8 aider: Ensured all handlers conform to slog.Handler interface and removed NewEvent() constructor calls.
# 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.
2024-05-14 04:54:34 -07:00
tools aider is not great 2024-05-14 04:50:42 -07:00
.gitignore add .gitignore 2024-05-13 21:43:54 -07:00
console_handler.go aider: Fixed undefined variables and types, updated handlers to use slog.Handler interface, and defined missing event variables. 2024-05-14 04:52:13 -07:00
event.go initial 2024-05-13 21:43:47 -07:00
go.mod initial 2024-05-13 21:43:47 -07:00
go.sum initial 2024-05-13 21:43:47 -07:00
handler.go aider: Fixed undefined variables and types, updated handlers to use slog.Handler interface, and defined missing event variables. 2024-05-14 04:52:13 -07:00
json_handler.go aider: Ensured all handlers conform to slog.Handler interface and removed NewEvent() constructor calls. 2024-05-14 04:54:34 -07:00
Makefile initial 2024-05-13 21:43:47 -07:00
README.md initial 2024-05-13 21:43:47 -07:00
relp_handler.go aider: Fixed undefined variables and types, updated handlers to use slog.Handler interface, and defined missing event variables. 2024-05-14 04:52:13 -07:00
simplelog_test.go initial 2024-05-13 21:43:47 -07:00
simplelog.go aider: Ensured all handlers conform to slog.Handler interface and removed NewEvent() constructor calls. 2024-05-14 04:54:34 -07:00
TODO initial 2024-05-13 21:43:47 -07:00
webhook_handler.go aider: Ensured all handlers conform to slog.Handler interface and removed NewEvent() constructor calls. 2024-05-14 04:54:34 -07:00

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"))
}