telemetry

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Oct 8, 2026 License: MIT Imports: 34 Imported by: 0

Documentation

Overview

Package telemetry is ynf's side of ynr's OpenTelemetry contract (ADR-011; ynr ADR-002, 003, 004 and 006). It sets up the official SDK once at process start, writes where the contract says, names everything from the registry in telemetry/registry, and never changes what ynf prints or how it exits: its errors are swallowed and counted.

Index

Constants

View Source
const (
	// The work item's store key (ADR-002). Never on a metric.
	AttrItemKey = "ynf.item.key"
	// The step's id, which names its side effects (ADR-005). Never on a metric.
	AttrStepID = "ynf.step.id"
	// The run's id, the step id and the run's number within the step. Never on a metric.
	AttrRunID = "ynf.run.id"
	// The lane's id: where the lane is defined, host first, then its name, or the lane's own explicit id. ynr stamps it on a run's records from ynf's run manifest, on ynf's behalf.
	AttrLane = "ynf.lane"
	// The harness the run actually used, so a repository that overrides a lane shows as a variant of the same factory. ynr stamps it from ynf's run manifest, on ynf's behalf.
	AttrLaneHarness = "ynf.lane.harness"
	// The focus the run actually used. ynr stamps it from ynf's run manifest, on ynf's behalf.
	AttrLaneFocus = "ynf.lane.focus"
	// The SHA-256 of the lane's normalised form, recorded with every decision (ADR-006).
	AttrPolicyHash = "ynf.policy.hash"
	// The epoch of the lease the step holds (ADR-005).
	AttrLeaseEpoch = "ynf.lease.epoch"
	// The repository the work's code goes to, in ynf's host-first form.
	AttrRepo = "ynf.repo"
	// The state the decision left the item in (ADR-006).
	AttrItemState = "ynf.item.state"
	// The kind of action a decision asked for (ADR-006).
	AttrAction = "ynf.action"
	// ynf's own outcome for a unit of work, which also sets the span's status.
	AttrOutcome = "ynf.outcome"
	// What ynf did with a CloudEvent it received.
	AttrIntakeResult = "ynf.intake.result"
	// The other system a call went to.
	AttrCallSystem = "ynf.call.system"
	// What the call did, such as pull_request or push. Never a name or text from the system.
	AttrCallOperation = "ynf.call.operation"
	// Which tokens a count is of.
	AttrTokenType = "ynf.token.type"
	// What kind of actor the handle in user.name names.
	AttrActorKind = "ynf.actor.kind"
	// Whether a lane control paused the lane (true) or resumed it (false).
	AttrPaused                  = "ynf.paused"
	AttrCloudeventsEventID      = "cloudevents.event_id"
	AttrCloudeventsEventSource  = "cloudevents.event_source"
	AttrCloudeventsEventSubject = "cloudevents.event_subject"
	AttrCloudeventsEventType    = "cloudevents.event_type"
	AttrGenAiResponseModel      = "gen_ai.response.model"
	AttrVcsRepositoryURLFull    = "vcs.repository.url.full"
)

Attribute names: ynf's own, then the OpenTelemetry attributes it uses.

View Source
const (
	// One step on one item (ADR-001), the root of the step's trace.
	SpanStep = "ynf.step"
	// Taking the item's lease.
	SpanClaim = "ynf.claim"
	// Reading the facts a decision needs from the ticket and the forge.
	SpanProbe = "ynf.probe"
	// The decider reading the item, the facts and the lane, and what it chose.
	SpanDecide = "ynf.decide"
	// One action a decision asked for.
	SpanAct = "ynf.act"
	// One run of a lane's runner on a worktree.
	SpanRun = "ynf.run"
	// A CloudEvent ynf received, recorded so a step can link to it (ynr ADR-003).
	SpanIntake = "ynf.intake"
	// A call out to another system: a forge, a tracker, git, ynm, docker or an executor.
	SpanCall = "ynf.call"
	// A person pausing or resuming a lane.
	SpanLaneControl = "ynf.lane.control"
)

Span names.

View Source
const (
	// A step began. A start with no matching span is how a crash shows.
	EventStepStarted = "ynf.step.started"
	// A run began.
	EventRunStarted = "ynf.run.started"
	// The decider chose. Its reasoning is in ynf's store, never in telemetry.
	EventDecisionMade = "ynf.decision.made"
	// A CloudEvent ynf received, written once inside its ynf.intake span: the one-way mirror of the control plane into telemetry. Nothing in ynf reads telemetry.
	EventIntakeReceived = "ynf.intake.received"
)

