moontel Reference · every public name Reference GitHub
Reference

Every public name.

Generated from the interfaces MoonBit prints for this module, so nothing public is missing and no signature was typed twice. The prose is the documentation comment beside the code.

Packages
three, and a facade
Entries
— entries
Source
pkg.generated.mbti
01

trace

moonbitstack/moontel/trace

The span model, the tracer that mints spans, the four samplers, and the two traits a caller implements: Sink for where spans go and Sampler for what is kept. Nothing here reads a clock, opens a socket or looks at a header.

Values

let always: Always

Samples everything, whatever the parent said. The starting point for a service whose traffic is small enough to keep whole.

let discard: Discard

Throws spans away. The default for a tracer whose owner has not decided where they go yet, and the sink a replay uses when it only cares that nothing crashes.

let dropped_nothing: Dropped

Nothing dropped, the value a span starts with.

let empty_trace_state: TraceState

No vendor state: what a new trace starts with, and what a hop that carries nothing forward hands on.

let never: Never

Samples nothing. Useful behind parent_based, where it is the answer for a parent that was not sampled.

fn parent_based(root~ : &Sampler, remote_sampled? : &Sampler, remote_unsampled? : &Sampler, local_sampled? : &Sampler, local_unsampled? : &Sampler) -> ParentBased

Parent-based sampling over root, which decides for the first span of a trace.

let random_flag: Byte

The random bit W3C Trace Context level 2 uses to say a trace id was drawn at random. Carried through a hop unchanged; this library does not read it.

fn ratio(ratio~ : Double) -> Ratio

A ratio sampler. A ratio outside 0 to 1 is clamped, so configuration read from a file cannot produce a sampler that samples more than everything or less than nothing.

let sampled_flag: Byte

The sampled bit of traceparent's flags field (W3C Trace Context §3.2.2.5).

let trace_limits: Limits

The OpenTelemetry defaults, from the trace SDK specification's span limits table.

let tracestate_limit: Int32

The list-members W3C Trace Context allows in one tracestate (§3.3.1).

Errors

error Refused Eq · Debug

Why a header, an id or a span id source was refused.

One error type for the whole module: a caller parsing an inbound traceparent and a caller handing the tracer a broken source of randomness both have one thing to catch.

Field(at~ : Int32, want~ : String)

A field that is not what the grammar asks for, at this offset in the input.

NotHex(at~ : Int32)

A character that has to be a lowercase hexadecimal digit and is not, at this offset. Uppercase is refused here because W3C Trace Context writes the fields in lowercase, and a parser that accepted both would let two spellings of one trace id into the same backend.

Zero(at~ : Int32)

An id that is all zero octets, which W3C Trace Context declares invalid.

TooMany(limit~ : Int32, got~ : Int32)

More list-members than the limit allows.

TooLong(limit~ : Int32, got~ : Int32)

A header longer than the limit, in octets.

Ids(want~ : Int32, got~ : Int32)

A source of randomness that did not hand back the number of octets asked for: rand(16) owes sixteen, and anything else is a broken generator rather than an id this library can guess at.

Also on the type: equal · not_equal · to_repr — derived, or required by an implementation above.

Types and methods

struct Always Sampler

Samples everything, whatever the parent said. The starting point for a service whose traffic is small enough to keep whole.

Also on the type: description · should_sample — derived, or required by an implementation above.

enum Decision Eq · Debug

The three answers a sampler can give.

Drop

Not recorded at all: the tracer hands back a span that forgets everything, so instrumented code needs no branch of its own.

RecordOnly

Recorded but not sent on: the span keeps its attributes and reaches the sink, and the sampled bit stays clear so its children are dropped too.

RecordAndSample

Recorded and sent on.

fn Decision::recording(Self) -> Bool

Whether the span reaches the sink with data in it.

fn Decision::sampled(Self) -> Bool

Whether the sampled bit is set and the trace continues.

Also on the type: equal · not_equal · to_repr — derived, or required by an implementation above.

struct Discard Sink

Throws spans away. The default for a tracer whose owner has not decided where they go yet, and the sink a replay uses when it only cares that nothing crashes.

Also on the type: accept — derived, or required by an implementation above.

struct Dropped Eq · Debug

