obs

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 3, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Overview

Package obs is the library's instrumentation: metrics, traces and the provenance log.

It exists because every one of this library's degradations — a coalesce, a drop, a suppression, a rejection, an eviction — has to be observable, and the alternative to one package owning that is the catalogue living in whichever package last needed a counter. In all four of the systems this design was written against, the dominant production failure is a degradation with no signal; a degradation that is not observable is a defect here, not a tuning opportunity.

Nothing is computed when it is disabled

Metrics, traces and logs are each disabled by leaving their provider nil, and a disabled configuration pays one predictable branch. The mechanism is a nil pointer with methods, not a no-op interface implementation: an interface would put an indirect call on the hot path and make the overhead budget a matter of the inliner's mood. Every method here begins by testing its receiver against nil, and calling any of them on a nil receiver is correct and free.

Cardinality

No causal identifier is ever a metric label — not the session, the event, the patch or the transition. Those live in traces and in the provenance log, which carry the whole chain without multiplying a time series per connection. Event and fragment label values are bounded by registration. The origin source is not, because nothing registers an effect, so it is bounded at the metric: sixty-four distinct values, then a collapse to "other" with an overflow counter that says the collapse happened.

The provenance log is not the metrics path

Provenance records are emitted by the session actor from the same values that construct the frame; the counters they are audited against are incremented by the framer and the transport. Different code, different sink. Coupling them would make the audit check a value against itself, which is the failure the audit exists to catch.

Index

Constants

View Source
const (
	InfoSampleThreshold = 100
	InfoSampleRate      = 100
)

InfoSampleThreshold is the records-per-second above which Info is sampled, and InfoSampleRate is the sampling. A record that survives sampling says so, because a sampled stream that does not admit it is a lie about volume.

View Source
const (
	AttrSessionID     = "gotthlive.session.id"
	AttrEventID       = "gotthlive.event.id"
	AttrEventName     = "gotthlive.event.name"
	AttrClientRef     = "gotthlive.event.client_ref"
	AttrSeenServerSeq = "gotthlive.event.seen_server_seq"
	AttrTransitionID  = "gotthlive.transition.id"
	AttrStateVersion  = "gotthlive.state.version"
	AttrPatchID       = "gotthlive.patch.id"
	AttrServerSeq     = "gotthlive.server_seq"
	AttrFragmentID    = "gotthlive.fragment.id"
	AttrSuppressed    = "gotthlive.fragment.suppressed"
	AttrFrameBytes    = "gotthlive.frame.bytes"
	AttrFrameKind     = "gotthlive.frame.kind"
	AttrWindowDepth   = "gotthlive.window.depth"
	AttrOriginKind    = "gotthlive.origin.kind"
	AttrOriginSource  = "gotthlive.origin.source"
	AttrResult        = "gotthlive.result"
	AttrMorphMicros   = "gotthlive.morph.duration_us"
	AttrApplyMicros   = "gotthlive.apply.duration_us"
	AttrTimingSource  = "gotthlive.timing.source"
)

Span attribute keys. They are constants because a typo in an attribute key is invisible until someone queries for it and finds nothing.

View Source
const (
	SpanEvent          = "gotthlive.event"
	SpanParse          = "gotthlive.parse"
	SpanAuthorize      = "gotthlive.authorize"
	SpanReduce         = "gotthlive.reduce"
	SpanRender         = "gotthlive.render"
	SpanRenderFragment = "gotthlive.render.fragment"
	SpanEncode         = "gotthlive.encode"
	SpanSend           = "gotthlive.send"
	SpanOrigin         = "gotthlive.origin"
	SpanClientMorph    = "gotthlive.client.morph"
	SpanEffect         = "gotthlive.effect."
)

Span names, in the tree order of one event.

SpanRenderFragment was drawn in instrumentation §3.1 and declared nowhere, deliberately: "a constant nothing starts is one more thing that reads as implemented", and the section said whoever starts the span declares the constant in the same change. This is that change.

View Source
const LabelOptionCap = 256

LabelOptionCap bounds how many distinct label values this package will hold a pre-built measurement option for.

Every label domain in this library is already bounded — by the protocol's enumerations (frame kind, rejection reason, close code, patch operation, transition result, panic site), by application registration (event name), or by SourceLabelCap. The cap is the belt to those braces: a value past it is still recorded, correctly and with the same attributes, it is simply built per call rather than cached. A cache that could grow without bound would be its own memory finding.