Event names.

View Source
const (
	// Runs, by outcome, lane and model.
	MetricRunCount = "ynf.run.count"
	// Tokens runs used, by model and kind, as the runner reports them.
	MetricRunTokens = "ynf.run.tokens"
	// What runs cost, by model, as the runner reports it. ynf never prices tokens itself.
	MetricRunCost = "ynf.run.cost"
	// Leases found expired when a step claimed the item, so a holder died or stalled.
	MetricLeaseExpired = "ynf.lease.expired"
)

Metric names.

View Source
const (
	// A run: the lane's sensors passed.
	OutcomeConverged = "converged"
	// A run: a cap ended it.
	OutcomeBudget = "budget"
	// A run: it stopped making progress.
	OutcomeStuck = "stuck"
	// A run: it changed what it must not.
	OutcomeTamper = "tamper"
	// A run: the operator's setup refused or broke it.
	OutcomeOperatorError = "operator_error"
	// A run: ynf or the runner failed.
	OutcomeError = "error"
	// A run: it was stopped.
	OutcomeAborted = "aborted"
	// A step: it ran to the point the item waits.
	OutcomeCompleted = "completed"
	// A step, phase or call: it ended in an error.
	OutcomeFailed = "failed"
	// A step: its lease was lost, so it stopped.
	OutcomeLost = "lost"
	// A step or claim: another instance holds the item.
	OutcomeHeld = "held"
	// A claim: the lease was taken.
	OutcomeClaimed = "claimed"
	// A phase or call: it succeeded.
	OutcomeOk = "ok"
	// An intake: the event was taken in.
	OutcomeAccepted = "accepted"
	// An intake: the event had been received before.
	OutcomeDeduplicated = "deduplicated"
	// An intake: the event was refused.
	OutcomeRejected = "rejected"
)

Values of ynf.outcome.

View Source
const (
	// The event was taken in.
	IntakeResultAccepted = "accepted"
	// The event had been received before.
	IntakeResultDeduplicated = "deduplicated"
	// The event was refused.
	IntakeResultRejected = "rejected"
)

Values of ynf.intake.result.

View Source
const (
	// A forge, such as GitHub.
	CallSystemForge = "forge"
	// A tracker.
	CallSystemTracker = "tracker"
	// Git, on the host.
	CallSystemGit = "git"
	// ynm, ynf's memory.
	CallSystemYnm = "ynm"
	// Docker, where an agent image is made.
	CallSystemDocker = "docker"
	// The executor that runs a lane's command.
	CallSystemExecutor = "executor"
	// ynh, where it builds an agent image.
	CallSystemYnh = "ynh"
)

Values of ynf.call.system.

View Source
const (
	// Input tokens.
	TokenTypeInput = "input"
	// Output tokens.
	TokenTypeOutput = "output"
	// Tokens read from a cache.
	TokenTypeCacheRead = "cache_read"
)

Values of ynf.token.type.

View Source
const (
	// A person.
	ActorKindHuman = "human"
	// Another automation.
	ActorKindBot = "bot"
	// ynf itself.
	ActorKindYnf = "ynf"
)

Values of ynf.actor.kind.

View Source
const Service = "ynf"

Service is the name ynf's records carry as service.name, which with the version identifies its registry (ynr ADR-007).

Variables

View Source
var CardinalityLimits = map[string]int{
	"gen_ai.response.model": 50,
	"ynf.lane":              100,
	"ynf.outcome":           20,
	"ynf.token.type":        3,
}

CardinalityLimits is the most distinct values each metric attribute may take, from the registry's ynr.cardinality annotations.

Functions

func Call

func Call[V any](ctx context.Context, t *T, system, operation string, fn func(context.Context) (V, error), benign ...error) (V, error)

Call spans a call out to another system (ynr ADR-006, rule 6): a child span of the work that made it, with the system and the operation, and an outcome from err. An error in benign, such as a not-found that is an answer, is a success. The error's text never reaches the span.

func Command

func Command(ctx context.Context, c *exec.Cmd)

Command gives a process ynf is about to start the trace context of ctx in its environment (ynr ADR-006, rule 4), keeping the environment it would have had. It does nothing when ctx carries no trace, so with telemetry off and no parent the process inherits exactly as before.

func Environ

func Environ(ctx context.Context, environ []string) []string

Environ is environ with the trace context of ctx set, replacing any the parent gave, for a process ynf starts. It returns environ unchanged when ctx carries no trace.