How much a span gave up at its limits. OpenTelemetry asks for the counts rather than for the dropped values, so a reader can tell "this span had no attributes" from "this span had three hundred and we kept 128".

attributes
Int32
events
Int32
links
Int32

Also on the type: equal · not_equal · to_repr — derived, or required by an implementation above.

struct Event Eq · Debug

Something that happened during a span: a name, when it happened, and what it carried. Events keep the order they were added in, which for timestamps handed in by the caller is not necessarily the order of those timestamps.

name
String
at
Int64
attributes
Array[(String, Value)]

Also on the type: equal · not_equal · to_repr — derived, or required by an implementation above.

struct Limits Eq · Debug

How much one span collects.

The defaults are OpenTelemetry's: 128 attributes, 128 events, 128 links, 128 attributes on an event, and no limit on the length of a value. They exist to keep instrumentation that loops over user data from taking the process down with it, so the counts are per span and fixed when the span starts.

attributes
Int32
events
Int32
links
Int32
attributes_per_event
Int32
attribute_value_length
Int32?

None keeps values of any length. A number cuts every string attribute to that many characters, counting each as one.

fn Limits::new(attributes? : Int32, events? : Int32, links? : Int32, attributes_per_event? : Int32, attribute_value_length? : Int32?) -> Self

The same defaults, with any of them changed.

Also on the type: equal · not_equal · to_repr — derived, or required by an implementation above.

struct Link Eq · Debug

A causal relationship to another span that is not this one's parent — a batch item, a message consumed later, a fan-in. The linked context travels with the link so a reader can join the two traces.

context
TraceContext
attributes
Array[(String, Value)]

Also on the type: equal · not_equal · to_repr — derived, or required by an implementation above.

struct Never Sampler

Samples nothing. Useful behind parent_based, where it is the answer for a parent that was not sampled.

Also on the type: description · should_sample — derived, or required by an implementation above.

struct ParentBased Sampler

Follows the parent's decision and asks root only when there is no parent.

This is the sampler to give a service, because it is what keeps a distributed trace whole: the hop that made the decision decided for the whole trace, and every later hop obeys it instead of rolling its own dice.

The specification's five rows are the five fields: root without a parent, then a parent from another process or this one, sampled or not. The default for a sampled parent is always and for an unsampled one never, so a program that names only root follows the parent.

root
&Sampler
remote_sampled
&Sampler
remote_unsampled
&Sampler
local_sampled
&Sampler
local_unsampled
&Sampler

Also on the type: description · should_sample — derived, or required by an implementation above.

struct Ratio Debug · Sampler

Samples a fixed fraction of traces, decided by the trace id rather than by a coin: the same id gets the same answer on every hop, in every process, and from every service, so a trace that one service keeps is not dropped by the next.

The fraction is read off the low 64 bits of the trace id, which the specification asks for so that two rates nest: raising the rate keeps every trace the lower rate kept.

ratio
Double

Also on the type: description · should_sample · to_repr — derived, or required by an implementation above.

struct Recorder Sink

Keeps spans in memory, in the order they finished.

Meant for replays, examples and the debugging of a single request; a service wants a sink that ships spans off the process, because this one grows without bound.

spans
@ref.Ref[Array[SpanData]]
fn Recorder::len(Self) -> Int32

How many spans are kept.

fn Recorder::new() -> Self

An empty recorder.

fn Recorder::spans(Self) -> Array[SpanData]

The spans kept so far, oldest first.

fn Recorder::take(Self) -> Array[SpanData]

The spans kept so far, and an empty recorder afterwards. Use this when a replay drives several requests through one tracer and reads one request's spans at a time.

Also on the type: accept — derived, or required by an implementation above.

struct SampleRequest

What a sampler is told about the span it is deciding on.

The four things a sampler may look at are all known before the span exists: the parent, the trace id the span will carry, its name and its kind. Later changes — a better name, an attribute discovered mid-request — cannot move the decision, which is why the decision is made here and not on the finished span.

parent
TraceContext?
trace_id
Bytes
name
String
kind
SpanKind
struct Span

The span a caller holds: an identity fixed at creation, and fields that fill in until it is finished.

Everything that changes after creation sits behind a Ref, so one span may be filled in from the task that opened it and read from another. A non-recording span — one the sampler dropped — accepts the same calls and forgets them, which is what lets instrumented code stay the same whatever the sampler decided.

