[teamai] Push 87 resource(s) from XingfenD
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
# Pipeline Patterns
|
||||
|
||||
Complete code examples for every `slog-multi` composition pattern.
|
||||
|
||||
## Fanout — Broadcast to All
|
||||
|
||||
Sends every record to every handler sequentially. Latency = sum of all handler latencies.
|
||||
|
||||
```go
|
||||
import slogmulti "github.com/samber/slog-multi"
|
||||
|
||||
logger := slog.New(
|
||||
slogmulti.Fanout(
|
||||
slog.NewJSONHandler(os.Stdout, nil), // stdout
|
||||
slog.NewTextHandler(logFile, nil), // file
|
||||
slogsentry.Option{Level: slog.LevelError}.NewSentryHandler(), // Sentry
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
**When to use:** Every destination must receive every record (audit logs, compliance). **When NOT to use:** Handlers have different record needs (use Router) or high latency (use Pool).
|
||||
|
||||
## Router — Predicate-Based Routing
|
||||
|
||||
Routes records to ALL handlers whose predicate matches. Unmatched records go nowhere unless a default handler is added.
|
||||
|
||||
```go
|
||||
logger := slog.New(
|
||||
slogmulti.Router().
|
||||
Add(sentryHandler, slogmulti.LevelIs(slog.LevelError)).
|
||||
Add(slackHandler, slogmulti.LevelIs(slog.LevelWarn)).
|
||||
Add(lokiHandler, slogmulti.LevelIs(slog.LevelInfo, slog.LevelDebug)).
|
||||
Add(slog.NewJSONHandler(os.Stdout, nil)). // catch-all: no predicate
|
||||
Handler(),
|
||||
)
|
||||
```
|
||||
|
||||
**Built-in predicates:**
|
||||
|
||||
```go
|
||||
slogmulti.LevelIs(slog.LevelError) // match specific levels
|
||||
slogmulti.LevelIsNot(slog.LevelDebug) // exclude levels
|
||||
slogmulti.MessageIs("payment processed") // exact message match
|
||||
slogmulti.MessageIsNot("healthcheck") // exclude exact message
|
||||
slogmulti.MessageContains("timeout") // partial message match
|
||||
slogmulti.MessageNotContains("debug") // exclude partial message
|
||||
slogmulti.AttrValueIs("module", "billing") // match attribute value
|
||||
slogmulti.AttrKindIs(slog.KindString) // match attribute kind
|
||||
```
|
||||
|
||||
**Custom predicate:**
|
||||
|
||||
```go
|
||||
func recordMatchRegion(region string) func(ctx context.Context, r slog.Record) bool {
|
||||
return func(ctx context.Context, r slog.Record) bool {
|
||||
match := false
|
||||
r.Attrs(func(attr slog.Attr) bool {
|
||||
if attr.Key == "region" && attr.Value.String() == region {
|
||||
match = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return match
|
||||
}
|
||||
}
|
||||
|
||||
logger := slog.New(
|
||||
slogmulti.Router().
|
||||
Add(slackUS, recordMatchRegion("us")).
|
||||
Add(slackEU, recordMatchRegion("eu")).
|
||||
Handler(),
|
||||
)
|
||||
```
|
||||
|
||||
**Warning:** Records matching no predicate are silently dropped. Always add a catch-all handler (no predicate) unless you intentionally want to discard unmatched records.
|
||||
|
||||
## FirstMatch — Short-Circuit Routing
|
||||
|
||||
Like Router but stops at the first matching handler. Each record goes to exactly one destination.
|
||||
|
||||
```go
|
||||
logger := slog.New(
|
||||
slogmulti.Router().
|
||||
Add(queryHandler, matchQueryLogs). // priority 1
|
||||
Add(requestHandler, matchRequestLogs). // priority 2
|
||||
Add(defaultHandler). // fallback
|
||||
FirstMatch().
|
||||
Handler(),
|
||||
)
|
||||
```
|
||||
|
||||
**When to use:** Priority-based routing where each record should be processed exactly once. Order matters — put the most specific handlers first.
|
||||
|
||||
## Failover — Sequential Fallback
|
||||
|
||||
Tries handlers in order until one succeeds (returns `nil` error). If primary fails, falls through to secondary.
|
||||
|
||||
```go
|
||||
logger := slog.New(
|
||||
slogmulti.Failover()(
|
||||
slogloki.Option{Level: slog.LevelDebug, Client: lokiClient}.NewLokiHandler(),
|
||||
slog.NewJSONHandler(localFile, nil), // fallback to local file
|
||||
slog.NewTextHandler(os.Stderr, nil), // last resort
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
**When to use:** Network sinks that may be unreliable (Loki, Logstash, remote syslog). Primary handles 99.9% of traffic; fallback catches the rest.
|
||||
|
||||
## Pool — Load-Balanced Dispatch
|
||||
|
||||
Randomly distributes each record to one handler from the pool. Useful when you have equivalent handlers and want to spread load.
|
||||
|
||||
```go
|
||||
logger := slog.New(
|
||||
slogmulti.Pool()(
|
||||
lokiHandler1, // shard 1
|
||||
lokiHandler2, // shard 2
|
||||
lokiHandler3, // shard 3
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
**When to use:** Multiple equivalent sinks where you want throughput distribution. Latency = single handler latency (not sum like Fanout).
|
||||
|
||||
## Pipe — Middleware Chain
|
||||
|
||||
Chains middleware functions that intercept, transform, or enrich records before they reach the final handler.
|
||||
|
||||
```go
|
||||
logger := slog.New(
|
||||
slogmulti.
|
||||
Pipe(samplingMiddleware). // step 1: drop noise
|
||||
Pipe(piiScrubbingMiddleware). // step 2: mask PII
|
||||
Pipe(traceInjectionMiddleware). // step 3: add trace_id
|
||||
Pipe(slogmulti.RecoverHandlerError( // step 4: catch handler panics
|
||||
func(ctx context.Context, record slog.Record, err error) {
|
||||
log.Println("handler error:", err)
|
||||
},
|
||||
)).
|
||||
Handler(slog.NewJSONHandler(os.Stdout, nil)),
|
||||
)
|
||||
```
|
||||
|
||||
## Inline Handlers and Middleware
|
||||
|
||||
Create quick handlers without defining a full struct.
|
||||
|
||||
```go
|
||||
// Inline handler — for testing or simple consumers
|
||||
handler := slogmulti.NewHandleInlineHandler(
|
||||
func(ctx context.Context, groups []string, attrs []slog.Attr, record slog.Record) error {
|
||||
fmt.Printf("LOG: %s %s\n", record.Level, record.Message)
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
// Inline middleware — intercept and transform records
|
||||
middleware := slogmulti.NewHandleInlineMiddleware(
|
||||
func(ctx context.Context, record slog.Record, next func(context.Context, slog.Record) error) error {
|
||||
record.AddAttrs(slog.String("service", "my-api"))
|
||||
return next(ctx, record)
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
## AttrFromContext — Request-Scoped Attributes
|
||||
|
||||
HTTP middlewares (`slog-gin`, `slog-echo`, etc.) inject request attributes into context. Backend handlers extract them via `AttrFromContext`.
|
||||
|
||||
```go
|
||||
// Backend handler extracts trace_id from context
|
||||
handler := slogsentry.Option{
|
||||
Level: slog.LevelError,
|
||||
AttrFromContext: []func(ctx context.Context) []slog.Attr{
|
||||
func(ctx context.Context) []slog.Attr {
|
||||
if traceID := ctx.Value("trace_id"); traceID != nil {
|
||||
return []slog.Attr{slog.String("trace_id", traceID.(string))}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}.NewSentryHandler()
|
||||
```
|
||||
|
||||
**Important:** `AttrFromContext` only works when the context actually contains the expected values. This requires an HTTP middleware (like `slog-gin`) to populate the context first. Without the middleware, `AttrFromContext` silently returns nil.
|
||||
|
||||
## Full Production Pipeline
|
||||
|
||||
Canonical ordering: sampling → middleware (PII, trace) → routing → sinks.
|
||||
|
||||
```go
|
||||
import (
|
||||
slogmulti "github.com/samber/slog-multi"
|
||||
slogsampling "github.com/samber/slog-sampling"
|
||||
slogformatter "github.com/samber/slog-formatter"
|
||||
slogsentry "github.com/samber/slog-sentry/v2"
|
||||
slogloki "github.com/samber/slog-loki/v3"
|
||||
)
|
||||
|
||||
// 1. Sampling: first 20 per 5s, then 10%
|
||||
sampling := slogsampling.ThresholdSamplingOption{
|
||||
Tick: 5 * time.Second, Threshold: 20, Rate: 0.1,
|
||||
}.NewMiddleware()
|
||||
|
||||
// 2. PII scrubbing
|
||||
pii := slogformatter.NewFormatterMiddleware(
|
||||
slogformatter.PIIFormatter("user"),
|
||||
slogformatter.IPAddressFormatter("client_ip"),
|
||||
)
|
||||
|
||||
// 3. Error recovery
|
||||
recovery := slogmulti.RecoverHandlerError(func(ctx context.Context, r slog.Record, err error) {
|
||||
log.Printf("slog handler error: %v", err)
|
||||
})
|
||||
|
||||
// 4. Sinks
|
||||
sentryHandler := slogsentry.Option{Level: slog.LevelError}.NewSentryHandler()
|
||||
lokiHandler := slogloki.Option{Level: slog.LevelDebug, Client: lokiClient}.NewLokiHandler()
|
||||
defer lokiClient.Stop() // flush buffered logs
|
||||
|
||||
// 5. Compose — errors bypass sampling, everything else is sampled
|
||||
logger := slog.New(
|
||||
slogmulti.
|
||||
Pipe(pii). // scrub PII on all records
|
||||
Pipe(recovery). // catch panics
|
||||
Handler(
|
||||
slogmulti.Router().
|
||||
Add(sentryHandler, slogmulti.LevelIs(slog.LevelError)). // errors: no sampling
|
||||
Add(slogmulti. // everything else: sampled
|
||||
Pipe(sampling).
|
||||
Handler(lokiHandler),
|
||||
).
|
||||
FirstMatch(). // stop at first matching route — errors won't fall through to sampled path
|
||||
Handler(),
|
||||
),
|
||||
)
|
||||
slog.SetDefault(logger)
|
||||
```
|
||||
Reference in New Issue
Block a user