View Source
const ProvenanceLogger = "gotthlive.provenance"

ProvenanceLogger is the name every provenance record carries, so an operator can route the stream to its own retention without matching on message text.

View Source
const SourceLabelCap = 64

SourceLabelCap bounds the origin-source label. Nothing registers an effect, so this cardinality cannot be bounded by registration the way event and fragment names are; it is bounded here instead, and the overflow counter is how an explosion becomes visible at runtime rather than never.

View Source
const SourceOverflowLabel = "other"

SourceOverflowLabel is what a source value past the cap is recorded as.

Variables

This section is empty.

Functions

This section is empty.

Types

type Field

type Field struct {
	// contains filtered or unexported fields
}

Field is one structured log attribute.

The library's log helpers take Field and nothing else, and that is the redaction boundary. There is no helper that accepts an arbitrary value, so a frame body, an application state value or an identity cannot be passed into a record by a caller who was not thinking about it. Redaction applied at the boundary is a property; redaction left to callers is a hope.

func Bool

func Bool(key string, value bool) Field

Bool returns a boolean field.

func Dur

func Dur(key string, d time.Duration) Field

Dur returns a duration field, recorded in milliseconds because that is the unit the budgets in this project are stated in.

func Err

func Err(err error) Field

Err returns an error field. The error's own text is included; error values in this library are constructed to carry a session, a causal identifier and an actionable next step, and never a payload.

func Int

func Int(key string, value int) Field

Int returns an integer field.

func Str

func Str(key, value string) Field

Str returns a string field.

func U64

func U64(key string, value uint64) Field

U64 returns an unsigned integer field, which is what every causal identifier in this library is.

type Logger

type Logger struct {
	// contains filtered or unexported fields
}

Logger is the library's log sink. A nil *Logger is the disabled configuration; every method tests its receiver.

Leaving it disabled also disables the provenance log, which makes the reverse lookup from a captured patch back to its cause unavailable. That is a documented consequence rather than a silent one: the audit harness and the provenance soak both fail closed when the logger is absent.

func NewLogger

func NewLogger(l *slog.Logger) *Logger

NewLogger wraps a consumer's logger. A nil logger returns nil.

func (*Logger) Debug

func (l *Logger) Debug(ctx context.Context, msg string, fields ...Field)

Debug emits a per-event or per-patch record. It is off in production by default, which is a decision the consumer's handler makes.

func (*Logger) Enabled

func (l *Logger) Enabled() bool

Enabled reports whether records are being emitted.

func (*Logger) Error

func (l *Logger) Error(ctx context.Context, msg string, fields ...Field)

Error emits a record an operator should act on: a recovered panic, an abandoned effect, a protocol violation, a fatal authorization denial, a fragment identifier collision. Nothing routine reaches it.

func (*Logger) Info

func (l *Logger) Info(ctx context.Context, msg string, fields ...Field)

Info emits a session-lifecycle record, sampled above the threshold.

func (*Logger) Lifecycle added in v0.2.0

func (l *Logger) Lifecycle(ctx context.Context, msg string, fields ...Field)

Lifecycle emits a record of the live UI service's own lifecycle — its sessions drained, its goroutines joined — at Info and never sampled: there is one per stop, and the operator reading a shutdown needs every one.

func (*Logger) Provenance

func (l *Logger) Provenance(ctx context.Context, p Provenance)

Provenance emits one transition record.

It is exempt from Info sampling by construction: a sampled provenance log cannot support a hundred-percent, zero-unknown guarantee, and the guarantee is the reason the stream exists.

func (*Logger) Warn

func (l *Logger) Warn(ctx context.Context, msg string, fields ...Field)

Warn emits a degradation record: coalescing engaged, a slow client degrading, a rate limit engaging, telemetry dropped.

type Metrics

type Metrics struct {

	// FragmentLabels enables the opt-in fragment label on the render
	// histogram. It is off by default because the product of fragments and
	// sessions is the one cardinality an application can grow without noticing.
	FragmentLabels bool
	// contains filtered or unexported fields
}

Metrics is the library's metric set. A nil *Metrics is a fully valid, fully disabled instrument: every method tests its receiver, so a configuration with no meter provider pays one branch per call site.

func NewMetrics

func NewMetrics(mp metric.MeterProvider) (*Metrics, error)

NewMetrics builds the metric set from a provider. A nil provider returns a nil *Metrics, which is the disabled configuration and is not an error.

