Skip to content

Commit 6bb2ed8

Browse files
authored
feat(logging): export structured log keys (#124)
1 parent ca81e13 commit 6bb2ed8

13 files changed

Lines changed: 247 additions & 164 deletions

File tree

docs/logging.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ them once per request and does not propagate them through headers. Background
3838
workers may install already-sampled values with `scontext.WithBuildID` and
3939
`scontext.WithConfigID`.
4040

41+
All structured-log field names emitted by SRouter are exported from the
42+
dependency-free `pkg/logkeys` package. Applications use constants such as
43+
`logkeys.TraceID`, `logkeys.BuildID`, and `logkeys.ConfigID` to keep their logs
44+
aligned with SRouter without depending on Zap.
45+
4146
Its level is chosen in this priority order:
4247

4348
1. `Error` for status codes `>= 500`.

examples/handler-error-middleware/main.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"github.com/Suhaibinator/SRouter/pkg/codec"
1111
"github.com/Suhaibinator/SRouter/pkg/common"
12+
"github.com/Suhaibinator/SRouter/pkg/logkeys"
1213
"github.com/Suhaibinator/SRouter/pkg/router"
1314
"github.com/Suhaibinator/SRouter/pkg/scontext"
1415
"go.uber.org/zap"
@@ -79,9 +80,9 @@ func ErrorLoggingMiddleware(logger *zap.Logger) common.Middleware {
7980

8081
// Log with structured fields
8182
logger.Error("Handler error occurred",
82-
zap.Error(err),
83-
zap.String("path", path),
84-
zap.String("method", method),
83+
zap.NamedError(logkeys.Error, err),
84+
zap.String(logkeys.Path, path),
85+
zap.String(logkeys.Method, method),
8586
)
8687
}
8788
})

examples/middleware/main.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"time"
99

1010
"github.com/Suhaibinator/SRouter/pkg/common"
11+
"github.com/Suhaibinator/SRouter/pkg/logkeys"
1112
"github.com/Suhaibinator/SRouter/pkg/middleware"
1213
"github.com/Suhaibinator/SRouter/pkg/router"
1314
"go.uber.org/zap"
@@ -110,20 +111,20 @@ func DetailedLoggingMiddleware(logger *zap.Logger) common.Middleware {
110111

111112
// Log the request
112113
logger.Info("Request received",
113-
zap.String("method", r.Method),
114-
zap.String("path", r.URL.Path),
115-
zap.String("remote_addr", r.RemoteAddr),
116-
zap.String("user_agent", r.UserAgent()),
114+
zap.String(logkeys.Method, r.Method),
115+
zap.String(logkeys.Path, r.URL.Path),
116+
zap.String(logkeys.RemoteAddr, r.RemoteAddr),
117+
zap.String(logkeys.UserAgent, r.UserAgent()),
117118
)
118119

119120
// Call the next handler
120121
next.ServeHTTP(lrw, r)
121122

122123
// Log the response
123124
logger.Info("Response sent",
124-
zap.String("method", r.Method),
125-
zap.String("path", r.URL.Path),
126-
zap.Int("status", lrw.statusCode),
125+
zap.String(logkeys.Method, r.Method),
126+
zap.String(logkeys.Path, r.URL.Path),
127+
zap.Int(logkeys.Status, lrw.statusCode),
127128
)
128129
})
129130
}

examples/trace-logging/main.go

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net/http"
88
"time"
99

