diff --git a/audit/event.go b/audit/event.go index ade48e9..787b650 100644 --- a/audit/event.go +++ b/audit/event.go @@ -145,6 +145,14 @@ func (e *AuditEvent) WithDataFromString(data string) *AuditEvent { return e.WithData(&rawMsg) } +// LevelAudit is a custom slog level for audit events, sitting between +// [slog.LevelInfo] (0) and [slog.LevelWarn] (4). Logging audit events at this +// dedicated level lets log infrastructure filter them independently from +// regular application logs. Pass it as the level argument to [AuditEvent.LogTo] +// and as the Level in a handler's [slog.HandlerOptions] so all ToolHive +// components share one definition rather than hardcoding the numeric value. +const LevelAudit = slog.Level(2) + // LogTo logs the audit event to the provided slog.Logger using the custom audit level. func (e *AuditEvent) LogTo(ctx context.Context, logger *slog.Logger, level slog.Level) { // Create slog attributes for the audit event diff --git a/audit/event_test.go b/audit/event_test.go index 58e7f7b..ef880d4 100644 --- a/audit/event_test.go +++ b/audit/event_test.go @@ -184,6 +184,16 @@ func TestConstants(t *testing.T) { t.Parallel() assert.Equal(t, "toolhive-api", ComponentToolHive) }) + + t.Run("audit level", func(t *testing.T) { + t.Parallel() + // Value must stay between Info and Warn so audit events can be + // filtered independently. Changing it is a breaking change for + // consumers that hardcode the numeric level. + assert.Equal(t, slog.Level(2), LevelAudit) + assert.Greater(t, LevelAudit, slog.LevelInfo) + assert.Less(t, LevelAudit, slog.LevelWarn) + }) } func TestEventMetadataExtra(t *testing.T) { @@ -249,8 +259,7 @@ func TestAuditEventLogTo(t *testing.T) { "transport": "sse", } - customLevel := slog.Level(2) - event.LogTo(context.Background(), logger, customLevel) + event.LogTo(context.Background(), logger, LevelAudit) logOutput := buf.String() require.NotEmpty(t, logOutput)