package observability import ( "context" "errors" "fmt" "io" "log/slog" "testing" "git.nakama.town/fmartingr/butterrobot/internal/config" ) // discardLogger returns a Sentry-wrapped logger that writes nowhere else. func discardLogger() *slog.Logger { return WrapLogger(slog.New(slog.NewTextHandler(io.Discard, nil)), config.SentryConfig{}) } func TestErrorAttrBecomesSentryException(t *testing.T) { transport := enableSentry(t, nil) wrapped := fmt.Errorf("sending message: %w", errors.New("connection refused")) discardLogger().Error("Error sending message", "error", wrapped, "message_id", 42) events := transport.captured() if len(events) != 1 { t.Fatalf("expected exactly one event, got %d", len(events)) } event := events[0] if len(event.Exception) == 0 { t.Fatal("expected the error attribute to become an exception") } // The chain is unwrapped, so the root cause drives issue grouping. last := event.Exception[len(event.Exception)-1] if last.Value != "sending message: connection refused" { t.Errorf("unexpected exception value: %q", last.Value) } data := event.Contexts[logContextKey] if data["message_id"] != int64(42) { t.Errorf("expected remaining attributes in the log context, got %v", data) } if _, present := data["error"]; present { t.Error("expected the error attribute to be promoted, not duplicated") } } func TestNonErrorLevelsProduceNoIssue(t *testing.T) { transport := enableSentry(t, nil) logger := discardLogger() logger.Info("Server starting on", "addr", ":8080") logger.Warn("Queue is full, dropping message") if events := transport.captured(); len(events) != 0 { t.Fatalf("expected no issues below ERROR, got %d", len(events)) } } func TestGroupedAttrsAreQualified(t *testing.T) { transport := enableSentry(t, nil) discardLogger().WithGroup("plugin").With("id", "util.ping").Error("Unknown action type") events := transport.captured() if len(events) != 1 { t.Fatalf("expected exactly one event, got %d", len(events)) } data := events[0].Contexts[logContextKey] if data["plugin.id"] != "util.ping" { t.Errorf("expected group-qualified key, got %v", data) } } func TestContextHubTagsReachTheEvent(t *testing.T) { transport := enableSentry(t, nil) ctx := WithTags(context.Background(), map[string]string{"platform": "telegram", "plugin": "util.ping"}) discardLogger().ErrorContext(ctx, "Error sending message") events := transport.captured() if len(events) != 1 { t.Fatalf("expected exactly one event, got %d", len(events)) } tags := events[0].Tags if tags["platform"] != "telegram" || tags["plugin"] != "util.ping" { t.Errorf("expected scoped tags on the event, got %v", tags) } }