context
TraceContext
parent
Bytes?
kind
SpanKind
start
Int64
recording
Bool
sink
&Sink
limits
Limits
span_name
@ref.Ref[String]
attribute_list
@ref.Ref[Array[(String, Value)]]
event_list
@ref.Ref[Array[Event]]
link_list
@ref.Ref[Array[Link]]
span_status
@ref.Ref[Status]
end_time
@ref.Ref[Int64?]
dropped
@ref.Ref[Dropped]
fn Span::add_event(Self, String, attributes? : Array[(String, Value)], at~ : Int64) -> Unit

Add an event. at is the moment the caller says it happened, so a span recorded after the fact keeps its real timeline.

fn Span::add_link(Self, Link) -> Unit

Add a link to another span after creation, past the link limit being counted rather than applied.

fn Span::attributes(Self) -> Array[(String, Value)]

A copy of the attributes set so far, in the order their keys first appeared.

fn Span::context(Self) -> TraceContext

The identity of the span. It does not change for the span's whole life and stays usable after it ends, because a later span may still name this one as its parent.

fn Span::events(Self) -> Array[Event]

A copy of the events added so far, in the order they were added.

fn Span::finish(Self, at~ : Int64) -> Unit

Finish the span at at, handing it to the sink once.

Finishing twice does nothing the second time, and finishing a span the sampler dropped hands nothing over. Child spans are untouched: they may still be running, and they keep naming this one as their parent.

fn Span::is_ended(Self) -> Bool

Whether the span has been finished. A finished span takes no more writes and is not handed to the sink a second time.

fn Span::is_recording(Self) -> Bool

Whether the span is collecting anything: false after it ends, and false for a span the sampler dropped. Instrumented code can use it to skip building expensive attributes.

fn Span::links(Self) -> Array[Link]

A copy of the links added so far.

fn Span::name(Self) -> String

The name the span carries now, which set_name may have changed since it started.

fn Span::record_exception(Self, message~ : String, at~ : Int64, type_? : String, stacktrace? : String, attributes? : Array[(String, Value)]) -> Unit

Record an error the way OpenTelemetry's exception conventions ask: an event named exception carrying exception.type, exception.message and exception.stacktrace, with any attributes of the caller's own taking precedence over those three.

fn Span::set_attribute(Self, String, Value) -> Unit

Set one attribute. A key already present is overwritten in place, so the attribute list keeps the order in which keys first appeared rather than the order of their last write.

Past the attribute limit the write is dropped and counted rather than applied: a span with a runaway loop of attributes must not take the process down with it.

fn Span::set_attributes(Self, Array[(String, Value)]) -> Unit

Set several attributes at once, each following the same rule as set_attribute.

fn Span::set_name(Self, String) -> Unit

Rename the span. The sampler has already decided, so a better name found during the request cannot change that decision — it changes what the backend draws.

fn Span::set_status(Self, Status) -> Unit

Set the status, judged against the order Ok above Error above Unset: Unset changes nothing, Ok is final, and a later Error replaces an earlier one so the message can improve.

fn Span::status(Self) -> Status

The status as it stands: Unset until a caller sets one.

struct SpanData Eq · Debug

A finished span, as a Sink receives it.

It is a value, not a handle: everything in it is settled, and a sink may keep it, queue it or throw it away. Nothing here is normalised — a span may begin and end at the same nanosecond, and an event may carry a time outside the span.

context
TraceContext
parent
Bytes?

The parent's span id, or None for the first span of a trace. The parent's own context is deliberately not carried: a child needs the id to say where it hangs, and nothing else about the parent.

name
String
kind
SpanKind
start
Int64
end
Int64
attributes
Array[(String, Value)]
events
Array[Event]
links
Array[Link]
status
Status
dropped
Dropped
fn SpanData::duration(Self) -> Int64

How long the span lasted, in nanoseconds. Negative only if the caller handed in an end before the start, which is left as it came.

fn SpanData::sampled(Self) -> Bool

Whether this span carries the sampled bit, which is the one thing a sink needs in order to choose between exporting and merely counting.

Also on the type: equal · not_equal · to_repr — derived, or required by an implementation above.

enum SpanKind Eq · Debug