func Finish

func Finish(span trace.Span, outcome string, attrs ...attribute.KeyValue)

Finish ends a unit of work's span with ynf's own outcome as an attribute and the span's status set from it (ynr ADR-006, rule 11). A failure carries the outcome as its status description, never an error's text, which could hold content.

func FromEnviron

func FromEnviron(ctx context.Context, environ []string) context.Context

FromEnviron joins the trace a parent gave this process: TRACEPARENT and TRACESTATE in environ. With none, ctx is returned as it is, and the first span starts a trace.

func FromHeaders

func FromHeaders(ctx context.Context, h http.Header) context.Context

FromHeaders joins the trace a client sent in W3C trace-context headers, for a request ynf serves (ynr ADR-006, rule 3).

func Handle

func Handle(host, login string) string

Handle is a person as telemetry names them: the handle they have in the system where they acted, qualified by that system's host, such as github.com/octocat. It is "" for a login that is empty or looks like an email address or a name with spaces, since those are never exported.

func IDs

func IDs(span trace.Span) (traceID, spanID string)

IDs are the trace and span ids of span, as lowercase hex, to keep on an item so a later step can link to it from another process. A span nobody records, such as a no-op provider's, has none: what it carries is its parent's.

func Inject

func Inject(ctx context.Context, env map[string]string)

Inject sets TRACEPARENT and, when there is one, TRACESTATE in env from the span in ctx, for a process ynf starts (ynr ADR-006, rule 4). It leaves env alone when ctx carries no trace.

func Intake

func Intake(ctx context.Context) (trace.SpanContext, bool)

Intake is the span context WithIntake noted in ctx.

func LaptopSpool

func LaptopSpool(env map[string]string) string

LaptopSpool is the laptop's default spool folder: $XDG_STATE_HOME/ynr/spool/local, with ~/.local/state when XDG_STATE_HOME is not set to an absolute path. It is "" when there is no home to resolve.

func LinkTo

func LinkTo(traceID, spanID string) (trace.Link, bool)

LinkTo is a span link to the span with these ids, or false when they are not a span's.

func Result

func Result(err error) string

Result is the outcome a call or phase ended with: ok, or failed with err.

func Scrub

func Scrub(s string) string

Scrub removes secrets and email addresses from s.

func Status

func Status(outcome string) codes.Code

Status is the span status an outcome gives. A step another instance holds is neither a success nor an error, so it is left unset.

func Swallowed

func Swallowed() int64

Swallowed is how many errors the SDK raised that ynf did not show.

func Tee

func Tee(primary slog.Handler, t *T) slog.Handler

Tee sends every record to primary, as it would go anyway, and a copy to ynf's OpenTelemetry logs. What primary writes is byte for byte what it wrote without the copy: the copy is made after it and its errors are not returned. It sends the copy only for records primary would write, so the level people chose applies to both. With no telemetry to write to, it returns primary itself.

func Transport

func Transport(next http.RoundTripper) http.RoundTripper

Transport is an http.RoundTripper that sends the W3C trace-context headers of the request's context. It is for calls to the eyelock tools (ynh, ynm, ynf, ynr), as ynr ADR-006 rule 4 has it: trace context goes only to the processes ynf spawns and to those tools, never to third parties such as forges, trackers or model APIs. Today ynf uses it for its calls to ynm.

func WithIntake

func WithIntake(ctx context.Context, span trace.Span) context.Context

WithIntake notes in ctx the ynf.intake span of the event the work in ctx is for, so the step it starts links to it.

Types

type Choice

type Choice struct {
	Mode Mode
	Dir  string
}

Choice is where telemetry goes, and for a spool, which folder.

func Choose

func Choose(environ []string) Choice

