Generates typed attributes, metric instruments, span starters, event emitters, and entity values from a semantic convention registry using OpenTelemetry Rust.
Stability: Development. Generated code targets opentelemetry and
opentelemetry_sdk 0.33.0.
From a checkout of this repository:
weaver registry generate \
--v2 \
-r {your registry} \
-t templates \
code/rust \
{your crate}/src/semconvFor a released package, replace -t templates with
-t 'https://github.com/TrogonStack/trogonstack-weaver-packages.git@v{version}[templates]'.
Declare mod semconv; in the consuming crate and add its dependencies:
[dependencies]
opentelemetry = "0.33.0"
opentelemetry_sdk = "0.33.0"
smallvec = { version = "=1.16.2", features = ["const_generics"] }Run cargo fmt after generation. Weaver writes only the files it produces, so
empty the generated directory before regenerating after removing conventions.
Keep the consuming crate's files outside that directory.
The root mod.rs carries SCHEMA_URL and declares the generated modules. The
layout mirrors the opentelemetry-semantic-conventions crate: one flat module
per signal, holding every namespace. Each generated type and function name
starts with its namespace, the first dotted segment of the registry name;
vendor_prefixes removes a leading vendor segment before choosing that
namespace. For example, myapp.task.id becomes attribute::MyappTaskId,
and the myapp.task.run span starter becomes trace::start_myapp_task_run.
| Module | API |
|---|---|
attribute |
Attribute newtypes and enum variants, KEY and conversions into KeyValue or log AnyValue. |
options |
Typed instrumentation options containing scope configuration. |
scope |
Scope attributes configuration; package identity is fixed. |
meter |
Schema-aware Meter, with new, inner, and a no-op Default. |
tracer |
Schema-aware Tracer, with new, inner, and a no-op Default. |
logger |
Schema-aware generic Logger, with new, inner, and a no-op Default. |
metric |
Synchronous instruments and observable counters, up-down counters, and gauges. |
trace |
Span starters, typed names, start attributes, and attributes allowed after starting. |
event |
Typed log event emitters and options for attributes, severity, and timestamp. |
resource |
Typed entity values, attribute iterators, and resources carrying the registry schema URL. |
Handle modules are emitted when the registry includes their corresponding
signals. Their new(provider, options) constructor takes the exact
options::InstrumentationOptions type, with metadata nested under scope:
let options = semconv::options::InstrumentationOptions {
scope: semconv::scope::Scope {
attributes: vec![opentelemetry::KeyValue::new("scope.kind", "worker")],
},
};
let tracer = semconv::tracer::Tracer::new(&provider, options);Use InstrumentationOptions::default() when no scope attributes are needed.
The constructors accept unsized providers, so a handle can come straight from the global provider without a wrapper type:
let meter = semconv::meter::Meter::new(
&*opentelemetry::global::meter_provider(),
semconv::options::InstrumentationOptions::default(),
);The instrumentation identity is fixed during generation. Set
instrumentation_name and instrumentation_version in the Weaver params to
choose its name and version. The name defaults to generated-semconv; an empty
version omits SDK version metadata. The registry supplies the schema URL.
Runtime options cannot override these values.
Configured scope attributes move into OpenTelemetry without rebuilding their storage. The shared scope and options modules are emitted when a handle is needed. Attribute keys, metric names, and event names retain their full registry names.
An attribute such as myapp.task.id becomes
attribute::MyappTaskId::from("task-1"). Convert the attribute into an OpenTelemetry KeyValue with
KeyValue::from(attribute) or attribute.into(). String attributes implement
From<String>, From<&'static str>, and From<opentelemetry::StringValue>.
Static strings retain their static storage. Own a dynamic borrowed string
explicitly, such as MyappTaskId::from(text.to_owned()). Scalar and array attributes
implement From for their backing types; string arrays accept Vec<String>
and native Vec<opentelemetry::StringValue>. Native storage moves into the SDK
without rebuilding the vector. Use MyappTaskId::from(value) or value.into() when
the attribute type is known. Enums expose named variants,
such as attribute::MyappTaskState::Running, rather than arbitrary constructors.
Supported backing types are StringValue, i64, f64, bool, and their Vec
forms. Enums can have string, integer, fractional, or boolean values.
String attributes implement AsRef<str>; arrays expose their borrowed slices
through AsRef<[T]>. Attributes implement Display
without building a temporary KeyValue; arrays follow OpenTelemetry's array
formatting. Boolean, integer, floating-point and enum attributes are Copy.
Deprecation metadata becomes Rust deprecation annotations.
Attributes also convert directly into opentelemetry::logs::AnyValue through
From. Generated event emitters pass the attribute type's KEY and the typed attribute to
the SDK log record.
Each metric becomes a typed instrument with a new(&meter) constructor and
an add or record method. Required attributes are typed arguments. Other
attributes are variants of the metric's Attr enum, such as metric::MyappTaskDurationAttr, passed as an iterator.
Observable constructors register a callback whose typed observer has the same
required arguments and optional attribute contract.
Recording required attributes uses a stack array. Optional attributes use an inline buffer sized for the required attributes and the lesser of the declared optional attributes or eight. Larger inputs spill to the heap; duplicate attributes retain their order. This avoids generated buffer allocations for ordinary attribute sets while preserving iterator input. Attribute values and the SDK may still require ownership or aggregation allocations.
Set annotations.code_generation.metric_value_type to int or double.
Rust's integer counters and histograms use u64; integer up-down counters and
gauges use i64. Double instruments use f64. Constructors follow the Rust
API and return instruments directly. Observable callbacks return ().
Histograms accept annotations.aggregation with method: explicithistogram
and parameters.boundaries. Boundaries must be a nonempty, strictly increasing
list of numbers. Without explicit aggregation, histogram_boundaries_by_unit
provides unit-specific boundaries; units absent from that map use SDK defaults.
A span starter takes a parent Context, a generated tracer, required typed
attributes, and optional start attributes. A registry name template whose
placeholders refer to required attributes is expanded from those values.
Otherwise the starter takes a typed Name, constructed with From for a static
string, owned String, or Cow<'static, str>. An empty name borrows the span's
registry type; static names stay borrowed and owned names move into the span.
All starter attributes are present before sampling. Sampling-relevant
attributes appear only in the start attribute enum. The separate late attribute
enum is accepted by set_attributes. The wrapper exposes end,
end_with_timestamp, record_error, set_status, and the underlying span.
into_context transfers the span into a parent context for nested spans and
normal OpenTelemetry context attachment. The generated tracer and span wrappers
retain the provider's concrete types. Their default type is the no-op API type;
into_context uses OpenTelemetry's synchronization and type erasure when
context integration is needed.
Derived names borrow attribute values and write directly into a preallocated
String, reserving known literal and string lengths plus space for other
values. Nonempty derived names allocate their output buffer. Required attributes use a
stack array and optional iterators, which the span builder collects into its
owned attribute buffer.
Emitters produce log records with the event name and no body. Required
attributes are typed parameters. Options accept declared attributes, severity,
and timestamp. Severity defaults to Info. The emitter checks whether the logger
accepts the event, sets the observed timestamp, and copies a valid trace context
from the supplied Context.
The last severity and timestamp options win. Declared attribute options remain typed until the enabled check succeeds, so disabled events avoid attribute conversion and context attachment. Optional attributes use inline storage for the lesser of the declared optional attributes or eight, with heap overflow for larger inputs. Enabled emitters add required attributes and then optional attributes directly to the record, preserving duplicates and their order.
Entity constructors take required identity attributes before required description attributes, followed by optional attributes. Required attributes remain typed fields, and optional attributes remain variants of the entity's attribute enum. Optional storage uses a bounded inline buffer and supports heap overflow when the caller supplies more values.
attributes() returns an iterator over SDK-owned KeyValue values. Borrowed
export clones only the values needed to give the SDK ownership, without
building an intermediate attribute vector. Convert an entity with
Resource::from(entity) or entity.into() to consume its fields and optional
values without cloning them. The resource retains the registry schema URL and
has no implicit SDK detector attributes. Default is available only when the
entity has no required attributes.
Metric, span, and entity refinements receive separate APIs and are emitted only when their base convention is emitted. Metric refinements retain their base instrument's name, description, unit, value type, and boundaries. Event refinements follow Weaver's resolved event name, which is the refinement id.
Imported attributes use their plain Rust backing type by default. Set
imported_crate_paths to use types from a separately generated dependency:
params:
imported_crate_paths:
error: upstreamThat mapping refers to upstream::attribute in the consuming crate, so
error.type becomes upstream::attribute::ErrorType. Both
registries must use compatible namespace and vendor-prefix settings.
Plain imported strings use opentelemetry::StringValue, and string arrays use
Vec<opentelemetry::StringValue>, preserving their SDK storage. Convert a static
string with .into() or StringValue::from at the call site.
Signal types carry their registry metadata as associated constants, usable in
const contexts. The generated code reads these constants, so each value
appears once.
| Type | Constants |
|---|---|
metric::MyappTaskDuration |
NAME, UNIT, DESCRIPTION |
metric::MyappQueueDepthObservable |
NAME, UNIT, DESCRIPTION, defined as the synchronous type's |
trace::MyappTaskRun |
NAME, the span type |
event::MyappTaskFinished |
NAME, the event name |
resource::MyappHost |
NAME, the entity type |
UNIT and DESCRIPTION are empty strings when the registry leaves them unset.
Refinements report the type they refine. Each event has a unit struct that
holds its NAME; emitting still goes through its emit_ function.
const DURATION: &str = metric::MyappTaskDuration::NAME;
assert_eq!(metric::MyappTaskDuration::UNIT, "s");| Parameter | Default | Purpose |
|---|---|---|
instrumentation_name |
generated-semconv |
Name baked into every generated instrumentation scope. |
instrumentation_version |
"" |
Version baked into generated scopes; empty omits SDK version metadata. |
vendor_prefixes |
[] |
Leading vendor segments removed when deriving namespaces and identifiers. |
exclude_deprecated |
false |
Exclude deprecated conventions and enum members. |
stable_only |
false |
Include only stable conventions and enum members. |
imported_crate_paths |
{} |
Namespace to Rust path for a separately generated dependency. |
histogram_boundaries_by_unit |
Duration boundaries for s |
Default histogram boundaries by unit; {} disables them. |
header_source |
"" |
Source description in the generated header. |
module_description |
"" |
Additional root module documentation. |
regenerate_command |
"" |
Regeneration instruction in the generated header. |
The manifest must provide a schema URL ending in a schema version. Naming collisions, unusable enums, invalid aggregation, conflicting refinements, and filtering away required signal attributes fail generation. Attribute names use ASCII letter-led dotted segments containing letters, digits, and underscores. Namespace segments must be lowercase and may contain digits and underscores.
Conditional requirements remain options because Rust cannot prove registry
conditions. Event bodies, span events, and links have no registry form; use
the underlying OpenTelemetry handles when needed. Template and any attribute
types are not generated.
mise run weaver:test:templates
mise run weaver:test:rustThe Rust check formats generated fixture code in temporary crates, runs consumer and documentation tests, verifies compile-fail examples, and runs Clippy with warnings denied. Snapshot checks compare the original Weaver output. The allocation fixture runs in release mode with a thread-local counting allocator and custom providers that do not allocate while recording. It checks generated buffer costs separately from SDK storage costs and verifies duplicate attributes beyond inline capacity.