diff --git a/internal/observability/log.go b/internal/observability/log.go index 686695a..63f9118 100644 --- a/internal/observability/log.go +++ b/internal/observability/log.go @@ -18,10 +18,13 @@ import ( ) const ( - ansiReset = "\x1b[0m" - ansiBlue = "\x1b[36m" - ansiYellow = "\x1b[33m" - ansiRed = "\x1b[31m" + ansiReset = "\x1b[0m" + ansiBlue = "\x1b[36m" + ansiStatusBlue = "\x1b[34m" + ansiGreen = "\x1b[32m" + ansiYellow = "\x1b[33m" + ansiOrange = "\x1b[38;5;208m" + ansiRed = "\x1b[31m" ) var colorLogs = shouldColorLogs() @@ -315,7 +318,7 @@ func popField(fields map[string]any, key string) string { func formatInlineField(key string, value any) string { switch key { case "status": - return fmt.Sprint(value) + return formatHTTPStatus(value) case "duration_ms": return formatDurationMillis(value) case "bytes": @@ -328,6 +331,28 @@ func formatInlineField(key string, value any) string { } } +func formatHTTPStatus(value any) string { + status := fmt.Sprint(value) + if !colorLogs || len(status) == 0 { + return status + } + + switch status[0] { + case '1': + return ansiStatusBlue + status + ansiReset + case '2': + return ansiGreen + status + ansiReset + case '3': + return ansiYellow + status + ansiReset + case '4': + return ansiOrange + status + ansiReset + case '5': + return ansiRed + status + ansiReset + default: + return status + } +} + func formatDurationMillis(value any) string { ms, ok := toFloat64(value) if !ok { diff --git a/internal/observability/log_test.go b/internal/observability/log_test.go index 0b478e3..66db046 100644 --- a/internal/observability/log_test.go +++ b/internal/observability/log_test.go @@ -34,3 +34,27 @@ func TestFormatLogEntryFormatsHTTPRequestCompactly(t *testing.T) { t.Fatalf("line should omit loopback ip: %q", line) } } + +func TestFormatHTTPStatusColorsByStatusFamily(t *testing.T) { + previousColorLogs := colorLogs + colorLogs = true + t.Cleanup(func() { + colorLogs = previousColorLogs + }) + + tests := map[any]string{ + 101: ansiStatusBlue + "101" + ansiReset, + 200: ansiGreen + "200" + ansiReset, + 302: ansiYellow + "302" + ansiReset, + 404: ansiOrange + "404" + ansiReset, + 500: ansiRed + "500" + ansiReset, + "unknown": "unknown", + } + + for input, want := range tests { + got := formatHTTPStatus(input) + if got != want { + t.Fatalf("formatHTTPStatus(%v) = %q, want %q", input, got, want) + } + } +}