Documentation
¶
Overview ¶
Package multiline aggregates log output spanning several physical lines — such as panic and exception stack traces — back into a single logical entry. Lines are fed one at a time to an Aggregator, grouped per key, and completed entries are handed to an Emitter callback.
The bundled matcher recognizes Go, Java (and Node.js), Python, .NET, Ruby, Rust and PHP stack traces; custom formats are declared in the patterns subpackage and selected with WithMatcher.
Index ¶
- type Aggregator
- func (a *Aggregator[T]) Add(ctx context.Context, key, line string, data T) error
- func (a *Aggregator[T]) AddAt(ctx context.Context, key, line string, when time.Time, data T) error
- func (a *Aggregator[T]) Bytes() int
- func (a *Aggregator[T]) Flush(ctx context.Context, key string) error
- func (a *Aggregator[T]) FlushBefore(ctx context.Context, t time.Time) error
- func (a *Aggregator[T]) Len() int
- func (a *Aggregator[T]) Pending(key string) bool
- func (a *Aggregator[T]) Stop(ctx context.Context) error
- type Emitter
- type Entry
- type Matcher
- type Option
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Aggregator ¶
type Aggregator[T any] struct { // contains filtered or unexported fields }
Aggregator joins log entries that span several lines into a single entry. Lines are grouped per key (see Aggregator.Add); when a group completes, the joined lines are passed to the emitter. Grouping is driven by a Matcher. An Aggregator is not safe for concurrent use.
func New ¶
func New[T any](emit Emitter[T], opts ...Option) *Aggregator[T]
New creates an aggregator that hands completed entries to emit. By default it recognizes the stack-trace formats in patterns.All; pass WithMatcher to change that.
Example ¶
package main
import (
"context"
"fmt"
"github.com/JohanLindvall/multiline"
)
func main() {
ml := multiline.New(func(_ context.Context, e multiline.Entry[struct{}]) error {
fmt.Printf("match=%q lines=%d text=%q\n", e.Match, e.Lines, e.Text)
return nil
})
ctx := context.Background()
for _, line := range []string{
"GET /healthz 200",
"java.lang.NullPointerException: boom",
"\tat com.example.Foo.bar(Foo.java:12)",
"\tat com.example.Main.main(Main.java:5)",
"shutting down",
} {
if err := ml.Add(ctx, "container-1", line, struct{}{}); err != nil {
panic(err)
}
}
if err := ml.Stop(ctx); err != nil {
panic(err)
}
}
Output: match="" lines=1 text="GET /healthz 200" match="java" lines=3 text="java.lang.NullPointerException: boom\n\tat com.example.Foo.bar(Foo.java:12)\n\tat com.example.Main.main(Main.java:5)" match="" lines=1 text="shutting down"
func (*Aggregator[T]) Add ¶
func (a *Aggregator[T]) Add(ctx context.Context, key, line string, data T) error
Add feeds a single line into the aggregator. The key groups related lines and is typically a container or stream id; an empty key bypasses aggregation and emits the line immediately. data rides along with the line and is handed back through the emitter. Add returns the first error produced by the emitter, if any. Entries fed via Add carry a zero Entry.When; staleness for Aggregator.FlushBefore is tracked with the aggregator clock only when a line is actually buffered, keeping the pass-through path free of clock reads.
func (*Aggregator[T]) AddAt ¶
AddAt is Aggregator.Add with an explicit time for the line, which Aggregator.FlushBefore compares against and Entry.When reports — pass the log's own timestamp to make time-based flushing robust when replaying old logs. Times are assumed to be non-decreasing across calls. A zero when is allowed (Add uses one): staleness falls back to the aggregator clock and Entry.When stays zero.
func (*Aggregator[T]) Bytes ¶ added in v0.0.7
func (a *Aggregator[T]) Bytes() int
Bytes returns the total text bytes currently buffered across all groups — a cheap gauge for memory monitoring.
func (*Aggregator[T]) Flush ¶
func (a *Aggregator[T]) Flush(ctx context.Context, key string) error
Flush emits the pending group for key, if any. Use it when a stream ends, for example when its container terminates.
func (*Aggregator[T]) FlushBefore ¶
FlushBefore emits every pending group last touched before t, freeing groups that have gone stale. Call it periodically, e.g. from a ticker:
ml.FlushBefore(ctx, time.Now().Add(-5*time.Second))
Groups are kept in last-touched order, so flushing stops at the first group touched at or after t. It returns the first error produced while emitting.
func (*Aggregator[T]) Len ¶ added in v0.0.7
func (a *Aggregator[T]) Len() int
Len returns the number of keys with buffered lines.
func (*Aggregator[T]) Pending ¶ added in v0.0.7
func (a *Aggregator[T]) Pending(key string) bool
Pending reports whether key has buffered lines.
type Emitter ¶
Emitter receives completed entries. Returning an error aborts the Add or flush call that produced the entry; lines already buffered in the same group are not re-delivered.
type Entry ¶
type Entry[T any] struct { // Text is the entry text; for an aggregated entry the source lines are // joined by "\n". It is left empty when [WithoutText] is configured. Text string // Texts is the entry's retained source lines, one element per line. It is // a view borrowed from internal buffers, valid only until the emitter // returns — copy it (e.g. slices.Clone) to retain. Writing the elements // to an io.Writer avoids Text's joined allocation entirely (see // [WithoutText]). Texts []string // Key is the key the entry's lines were added under. It allows chaining // aggregation stages: an emitter can feed another Aggregator keyed by // entry.Key (see examples/cri). Key string // Match names the format that aggregated this entry (a patterns.StateSet // name such as "go" or "java"). It is "" when the line passed through // as-is. Match string // When is the time of the entry's first source line, as passed to AddAt. // Lines fed without a time (Add, or AddAt with a zero when) carry a zero // When. When time.Time // Data is the value passed to Add for the entry's first source line. Data T // Lines is the number of source lines the entry represents. It counts // lines dropped by WithMaxLines/WithMaxBytes, so it can exceed the number // of lines in Text. Lines int // Truncated is set when lines belonging to this entry were dropped or cut // by WithMaxLines/WithMaxBytes. Truncated bool }
Entry is one completed log entry handed to the Emitter.
type Matcher ¶
type Matcher interface {
// Step applies line to the active states and returns the new active set,
// plus the index of an accepting state the line landed in (-1 if none).
// An empty next means line does not continue any active state. Step must
// not retain or modify the active slice; the aggregator retains the
// returned slice until the group's next line, so implementations must
// return slices they will never mutate (shared immutable slices are
// fine).
Step(line string, active []int) (next []int, accepted int)
// Format returns the format name reported as [Entry].Match for a group
// that completed in the state at index.
Format(index int) string
}
Matcher decides how successive lines are grouped. Implementations track matcher state as opaque int indices, where index 0 is the start state a new group begins from. The built-in implementation is patterns.StateMachine; implementations must be immutable or otherwise safe for the (single-threaded) use an Aggregator makes of them.
type Option ¶
type Option func(*config)
Option configures an Aggregator at construction time.
func WithClock ¶
WithClock replaces time.Now as the source of the arrival times that Aggregator.Add stamps groups with (used by FlushBefore). Prefer Aggregator.AddAt to supply per-line times, e.g. log timestamps.
func WithMatcher ¶
WithMatcher selects a custom Matcher (typically a patterns.StateMachine built via patterns.Compile) instead of the built-in one.
func WithMaxBytes ¶
WithMaxBytes caps the total text bytes retained in a single group. The line that crosses the limit is cut on a UTF-8 rune boundary, subsequent lines are dropped, and the resulting entry is flagged Truncated. A value <= 0 means unlimited.
func WithMaxGroups ¶
WithMaxGroups caps the number of keys with pending lines. Adding a line for a new key beyond the cap flushes the least recently touched group first. A value <= 0 means unlimited. This guards against unbounded key cardinality (note that Go maps keep their high-water bucket memory, so the cap also bounds that); time-based flushing is Aggregator.FlushBefore.
func WithMaxLines ¶
WithMaxLines caps the number of lines retained in a single group. Further lines are dropped while matching continues normally, and the resulting entry is flagged Truncated. A value <= 0 means unlimited. This guards against an unterminated match growing without bound.
func WithoutText ¶ added in v0.0.9
func WithoutText() Option
WithoutText skips building Entry.Text (it is left empty), for emitters that consume Entry.Texts instead. This avoids joining an aggregated entry's lines into one string — for a large capped trace, a copy the size of the whole entry.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package cri rejoins Kubernetes CRI log lines back into whole application lines, as a stage in front of stack-trace aggregation.
|
Package cri rejoins Kubernetes CRI log lines back into whole application lines, as a stage in front of stack-trace aggregation. |
|
examples
|
|
|
cri
command
Command cri shows the two-stage Kubernetes pipeline: the cri package rejoins CRI partial-line fragments into whole application lines and feeds them — prefixes stripped, keyed per stream, stamped with their log timestamps — into the normal stack-trace aggregation.
|
Command cri shows the two-stage Kubernetes pipeline: the cri package rejoins CRI partial-line fragments into whole application lines and feeds them — prefixes stripped, keyed per stream, stamped with their log timestamps — into the normal stack-trace aggregation. |
|
custom
command
|
|
|
simple
command
|
|
|
Package patterns contains the declarative state-machine matcher used by the multiline aggregator, together with the bundled stack-trace definitions for common languages (see All).
|
Package patterns contains the declarative state-machine matcher used by the multiline aggregator, together with the bundled stack-trace definitions for common languages (see All). |