feat(logger): include attributes in persisted log message
Store attributes as key=value pairs in the SQLite log message so they are visible in the web UI. job_id is extracted into the dedicated column and excluded from the message text.
This commit is contained in:
parent
2fd56d0f36
commit
715d680f97
@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"coda/internal/models"
|
||||
@ -74,17 +75,26 @@ func (h *Handler) Handle(ctx context.Context, r slog.Record) error {
|
||||
|
||||
entry := models.LogEntry{
|
||||
Level: r.Level.String(),
|
||||
Message: r.Message,
|
||||
Timestamp: r.Time,
|
||||
}
|
||||
|
||||
var msg strings.Builder
|
||||
msg.WriteString(r.Message)
|
||||
|
||||
r2.Attrs(func(a slog.Attr) bool {
|
||||
if a.Key == "job_id" && a.Value.Kind() == slog.KindString {
|
||||
entry.JobID = a.Value.String()
|
||||
return true
|
||||
}
|
||||
msg.WriteByte(' ')
|
||||
msg.WriteString(a.Key)
|
||||
msg.WriteByte('=')
|
||||
msg.WriteString(fmt.Sprintf("%v", a.Value.Any()))
|
||||
return true
|
||||
})
|
||||
|
||||
entry.Message = msg.String()
|
||||
|
||||
select {
|
||||
case h.ch <- entry:
|
||||
default:
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"coda/internal/models"
|
||||
@ -92,6 +93,37 @@ func TestHandlerRespectsLevel(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerIncludesAttrsInMessage(t *testing.T) {
|
||||
st := openTestStore(t)
|
||||
|
||||
h := New(st.Logs, Options{Level: slog.LevelInfo, Stdout: io.Discard})
|
||||
log := slog.New(h)
|
||||
|
||||
log.Warn("path outside music path", "path", "/mnt/media/Music/Artist", "music_path", "/music")
|
||||
|
||||
if err := h.Close(); err != nil {
|
||||
t.Fatalf("Close: %v", err)
|
||||
}
|
||||
|
||||
logs, err := st.Logs.List(context.Background(), store.ListLogsParams{Limit: 10})
|
||||
if err != nil {
|
||||
t.Fatalf("List: %v", err)
|
||||
}
|
||||
if len(logs) != 1 {
|
||||
t.Fatalf("expected 1 log, got %d", len(logs))
|
||||
}
|
||||
msg := logs[0].Message
|
||||
if !strings.Contains(msg, "path outside music path") {
|
||||
t.Errorf("message missing base text: %q", msg)
|
||||
}
|
||||
if !strings.Contains(msg, "path=/mnt/media/Music/Artist") {
|
||||
t.Errorf("message missing path attr: %q", msg)
|
||||
}
|
||||
if !strings.Contains(msg, "music_path=/music") {
|
||||
t.Errorf("message missing music_path attr: %q", msg)
|
||||
}
|
||||
}
|
||||
|
||||
type failingWriter struct{}
|
||||
|
||||
func (failingWriter) Insert(ctx context.Context, e *models.LogEntry) error {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user