What a span is about, from the point of view of the process that made it. The kind is part of the sampling input and of what a backend draws, so it is settled when the span starts.

Internal

Server

Client

Producer

Consumer

Also on the type: equal · not_equal · to_repr — derived, or required by an implementation above.

enum Status Eq · Debug · Show

How the operation ended, as the caller judges it.

The three values form a total order, Ok above Error above Unset, and a status is judged against that order rather than by "last write wins": Ok is final, Error may replace another Error because the message may improve, and Unset is what a span already has.

Unset means "the caller has no opinion", which is not a failure: an instrumentation library leaves it alone unless the semantic conventions say otherwise.

Unset

Ok

Error(String)

Also on the type: equal · not_equal · output · to_repr · to_string — derived, or required by an implementation above.

struct TraceContext Eq · Debug

The identity of a span: the trace it belongs to, its own id, the flags travelling with it, and the vendor state riding along.

Ids are octets, not text: sixteen for the trace and eight for the span, as W3C Trace Context lays them out. Hexadecimal is a spelling used at the wire and in logs, and it is produced there rather than stored here.

remote is the specification's IsRemote: true for a context read off the wire, false for one this process minted. It does not travel in any header.

trace_id
Bytes
span_id
Bytes
flags
Byte
state
TraceState
remote
Bool
fn TraceContext::is_valid(Self) -> Bool

Whether the trace id is sixteen octets and the span id eight, neither all zero. A context that fails this is not a trace to join.

fn TraceContext::new(trace_id~ : Bytes, span_id~ : Bytes, flags? : Byte, state? : TraceState, remote? : Bool) -> Self

Build a context from ids the caller already has. Nothing is validated: this is the shape a tracer mints and a sink reads, and a caller holding a Span already has a valid one. A context read off the wire comes from parse instead, which does validate.

fn TraceContext::sampled(Self) -> Bool

Whether the sampled bit is set: the flag that says this span, and the children that carry it, are wanted by whoever reads the traces.

fn TraceContext::to_traceparent(Self) -> String

The traceparent header value for this context: version 00, the trace id, the span id, and the flags, in lowercase hexadecimal.

fn TraceContext::with_sampled(Self, sampled~ : Bool) -> Self

The same context with the sampled bit set or cleared, which is what a sampler's decision turns into. Every other flag travels as it came: a version of the header this library has not met may have put meaning in them.

Also on the type: equal · not_equal · to_repr — derived, or required by an implementation above.

struct TraceState Eq · Debug

Vendor state travelling with the trace, as an ordered list of at most 32 list-members (W3C Trace Context §3.3.1).

It is a list, not a map: the order is what a hop writes back, the first member belongs to the closest hop, and two members may not share a key. The update operations return a new state rather than editing this one, so that a caller holding the state that arrived cannot change what it arrived as.

members
Array[(String, String)]
fn TraceState::delete(Self, String) -> Self

The same state without the member key names, and unchanged when there is none.

fn TraceState::get(Self, String) -> String?

The value key is bound to, or None when the state has no such member.

fn TraceState::insert(Self, key~ : String, value~ : String) -> Self raise Refused

The same state with key set to value as the member closest to this hop (W3C Trace Context §3.3.1): an existing member with that key is replaced rather than moved, and a list already at 32 members drops its rightmost.

Building a header to send means adding your own entry to what came in, which is the one mutation a hop is allowed to make.

fn TraceState::is_empty(Self) -> Bool

Whether the hop carried no vendor state at all.

fn TraceState::len(Self) -> Int32

How many list-members the state carries.

fn TraceState::parse(StringView) -> Self raise Refused

Parse a tracestate header value.

Strict on purpose: a list-member that does not match the grammar or a list longer than 32 members refuses the whole header. A hop that kept the parseable half of a broken tracestate would hand the next hop something the sender never wrote, and W3C Trace Context §3.3.1 asks for the header to be dropped instead.

The one place the reading is wider than §3.3.1's ABNF is the key: the W3C Trace Context's reference parser is followed there, so foo@ and foo@@bar are list-members like any other. check_key says why, and says what is still out.

A key that appears twice is the one thing handled member by member: the first member with that key wins and the later one is dropped, which is the choice W3C Trace Context level 2 allows ("pass the duplicate on as-is or drop one of them") and the one W3C Trace Context's reference parser makes.