func (*Metrics) ClientTelemetryDropped

func (m *Metrics) ClientTelemetryDropped(ctx context.Context, reason string)

ClientTelemetryDropped counts a report naming a patch this session did not send, which is either a forgery or a stale echo and is never used to fabricate a span.

func (*Metrics) ClientTiming

func (m *Metrics) ClientTiming(ctx context.Context, morphMicros, applyMicros uint32)

ClientTiming records a client-reported morph and apply duration. Both are untrusted input, are named client_ so no dashboard mistakes them for a server measurement, and are bounded by the schema so a fabricated value cannot skew a histogram to infinity.

func (*Metrics) ConnectionClosed

func (m *Metrics) ConnectionClosed(ctx context.Context, codeLabel string)

ConnectionClosed counts one close by its code label.

func (*Metrics) ConnectionOpened

func (m *Metrics) ConnectionOpened(ctx context.Context)

ConnectionOpened counts a connection admitted to the live registry.

func (*Metrics) Effect

func (m *Metrics) Effect(ctx context.Context, source, result string)

Effect counts one executed effect. The source label is capped, because nothing registers an effect.

func (*Metrics) EffectOverran added in v0.2.0

func (m *Metrics) EffectOverran(ctx context.Context)

EffectOverran counts one session whose effects were still running when the drain window closed. Shutdown keeps joining them: the count is the signal that an effect is slow to honour cancellation, not that one was abandoned.

func (*Metrics) Enabled

func (m *Metrics) Enabled() bool

Enabled reports whether metrics are being recorded.

func (*Metrics) EncodeDuration

func (m *Metrics) EncodeDuration(ctx context.Context, seconds float64)

EncodeDuration records validation plus marshal time.

func (*Metrics) EventReceived

func (m *Metrics) EventReceived(ctx context.Context, name string)

EventReceived counts one event dispatched to a reducer.

func (*Metrics) EventRejected

func (m *Metrics) EventRejected(ctx context.Context, reason string)

EventRejected counts one event refused before a reducer saw it.

func (*Metrics) FrameReceived

func (m *Metrics) FrameReceived(ctx context.Context, kind string, bytes int)

FrameReceived counts one accepted inbound frame and its size.

This is the connection read pump's per-frame path, and the one the G2 baseline's goroutine-stack line turned out to be about (§5.1, and the remediation section that corrects it). Every label set it uses is built once per label value and reused.

func (*Metrics) FrameRejected

func (m *Metrics) FrameRejected(ctx context.Context, reason string)

FrameRejected counts one refused inbound frame.

func (*Metrics) FrameSent

func (m *Metrics) FrameSent(ctx context.Context, kind string, bytes int)

FrameSent counts one written frame and its size. Only the framer calls it.

func (*Metrics) Goroutines

func (m *Metrics) Goroutines(ctx context.Context, delta int64)

Goroutines adjusts the count of goroutines this library owns.

func (*Metrics) MailboxDepth

func (m *Metrics) MailboxDepth(ctx context.Context, depth int)

MailboxDepth samples mailbox occupancy.

func (*Metrics) OutboundInvalid

func (m *Metrics) OutboundInvalid(ctx context.Context, kind string)

OutboundInvalid counts a frame this library constructed and could not validate. Any non-zero value is actionable, and it is never a client's doing: the frame was built here, from state this library owns.

It used to say "any non-zero value is a library bug", and that sent the person holding the pager to the wrong repository. Some of what a frame carries comes from the application — Event.Contributing above all — and until D-18 an over-long one reached the outbound validator and was counted here, so an application could raise an alert about its own input that named this library as the defect. The emit path now rejects that input with an error naming the caller, so what is left really is a defect in this library: either a frame built wrong, or a bound it failed to apply before building one.

func (*Metrics) Panic

func (m *Metrics) Panic(ctx context.Context, site string)

Panic counts one recovered panic by site.

func (*Metrics) PatchCoalesced

func (m *Metrics) PatchCoalesced(ctx context.Context)

PatchCoalesced counts one patch carrying transitions that were not individually emitted.

func (*Metrics) PatchesSent

func (m *Metrics) PatchesSent(ctx context.Context, op string, n int)

PatchesSent counts emitted fragment updates by operation.

func (*Metrics) PatchesSuppressed

func (m *Metrics) PatchesSuppressed(ctx context.Context, n int)

