wideevents
About 878 words
Enforces the wide events logging pattern from loggingsucks.com.
Category
Observability
What It Checks
This analyzer enforces the philosophy from loggingsucks.com:
- Banned loggers - logrus, stdlib log, fmt.Print (use zap instead)
- Single event per function - no scattered logs (consider emitting one wide event)
- Structured fields on log calls - use
zap.String(),zap.Error(), etc. - Request context in wide events - include trace_id, request_id, or span_id (see Request Context Fields)
- Span attributes when context is available - use
trace.SpanFromContext(ctx)andspan.SetAttributes()
Supported Logging Frameworks
- zap -
zap.L().Info(),zap.L().Error(), the sugared logger'sInfow(), etc. - otelzap -
otelzap.L().InfoContext(),otelzap.L().WithError().ErrorContext(),otelzap.L().Ctx(ctx).Info(), etc. - log/slog -
slog.Info(),logger.Warn(), with key-value pairs orslog.Attrfields
Log calls are recognized by type, not by name: only methods of these loggers (and the banned logrus and stdlib log) count. http.Error, gRPC's status.Error or a method named Info on your own type are not logging.
The analyzer recognizes:
- Context-aware methods (
*Contextsuffix) and otelzap loggers bound withCtx(ctx), which auto-extract trace context - Method chaining (
.WithError(),.With()) that adds structured fields;.Named()and.WithOptions()add none
Request Context Fields
A wide event has request context when one of its fields is named, ignoring case and the separators _, - and ., with one of these names or ends in one: trace_id, span_id, request_id, req_id, correlation_id, correlation, user_id, service, traceparent. So dd.trace_id, http.request_id and traceId count; id and user don't.
Only functions that take a context.Context are asked for request context: a function without one isn't handling a request, so a startup, shutdown or CLI event has nothing to correlate with.
Why It Matters
"One log line per request per service with 50+ structured fields beats 15 scattered log statements."
Scattered logs make debugging impossible at scale. See Wide Events Pattern for the full philosophy.
Examples
Bad: Scattered Logs
func ProcessRequest(ctx context.Context, req *Request) error {
log.Info("starting request") // Scattered log #1
user, err := getUser(ctx, req.UserID)
if err != nil {
log.Error("failed to get user", err) // Scattered log #2
return err
}
log.Info("got user") // Scattered log #3
return nil
}Good: Single Wide Event
func ProcessRequest(ctx context.Context, req *Request) error {
start := time.Now()
var result string
defer func() {
logger.Info("request processed",
zap.String("request_id", req.ID),
zap.String("user_id", req.UserID),
zap.String("result", result),
zap.Duration("duration", time.Since(start)),
)
}()
user, err := getUser(ctx, req.UserID)
if err != nil {
result = "user_fetch_failed"
return err
}
result = "success"
return nil
}Bad: Banned Logger
import "github.com/sirupsen/logrus"
func Process() {
logrus.Info("message") // Banned: use zap instead
}Good: Allowed Logger
import "go.uber.org/zap"
func Process() {
zap.L().Info("message",
zap.String("key", "value"),
)
}Bad: Log in Loop
for _, item := range items {
logger.Info("processing item", zap.String("id", item.ID)) // Log spam!
}Good: Aggregate and Log Once
var processedIDs []string
for _, item := range items {
process(item)
processedIDs = append(processedIDs, item.ID)
}
logger.Info("processed items", zap.Strings("ids", processedIDs))Bad: Logging Without Span Attributes
func ProcessRequest(ctx context.Context, req *Request) error {
// Has context but only logs - doesn't use span!
logger.Info("processing request",
zap.String("request_id", req.ID),
)
return nil
}Good: Using Span Attributes
import "go.opentelemetry.io/otel/trace"
func ProcessRequest(ctx context.Context, req *Request) error {
span := trace.SpanFromContext(ctx)
span.SetAttributes(
attribute.String("request_id", req.ID),
attribute.String("user_id", req.UserID),
)
// ... process request ...
span.SetAttributes(attribute.String("result", "success"))
return nil
}Best: Span Attributes + Single Wide Event
func ProcessRequest(ctx context.Context, req *Request) error {
span := trace.SpanFromContext(ctx)
start := time.Now()
defer func() {
// Add final attributes to span
span.SetAttributes(
attribute.Int64("duration_ms", time.Since(start).Milliseconds()),
)
// Emit single wide event for logs/metrics
logger.Info("request processed",
zap.String("trace_id", span.SpanContext().TraceID().String()),
zap.String("request_id", req.ID),
zap.Duration("duration", time.Since(start)),
)
}()
span.SetAttributes(
attribute.String("request_id", req.ID),
attribute.String("user_id", req.UserID),
)
return processInternal(ctx, req)
}Allowed Patterns
Debug Logging
zap.Debug is allowed for development:
zap.L().Debug("intermediate state", zap.Any("data", data)) // OKContext-Aware Methods (otelzap)
Methods ending in Context (like ErrorContext, InfoContext, WarnContext) automatically extract trace context from the context.Context parameter, so they don't need explicit trace_id/span_id fields:
import "github.com/spechtlabs/go-otel-utils/otelzap"
func ProcessRequest(ctx context.Context, req *Request) error {
// OK - ErrorContext automatically extracts trace_id/span_id from ctx
otelzap.L().ErrorContext(ctx, "request failed",
zap.String("request_id", req.ID),
)
return err
}Method Chaining
Fields added via method chaining (.WithError(), .With(), etc.) are recognized as structured fields:
// OK - WithError adds structured error field
otelzap.L().WithError(err).ErrorContext(ctx, "operation failed")
// OK - With adds structured fields, here the request context
logger.With(zap.String("request_id", req.ID)).Info("starting")Test Functions
Logging in tests is not checked:
func TestProcess(t *testing.T) {
log.Println("test output") // OK in tests
}CLI Output
fmt.Print* functions are allowed in CLI packages (cmd/ and cli/ directories) since they're used for user output, not logging:
// In cmd/mycli/main.go or internal/cli/output.go
func printResult(result string) {
fmt.Println(result) // OK - user output in CLI code
}Configuration
# .golint-sl.yaml
analyzers:
wideevents: true # enabled by defaultWhen to Disable
- Projects using different logging patterns (e.g., controller-runtime)
- CLI tools where structured logging is overkill
- Libraries (let consumers decide logging)
analyzers:
wideevents: falseRelated Analyzers
- contextlogger - Context-based logging
- contextpropagation - Context propagation