Whitespace before a member and after a value is dropped, as is whitespace around the commas; whitespace anywhere else is part of a key or a value that the grammar does not allow.

fn TraceState::to_header(Self) -> String

The header value: members in order, key=value, comma-separated, with no whitespace (W3C Trace Context §3.3.1.2). Empty when there is no state, which on the wire is the same as no header being sent.

Also on the type: equal · not_equal · to_repr — derived, or required by an implementation above.

struct Tracer

Makes spans, and the one place ids and the sampling decision are settled.

A tracer holds three things it cannot invent for itself: where randomness comes from, where finished spans go, and who decides whether a trace is wanted. The clock is the fourth, and it does not even reach the tracer — every call that needs a time is handed one.

rand
(Int32) -> Bytes
sink
&Sink
sampler
&Sampler
limits
Limits
fn Tracer::new(rand~ : (Int32) -> Bytes, sink~ : &Sink, sampler~ : &Sampler, limits? : Limits) -> Self

A tracer drawing ids from rand, handing finished spans to sink, and asking sampler about every span it starts.

rand(n) must return exactly n octets and must not return all zeros: eight octets of zeros is not a valid span id, and a generator that produces one is broken rather than unlucky. A call that breaks either rule makes start refuse; see Refused::Ids and Refused::Zero.

fn Tracer::start(Self, String, parent? : TraceContext, kind? : SpanKind, at~ : Int64, attributes? : Array[(String, Value)], links? : Array[Link]) -> Span raise Refused

Start a span.

With a parent whose context is valid, the new span joins that trace: it takes the parent's trace id and trace state, and parent_based sampling follows the parent's decision. Without one it starts a trace of its own, with a fresh trace id and no vendor state.

at is the start of the operation in nanoseconds. attributes and links are set before the span is handed back; a sampler sees them, which is why they are parameters here rather than calls on the span afterwards.

enum Value Eq · Debug · Show

One attribute value.

The arrays are separate variants rather than one array of Value because OpenTelemetry requires an array attribute to be homogeneous, and because that is what an exporter has to write: a mixed array has no single type tag.

Bool(Bool)

Int(Int64)

A signed 64-bit integer. Nanosecond timestamps and byte counts both fit; a Double would lose the low digits of a nanosecond clock.

Double(Double)

Str(String)

Bools(Array[Bool])

Ints(Array[Int64])

Doubles(Array[Double])

Strs(Array[String])

Also on the type: equal · not_equal · output · to_repr · to_string — derived, or required by an implementation above.

Traits

trait Sampler

Whether a span is wanted, and how much of it.

A caller can plug in its own: the four here are the ones the specification names, and anything with a decision — a rate limit read from configuration, a list of paths that must never be dropped — is another implementation of this.

fn Sampler::should_sample(Self, SampleRequest) -> Decision

Decide whether the span is wanted, and how much of it to record.

fn Sampler::description(Self) -> String

A short name for logs and debug pages, e.g. TraceIdRatioBased{0.25}.

trait Sink

Where finished spans go.

This is the one seam this library leaves open: exporting to OTLP, to a file, to a ring buffer that a background task drains, or to nothing at all is the caller's business, and the shapes that do it belong to whoever owns the transport. A sink is called on the task that finished the span, so an implementation that writes to a socket should hand the span to a queue rather than block the request.

A sink receives every recorded span, including the ones the sampler dropped from the trace: SpanData::sampled is the bit that separates "keep for counting" from "send to the backend".

fn Sink::accept(Self, SpanData) -> Unit

Take one finished span. Called on the task that finished it, so an implementation that writes to a socket should queue rather than block.

02

propagate

moonbitstack/moontel/propagate

W3C Trace Context and W3C Baggage at the header level: what the values look like on the wire, how to read them off a request, and how to put them back on the next one. The two calls a service uses are extract and inject.

Values

let baggage_byte_limit: Int32

The size W3C Baggage §3.2.1.3 requires a platform to propagate: 8192 octets of combined header.

let baggage_header: String

The header baggage travels in (W3C Baggage §3.2.1).

let baggage_member_limit: Int32

The list-members W3C Baggage §3.2.1.3 requires a platform to propagate: a header of 64 members or fewer must survive the hop whole.

let empty_baggage: Baggage

Nothing carried, which a request with no baggage header arrives with.

fn extract(Array[(String, String)]) -> Extracted

Read the propagation headers out of headers, which is the shape both an ASGI scope and an outgoing request carry.

Header names are matched without regard to case; a value is taken as it is, with the whitespace around it dropped.

Two traceparent headers refuse each other: the specification says a request with more than one is invalid, and picking one of the two would be guessing which proxy wrote the one that matters. Several tracestate or baggage headers are combined in order, as RFC 7230 allows a field to be split across lines.

A header that does not parse leaves that field empty and the rest intact: a mangled tracestate must not cost a hop the trace, and a mangled baggage must not cost it the trace either.

fn inject(Array[(String, String)], Extracted) -> Array[(String, String)]

Put extracted back into headers: the propagation headers already there are removed and rewritten from the context and the bag, and everything else is left exactly as it was.

Rewriting rather than appending is what makes this safe to call on a request that already carried a context — the one from the sender must not survive next to the one this hop decided on.

fn parse(StringView, tracestate? : StringView) -> @trace.TraceContext raise @trace.Refused

A parsed traceparent, plus the vendor state that came with it.

version is kept even though nothing here acts on it: a caller debugging a failed handshake wants to know which version of the header the other side spoke, and a version above zero may one day carry fields this library does not read.

The ids are validated on the way in — sixteen and eight octets, neither all zero — because a context that fails that is not a trace to join, and passing it on would make a broken hop look like a working one.

fn parse_traceparent(StringView) -> @trace.TraceContext raise @trace.Refused

Parse a traceparent header value, without any vendor state.

The grammar is W3C Trace Context §3.2:

``text version(2)-trace-id(32)-parent-id(16)-trace-flags(2) ``

A version this library does not know may append fields, but only after a -: 00 headers end at 55 characters, and 00-…-01.whatever is refused rather than read as a version-0 header with a suffix. Version ff is refused by the specification, as is an all-zero id.

Whitespace around the value is dropped. HTTP allows OWS around a field value, and W3C Trace Context's examples send it; whitespace anywhere inside is refused as the illegal character it is.

fn parse_tracestate(StringView) -> @trace.TraceState raise @trace.Refused

Parse a tracestate header value. The grammar lives with the type that holds the state; this is the name a caller reaching for the header uses.

let traceparent_header: String

The header traceparent travels in (W3C Trace Context §3.2.1). Lowercase on the wire; lookups here compare without regard to case.

let tracestate_header: String

The header tracestate travels in (W3C Trace Context §3.3.1).

Types and methods

struct Baggage Eq · Debug

Keys and values carried alongside a trace, ordered, with duplicates allowed.

Order is part of the data: W3C Baggage asks a hop to preserve the order of duplicate entries when it mutates the list, so this is an array rather than a map. get answers with the first member carrying a key, which is the one a reader asking "what did the caller say" wants.

members
Array[Member]
fn Baggage::add(Self, Member) -> Self

The same bag with member added at the end, past the member limit keeping the bag as it was rather than growing an unusable header.

fn Baggage::get(Self, String) -> String?

The value of the first member keyed key, or None.

fn Baggage::is_empty(Self) -> Bool

Whether the bag has no members.

fn Baggage::len(Self) -> Int32

How many members the bag carries.

fn Baggage::new(members? : Array[Member]) -> Self

A bag carrying members, in the order given.

fn Baggage::parse(StringView) -> Self raise @trace.Refused

Parse a baggage header value (W3C Baggage §3.2).

Whitespace around a member, around an =, and around the commas is dropped; a member that does not match the grammar refuses the whole header, because a half-read bag is a bag the next hop would rewrite. Values are percent-decoded and sequences that are not UTF-8 become U+FFFD, as §3.2.2.2 asks.

The limits are the ones a platform must propagate — 64 members and 8192 octets — checked against the value as it arrived, and enforced by refusing rather than truncating: a caller that receives a bag too big to be legal has received something a middlebox either wrote or mangled, and guessing which members it meant is not this library's business.

fn Baggage::to_header(Self) -> String

The header value: members in order, key=value with properties after ;, and every character outside the baggage-octet set percent-encoded in uppercase hexadecimal (W3C Baggage §3.2.2.2). Empty when the bag is empty, which on the wire is the same as sending no header. encode_value says why a space is %20 rather than the + form encoding would use.