10+
"github.com/Suhaibinator/SRouter/pkg/logkeys"
1011
"github.com/Suhaibinator/SRouter/pkg/router"
1112
"github.com/Suhaibinator/SRouter/pkg/scontext" // Keep scontext
1213
"go.uber.org/zap"
@@ -67,9 +68,9 @@ func main() {
6768

6869
// Log with trace ID
6970
logger.Info("Processing request",
70-
zap.String("trace_id", traceID),
71-
zap.String("build_id", buildID),
72-
zap.String("config_id", configID),
71+
zap.String(logkeys.TraceID, traceID),
72+
zap.String(logkeys.BuildID, buildID),
73+
zap.String(logkeys.ConfigID, configID),
7374
zap.String("handler", "hello"),
7475
)
7576

@@ -78,9 +79,9 @@ func main() {
7879

7980
// Log again with the same trace ID
8081
logger.Info("Request processed successfully",
81-
zap.String("trace_id", traceID),
82-
zap.String("build_id", buildID),
83-
zap.String("config_id", configID),
82+
zap.String(logkeys.TraceID, traceID),
83+
zap.String(logkeys.BuildID, buildID),
84+
zap.String(logkeys.ConfigID, configID),
8485
zap.String("handler", "hello"),
8586
)
8687

@@ -100,7 +101,7 @@ func main() {
100101

101102
// Log with trace ID
102103
logger.Info("Received request, calling downstream service",
103-
zap.String("trace_id", traceID),
104+
zap.String(logkeys.TraceID, traceID),
104105
zap.String("handler", "downstream"),
105106
)
106107

@@ -109,8 +110,8 @@ func main() {
109110
req, err := http.NewRequest("GET", "http://localhost:8082/hello", nil)
110111
if err != nil {
111112
logger.Error("Failed to create request",
112-
zap.String("trace_id", traceID),
113-
zap.Error(err),
113+
zap.String(logkeys.TraceID, traceID),
114+
zap.NamedError(logkeys.Error, err),
114115
)
115116
http.Error(w, "Failed to create request", http.StatusInternalServerError)
116117
return
@@ -124,8 +125,8 @@ func main() {
124125
resp, err := client.Do(req)
125126
if err != nil {
126127
logger.Error("Failed to call downstream service",
127-
zap.String("trace_id", traceID),
128-
zap.Error(err),
128+
zap.String(logkeys.TraceID, traceID),
129+
zap.NamedError(logkeys.Error, err),
129130
)
130131
http.Error(w, "Failed to call downstream service", http.StatusInternalServerError)
131132
return
@@ -134,8 +135,8 @@ func main() {
134135

135136
// Log success
136137
logger.Info("Downstream service call successful",
137-
zap.String("trace_id", traceID),
138-
zap.Int("status", resp.StatusCode),
138+
zap.String(logkeys.TraceID, traceID),
139+
zap.Int(logkeys.Status, resp.StatusCode),
139140
)
140141

141142
// Return a response

examples/websocket/main.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net/url"
1010
"time"
1111

12+
"github.com/Suhaibinator/SRouter/pkg/logkeys"
1213
"github.com/Suhaibinator/SRouter/pkg/router"
1314
"github.com/gorilla/websocket"
1415
"go.uber.org/zap"
@@ -59,7 +60,7 @@ func main() {
5960
Handler: func(w http.ResponseWriter, r *http.Request) {
6061
conn, err := upgrader.Upgrade(w, r, nil)
6162
if err != nil {
62-
logger.Error("upgrade failed", zap.Error(err))
63+
logger.Error("upgrade failed", zap.NamedError(logkeys.Error, err))
6364
return
6465
}
6566
defer func() { _ = conn.Close() }()

pkg/logkeys/keys.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Package logkeys defines the canonical structured-log field names emitted by
2+
// SRouter. Applications can use the same keys to keep their logs aligned with
3+
// the router without depending on a particular logging implementation.
4+
package logkeys
5+
6+
const (
7+
Actual = "actual"
8+
AuthTokenSource = "auth_token_source"
9+
Bucket = "bucket"
10+
BuildID = "build_id"
11+
Bytes = "bytes"
12+
ClampedValue = "clamped_value"
13+
ClientIP = "client_ip"
14+
ConfigID = "config_id"
15+
Duration = "duration"
16+
Error = "error"
17+
Expected = "expected"
18+
Fallback = "fallback"
19+
HeaderName = "header_name"
20+
Invariant = "invariant"
21+
InvalidStatusCode = "invalid_status_code"
22+
IP = "ip"
23+
Key = "key"
24+
Limit = "limit"
25+
Method = "method"
26+
Methods = "methods"
27+
MetricName = "metric_name"
28+
Operation = "operation"
29+
OriginalMessage = "original_message"
30+
OriginalStatus = "original_status"
31+
Panic = "panic"
32+
Path = "path"
33+
ProvidedBuckets = "provided_buckets"
34+
Reason = "reason"
35+
Remaining = "remaining"
36+
RemoteAddr = "remote_addr"
37+
ResetDuration = "reset_duration"
38+
RetryAfterSeconds = "retry_after_seconds"
39+
Stack = "stack"
40+
Stage = "stage"
41+
Status = "status"
42+
StatusCode = "status_code"
43+
Strategy = "strategy"
44+
Timeout = "timeout"
45+
TraceID = "trace_id"
46+
UserAgent = "user_agent"
47+
Window = "window"
48+
)

pkg/logkeys/keys_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package logkeys
2+
3+
import "testing"
4+
5+
func TestCorrelationKeys(t *testing.T) {
6+
tests := []struct {
7+
name string
8+
got string
9+
want string
10+
}{
11+
{name: "trace", got: TraceID, want: "trace_id"},
12+
{name: "build", got: BuildID, want: "build_id"},
13+
{name: "config", got: ConfigID, want: "config_id"},
14+
}
15+
for _, test := range tests {
16+
if test.got != test.want {
17+
t.Errorf("%s log key = %q, want %q", test.name, test.got, test.want)
18+
}
19+
}
20+
}

pkg/metrics/prometheus/adapter.go

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/prometheus/client_golang/prometheus"
99
"go.uber.org/zap"
1010

11+
"github.com/Suhaibinator/SRouter/pkg/logkeys"
1112
srouter_metrics "github.com/Suhaibinator/SRouter/pkg/metrics"
1213
)
1314

@@ -106,7 +107,7 @@ func (b *PrometheusCounterBuilder) Build() srouter_metrics.Counter {
106107
// and Build can be called from the request path, so never panic.
107108
// The metric still works locally; it just won't be exported.
108109
b.registry.logger.Error("Failed to register Prometheus counter; metric will not be exported",
109-
zap.String("metric_name", b.opts.Name), zap.Error(err))
110+
zap.String(logkeys.MetricName, b.opts.Name), zap.NamedError(logkeys.Error, err))
110111
}
111112
}
112113
tags := make(srouter_metrics.Tags, len(b.opts.ConstLabels))
@@ -120,7 +121,7 @@ func (b *PrometheusCounterBuilder) Build() srouter_metrics.Counter {
120121
} else {
121122
// Never panic in the request path; keep the unregistered metric.
122123
b.registry.logger.Error("Failed to register Prometheus counter; metric will not be exported",
123-
zap.String("metric_name", b.opts.Name), zap.Error(err))
124+
zap.String(logkeys.MetricName, b.opts.Name), zap.NamedError(logkeys.Error, err))
124125
}
125126
}
126127
tags := make(srouter_metrics.Tags, len(b.opts.ConstLabels))
@@ -188,7 +189,7 @@ func (b *PrometheusGaugeBuilder) Build() srouter_metrics.Gauge {
188189
} else {
189190
// Never panic in the request path; keep the unregistered metric.
190191
b.registry.logger.Error("Failed to register Prometheus gauge; metric will not be exported",
191-
zap.String("metric_name", b.opts.Name), zap.Error(err))
192+
zap.String(logkeys.MetricName, b.opts.Name), zap.NamedError(logkeys.Error, err))
192193
}
193194
}
194195
tags := make(srouter_metrics.Tags, len(b.opts.ConstLabels))
@@ -202,7 +203,7 @@ func (b *PrometheusGaugeBuilder) Build() srouter_metrics.Gauge {
202203
} else {
203204
// Never panic in the request path; keep the unregistered metric.
204205
b.registry.logger.Error("Failed to register Prometheus gauge; metric will not be exported",
205-
zap.String("metric_name", b.opts.Name), zap.Error(err))
206+
zap.String(logkeys.MetricName, b.opts.Name), zap.NamedError(logkeys.Error, err))
206207
}
207208
}
208209
tags := make(srouter_metrics.Tags, len(b.opts.ConstLabels))
@@ -279,7 +280,7 @@ func (b *PrometheusHistogramBuilder) Build() srouter_metrics.Histogram {
279280
} else {
280281
// Never panic in the request path; keep the unregistered metric.
281282
b.registry.logger.Error("Failed to register Prometheus histogram; metric will not be exported",
282-
zap.String("metric_name", b.opts.Name), zap.Error(err))
283+
zap.String(logkeys.MetricName, b.opts.Name), zap.NamedError(logkeys.Error, err))
283284
}
284285
}
285286
tags := make(srouter_metrics.Tags, len(b.opts.ConstLabels))
@@ -293,7 +294,7 @@ func (b *PrometheusHistogramBuilder) Build() srouter_metrics.Histogram {
293294
} else {
294295
// Never panic in the request path; keep the unregistered metric.
295296
b.registry.logger.Error("Failed to register Prometheus histogram; metric will not be exported",
296-
zap.String("metric_name", b.opts.Name), zap.Error(err))
297+
zap.String(logkeys.MetricName, b.opts.Name), zap.NamedError(logkeys.Error, err))
297298
}
298299
}
299300
tags := make(srouter_metrics.Tags, len(b.opts.ConstLabels))
@@ -357,15 +358,15 @@ func (b *PrometheusSummaryBuilder) MaxAge(age time.Duration) srouter_metrics.Sum
357358
func (b *PrometheusSummaryBuilder) AgeBuckets(buckets int) srouter_metrics.SummaryBuilder {
358359
if buckets < 0 {
359360
b.registry.logger.Warn("Invalid negative value provided for AgeBuckets, defaulting to 0",
360-
zap.Int("provided_buckets", buckets),
361-
zap.String("metric_name", b.opts.Name),
361+
zap.Int(logkeys.ProvidedBuckets, buckets),
362+
zap.String(logkeys.MetricName, b.opts.Name),
362363
)
363364
b.opts.AgeBuckets = 0
364365
} else if buckets > math.MaxUint32 {
365366
b.registry.logger.Warn("Value provided for AgeBuckets exceeds MaxUint32, clamping",
366-
zap.Int("provided_buckets", buckets),
367-
zap.Uint32("clamped_value", math.MaxUint32),
368-
zap.String("metric_name", b.opts.Name),
367+
zap.Int(logkeys.ProvidedBuckets, buckets),
368+
zap.Uint32(logkeys.ClampedValue, math.MaxUint32),
369+
zap.String(logkeys.MetricName, b.opts.Name),
369370
)
370371
b.opts.AgeBuckets = math.MaxUint32
371372
} else {
@@ -403,7 +404,7 @@ func (b *PrometheusSummaryBuilder) Build() srouter_metrics.Summary {
403404
} else {
404405
// Never panic in the request path; keep the unregistered metric.
405406
b.registry.logger.Error("Failed to register Prometheus summary; metric will not be exported",
406-
zap.String("metric_name", b.opts.Name), zap.Error(err))
407+
zap.String(logkeys.MetricName, b.opts.Name), zap.NamedError(logkeys.Error, err))
407408
}
408409
}
409410
tags := make(srouter_metrics.Tags, len(b.opts.ConstLabels))
@@ -417,7 +418,7 @@ func (b *PrometheusSummaryBuilder) Build() srouter_metrics.Summary {
417418
} else {
418419
// Never panic in the request path; keep the unregistered metric.
419420
b.registry.logger.Error("Failed to register Prometheus summary; metric will not be exported",
420-
zap.String("metric_name", b.opts.Name), zap.Error(err))
421+
zap.String(logkeys.MetricName, b.opts.Name), zap.NamedError(logkeys.Error, err))
421422
}
422423
}
423424
tags := make(srouter_metrics.Tags, len(b.opts.ConstLabels))

