Documentation
¶
Overview ¶
Package promolog provides per-request trace capture with promote-on-error semantics. Each request buffers its slog records locally; only when an error occurs is the buffer promoted to a Storer implementation for later retrieval. The core package has zero external dependencies. See github.com/catgoose/promolog/sqlite for a SQLite-backed Storer.
Index ¶
- Variables
- func AutoPromoteMiddleware(store Storer, policies ...PromotionPolicy) func(http.Handler) http.Handler
- func BodyCaptureMiddleware(opts ...BodyCaptureOption) func(http.Handler) http.Handler
- func CorrelationMiddleware(next http.Handler) http.Handler
- func CorrelationMiddlewareWithLimit(limit int) func(http.Handler) http.Handler
- func CorrelationTransport(base http.RoundTripper) http.RoundTripper
- func GetParentRequestID(ctx context.Context) string
- func GetRequestDuration(r *http.Request) time.Duration
- func GetRequestID(ctx context.Context) string
- func GetRequestStartTime(ctx context.Context) time.Time
- func NewBufferContext(ctx context.Context) context.Context
- func NewBufferContextWithLimit(ctx context.Context, limit int) context.Context
- func NewCorrelatedClient(base *http.Client) *http.Client
- func StatusCodeStr(code int) string
- func TraceFields(t Trace) map[string]string
- func WireExporter(store Storer, exporter Exporter, ...)
- type AggregateFilter
- type AggregateResult
- type BodyCaptureOption
- type Buffer
- func (b *Buffer) Append(e Entry)
- func (b *Buffer) Entries() []Entry
- func (b *Buffer) RequestBody() string
- func (b *Buffer) ResponseBody() string
- func (b *Buffer) SetRequestBody(body string)
- func (b *Buffer) SetResponseBody(body string)
- func (b *Buffer) Snapshot() []Entrydeprecated
- func (b *Buffer) Tag(key, value string)
- func (b *Buffer) Tags() map[string]string
- type Entry
- type Exporter
- type FilterOptions
- type FilterRule
- type Handler
- type HandlerOption
- type PromotionPolicy
- type RetentionEngine
- type RetentionRule
- type RuleAction
- type RuleEngine
- type Storer
- type Trace
- type TraceFilter
- type TraceSummary
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrDuplicateTrace = errors.New("promolog: duplicate request ID")
ErrDuplicateTrace is returned when a trace with the same request ID already exists.
var RequestIDKey = requestIDKeyType{}
Functions ¶
func AutoPromoteMiddleware ¶ added in v0.2.21
func AutoPromoteMiddleware(store Storer, policies ...PromotionPolicy) func(http.Handler) http.Handler
AutoPromoteMiddleware returns middleware that captures the response status code and evaluates the given PromotionPolicy values after the downstream handler returns. If any policy matches, the request's buffer is promoted to the store automatically — no manual Promote call is needed.
This middleware must be applied after CorrelationMiddleware so that the request context contains both a request ID and a Buffer.
Manual promotion (calling store.Promote directly) remains available as an escape hatch for cases not covered by policies.
Usage:
policies := []promolog.PromotionPolicy{
promolog.StatusPolicy(500),
}
mux := http.NewServeMux()
handler := promolog.CorrelationMiddleware(
promolog.AutoPromoteMiddleware(store, policies...)(mux),
)
func BodyCaptureMiddleware ¶ added in v0.2.21
func BodyCaptureMiddleware(opts ...BodyCaptureOption) func(http.Handler) http.Handler
BodyCaptureMiddleware returns middleware that captures request and response bodies into the per-request Buffer. It must be applied after CorrelationMiddleware so that the context contains a Buffer.
Usage:
handler := promolog.CorrelationMiddleware(
promolog.BodyCaptureMiddleware()(
promolog.AutoPromoteMiddleware(store, policies...)(mux),
),
)
func CorrelationMiddleware ¶
CorrelationMiddleware is stdlib HTTP middleware that sets up per-request correlation for promolog. It generates a unique request ID (or reuses one from the incoming X-Request-ID header), sets the X-Request-ID response header, stores the ID in the request context, and initializes a promolog Buffer for log capture.
Usage with net/http:
mux := http.NewServeMux()
http.ListenAndServe(":8080", promolog.CorrelationMiddleware(mux))
Usage with Echo:
e.Use(echo.WrapMiddleware(promolog.CorrelationMiddleware))
Example ¶
package main
import (
"fmt"
)
func main() {
// CorrelationMiddleware generates a request ID, sets the X-Request-ID
// header, and initializes a promolog Buffer on each request's context.
// See the package-level docs for HTTP handler usage.
fmt.Println("wrap with promolog.CorrelationMiddleware(handler)")
}
Output: wrap with promolog.CorrelationMiddleware(handler)
func CorrelationMiddlewareWithLimit ¶ added in v0.2.20
CorrelationMiddlewareWithLimit works like CorrelationMiddleware but initialises the per-request Buffer with the given entry limit. A limit of 0 means unlimited.
func CorrelationTransport ¶ added in v0.2.21
func CorrelationTransport(base http.RoundTripper) http.RoundTripper
CorrelationTransport returns an http.RoundTripper that reads the request ID from the outgoing request's context and sets it as the X-Request-ID header. If no request ID is present in the context the request is passed through unmodified. When base is nil, http.DefaultTransport is used.
func GetParentRequestID ¶ added in v0.2.21
GetParentRequestID retrieves the parent request ID from the context, or returns an empty string if none is set.
func GetRequestDuration ¶ added in v0.2.21
GetRequestDuration returns the elapsed time since the request started. If no start time is present in the context it returns 0.
func GetRequestID ¶
GetRequestID retrieves the request ID from the context, or returns an empty string if none is set.
func GetRequestStartTime ¶ added in v0.2.21
GetRequestStartTime retrieves the request start time from the context, or returns the zero time if none is set.
func NewBufferContext ¶
NewBufferContext returns a new context with an empty, unlimited Buffer attached. For a size-limited buffer, use NewBufferContextWithLimit.
func NewBufferContextWithLimit ¶ added in v0.2.20
NewBufferContextWithLimit returns a new context with a size-limited Buffer. The limit caps the number of entries kept. When the limit is exceeded the buffer retains the first and last entries and inserts a synthetic entry noting how many middle entries were elided. A limit of 0 means unlimited.
func NewCorrelatedClient ¶ added in v0.2.21
NewCorrelatedClient returns a shallow copy of base (or http.DefaultClient when base is nil) whose transport propagates request IDs via the X-Request-ID header. The original client is not modified.
func StatusCodeStr ¶ added in v0.2.21
func TraceFields ¶ added in v0.2.21
TraceFields extracts the standard field map from a Trace, suitable for passing to RuleEngine.Match.
func WireExporter ¶ added in v0.2.21
func WireExporter(store Storer, exporter Exporter, getTrace func(ctx context.Context, requestID string) (*Trace, error))
WireExporter connects an Exporter to a Storer's OnPromote callback so that every promoted trace is exported asynchronously. The export runs in a separate goroutine to avoid blocking the promote path.
To use multiple exporters, call WireExporter once for each.
Note: because SetOnPromote replaces any previously registered callback, this helper wraps the existing callback (if any) so both are invoked.
Types ¶
type AggregateFilter ¶ added in v0.2.21
type AggregateFilter struct {
GroupBy string // "route", "status_code", "method", "error_chain"
Window time.Duration // time window to aggregate over
MinCount int // minimum count to include in results
}
AggregateFilter controls how traces are grouped for aggregation.
type AggregateResult ¶ added in v0.2.21
type AggregateResult struct {
Key string // the grouped value (e.g., "/api/users")
Count int // number of traces in this group
TopErrors []string // most common error chains in this group
}
AggregateResult is a single aggregation bucket.
type BodyCaptureOption ¶ added in v0.2.21
type BodyCaptureOption func(*bodyCaptureConfig)
BodyCaptureOption configures the BodyCaptureMiddleware.
func WithMaxBodySize ¶ added in v0.2.21
func WithMaxBodySize(n int) BodyCaptureOption
WithMaxBodySize sets the maximum number of bytes captured from the request and response bodies. Bodies larger than this are truncated. The default is 64 KiB.
func WithRedactor ¶ added in v0.2.21
func WithRedactor(fn func(body []byte) []byte) BodyCaptureOption
WithRedactor registers a function that is applied to captured bodies before they are stored in the Buffer. Use this to strip sensitive data such as passwords or tokens.
type Buffer ¶
type Buffer struct {
// contains filtered or unexported fields
}
Buffer is a per-request log buffer stored in the request context. It is safe for concurrent use.
When a limit is set (via NewBuffer), the buffer keeps the first half and last half of entries. Middle entries are dropped and replaced with a synthetic entry indicating how many were elided. A limit of 0 means unlimited.
func GetBuffer ¶
GetBuffer retrieves the per-request Buffer from the context, or nil.
Example ¶
package main
import (
"context"
"fmt"
"time"
"github.com/catgoose/promolog"
)
func main() {
// Attach a buffer to the context.
ctx := promolog.NewBufferContext(context.Background())
buf := promolog.GetBuffer(ctx)
buf.Append(promolog.Entry{
Time: time.Now(),
Level: "INFO",
Message: "manual entry",
})
fmt.Println(len(buf.Entries()))
// Without a buffer, GetBuffer returns nil.
empty := promolog.GetBuffer(context.Background())
fmt.Println(empty)
}
Output: 1 <nil>
func NewBuffer ¶ added in v0.2.20
NewBuffer creates a Buffer with the given entry limit. A limit of 0 means unlimited (the same as using &Buffer{} directly).
func (*Buffer) Entries ¶
Entries returns a copy of the current entries. It is safe for concurrent use. When a limit is active and entries were elided, a synthetic entry is inserted between the head and tail portions indicating how many entries were dropped.
func (*Buffer) RequestBody ¶ added in v0.2.21
RequestBody returns the stored request body. It is safe for concurrent use.
func (*Buffer) ResponseBody ¶ added in v0.2.21
ResponseBody returns the stored response body. It is safe for concurrent use.
func (*Buffer) SetRequestBody ¶ added in v0.2.21
SetRequestBody stores the request body in the buffer. It is safe for concurrent use.
func (*Buffer) SetResponseBody ¶ added in v0.2.21
SetResponseBody stores the response body in the buffer. It is safe for concurrent use.
type Entry ¶
type Entry struct {
Time time.Time `json:"time"`
Level string `json:"level"`
Message string `json:"msg"`
Attrs map[string]string `json:"attrs,omitempty"`
}
Entry is a single captured log record.
type Exporter ¶ added in v0.2.21
type Exporter interface {
// Export sends a single trace to the export destination.
Export(ctx context.Context, trace Trace) error
// Close flushes any buffered data and releases resources.
Close() error
}
Exporter defines the interface for exporting promoted traces to external systems. Implementations live in the export/ subpackages.
type FilterOptions ¶
type FilterOptions struct {
StatusCodes []int
Methods []string
TagKeys []string // distinct tag keys across all traces
RemoteIPs []string
Routes []string
UserIDs []string
Tags map[string][]string // distinct values per tag key
}
FilterOptions holds distinct values available for filter dropdowns.
type FilterRule ¶ added in v0.2.21
type FilterRule struct {
ID int `json:"id"`
Name string `json:"name"`
Field string `json:"field"` // "remote_ip", "route", "status_code", "method", "user_agent", "user_id"
Operator string `json:"operator"` // "equals", "contains", "starts_with", "matches_glob"
Value string `json:"value"`
Action string `json:"action"` // "suppress", "always_promote", "tag", "short_ttl"
Enabled bool `json:"enabled"`
CreatedAt time.Time `json:"created_at"`
}
FilterRule represents a runtime filter rule that can suppress, promote, tag, or set a short TTL on traces based on request metadata.
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler is a slog.Handler that captures log records into a per-request Buffer when the record is associated with a request ID.
func NewHandler ¶
func NewHandler(inner slog.Handler, opts ...HandlerOption) *Handler
NewHandler wraps an existing slog.Handler so that every record with a request_id attribute is also buffered per-request for promote-on-error. Optional HandlerOption values can configure buffer limits and other settings.
Example ¶
package main
import (
"context"
"fmt"
"io"
"log/slog"
"github.com/catgoose/promolog"
)
func main() {
// Wrap any slog.Handler to capture log records per-request.
inner := slog.NewTextHandler(io.Discard, &slog.HandlerOptions{Level: slog.LevelDebug})
handler := promolog.NewHandler(inner)
logger := slog.New(handler)
// Attach a request ID and buffer to the context.
ctx := context.WithValue(context.Background(), promolog.RequestIDKey, "req-001")
ctx = promolog.NewBufferContext(ctx)
// Log normally; records are captured in the per-request buffer.
logger.InfoContext(ctx, "handling request", "path", "/api/users")
logger.DebugContext(ctx, "loaded 42 rows")
buf := promolog.GetBuffer(ctx)
fmt.Println(len(buf.Entries()))
}
Output: 2
func (*Handler) BufferLimit ¶ added in v0.2.20
BufferLimit returns the buffer entry limit configured on this Handler. A value of 0 means unlimited.
type HandlerOption ¶ added in v0.2.20
type HandlerOption func(*Handler)
HandlerOption configures optional Handler behaviour.
func WithBufferLimit ¶ added in v0.2.20
func WithBufferLimit(n int) HandlerOption
WithBufferLimit sets a cap on the number of log entries the per-request Buffer will retain. When the limit is exceeded the buffer keeps the first and last entries and inserts a synthetic entry noting how many middle entries were elided. A limit of 0 (the default) means unlimited.
type PromotionPolicy ¶ added in v0.2.21
type PromotionPolicy struct {
// Name is a human-readable label for debugging / logging.
Name string
// Predicate returns true when the request should be promoted.
Predicate func(r *http.Request, statusCode int) bool
}
PromotionPolicy decides whether a completed request's buffer should be promoted to the store. Policies are evaluated by AutoPromoteMiddleware after the downstream handler returns.
func LatencyPolicy ¶ added in v0.2.21
func LatencyPolicy(threshold time.Duration) PromotionPolicy
LatencyPolicy returns a PromotionPolicy that promotes any request whose duration exceeds threshold. It reads the request start time stored in the context by CorrelationMiddleware; if no start time is present the predicate returns false.
func RoutePolicy ¶ added in v0.2.21
func RoutePolicy(pattern string, predicate func(statusCode int) bool) PromotionPolicy
RoutePolicy returns a PromotionPolicy that promotes when the request path matches pattern (using path.Match) and predicate reports true for the response status code. pattern follows the same rules as path.Match (e.g. "/api/*").
func SamplePolicy ¶ added in v0.2.21
func SamplePolicy(rate float64, rng *rand.Rand) PromotionPolicy
SamplePolicy returns a PromotionPolicy that promotes a random fraction of requests. rate must be in the closed interval [0, 1] (e.g. 0.01 = 1%). Values outside that range, or NaN, panic at construction time because they indicate a configuration bug that should fail loudly rather than silently misbehave. An optional *rand.Rand source can be provided for deterministic testing; when nil a default source is used.
func StatusPolicy ¶ added in v0.2.21
func StatusPolicy(minCode int) PromotionPolicy
StatusPolicy returns a PromotionPolicy that promotes when the response status code is >= minCode. A typical default is 500 (server errors only).
type RetentionEngine ¶ added in v0.2.21
type RetentionEngine struct {
// contains filtered or unexported fields
}
RetentionEngine evaluates retention rules against trace metadata. It holds rules in memory for fast evaluation. A nil or zero-value RetentionEngine never matches (the default TTL applies).
func NewRetentionEngine ¶ added in v0.2.21
func NewRetentionEngine(rules []RetentionRule) *RetentionEngine
NewRetentionEngine creates a RetentionEngine loaded with the given rules. Only enabled rules are retained.
func (*RetentionEngine) HasRules ¶ added in v0.2.21
func (re *RetentionEngine) HasRules() bool
HasRules reports whether the engine has any enabled rules loaded.
func (*RetentionEngine) Match ¶ added in v0.2.21
func (re *RetentionEngine) Match(fields map[string]string) (RetentionRule, bool)
Match evaluates all loaded retention rules against the provided field values. It returns the matching rule with the shortest TTL (most aggressive retention). If no rule matches, matched is false.
type RetentionRule ¶ added in v0.2.21
type RetentionRule struct {
ID int `json:"id"`
Name string `json:"name"`
Field string `json:"field"` // "route", "status_code", "method", etc.
Operator string `json:"operator"` // "equals", "contains", "starts_with", "matches_glob"
Value string `json:"value"`
TTLHours int `json:"ttl_hours"` // retention period in hours
Enabled bool `json:"enabled"`
CreatedAt time.Time `json:"created_at"`
}
RetentionRule defines a per-route/status retention policy. Traces matching the rule are retained for the rule's TTL instead of the global default.
type RuleAction ¶ added in v0.2.21
type RuleAction struct {
// Action is the action to take: "suppress", "always_promote", "tag", "short_ttl".
Action string
// Rule is the filter rule that matched.
Rule FilterRule
}
RuleAction is the result returned when a filter rule matches.
type RuleEngine ¶ added in v0.2.21
type RuleEngine struct {
// contains filtered or unexported fields
}
RuleEngine evaluates filter rules against request metadata. It holds rules in memory for fast evaluation. A nil or zero-value RuleEngine never matches (preserving existing behavior when no rules are configured).
func NewRuleEngine ¶ added in v0.2.21
func NewRuleEngine(rules []FilterRule) *RuleEngine
NewRuleEngine creates a RuleEngine loaded with the given rules. Only enabled rules are retained.
func (*RuleEngine) Match ¶ added in v0.2.21
func (re *RuleEngine) Match(fields map[string]string) (RuleAction, bool)
Match evaluates all loaded rules against the provided field values. It returns the first matching rule's action. If no rule matches, matched is false.
type Storer ¶
type Storer interface {
InitSchema() error
SetOnPromote(fn func(TraceSummary))
Promote(ctx context.Context, trace Trace) error
PromoteAt(ctx context.Context, trace Trace, createdAt time.Time) error
Get(ctx context.Context, requestID string) (*Trace, error)
ListTraces(ctx context.Context, f TraceFilter) ([]TraceSummary, int, error)
AvailableFilters(ctx context.Context, f TraceFilter) (FilterOptions, error)
DeleteTrace(ctx context.Context, requestID string) error
StartCleanup(ctx context.Context, ttl time.Duration, interval time.Duration)
CreateRule(ctx context.Context, rule FilterRule) (FilterRule, error)
ListRules(ctx context.Context) ([]FilterRule, error)
UpdateRule(ctx context.Context, rule FilterRule) error
DeleteRule(ctx context.Context, id int) error
CreateRetentionRule(ctx context.Context, rule RetentionRule) (RetentionRule, error)
ListRetentionRules(ctx context.Context) ([]RetentionRule, error)
UpdateRetentionRule(ctx context.Context, rule RetentionRule) error
DeleteRetentionRule(ctx context.Context, id int) error
Aggregate(ctx context.Context, f AggregateFilter) ([]AggregateResult, error)
}
Storer defines the interface for trace persistence. Useful for mocking in tests.
type Trace ¶ added in v0.2.20
type Trace struct {
RequestID string
ParentRequestID string `json:"parent_request_id,omitempty"`
ErrorChain string
StatusCode int
Route string
Method string
UserAgent string
RemoteIP string
UserID string
Tags map[string]string
Entries []Entry
RequestBody string `json:"request_body,omitempty"`
ResponseBody string `json:"response_body,omitempty"`
CreatedAt time.Time
}
Trace contains all the information captured when a request is promoted. ErrorChain is optional and may be empty for non-error promotions.
type TraceFilter ¶
type TraceFilter struct {
Q string
Status string
Method string
Tags map[string]string // filter traces by tag key-value pairs
Sort string
Dir string
Page int
PerPage int
}
TraceFilter holds all filter parameters for ListTraces.
type TraceSummary ¶
type TraceSummary struct {
RequestID string
ParentRequestID string `json:"parent_request_id,omitempty"`
ErrorChain string
StatusCode int
Route string
Method string
RemoteIP string
UserID string
Tags map[string]string
CreatedAt time.Time
}
TraceSummary is a lightweight row for list views (no log entries).
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
export
|
|
|
json
Package jsonexport provides a promolog.Exporter that writes traces as JSON lines to an io.Writer.
|
Package jsonexport provides a promolog.Exporter that writes traces as JSON lines to an io.Writer. |
|
webhook
Package webhook provides a promolog.Exporter that POSTs traces as JSON to a configurable HTTP endpoint.
|
Package webhook provides a promolog.Exporter that POSTs traces as JSON to a configurable HTTP endpoint. |
|
sqlite
module
|