Also on the type: equal · not_equal · to_repr — derived, or required by an implementation above.

struct Extracted Eq · Debug

What a hop takes out of a set of request headers: the trace it joins, if the request carried one, and the baggage that came with it.

One value rather than two calls, because a hop that reads one and forgets the other is a bug this shape makes hard to write: inject puts back exactly what extract found, changed only where the caller changed it.

context
@trace.TraceContext?

The parent context, or None when the request carried no context or carried one this library refused. A refused context is not an error here: W3C Trace Context says a hop starts a new trace rather than failing the request.

baggage
Baggage

Also on the type: equal · not_equal · to_repr — derived, or required by an implementation above.

struct Member Eq · Debug

One baggage entry: a key, a value, and any properties attached to it.

The value is stored decoded, as text. The wire form is percent-encoded ASCII, and decoding is where a hop stops caring how the value was spelled.

key
String
value
String
properties
Array[Property]

Also on the type: equal · not_equal · to_repr — derived, or required by an implementation above.

struct Property Eq · Debug

One metadata entry on a baggage member: key or key=value, after the ; of a list-member (W3C Baggage §3.2.2.3). The specification gives properties no meaning of their own; they are carried so a hop that does not understand one can pass it on unchanged.

key
String
value
String?

None for a property written as a bare key.

Also on the type: equal · not_equal · to_repr — derived, or required by an implementation above.

03

asgi

moonbitstack/moontel/asgi

One middleware: a server span per HTTP request, joined to the trace the request arrived with, and the context written back on the response. It implements the framework's Layer, so it stacks with the rest.

Values

fn method_and_path(String, String) -> String

The default span name: the method and the path, e.g. GET /orders/42.

Types and methods

struct Tracing @mid.Layer

One server span per HTTP request, joined to the trace the request arrived with.

The layer does three things and no more: it reads traceparent and baggage off the request, it opens a span of kind Server as a child of what it read, and it writes the resulting context back on the response. A request that carried no context starts a trace; one that carried a broken context also starts a trace, because refusing the request over a header would be worse than a missing link in the trace.

The span reaches the sink when the application returns, with the response status on it. A request the application fails is recorded as an exception event before the error is passed on to the server, which is what an ASGI server keeps.

tracer
@trace.Tracer
now
() -> Int64

The clock. This library never reads one itself, so a replay can drive a request through a timeline it chose and a service can hand in whichever clock it already trusts. Nanoseconds since any fixed origin.

name
(String, String) -> String

How a request is named. The default is the method and the path, which is what the middleware can see; a site that knows its routes should pass the route template instead, so that /orders/42 and /orders/43 are one span name rather than a thousand.

baggage
Bool

Whether baggage rides along to the response. On by default: a hop that quietly drops what the caller attached is worse than one that never had it.

ignore
Array[String]

Paths to leave untraced. A health check every second is noise a backend pays for and nobody reads.

fn Tracing::new(tracer~ : @trace.Tracer, now~ : () -> Int64, name? : (String, String) -> String, baggage? : Bool, ignore? : Array[String]) -> Self

A tracing layer over tracer.

fn Tracing::wrap(Self, async (@spec.Scope, async () -> @spec.Event, async (@spec.Event) -> Unit) -> Unit) -> async (@spec.Scope, async () -> @spec.Event, async (@spec.Event) -> Unit) -> Unit

Wraps an application in a server span per request. wrap is the Layer method, so the middleware can sit in a stack beside the framework's own layers as well as around a single application.

04

moontel

moonbitstack/moontel

One import for a program that wants all three packages. A type is re-exported, not copied, so it is the same type the package defines; a constructor is still reached through the package that owns it.

facade pub using @trace every public name

Tracer, Span, TraceContext, Value, Limits, the four samplers, Recorder and Discard, and the traits Sink and Sampler.

facade pub using @propagate every public name

extract, inject, parse, parse_traceparent, parse_tracestate, Baggage, Extracted, Member, Property, and the three header names.

facade pub using @asgi every public name

Tracing, and method_and_path.

note constructors

A re-exported enum or struct arrives without its constructors: that is the language's rule, not a decision here. To build an attribute value, import trace and write @trace.Str("…"); the facade alone cannot spell it.