PatchesSuppressed counts renders dropped for producing unchanged bytes.

func (*Metrics) RenderDuration

func (m *Metrics) RenderDuration(ctx context.Context, seconds float64, fragment string)

RenderDuration records the time spent rendering a transition's fragments.

func (*Metrics) ResyncRequest

func (m *Metrics) ResyncRequest(ctx context.Context, result string, bytes int)

ResyncRequest counts one resync request by result, and the snapshot's size when it produced one.

func (*Metrics) SendDuration

func (m *Metrics) SendDuration(ctx context.Context, seconds float64)

SendDuration records time in the transport write.

func (*Metrics) SessionsActive

func (m *Metrics) SessionsActive(ctx context.Context, delta int64)

SessionsActive follows registry membership, independently of handshake refusals.

func (*Metrics) SlowClientEvent

func (m *Metrics) SlowClientEvent(ctx context.Context)

SlowClientEvent counts one synthesized backpressure event.

func (*Metrics) SourceLabel

func (m *Metrics) SourceLabel(ctx context.Context, source string) string

SourceLabel maps an origin source to its label value, collapsing to "other" once the cap is reached and counting that it happened.

Traces and the provenance log carry the full value; only the label is capped, so nothing is lost, it is only moved to the signal that can afford the cardinality.

func (*Metrics) TrackedBytes

func (m *Metrics) TrackedBytes(ctx context.Context, delta int64)

TrackedBytes adjusts the exactly-sized per-session total. It covers only structures the library owns and can size: the window, the mailbox and ack backing arrays, the fragment hashes and the registry. Go has no per-goroutine heap attribution and this does not pretend otherwise.

func (*Metrics) Transition

func (m *Metrics) Transition(ctx context.Context, result string, seconds float64, event string)

Transition counts one reducer invocation by result.

func (*Metrics) WindowDepth

func (m *Metrics) WindowDepth(ctx context.Context, depth int)

WindowDepth samples the unacknowledged window, which is the slow-client signal and is exported so degradation is visible before eviction.

type Provenance

type Provenance struct {
	// SessionID scopes every other identifier here. The rest are uint64
	// counters minted per session, so a value is only globally unique paired
	// with this.
	SessionID string

	// EventID is the causal root: the server-minted identity of the event that
	// caused the transition, zero when the server started it itself.
	EventID uint64

	// ClientRef is the browser's own handle for that event, echoed so a client
	// log and a server log can be joined. It is the one untrusted value in the
	// row.
	ClientRef uint64

	// TransitionID names the reducer invocation, one per invocation including
	// one that changed nothing.
	TransitionID uint64

	// StateVersion rises if and only if the transition changed state. Holding
	// it beside TransitionID is what makes that property checkable from the
	// log alone.
	StateVersion uint64

	// PatchID names the emitted frame, and is zero when the transition emitted
	// none — a suppressed render still gets a row.
	PatchID uint64

	// ServerSeq is the frame's place in the session's outbound order, zero for
	// the same reason as PatchID.
	ServerSeq uint64

	// OriginKind is the category of cause: "client_event", "effect", "timer",
	// "pubsub", "mount" or "resync".
	OriginKind string

	// OriginSource is the specific cause within that category, such as
	// "event:cart.add". It is composed from application-supplied halves and is
	// validated before it reaches a frame.
	OriginSource string

	// FragmentIDs are the regions this transition patched, in wire order.
	// Empty means the render was suppressed.
	FragmentIDs []string

	// ContributingEventIDs are the events whose changes this patch carries but
	// which were not individually patched, because coalescing collapsed them.
	// This field is why coalescing does not lose provenance.
	ContributingEventIDs []uint64

	// SupersededFromSeq and SupersededThroughSeq are the inclusive server_seq
	// range a resync snapshot replaced, zero on everything else. They are the
	// only surviving record that the replaced patches existed.
	SupersededFromSeq uint64

	// SupersededThroughSeq is the upper end of that range. See
	// SupersededFromSeq.
	SupersededThroughSeq uint64
}

Provenance is one transition's causal row: everything needed to answer "which event produced the markup the user is looking at" from a log query alone.

A transition that emitted no patch — a suppressed render — still gets a record, with a zero patch identifier. Without it, the property that the state version rises exactly when state changed would be unverifiable, because the transitions that produced nothing would be invisible.

type Span

type Span struct {
	// contains filtered or unexported fields
}