Choose picks where to write, in the contract's order (ynr ADR-006, rule 1): the operator's OTEL_EXPORTER_OTLP_* if any is set; otherwise the spool, if YNR_SPOOL names a folder or the laptop default, $XDG_STATE_HOME/ynr/spool/local (XDG's default is ~/.local/state), exists; otherwise nothing. When ynf writes to the spool itself it writes into the folder named, not a subfolder of it. environ is the environment, as os.Environ gives it.

func ChooseWith

func ChooseWith(environ []string, cfg Settings) Choice

ChooseWith is Choose with ynf's own configuration. With a spool root configured and the collector on, ynf writes to the spool whatever the operator set; with a root and no collector the operator's endpoint still wins, as the contract says, and otherwise the root's factory/ folder is where ynf writes, ahead of YNR_SPOOL and the laptop default.

type Mode

type Mode string

Mode is where telemetry goes.

const (
	ModeOTLP  Mode = "otlp"  // the operator's OTEL_EXPORTER_OTLP_*, over the network
	ModeSpool Mode = "spool" // OTLP JSON lines in a spool folder, which ynr serve reads
	ModeNone  Mode = "none"  // the SDK's no-op providers: nothing is written
)

The modes, in the order Choose tries them (ynr ADR-004).

type Options

type Options struct {
	// Version is service.version: ynf's own.
	Version string
	// Environ is the environment the choice and the resource are read from; default os.Environ.
	Environ []string
	// Watch is for a long-lived process, such as serve: when no spool was found it looks again
	// once a minute, so one that starts after ynf is still found.
	Watch bool
	// WatchEvery is how often Watch looks; default a minute.
	WatchEvery time.Duration
	// Spool is ynf's own configuration of where it writes: the spool root, and the collector.
	Spool Settings
	// FlushTimeout bounds the flush at exit; default 2 seconds.
	FlushTimeout time.Duration
}

Options configures Setup.

type Settings

type Settings struct {
	// Root is the spool root. When it is set, ynf writes its own telemetry to factory/ under it.
	Root string
	// Collector is true when ynf starts ynr serve for the job. The spool then wins over the
	// operator's OTEL_EXPORTER_OTLP_*, which ynr serve ships to instead (ynr ADR-004).
	Collector bool
}

Settings are ynf's own configuration of where it writes (ADR-009): the spool root, and whether the collector is on.

type T

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

T is a process's telemetry. Every method is safe on a nil T, which writes nothing.

func Setup

func Setup(ctx context.Context, o Options) *T

Setup sets up telemetry once, at process start, choosing where to write by Choose. Call Shutdown at exit.

func (*T) Active

func (t *T) Active() bool

Active reports whether telemetry is being written, or may be soon: the spans, calls and copies of log records are worth making. When it is not, ynf does exactly what it did before.

func (*T) Choice

func (t *T) Choice() Choice

Choice is where this process writes now.

func (*T) Context

func (t *T) Context(ctx context.Context) context.Context

Context joins the trace this process was given, in TRACEPARENT and TRACESTATE (ynr ADR-006, rule 3). With none, ctx is returned as it is and the first span starts a trace.

func (*T) Event

func (t *T) Event(ctx context.Context, name string, attrs ...attribute.KeyValue)

Event writes an event: a log record with an event name, correlated to the span in ctx, so it is inside that span's unit of work. The attributes are names from the registry.

func (*T) LeaseExpired

func (t *T) LeaseExpired(ctx context.Context, lane string)

LeaseExpired counts a lease found expired when a step claimed its item: its holder died or stalled.

func (*T) RunFinished

func (t *T) RunFinished(ctx context.Context, outcome, lane, model string, u Usage)

RunFinished counts a finished run: by outcome, lane and model, with what it spent by model. The attributes are the low-cardinality ones the registry names, never an item key or a run id.

func (*T) Shutdown

func (t *T) Shutdown(ctx context.Context)

Shutdown ends telemetry with a bounded flush (ynr ADR-006, rule 12): the providers' ForceFlush, then the spool's Sync, then the providers' Shutdown, then the spool's Close. It never fails.

func (*T) Tracer

func (t *T) Tracer() trace.Tracer

Tracer is ynf's tracer. It follows the providers, so spans started after a spool is found reach it.

type Usage

type Usage struct {
	InputTokens, OutputTokens, CacheReadTokens int
	CostUSD                                    float64
}

Usage is what a run spent, as its runner reports it.

Directories

Path Synopsis
Command gen writes internal/telemetry/names.go from telemetry/registry, ynf's Weaver registry.
Command gen writes internal/telemetry/names.go from telemetry/registry, ynf's Weaver registry.
Package registry reads ynf's telemetry registry (telemetry/registry), an OpenTelemetry Weaver registry, and writes the Go constants generated from it.
Package registry reads ynf's telemetry registry (telemetry/registry), an OpenTelemetry Weaver registry, and writes the Go constants generated from it.
Package spooltest reads a spool folder back, for tests that prove what ynf wrote: the OTLP JSON lines of every file in it, parsed into spans, events, log records and metric points.
Package spooltest reads a spool folder back, for tests that prove what ynf wrote: the OTLP JSON lines of every file in it, parsed into spans, events, log records and metric points.

Jump to

Keyboard shortcuts

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