pkg/middleware/auth.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"strings"
77

88
"github.com/Suhaibinator/SRouter/pkg/common"
9+
"github.com/Suhaibinator/SRouter/pkg/logkeys"
910
"github.com/Suhaibinator/SRouter/pkg/scontext"
1011
"go.uber.org/zap"
1112
)
@@ -125,12 +126,12 @@ func AuthenticationWithProvider[T comparable, U any](
125126
traceID = GenerateTraceID()
126127
}
127128
logger.Info("Authentication failed",
128-
zap.String("reason", "credentials rejected"),
129-
zap.String("method", r.Method),
130-
zap.String("path", r.URL.Path),
131-
zap.String("remote_addr", r.RemoteAddr),
132-
zap.Int("status_code", http.StatusUnauthorized),
133-
zap.String("trace_id", traceID),
129+
zap.String(logkeys.Reason, "credentials rejected"),
130+
zap.String(logkeys.Method, r.Method),
131+
zap.String(logkeys.Path, r.URL.Path),
132+
zap.String(logkeys.RemoteAddr, r.RemoteAddr),
133+
zap.Int(logkeys.StatusCode, http.StatusUnauthorized),
134+
zap.String(logkeys.TraceID, traceID),
134135
)
135136
http.Error(w, "Unauthorized", http.StatusUnauthorized)
136137
return
@@ -347,12 +348,12 @@ func AuthenticationWithUserProvider[T comparable, U any](
347348
traceID = GenerateTraceID()
348349
}
349350
logger.Info("Authentication failed",
350-
zap.Error(err),
351-
zap.String("method", r.Method),
352-
zap.String("path", r.URL.Path),
353-
zap.String("remote_addr", r.RemoteAddr),
354-
zap.Int("status_code", http.StatusUnauthorized),
355-
zap.String("trace_id", traceID),
351+
zap.NamedError(logkeys.Error, err),
352+
zap.String(logkeys.Method, r.Method),
353+
zap.String(logkeys.Path, r.URL.Path),
354+
zap.String(logkeys.RemoteAddr, r.RemoteAddr),
355+
zap.Int(logkeys.StatusCode, http.StatusUnauthorized),
356+
zap.String(logkeys.TraceID, traceID),
356357
)
357358
http.Error(w, "Unauthorized", http.StatusUnauthorized)
358359
return

0 commit comments

Comments
 (0)