Span is a nil-safe wrapper over an OpenTelemetry span.

func (Span) End

func (s Span) End()

End closes the span.

func (Span) RecordError

func (s Span) RecordError(err error)

RecordError marks the span as failed.

func (Span) Ref

func (s Span) Ref() SpanRef

Ref returns a compact reference to this span, for storing in the outbound window until the client reports back.

func (Span) SetAttributes

func (s Span) SetAttributes(attrs ...attribute.KeyValue)

SetAttributes adds attributes to the span.

type SpanRef

type SpanRef struct {
	// TraceID is the W3C trace identifier, raw rather than a trace.TraceID so
	// this struct's size is a fact about this file.
	TraceID [16]byte

	// SpanID identifies the span within that trace.
	SpanID [8]byte

	// Flags is the W3C trace-flags byte, which carries the sampled bit. It is
	// kept because a reconstructed context that lost it would relocate the
	// sampling decision to link time.
	Flags byte
	// contains filtered or unexported fields
}

SpanRef is a 32-byte reference to a span, held per slot in the outbound window so a client's morph timing can be linked back to the span that encoded the patch.

It is deliberately not a trace.SpanContext. A real SpanContext is 56 to 64 bytes on amd64 once the trace flags, the remote bit and a TraceState's slice header are counted, and this library's memory budget is made of decisions this size. Reconstructing the context at link time is exact for this use, because the library never populates a TraceState.

func (SpanRef) SpanContext

func (r SpanRef) SpanContext() trace.SpanContext

SpanContext reconstructs the reference for linking.

type Tracer

type Tracer struct {
	// contains filtered or unexported fields
}

Tracer wraps a provider. A nil *Tracer is the disabled configuration and every method on it is correct and free.

The provider is taken explicitly rather than read from the OpenTelemetry global. That is an architectural constraint and not a style preference: it is what lets this library depend on the trace and metric API submodules instead of the root module that carries the global.

func NewTracer

func NewTracer(tp trace.TracerProvider) *Tracer

NewTracer builds a tracer from a provider. A nil provider returns nil, which is the disabled configuration and is not an error.

func (*Tracer) Enabled

func (t *Tracer) Enabled() bool

Enabled reports whether spans are being recorded.

It is the single boolean instrumentation §4.2 requires a disabled configuration to branch on, and hot-path callers check it *before* building an attribute list rather than relying on the nil check inside Start. The difference is measurable and is not a style preference: the variadic attrs escape into the tracer, so a call site that builds them unconditionally heap-allocates the backing array on every event even when tracing is off.

func (*Tracer) Start

func (t *Tracer) Start(ctx context.Context, name string, attrs ...attribute.KeyValue) (context.Context, Span)

Start begins a span. On a nil tracer it returns the context unchanged and a span whose methods do nothing.

func (*Tracer) StartChildOf

func (t *Tracer) StartChildOf(ctx context.Context, name string, parent SpanRef, attrs ...attribute.KeyValue) (context.Context, Span)

StartChildOf begins a span whose parent is a span that has already ended, named by the reference the caller carried across a goroutine boundary.

This is FR-36 clause 4's mechanism. An ended span is still a valid parent — a parent-child edge asserts that the parent's work caused the child's, not that the parent's clock encloses it — so the reference the ingress already holds is enough to make the whole server-side path descend from one root. The consequence is the one clause 4 exists for: a sampler decides once, at that root, and every span below inherits the decision. Three roots meant three decisions, and under the documented default `ParentBased(TraceIDRatioBased(0.05))` that produced 0 of 300 interactions recording both `authorize` and `event` (L9-1's C-30 measurement).

An invalid reference is not an error and is not faked: the span starts from whatever the context already carries, which for a server-initiated transition is nothing, and it becomes a root. That is truthful — no client frame authorized it — and it is why this does not silently invent an edge.

func (*Tracer) StartLinked

func (t *Tracer) StartLinked(ctx context.Context, name string, to SpanRef, attrs ...attribute.KeyValue) (context.Context, Span)

StartLinked begins a span linked to a span that has already ended. It is how a client's morph timing rejoins the trace that produced the patch: the timing arrives after the encode span closed, and the span's start timestamp is derived rather than observed (instrumentation §3.3), so a parent edge would assert an enclosure this design does not measure.

It is the last link site under FR-36 clause 3. The other one — the transition linking back to authorization — became a real parent edge under clause 4, which clause 3 says is always permitted.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL