Skip to content

Latest commit

 

History

History
290 lines (235 loc) · 15.4 KB

README.md

File metadata and controls

290 lines (235 loc) · 15.4 KB

Rust Template Package

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.

Generate and use a module

From a checkout of this repository:

weaver registry generate \
  --v2 \
  -r {your registry} \
  -t templates \
  code/rust \
  {your crate}/src/semconv

For 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.

Generated API

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.

Typed attributes

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.

Metrics

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.

Spans

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.

Events

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.

Entities

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.

Refinements and dependencies

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: upstream

That 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 metadata

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");

Parameters

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.

Generation errors and limits

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.

Validate a change

mise run weaver:test:templates
mise run weaver:test:rust

The 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.