diff --git a/.config/mise/tasks/weaver/test/_default b/.config/mise/tasks/weaver/test/_default index 82e31c6..fad21d6 100755 --- a/.config/mise/tasks/weaver/test/_default +++ b/.config/mise/tasks/weaver/test/_default @@ -1,3 +1,3 @@ #!/usr/bin/env bash -#MISE description="Run every policy and template package test, and compile the generated Go" -#MISE depends=["weaver:test:policies", "weaver:test:templates", "weaver:test:go"] +#MISE description="Run every policy and template package test, and validate generated Go and Rust" +#MISE depends=["weaver:test:policies", "weaver:test:templates", "weaver:test:go", "weaver:test:rust"] diff --git a/.config/mise/tasks/weaver/test/rust b/.config/mise/tasks/weaver/test/rust new file mode 100755 index 0000000..8a6b1db --- /dev/null +++ b/.config/mise/tasks/weaver/test/rust @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +#MISE description="Check that generated Rust is formatted, tested, and passes Clippy" +set -euo pipefail +exec ./buildscripts/check_rust_output.sh diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 785e8ad..e331b4a 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -38,6 +38,7 @@ jobs: - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0 - run: mise run weaver:test:templates - run: mise run weaver:test:go + - run: mise run weaver:test:rust markdown: name: Markdown diff --git a/AGENTS.md b/AGENTS.md index 906333e..7661133 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -38,6 +38,10 @@ Each template package includes: Generated Go must pass `gofmt`, `go build`, and `go vet`, which `mise run weaver:test:go` checks. +Generated Rust must pass formatting, consumer tests, and Clippy, which +`mise run weaver:test:rust` checks against the pinned OpenTelemetry Rust API +and SDK. + ## Contribution guidelines - Keep changes to the package being modified. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7aa7630..26bb9d4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,6 +55,11 @@ Run `mise tasks` to list every task and what it does. Every test must pass before a pull request is merged. The `Checks` workflow runs the same tasks. +For Rust packages, `mise run weaver:test:rust` compiles the expected output in +temporary crates and runs formatting, consumer tests, and Clippy. Put consumer +tests in a case's `test.rs`, importing the generated crate as +`generated_semconv`. + Keep test registries synthetic. Use neutral namespaces such as `myapp` and `auth` rather than conventions from a real system. diff --git a/README.md b/README.md index 12bf611..6f5bd25 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ is versioned and released from this repository, ready to be referenced by tag. **The packages turn a semantic convention registry into code and keep that registry fit for generation.** Templates generate typed telemetry helpers, such -as Go attribute values and metric instruments on the OpenTelemetry Go API. +as typed attribute values and signal helpers on the OpenTelemetry Go and +Rust APIs. Policies run during `weaver registry check` and reject conventions a template cannot render, before any code is written. @@ -29,6 +30,9 @@ Templates live under [`templates`](templates) and policies under [`policies`](policies). Each package directory has a README describing what it does and the params it accepts. +- [Go code generation](templates/code/go/README.md) +- [Rust code generation](templates/code/rust/README.md) + ## Using a package Weaver accepts a Git repository as a template or policy location, in the form diff --git a/buildscripts/check_rust_output.sh b/buildscripts/check_rust_output.sh new file mode 100755 index 0000000..47a2bf4 --- /dev/null +++ b/buildscripts/check_rust_output.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash + +set -euo pipefail + +OTEL_RUST_VERSION="${OTEL_RUST_VERSION:-0.33.0}" +WEAVER="${WEAVER:-weaver}" +ROOT="$(pwd)" +PACKAGE_DIR="${ROOT}/templates/code/rust" +TEMPLATES_ROOT="$(realpath "${PACKAGE_DIR}/../..")" + +WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/weaver-rust.XXXXXX")" +trap 'rm -rf "${WORK_DIR}"' EXIT +export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-${WORK_DIR}/target}" + +for test_dir in "${PACKAGE_DIR}"/tests/*/; do + test_name="$(basename "${test_dir}")" + [[ -f "${test_dir}expected-error.txt" ]] && continue + expected="${test_dir}expected" + if [[ ! -f "${expected}/mod.rs" ]]; then + echo " FAIL: ${test_name} is missing expected/mod.rs." + exit 1 + fi + echo "-> Checking Rust [${test_name}] ..." + + crate_dir="${WORK_DIR}/${test_name}" + mkdir -p "${crate_dir}/src" + cp -R "${expected}/." "${crate_dir}/src/" + mv "${crate_dir}/src/mod.rs" "${crate_dir}/src/lib.rs" + if [[ -f "${test_dir}test.rs" ]]; then + mkdir -p "${crate_dir}/tests" + cp "${test_dir}test.rs" "${crate_dir}/tests/attributes.rs" + fi + if [[ -d "${test_dir}tests" ]]; then + mkdir -p "${crate_dir}/tests" + cp -R "${test_dir}tests/." "${crate_dir}/tests/" + fi + + cat >"${crate_dir}/Cargo.toml" <"${dependency_dir}/Cargo.toml" <>"${crate_dir}/Cargo.toml" + ( + cd "${dependency_dir}" + cargo fmt --all || exit 1 + cargo fmt --all --check || exit 1 + cargo clippy --all-targets -- -D warnings || exit 1 + ) || { echo " FAIL: ${test_name} dependency fails Rust validation."; exit 1; } + fi + + ( + cd "${crate_dir}" + cargo fmt --all || exit 1 + cargo fmt --all --check || exit 1 + if [[ "${test_name}" == allocations || -f "${test_dir}tests/allocations.rs" ]]; then + cargo test --release -- --nocapture || exit 1 + else + cargo test || exit 1 + fi + cargo clippy --all-targets -- -D warnings || exit 1 + for invalid_consumer in "${test_dir}"compile-fail/*.rs; do + [[ -f "${invalid_consumer}" ]] || continue + expected_error="${invalid_consumer%.rs}.expected-error.txt" + if [[ ! -s "${expected_error}" ]]; then + echo " FAIL: ${invalid_consumer} is missing its expected diagnostic." + exit 1 + fi + mkdir -p examples + cp "${invalid_consumer}" examples/invalid_consumer.rs + if cargo check --example invalid_consumer >diagnostics.txt 2>&1; then + echo " FAIL: ${invalid_consumer} compiled successfully." + exit 1 + fi + if ! grep -Fq -f "${expected_error}" diagnostics.txt; then + cat diagnostics.txt + echo " FAIL: ${invalid_consumer} did not fail with its expected diagnostic." + exit 1 + fi + rm examples/invalid_consumer.rs + echo " PASS: $(basename "${invalid_consumer}") rejects its invalid consumer." + done + ) || { echo " FAIL: ${test_name} does not pass Rust validation."; exit 1; } + echo " PASS: ${test_name} formats, tests, and passes Clippy." +done diff --git a/mise.toml b/mise.toml index 1013992..61eff18 100644 --- a/mise.toml +++ b/mise.toml @@ -3,4 +3,5 @@ go = "1.27.1" jq = "1.8.1" node = "24.21.0" +rust = { version = "1.99.0", profile = "minimal", components = ["rustfmt", "clippy"] } "npm:prettier" = "3.9.9" diff --git a/templates/code/rust/README.md b/templates/code/rust/README.md new file mode 100644 index 0000000..f39ff89 --- /dev/null +++ b/templates/code/rust/README.md @@ -0,0 +1,253 @@ +# 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: + +```sh +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: + +```toml +[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. +Each attribute namespace lives in a sibling `attr.rs` file, alongside +the signal modules. Names use the first dotted +segment as their namespace; `vendor_prefixes` removes a leading vendor segment +before choosing that namespace. + +| Module | API | +| ------------------- | ---------------------------------------------------------------------------------------------- | +| `attr` | 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. | +| `span` | Span starters, typed names, start attributes, and attributes allowed after starting. | +| `event` | Typed log event emitters and options for attributes, severity, and timestamp. | +| `entity` | 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`: + +```rust +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 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 +`myappattr::TaskIdAttr::from("task-1")`. Convert the attribute into an OpenTelemetry `KeyValue` with +`KeyValue::from(attribute)` or `attribute.into()`. String attributes implement +`From`, `From<&'static str>`, and `From`. +Static strings retain their static storage. Own a dynamic borrowed string +explicitly, such as `Attr::from(text.to_owned())`. Scalar and array attributes +implement `From` for their backing types; string arrays accept `Vec` +and native `Vec`. Native storage moves into the SDK +without rebuilding the vector. Use `Attr::from(value)` or `value.into()` when +the attribute type is known. Enums expose named variants, +such as `myappattr::TaskStateAttr::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`; 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 `Attr::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 instrument's `Attr` enum, 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: + +```yaml +params: + imported_crate_paths: + error: upstream +``` + +That mapping refers to `upstream::errorattr` in the consuming crate. Both +registries must use compatible namespace and vendor-prefix settings. +Plain imported strings use `opentelemetry::StringValue`, and string arrays use +`Vec`, preserving their SDK storage. Convert a static +string with `.into()` or `StringValue::from` at the call site. + +## 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 + +```sh +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. diff --git a/templates/code/rust/attributes.rs.j2 b/templates/code/rust/attributes.rs.j2 new file mode 100644 index 0000000..7ae0192 --- /dev/null +++ b/templates/code/rust/attributes.rs.j2 @@ -0,0 +1,167 @@ +{%- filter trim %} +// Generated by OpenTelemetry Weaver. +{% for attr in ctx.attributes %} +{% set rust_type = "::opentelemetry::StringValue" if attr.value_type == "string" else ("Vec<::opentelemetry::StringValue>" if attr.value_type == "string[]" else attr.value_type | map_text("rust_type")) %} +{{ [attr.brief, attr.note] | select | join("\n\n") | comment | indent(0) }} +{{ ("#[deprecated(note = " ~ attr.deprecated_literal ~ ")]\n") if attr.deprecated_literal else "" }}#[derive(Debug, Clone, PartialEq{% if attr.type is mapping or attr.value_type in ["int", "double", "boolean"] %}, Copy{% endif %})] +{% if attr.type is mapping %} +pub enum {{ attr.name }} { +{% for member in attr.type.members %} +{% set member_doc = [member.brief, member.note] | select | join("\n\n") %} +{% if member_doc %} + {{ member_doc | comment | indent(4) }} +{% endif %} + {{ ("#[deprecated(note = " ~ member.deprecated_literal ~ ")]\n ") if member.deprecated_literal else "" }}{{ member.name }}, +{% endfor %} +} +{% else %} +pub struct {{ attr.name }}({{ rust_type }}); +{% endif %} + +#[allow(deprecated)] +impl {{ attr.name }} { + pub const KEY: &'static str = {{ attr.key_literal }}; +} +{% if attr.type is not mapping %} + +#[allow(deprecated)] +impl From<{{ rust_type }}> for {{ attr.name }} { + #[inline] + fn from(value: {{ rust_type }}) -> Self { + Self(value) + } +} +{% if attr.value_type == "string" %} + +#[allow(deprecated)] +impl From for {{ attr.name }} { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for {{ attr.name }} { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} +{% elif attr.value_type == "string[]" %} + +#[allow(deprecated)] +impl From> for {{ attr.name }} { + fn from(value: Vec) -> Self { + Self(value.into_iter().map(Into::into).collect()) + } +} +{% endif %} +{% endif %} +{% if attr.value_type == "string" %} + +#[allow(deprecated)] +impl AsRef for {{ attr.name }} { + #[inline] + fn as_ref(&self) -> &str { +{% if attr.type is mapping %} + match self { +{% for member in attr.type.members %} + {{ attr.name }}::{{ member.name }} => {{ member.literal }}, +{% endfor %} + } +{% else %} + self.0.as_str() +{% endif %} + } +} +{% elif "[]" in attr.value_type %} +{% set element_type = "::opentelemetry::StringValue" if attr.value_type == "string[]" else attr.value_type[:-2] | map_text("rust_type") %} + +#[allow(deprecated)] +impl AsRef<[{{ element_type }}]> for {{ attr.name }} { + #[inline] + fn as_ref(&self) -> &[{{ element_type }}] { + &self.0 + } +} +{% endif %} + +#[allow(deprecated)] +impl ::std::fmt::Display for {{ attr.name }} { +{% if "[]" not in attr.value_type and (attr.type is not mapping or attr.type.members | length <= 8) %} + #[inline] +{% endif %} + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { +{% if attr.value_type == "string" %} + formatter.write_str(self.as_ref()) +{% elif attr.type is mapping %} + let value: {{ attr.value_type | map_text("rust_type") }} = match self { +{% for member in attr.type.members %} + {{ attr.name }}::{{ member.name }} => {{ member.literal }}{% if attr.value_type == "double" %}_f64{% endif %}, +{% endfor %} + }; + ::std::fmt::Display::fmt(&value, formatter) +{% elif "[]" in attr.value_type %} + formatter.write_str("[")?; + for (index, value) in self.0.iter().enumerate() { + if index != 0 { + formatter.write_str(",")?; + } +{% if attr.value_type == "string[]" %} + write!(formatter, "\"{value}\"")?; +{% else %} + ::std::fmt::Display::fmt(value, formatter)?; +{% endif %} + } + formatter.write_str("]") +{% else %} + ::std::fmt::Display::fmt(&self.0, formatter) +{% endif %} + } +} + +#[allow(deprecated)] +impl From<{{ attr.name }}> for ::opentelemetry::KeyValue { +{% if attr.type is not mapping or attr.type.members | length <= 8 %} + #[inline] +{% endif %} + fn from(value: {{ attr.name }}) -> Self { +{% if attr.type is mapping %} + let value: {{ "&'static str" if attr.value_type == "string" else attr.value_type | map_text("rust_type") }} = match value { +{% for member in attr.type.members %} + {{ attr.name }}::{{ member.name }} => {{ member.literal }}{% if attr.value_type == "double" %}_f64{% endif %}, +{% endfor %} + }; + ::opentelemetry::KeyValue::new({{ attr.name }}::KEY, value) +{% elif "[]" in attr.value_type %} + ::opentelemetry::KeyValue::new({{ attr.name }}::KEY, ::opentelemetry::Value::Array(::opentelemetry::Array::{{ attr.value_type | map_text("rust_array") }}(value.0))) +{% else %} + ::opentelemetry::KeyValue::new({{ attr.name }}::KEY, value.0) +{% endif %} + } +} + +#[allow(deprecated)] +impl From<{{ attr.name }}> for ::opentelemetry::logs::AnyValue { +{% if attr.type is not mapping or attr.type.members | length <= 8 %} + #[inline] +{% endif %} + fn from(value: {{ attr.name }}) -> Self { +{% if attr.type is mapping %} + let value: {{ "&'static str" if attr.value_type == "string" else attr.value_type | map_text("rust_type") }} = match value { +{% for member in attr.type.members %} + {{ attr.name }}::{{ member.name }} => {{ member.literal }}{% if attr.value_type == "double" %}_f64{% endif %}, +{% endfor %} + }; + value.into() +{% elif "[]" in attr.value_type %} + value.0.into_iter().collect::<::opentelemetry::logs::AnyValue>() +{% else %} + value.0.into() +{% endif %} + } +} + +{% endfor %} +{% endfilter -%}{{ "\n" -}} diff --git a/templates/code/rust/entities.rs.j2 b/templates/code/rust/entities.rs.j2 new file mode 100644 index 0000000..7483a33 --- /dev/null +++ b/templates/code/rust/entities.rs.j2 @@ -0,0 +1,85 @@ +{%- filter trim %} +{% import "signal_helpers.j2" as s -%} +{% macro borrowed(a, expression) -%} +{%- set kind = a.attr_type if a.imported else a.type -%} +{{ expression }}{% if kind is not mapping and kind not in ["int", "double", "boolean"] %}.clone(){% endif %} +{%- endmacro %} +{% filter regex_replace("(?m)(^[ \\t]*///[^\\n]*\\n)([ \\t]*\\n)+", "${1}") %} +// Code generated by weaver. DO NOT EDIT. +#![allow(deprecated)] +{% for def in ctx.entities %} +{%- set name = s.exported(def.short_name) ~ "Entity" %} +{% if def.optional %} +#[derive(Clone, Debug)] +pub enum {{ name }}Attr { +{% for opt in def.optional %} +{% if opt.requirement_doc %} + {{ opt.requirement_doc | comment | indent(4) }} +{% endif %} + {{ s.exported(opt.attr_short_name) }}({{ s.attr_type(opt) }}), +{% endfor %} +} + +impl From<{{ name }}Attr> for ::opentelemetry::KeyValue { + fn from(value: {{ name }}Attr) -> Self { + match value { +{% for opt in def.optional %} + {{ name }}Attr::{{ s.exported(opt.attr_short_name) }}(value) => {{ s.attr_kv(opt, "value") }}, +{% endfor %} + } + } +} +{% endif %} +{{ s.docs(def) }} +{{ def.identity_doc | comment if def.identity_doc else '' }} +{{ def.description_doc | comment if def.description_doc else '' }} +{% if def.refines %}{{ ("Refines `" ~ def.refines ~ "`.") | comment }}{% endif %} +{{ s.deprecated(def) }} +#[derive(Clone, Debug{% if not def.required %}, Default{% endif %})] +pub struct {{ name }} { +{% for req in def.required %} + {{ s.ident(req.attr_param) }}: {{ s.attr_type(req) }}, +{% endfor %} +{% if def.optional %} + options: ::smallvec::SmallVec<[{{ name }}Attr; {{ 8 if def.optional | length > 8 else def.optional | length }}]>, +{% endif %} +} + +impl {{ name }} { + pub fn new({% for req in def.required %}{{ s.ident(req.attr_param) }}: {{ s.attr_type(req) }}, {% endfor %}{% if def.optional %}options: impl IntoIterator{% endif %}) -> Self { + Self { +{% for req in def.required %} + {{ s.ident(req.attr_param) }}, +{% endfor %} +{% if def.optional %} + options: options.into_iter().collect(), +{% endif %} + } + } + + pub fn attributes(&self) -> impl Iterator + '_ { + ::std::iter::empty() +{% for req in def.required %} + .chain(::std::iter::once_with(move || {{ s.attr_kv(req, borrowed(req, "self." ~ s.ident(req.attr_param))) }})) +{% endfor %} +{% if def.optional %} + .chain(self.options.iter().cloned().map(::opentelemetry::KeyValue::from)) +{% endif %} + } +} + +impl From<{{ name }}> for ::opentelemetry_sdk::Resource { + fn from({{ "value" if def.required or def.optional else "_value" }}: {{ name }}) -> Self { + let attributes = [ +{% for req in def.required %} + {{ s.attr_kv(req, "value." ~ s.ident(req.attr_param)) }}, +{% endfor %} + ].into_iter(){% if def.optional %}.chain(value.options.into_iter().map(::opentelemetry::KeyValue::from)){% endif %}; + ::opentelemetry_sdk::Resource::builder_empty() + .with_schema_url(attributes, super::SCHEMA_URL) + .build() + } +} +{% endfor %} +{% endfilter %} +{% endfilter -%}{{ "\n" -}} diff --git a/templates/code/rust/events.rs.j2 b/templates/code/rust/events.rs.j2 new file mode 100644 index 0000000..2af44aa --- /dev/null +++ b/templates/code/rust/events.rs.j2 @@ -0,0 +1,62 @@ +{%- filter trim %} +#![allow(deprecated)] +use ::opentelemetry::logs::LogRecord; +use ::opentelemetry::trace::TraceContextExt; + +{% import "signal_helpers.j2" as h %} +{% for def in ctx.events %} +{% set name = h.exported(def.short_name) or 'Event' %} +#[derive(Clone, Debug)] +pub enum {{ name }}Option { + Severity(::opentelemetry::logs::Severity), + Timestamp(::std::time::SystemTime), +{% for opt in def.optional %} +{% if opt.requirement_doc %} + {{ opt.requirement_doc | comment | indent(4) }} +{% endif %} + {{ h.exported(opt.attr_short_name) }}({{ h.attr_type(opt) }}), +{% endfor %} +} +{{ (h.docs(def) ~ "\n" ~ h.deprecated(def)) | trim }} +pub fn {{ h.signal_fn('emit', def.short_name) }}(context: &::opentelemetry::Context, logger: &super::logger::Logger{% for req in def.required %}, {{ h.ident(req.attr_param) }}: {{ h.attr_type(req) }}{% endfor %}, options: impl IntoIterator) { + let mut severity = ::opentelemetry::logs::Severity::Info; + let mut timestamp = None; +{% if def.optional %} + let mut attributes = ::smallvec::SmallVec::<[{{ name }}Option; {{ [def.optional | length, 8] | min }}]>::new(); +{% endif %} + for option in options { + match option { + {{ name }}Option::Severity(value) => severity = value, + {{ name }}Option::Timestamp(value) => timestamp = Some(value), +{% if def.optional %} + attribute => attributes.push(attribute), +{% endif %} + } + } + if !logger.inner().event_enabled(severity, "", Some({{ h.literal(def.name) }})) { return; } + let _context_guard = context.clone().attach(); + let mut record = logger.inner().create_log_record(); + record.set_event_name({{ h.literal(def.name) }}); + record.set_severity_number(severity); + record.set_observed_timestamp(::std::time::SystemTime::now()); + if let Some(timestamp) = timestamp { record.set_timestamp(timestamp); } + let span = context.span(); + let span_context = span.span_context(); + if span_context.is_valid() { record.set_trace_context(span_context.trace_id(), span_context.span_id(), Some(span_context.trace_flags())); } +{% for req in def.required %} + {{ h.attr_log("record", req, h.ident(req.attr_param)) }}; +{% endfor %} +{% if def.optional %} + for option in attributes { + match option { +{% for opt in def.optional %} + {{ name }}Option::{{ h.exported(opt.attr_short_name) }}(value) => {{ h.attr_log('record', opt, 'value') }}, +{% endfor %} + {{ name }}Option::Severity(_) | {{ name }}Option::Timestamp(_) => continue, + } + } +{% endif %} + logger.inner().emit(record); +} +{% endfor %} +{% endfilter -%}{{ "\n" -}} diff --git a/templates/code/rust/logger.rs.j2 b/templates/code/rust/logger.rs.j2 new file mode 100644 index 0000000..03e53a2 --- /dev/null +++ b/templates/code/rust/logger.rs.j2 @@ -0,0 +1,18 @@ +{%- filter trim %} +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Logger::Logger>(L); + +impl Logger { + pub fn new>(provider: &P, options: InstrumentationOptions) -> Self { + let logger = provider.logger_with_scope(options.scope.into()); + Self(logger) + } + pub fn inner(&self) -> &L { &self.0 } +} + +impl Default for Logger { + fn default() -> Self { Self::new(&opentelemetry::logs::NoopLoggerProvider::new(), InstrumentationOptions::default()) } +} +{% endfilter -%}{{ "\n" -}} diff --git a/templates/code/rust/meter.rs.j2 b/templates/code/rust/meter.rs.j2 new file mode 100644 index 0000000..4b4c5fa --- /dev/null +++ b/templates/code/rust/meter.rs.j2 @@ -0,0 +1,19 @@ +{%- filter trim %} +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Meter(opentelemetry::metrics::Meter); + +impl Meter { + pub fn new(provider: &impl opentelemetry::metrics::MeterProvider, options: InstrumentationOptions) -> Self { + let meter = provider.meter_with_scope(options.scope.into()); + Self(meter) + } + pub fn inner(&self) -> &opentelemetry::metrics::Meter { &self.0 } +} + +impl Default for Meter { + fn default() -> Self { Self::new(&opentelemetry::metrics::noop::NoopMeterProvider::new(), InstrumentationOptions::default()) } +} + +{% endfilter -%}{{ "\n" -}} diff --git a/templates/code/rust/metrics.rs.j2 b/templates/code/rust/metrics.rs.j2 new file mode 100644 index 0000000..42535b9 --- /dev/null +++ b/templates/code/rust/metrics.rs.j2 @@ -0,0 +1,107 @@ +{%- filter trim %} +#![allow(deprecated)] +{% import "signal_helpers.j2" as h %} +{% for def in ctx.metrics %} +{% set suffix = def.instrument | map_text("metric_instrument") %} +{% set name = h.exported(def.short_name) ~ suffix %} +{% set method = 'add' if def.instrument in ['counter','updowncounter'] else 'record' %} +{% set value_type = 'f64' if def.annotations.code_generation.metric_value_type == 'double' else ('i64' if def.instrument in ['updowncounter','gauge'] else 'u64') %} +{% set builder = value_type ~ '_' ~ ('up_down_counter' if def.instrument == 'updowncounter' else def.instrument) %} +{% if def.optional %} +#[derive(Clone, Debug)] +pub enum {{ name }}Attr { +{% for opt in def.optional %} +{% if opt.requirement_doc %} + {{ opt.requirement_doc | comment | indent(4) }} +{% endif %} + {{ h.exported(opt.attr_short_name) }}({{ h.attr_type(opt) }}), +{% endfor %} +} + +impl From<{{ name }}Attr> for ::opentelemetry::KeyValue { + fn from(value: {{ name }}Attr) -> Self { + match value { +{% for opt in def.optional %} + {{ name }}Attr::{{ h.exported(opt.attr_short_name) }}(value) => {{ h.attr_kv(opt, 'value') }}, +{% endfor %} + } + } +} +{% endif %} +{{ (h.docs(def) ~ "\n" ~ h.deprecated(def)) | trim }} +#[derive(Clone)] +pub struct {{ name }} { + instrument: ::opentelemetry::metrics::{{ suffix }}<{{ value_type }}>, +} + +impl {{ name }} { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().{{ builder }}({{ h.literal(def.instrument_name) }}) + .with_description({{ h.literal(def.description | trim) }}) + .with_unit({{ h.literal(def.unit) }}) +{% if def.bucket_boundaries %} + .with_boundaries(vec![{% for bound in def.bucket_boundaries %}{{ bound }}_f64{% if not loop.last %}, {% endif %}{% endfor %}]) +{% endif %} + .build(); + Self { instrument } + } + pub fn {{ method }}(&self, value: {{ value_type }}{% for req in def.required %}, {{ h.ident(req.attr_param) }}: {{ h.attr_type(req) }}{% endfor %}{% if def.optional %}, options: impl IntoIterator{% endif %}) { +{% if def.optional %} + let mut attributes = ::smallvec::SmallVec::<[::opentelemetry::KeyValue; {{ def.required | length + ([def.optional | length, 8] | min) }}]>::new(); +{% if def.required %} + attributes.extend([{% for req in def.required %}{{ h.attr_kv(req, h.ident(req.attr_param)) }}{% if not loop.last %}, {% endif %}{% endfor %}]); +{% endif %} + attributes.extend(options.into_iter().map(::opentelemetry::KeyValue::from)); +{% else %} + let attributes = [{% for req in def.required %}{{ h.attr_kv(req, h.ident(req.attr_param)) }}{% if not loop.last %}, {% endif %}{% endfor %}]; +{% endif %} + self.instrument.{{ method }}(value, &attributes); + } +} + +impl Default for {{ name }} { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} +{% if def.instrument != 'histogram' %} +{% set observable = h.exported(def.short_name) ~ 'Observable' ~ suffix %} +pub struct {{ name }}Observer<'a> { + observer: &'a dyn ::opentelemetry::metrics::AsyncInstrument<{{ value_type }}>, +} + +impl {{ name }}Observer<'_> { + pub fn observe(&self, value: {{ value_type }}{% for req in def.required %}, {{ h.ident(req.attr_param) }}: {{ h.attr_type(req) }}{% endfor %}{% if def.optional %}, options: impl IntoIterator{% endif %}) { +{% if def.optional %} + let mut attributes = ::smallvec::SmallVec::<[::opentelemetry::KeyValue; {{ def.required | length + ([def.optional | length, 8] | min) }}]>::new(); +{% if def.required %} + attributes.extend([{% for req in def.required %}{{ h.attr_kv(req, h.ident(req.attr_param)) }}{% if not loop.last %}, {% endif %}{% endfor %}]); +{% endif %} + attributes.extend(options.into_iter().map(::opentelemetry::KeyValue::from)); +{% else %} + let attributes = [{% for req in def.required %}{{ h.attr_kv(req, h.ident(req.attr_param)) }}{% if not loop.last %}, {% endif %}{% endfor %}]; +{% endif %} + self.observer.observe(value, &attributes); + } +} +{{ h.deprecated(def) }} +#[derive(Clone)] +pub struct {{ observable }} { + _instrument: ::opentelemetry::metrics::Observable{{ suffix }}<{{ value_type }}>, +} + +impl {{ observable }} { + pub fn new(meter: &super::meter::Meter, callback: impl Fn({{ name }}Observer<'_>) + Send + Sync + 'static) -> Self { + let instrument = meter.inner().{{ value_type }}_observable_{{ 'up_down_counter' if def.instrument == 'updowncounter' else def.instrument }}({{ h.literal(def.instrument_name) }}) + .with_description({{ h.literal(def.description | trim) }}) + .with_unit({{ h.literal(def.unit) }}) + .with_callback(move |observer| callback({{ name }}Observer { observer })) + .build(); + Self { _instrument: instrument } + } +} + +impl Default for {{ observable }} { + fn default() -> Self { Self::new(&super::meter::Meter::default(), |_| {}) } +} +{% endif %} +{% endfor %} +{% endfilter -%}{{ "\n" -}} diff --git a/templates/code/rust/mod.rs.j2 b/templates/code/rust/mod.rs.j2 new file mode 100644 index 0000000..229f253 --- /dev/null +++ b/templates/code/rust/mod.rs.j2 @@ -0,0 +1,31 @@ +{%- filter trim %} +{% if params.module_description %}{{ params.module_description | comment | regex_replace("///", "//!") }} +{% endif %}// Generated by OpenTelemetry Weaver. +{% if params.header_source -%} +// Source: {{ params.header_source | replace("\n", " ") | replace("\r", " ") }} +{% endif %} +{% if params.regenerate_command -%} +// Regenerate: {{ params.regenerate_command | replace("\n", " ") | replace("\r", " ") }} +{% endif %} +pub const SCHEMA_URL: &str = {{ ctx.schema_literal }}; +{% for namespace in ctx.namespaces %} +pub mod {{ namespace }}; +{% endfor %} + +{% if ctx.has_metrics or ctx.has_spans or ctx.has_events %} +pub mod scope; +pub mod options; +{% endif %} +{% if ctx.has_metrics %} +pub mod meter; +{% endif %} +{% if ctx.has_spans %} +pub mod tracer; +{% endif %} +{% if ctx.has_events %} +pub mod logger; +{% endif %} +{% for module in ctx.signal_modules %} +pub mod {{ module }}; +{% endfor %} +{% endfilter -%}{{ "\n" -}} diff --git a/templates/code/rust/options.rs.j2 b/templates/code/rust/options.rs.j2 new file mode 100644 index 0000000..b0e6496 --- /dev/null +++ b/templates/code/rust/options.rs.j2 @@ -0,0 +1,8 @@ +{%- filter trim %} +use super::scope::Scope; + +#[derive(Clone, Debug, Default)] +pub struct InstrumentationOptions { + pub scope: Scope, +} +{% endfilter -%}{{ "\n" -}} diff --git a/templates/code/rust/scope.rs.j2 b/templates/code/rust/scope.rs.j2 new file mode 100644 index 0000000..3979c4a --- /dev/null +++ b/templates/code/rust/scope.rs.j2 @@ -0,0 +1,19 @@ +{%- filter trim %} +{% import "signal_helpers.j2" as h %} +#[derive(Clone, Debug, Default)] +pub struct Scope { + pub attributes: Vec<::opentelemetry::KeyValue>, +} + +impl From for ::opentelemetry::InstrumentationScope { + fn from(value: Scope) -> Self { + Self::builder({{ h.literal(params.instrumentation_name) }}) +{% if params.instrumentation_version %} + .with_version({{ h.literal(params.instrumentation_version) }}) +{% endif %} + .with_schema_url(super::SCHEMA_URL) + .with_attributes(value.attributes) + .build() + } +} +{% endfilter -%}{{ "\n" -}} diff --git a/templates/code/rust/signal_helpers.j2 b/templates/code/rust/signal_helpers.j2 new file mode 100644 index 0000000..90ef6e9 --- /dev/null +++ b/templates/code/rust/signal_helpers.j2 @@ -0,0 +1,28 @@ +{% macro literal(value) -%} +"{%- for char in value -%}{{ (char | tojson | replace("\\b", "\\u{0008}") | replace("\\f", "\\u{000c}") | regex_replace("\\\\u([0-9A-Fa-f]{4})", "\\u{${1}}"))[1:-1] }}{%- endfor -%}" +{%- endmacro %} +{% macro exported(value) -%}{% for dotted in value.split(".") %}{% for segment in dotted.split("_") %}{{ segment[:1] | upper }}{{ segment[1:] }}{% endfor %}{% endfor %}{%- endmacro %} +{% macro ident(value) -%}r#{{ value | snake_case }}{%- endmacro %} +{% macro signal_fn(prefix, short_name) -%}{{ ident(prefix ~ ('.' ~ short_name if short_name else '')) }}{%- endmacro %} +{% macro attr_type(a) -%} +{%- if a.imported -%}{{ "::opentelemetry::StringValue" if a.attr_type == "string" else ("Vec<::opentelemetry::StringValue>" if a.attr_type == "string[]" else a.attr_type | map_text("rust_type")) }} +{%- else -%}{{ a.attr_import_path if a.attr_import_path else 'super::' ~ a.attr_namespace ~ 'attr' }}::{{ exported(a.attr_short_name) }}Attr +{%- endif -%} +{%- endmacro %} +{% macro attr_kv(a, value) -%} +{%- if a.imported -%}::opentelemetry::KeyValue::new({{ literal(a.key) }}, {% if "[]" in a.attr_type %}::opentelemetry::Value::Array(::opentelemetry::Array::{{ a.attr_type | map_text("rust_array") }}({{ value }})){% else %}{{ value }}{% endif %}) +{%- else -%}::opentelemetry::KeyValue::from({{ value }}) +{%- endif -%} +{%- endmacro %} +{% macro attr_log(record, a, value) -%} +{{ record }}.add_attribute({% if a.imported %}{{ literal(a.key) }}{% else %}{{ attr_type(a) }}::KEY{% endif %}, {% if a.imported and '[]' in a.attr_type %}{{ value }}.into_iter().collect::<::opentelemetry::logs::AnyValue>(){% else %}{{ value }}{% endif %}) +{%- endmacro %} +{% macro deprecated(def) -%} +{%- if def.deprecated -%}#[deprecated(note = {{ literal((def.deprecated.note if def.deprecated.note else 'Deprecated by the semantic convention registry')) }})] +{%- endif -%} +{%- endmacro %} +{% macro docs(def) -%} +{{ ([def.brief, def.note] | select | join('\n\n')) | comment | trim }} +{%- if def.requirement_level == 'opt_in' %}{{ "\n" }}/// Opt-in: record only when the user requests it. +{%- endif -%} +{%- endmacro %} diff --git a/templates/code/rust/spans.rs.j2 b/templates/code/rust/spans.rs.j2 new file mode 100644 index 0000000..9a11638 --- /dev/null +++ b/templates/code/rust/spans.rs.j2 @@ -0,0 +1,155 @@ +{%- filter trim %} +#![allow(deprecated)] +use ::opentelemetry::trace::{TraceContextExt, Tracer}; +{% macro needs_name_formatting(definitions) -%} +{%- for definition in definitions -%} +{%- for part in definition.name_parts or [] -%} +{%- if part.attr -%}yes{%- endif -%} +{%- endfor -%} +{%- endfor -%} +{%- endmacro %} +{% if needs_name_formatting(ctx.spans) %} +use ::std::fmt::Write; +{% endif %} + +{% import "signal_helpers.j2" as h %} +{# Floating-point names need space for full decimal notation, including subnormals. #} +{% macro name_capacity(a, value) -%} +{% set t = a.attr_type if a.imported else a.type %} +{%- if t == 'string' or (t is mapping and t.members[0].value is string) -%} +{% if a.imported %}{{ value }}.as_str().len(){% else %}::std::convert::AsRef::::as_ref(&{{ value }}).len(){% endif %} +{%- elif t in ['string[]', 'int[]', 'double[]', 'boolean[]'] -%} +{%- set element = '::opentelemetry::StringValue' if t == 'string[]' else ('i64' if t == 'int[]' else ('f64' if t == 'double[]' else 'bool')) -%} +{%- set values = value if a.imported else '::std::convert::AsRef::<[' ~ element ~ ']>::as_ref(&' ~ value ~ ')' -%} +{%- if t == 'string[]' -%} +{{ values }}.iter().map(|value| value.as_str().len() + 3).sum::() + 2 +{%- else -%} +{{ values }}.len() * {{ 328 if t == 'double[]' else (21 if t == 'int[]' else 6) }} + 2 +{%- endif -%} +{%- elif t == 'int' -%}20 +{%- elif t == 'double' or t is mapping and t.members[0].value is number -%}328 +{%- else -%}5 +{%- endif -%} +{%- endmacro %} +{% for def in ctx.spans %} +{% set name = h.exported(def.short_name) %} +{% set span = name ~ 'Span' %} +{% if def.optional %} +#[derive(Clone, Debug)] +pub enum {{ name }}StartAttr { +{% for opt in def.optional %} +{% if opt.requirement_doc %} + {{ opt.requirement_doc | comment | indent(4) }} +{% endif %} + {{ h.exported(opt.attr_short_name) }}({{ h.attr_type(opt) }}), +{% endfor %} +} + +impl From<{{ name }}StartAttr> for ::opentelemetry::KeyValue { + fn from(value: {{ name }}StartAttr) -> Self { + match value { +{% for opt in def.optional %} + {{ name }}StartAttr::{{ h.exported(opt.attr_short_name) }}(value) => {{ h.attr_kv(opt, 'value') }}, +{% endfor %} + } + } +} +{% endif %} +{% if def.has_late_attrs %} +#[derive(Clone, Debug)] +pub enum {{ name }}Attr { +{% for opt in def.optional if not opt.sampling_relevant %} + {{ h.exported(opt.attr_short_name) }}({{ h.attr_type(opt) }}), +{% endfor %} +} + +impl From<{{ name }}Attr> for {{ name }}StartAttr { + fn from(value: {{ name }}Attr) -> Self { + match value { +{% for opt in def.optional if not opt.sampling_relevant %} + {{ name }}Attr::{{ h.exported(opt.attr_short_name) }}(value) => Self::{{ h.exported(opt.attr_short_name) }}(value), +{% endfor %} + } + } +} +{% endif %} +{% if not def.name_parts %} +#[derive(Clone, Debug, Default)] +pub struct {{ name }}Name(::std::borrow::Cow<'static, str>); + +impl From<&'static str> for {{ name }}Name { + fn from(value: &'static str) -> Self { Self(::std::borrow::Cow::Borrowed(value)) } +} + +impl From for {{ name }}Name { + fn from(value: String) -> Self { Self(::std::borrow::Cow::Owned(value)) } +} + +impl From<::std::borrow::Cow<'static, str>> for {{ name }}Name { + fn from(value: ::std::borrow::Cow<'static, str>) -> Self { Self(value) } +} +{% endif %} +{{ (h.docs(def) ~ "\n" ~ h.deprecated(def)) | trim }} +pub struct {{ span }} { + span: S, +} + +impl {{ span }} { + pub fn into_context(self, parent: &::opentelemetry::Context) -> ::opentelemetry::Context + where S: Send + Sync + 'static, + { + parent.with_span(self.span) + } + pub fn span(&mut self) -> &mut S { &mut self.span } + pub fn end(&mut self) { ::opentelemetry::trace::Span::end(&mut self.span); } + pub fn end_with_timestamp(&mut self, timestamp: ::std::time::SystemTime) { ::opentelemetry::trace::Span::end_with_timestamp(&mut self.span, timestamp); } + pub fn record_error(&mut self, error: &dyn ::std::error::Error) { ::opentelemetry::trace::Span::record_error(&mut self.span, error); } + pub fn set_status(&mut self, status: ::opentelemetry::trace::Status) { ::opentelemetry::trace::Span::set_status(&mut self.span, status); } +{% if def.has_late_attrs %} + pub fn set_attributes(&mut self, attributes: impl IntoIterator) { + for attribute in attributes { ::opentelemetry::trace::Span::set_attribute(&mut self.span, ::opentelemetry::KeyValue::from({{ name }}StartAttr::from(attribute))); } + } +{% endif %} +} + +impl Default for {{ span }} { + fn default() -> Self { + Self { span: super::tracer::Tracer::default().inner().start({{ h.literal(def.type) }}) } + } +} +{{ h.deprecated(def) }} +pub fn {{ h.signal_fn('start', def.short_name) }}(context: &::opentelemetry::Context, tracer: &super::tracer::Tracer{% if not def.name_parts %}, name: {{ name }}Name{% endif %}{% for req in def.required %}, {{ h.ident(req.attr_param) }}: {{ h.attr_type(req) }}{% endfor %}{% if def.optional %}, options: impl IntoIterator{% endif %}) -> {{ span }} { +{% if def.name_parts %} + let mut name = String::with_capacity({% for part in def.name_parts %}{% if not loop.first %} + {% endif %}{% if part.attr %}{{ name_capacity(part.attr, h.ident(part.attr.attr_param)) }}{% else %}{{ h.literal(part.text) }}.len(){% endif %}{% endfor %}); +{% for part in def.name_parts %} +{% if part.attr %} +{% if part.attr.imported and '[]' in part.attr.attr_type %} + name.push('['); + for (index, value) in {{ h.ident(part.attr.attr_param) }}.iter().enumerate() { + if index > 0 { name.push(','); } + write!(&mut name, {% if part.attr.attr_type == 'string[]' %}"\"{}\""{% else %}"{}"{% endif %}, value).expect("writing a span name to String cannot fail"); + } + name.push(']'); +{% else %} + write!(&mut name, "{}", {{ h.ident(part.attr.attr_param) }}).expect("writing a span name to String cannot fail"); +{% endif %} +{% else %} +{% if part.text | length == 1 %} + name.push('{{ h.literal(part.text)[1:-1] | replace("'", "\\'") }}'); +{% else %} + name.push_str({{ h.literal(part.text) }}); +{% endif %} +{% endif %} +{% endfor %} +{% else %} + let name = if name.0.is_empty() { ::std::borrow::Cow::Borrowed({{ h.literal(def.type) }}) } else { name.0 }; +{% endif %} + let attributes: [::opentelemetry::KeyValue; {{ def.required | length }}] = [{% for req in def.required %}{{ h.attr_kv(req, h.ident(req.attr_param)) }}{% if not loop.last %}, {% endif %}{% endfor %}]; + let span = tracer.inner().span_builder(name) + .with_kind(::opentelemetry::trace::SpanKind::{{ def.kind | pascal_case }}) + .with_attributes(attributes{% if def.optional %}.into_iter().chain(options.into_iter().map(::opentelemetry::KeyValue::from)){% endif %}) + .start_with_context(tracer.inner(), context); + {{ span }} { span } +} +{% endfor %} +{% endfilter -%}{{ "\n" -}} diff --git a/templates/code/rust/tests/allocations/compile-fail/scope_name.expected-error.txt b/templates/code/rust/tests/allocations/compile-fail/scope_name.expected-error.txt new file mode 100644 index 0000000..9108842 --- /dev/null +++ b/templates/code/rust/tests/allocations/compile-fail/scope_name.expected-error.txt @@ -0,0 +1 @@ +has no field named `name` diff --git a/templates/code/rust/tests/allocations/compile-fail/scope_name.rs b/templates/code/rust/tests/allocations/compile-fail/scope_name.rs new file mode 100644 index 0000000..7179456 --- /dev/null +++ b/templates/code/rust/tests/allocations/compile-fail/scope_name.rs @@ -0,0 +1,10 @@ +use generated_semconv::{options::InstrumentationOptions, scope::Scope}; + +fn main() { + let _ = InstrumentationOptions { + scope: Scope { + name: "caller-identity".into(), + ..Scope::default() + }, + }; +} diff --git a/templates/code/rust/tests/allocations/compile-fail/scope_schema.expected-error.txt b/templates/code/rust/tests/allocations/compile-fail/scope_schema.expected-error.txt new file mode 100644 index 0000000..fd96262 --- /dev/null +++ b/templates/code/rust/tests/allocations/compile-fail/scope_schema.expected-error.txt @@ -0,0 +1 @@ +has no field named `schema_url` diff --git a/templates/code/rust/tests/allocations/compile-fail/scope_schema.rs b/templates/code/rust/tests/allocations/compile-fail/scope_schema.rs new file mode 100644 index 0000000..0bc8d56 --- /dev/null +++ b/templates/code/rust/tests/allocations/compile-fail/scope_schema.rs @@ -0,0 +1,10 @@ +use generated_semconv::{options::InstrumentationOptions, scope::Scope}; + +fn main() { + let _ = InstrumentationOptions { + scope: Scope { + schema_url: Some("https://example.com/caller/1.0.0".into()), + ..Scope::default() + }, + }; +} diff --git a/templates/code/rust/tests/allocations/compile-fail/scope_version.expected-error.txt b/templates/code/rust/tests/allocations/compile-fail/scope_version.expected-error.txt new file mode 100644 index 0000000..d66263f --- /dev/null +++ b/templates/code/rust/tests/allocations/compile-fail/scope_version.expected-error.txt @@ -0,0 +1 @@ +has no field named `version` diff --git a/templates/code/rust/tests/allocations/compile-fail/scope_version.rs b/templates/code/rust/tests/allocations/compile-fail/scope_version.rs new file mode 100644 index 0000000..379b683 --- /dev/null +++ b/templates/code/rust/tests/allocations/compile-fail/scope_version.rs @@ -0,0 +1,10 @@ +use generated_semconv::{options::InstrumentationOptions, scope::Scope}; + +fn main() { + let _ = InstrumentationOptions { + scope: Scope { + version: Some("1.2.3".into()), + ..Scope::default() + }, + }; +} diff --git a/templates/code/rust/tests/allocations/compile-fail/sdk_scope.expected-error.txt b/templates/code/rust/tests/allocations/compile-fail/sdk_scope.expected-error.txt new file mode 100644 index 0000000..545f57b --- /dev/null +++ b/templates/code/rust/tests/allocations/compile-fail/sdk_scope.expected-error.txt @@ -0,0 +1 @@ +expected `Scope`, found `InstrumentationScope` diff --git a/templates/code/rust/tests/allocations/compile-fail/sdk_scope.rs b/templates/code/rust/tests/allocations/compile-fail/sdk_scope.rs new file mode 100644 index 0000000..925bfe1 --- /dev/null +++ b/templates/code/rust/tests/allocations/compile-fail/sdk_scope.rs @@ -0,0 +1,9 @@ +use generated_semconv::options::InstrumentationOptions; + +fn main() { + let _ = InstrumentationOptions { + scope: opentelemetry::InstrumentationScope::builder("caller-identity") + .with_version("1.2.3") + .build(), + }; +} diff --git a/templates/code/rust/tests/allocations/expected/logger.rs b/templates/code/rust/tests/allocations/expected/logger.rs new file mode 100644 index 0000000..78bcc02 --- /dev/null +++ b/templates/code/rust/tests/allocations/expected/logger.rs @@ -0,0 +1,16 @@ +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Logger::Logger>(L); + +impl Logger { + pub fn new>(provider: &P, options: InstrumentationOptions) -> Self { + let logger = provider.logger_with_scope(options.scope.into()); + Self(logger) + } + pub fn inner(&self) -> &L { &self.0 } +} + +impl Default for Logger { + fn default() -> Self { Self::new(&opentelemetry::logs::NoopLoggerProvider::new(), InstrumentationOptions::default()) } +} diff --git a/templates/code/rust/tests/allocations/expected/meter.rs b/templates/code/rust/tests/allocations/expected/meter.rs new file mode 100644 index 0000000..a330bd2 --- /dev/null +++ b/templates/code/rust/tests/allocations/expected/meter.rs @@ -0,0 +1,16 @@ +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Meter(opentelemetry::metrics::Meter); + +impl Meter { + pub fn new(provider: &impl opentelemetry::metrics::MeterProvider, options: InstrumentationOptions) -> Self { + let meter = provider.meter_with_scope(options.scope.into()); + Self(meter) + } + pub fn inner(&self) -> &opentelemetry::metrics::Meter { &self.0 } +} + +impl Default for Meter { + fn default() -> Self { Self::new(&opentelemetry::metrics::noop::NoopMeterProvider::new(), InstrumentationOptions::default()) } +} diff --git a/templates/code/rust/tests/allocations/expected/mod.rs b/templates/code/rust/tests/allocations/expected/mod.rs new file mode 100644 index 0000000..afe3186 --- /dev/null +++ b/templates/code/rust/tests/allocations/expected/mod.rs @@ -0,0 +1,10 @@ +// Generated by OpenTelemetry Weaver. +pub const SCHEMA_URL: &str = "https://example.com/schemas/allocations/1.0.0"; +pub mod probeattr; + +pub mod scope; +pub mod options; +pub mod meter; +pub mod logger; +pub mod probeevent; +pub mod probemetric; diff --git a/templates/code/rust/tests/allocations/expected/options.rs b/templates/code/rust/tests/allocations/expected/options.rs new file mode 100644 index 0000000..69a821a --- /dev/null +++ b/templates/code/rust/tests/allocations/expected/options.rs @@ -0,0 +1,6 @@ +use super::scope::Scope; + +#[derive(Clone, Debug, Default)] +pub struct InstrumentationOptions { + pub scope: Scope, +} diff --git a/templates/code/rust/tests/allocations/expected/probeattr.rs b/templates/code/rust/tests/allocations/expected/probeattr.rs new file mode 100644 index 0000000..7028f94 --- /dev/null +++ b/templates/code/rust/tests/allocations/expected/probeattr.rs @@ -0,0 +1,284 @@ +// Generated by OpenTelemetry Weaver. +/// A synthetic boolean value. +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct FlagAttr(bool); + +#[allow(deprecated)] +impl FlagAttr { + pub const KEY: &'static str = "probe.flag"; +} + +#[allow(deprecated)] +impl From for FlagAttr { + #[inline] + fn from(value: bool) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for FlagAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::std::fmt::Display::fmt(&self.0, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: FlagAttr) -> Self { + ::opentelemetry::KeyValue::new(FlagAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: FlagAttr) -> Self { + value.0.into() + } +} + +/// A synthetic fractional value. +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct FractionAttr(f64); + +#[allow(deprecated)] +impl FractionAttr { + pub const KEY: &'static str = "probe.fraction"; +} + +#[allow(deprecated)] +impl From for FractionAttr { + #[inline] + fn from(value: f64) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for FractionAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::std::fmt::Display::fmt(&self.0, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: FractionAttr) -> Self { + ::opentelemetry::KeyValue::new(FractionAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: FractionAttr) -> Self { + value.0.into() + } +} + +/// A synthetic text value. +#[derive(Debug, Clone, PartialEq)] +pub struct LabelAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl LabelAttr { + pub const KEY: &'static str = "probe.label"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for LabelAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for LabelAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for LabelAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for LabelAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for LabelAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: LabelAttr) -> Self { + ::opentelemetry::KeyValue::new(LabelAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: LabelAttr) -> Self { + value.0.into() + } +} + +/// Optional numeric measurement identity. +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct OptionalAttr(i64); + +#[allow(deprecated)] +impl OptionalAttr { + pub const KEY: &'static str = "probe.optional"; +} + +#[allow(deprecated)] +impl From for OptionalAttr { + #[inline] + fn from(value: i64) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for OptionalAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::std::fmt::Display::fmt(&self.0, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: OptionalAttr) -> Self { + ::opentelemetry::KeyValue::new(OptionalAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: OptionalAttr) -> Self { + value.0.into() + } +} + +/// Required numeric measurement identity. +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct RequiredAttr(i64); + +#[allow(deprecated)] +impl RequiredAttr { + pub const KEY: &'static str = "probe.required"; +} + +#[allow(deprecated)] +impl From for RequiredAttr { + #[inline] + fn from(value: i64) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for RequiredAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::std::fmt::Display::fmt(&self.0, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: RequiredAttr) -> Self { + ::opentelemetry::KeyValue::new(RequiredAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: RequiredAttr) -> Self { + value.0.into() + } +} + +/// Numeric values attached to an event. +#[derive(Debug, Clone, PartialEq)] +pub struct ValuesAttr(Vec); + +#[allow(deprecated)] +impl ValuesAttr { + pub const KEY: &'static str = "probe.values"; +} + +#[allow(deprecated)] +impl From> for ValuesAttr { + #[inline] + fn from(value: Vec) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl AsRef<[i64]> for ValuesAttr { + #[inline] + fn as_ref(&self) -> &[i64] { + &self.0 + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for ValuesAttr { + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str("[")?; + for (index, value) in self.0.iter().enumerate() { + if index != 0 { + formatter.write_str(",")?; + } + ::std::fmt::Display::fmt(value, formatter)?; + } + formatter.write_str("]") + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: ValuesAttr) -> Self { + ::opentelemetry::KeyValue::new(ValuesAttr::KEY, ::opentelemetry::Value::Array(::opentelemetry::Array::I64(value.0))) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: ValuesAttr) -> Self { + value.0.into_iter().collect::<::opentelemetry::logs::AnyValue>() + } +} diff --git a/templates/code/rust/tests/allocations/expected/probeevent.rs b/templates/code/rust/tests/allocations/expected/probeevent.rs new file mode 100644 index 0000000..5db4b38 --- /dev/null +++ b/templates/code/rust/tests/allocations/expected/probeevent.rs @@ -0,0 +1,43 @@ +#![allow(deprecated)] +use ::opentelemetry::logs::LogRecord; +use ::opentelemetry::trace::TraceContextExt; + +#[derive(Clone, Debug)] +pub enum RecordedOption { + Severity(::opentelemetry::logs::Severity), + Timestamp(::std::time::SystemTime), + Optional(super::probeattr::OptionalAttr), + Values(super::probeattr::ValuesAttr), +} +/// A measurement was recorded. +pub fn r#emit_recorded(context: &::opentelemetry::Context, logger: &super::logger::Logger, r#probe_required: super::probeattr::RequiredAttr, options: impl IntoIterator) { + let mut severity = ::opentelemetry::logs::Severity::Info; + let mut timestamp = None; + let mut attributes = ::smallvec::SmallVec::<[RecordedOption; 2]>::new(); + for option in options { + match option { + RecordedOption::Severity(value) => severity = value, + RecordedOption::Timestamp(value) => timestamp = Some(value), + attribute => attributes.push(attribute), + } + } + if !logger.inner().event_enabled(severity, "", Some("probe.recorded")) { return; } + let _context_guard = context.clone().attach(); + let mut record = logger.inner().create_log_record(); + record.set_event_name("probe.recorded"); + record.set_severity_number(severity); + record.set_observed_timestamp(::std::time::SystemTime::now()); + if let Some(timestamp) = timestamp { record.set_timestamp(timestamp); } + let span = context.span(); + let span_context = span.span_context(); + if span_context.is_valid() { record.set_trace_context(span_context.trace_id(), span_context.span_id(), Some(span_context.trace_flags())); } + record.add_attribute(super::probeattr::RequiredAttr::KEY, r#probe_required); + for option in attributes { + match option { + RecordedOption::Optional(value) => record.add_attribute(super::probeattr::OptionalAttr::KEY, value), + RecordedOption::Values(value) => record.add_attribute(super::probeattr::ValuesAttr::KEY, value), + RecordedOption::Severity(_) | RecordedOption::Timestamp(_) => continue, + } + } + logger.inner().emit(record); +} diff --git a/templates/code/rust/tests/allocations/expected/probemetric.rs b/templates/code/rust/tests/allocations/expected/probemetric.rs new file mode 100644 index 0000000..843bb72 --- /dev/null +++ b/templates/code/rust/tests/allocations/expected/probemetric.rs @@ -0,0 +1,123 @@ +#![allow(deprecated)] +#[derive(Clone, Debug)] +pub enum OptionalCounterAttr { + Optional(super::probeattr::OptionalAttr), +} + +impl From for ::opentelemetry::KeyValue { + fn from(value: OptionalCounterAttr) -> Self { + match value { + OptionalCounterAttr::Optional(value) => ::opentelemetry::KeyValue::from(value), + } + } +} +/// Measurements with optional numeric attributes. +#[derive(Clone)] +pub struct OptionalCounter { + instrument: ::opentelemetry::metrics::Counter, +} + +impl OptionalCounter { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().u64_counter("probe.optional") + .with_description("Measurements with optional numeric attributes.") + .with_unit("{measurement}") + .build(); + Self { instrument } + } + pub fn add(&self, value: u64, r#probe_required: super::probeattr::RequiredAttr, options: impl IntoIterator) { + let mut attributes = ::smallvec::SmallVec::<[::opentelemetry::KeyValue; 2]>::new(); + attributes.extend([::opentelemetry::KeyValue::from(r#probe_required)]); + attributes.extend(options.into_iter().map(::opentelemetry::KeyValue::from)); + self.instrument.add(value, &attributes); + } +} + +impl Default for OptionalCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} +pub struct OptionalCounterObserver<'a> { + observer: &'a dyn ::opentelemetry::metrics::AsyncInstrument, +} + +impl OptionalCounterObserver<'_> { + pub fn observe(&self, value: u64, r#probe_required: super::probeattr::RequiredAttr, options: impl IntoIterator) { + let mut attributes = ::smallvec::SmallVec::<[::opentelemetry::KeyValue; 2]>::new(); + attributes.extend([::opentelemetry::KeyValue::from(r#probe_required)]); + attributes.extend(options.into_iter().map(::opentelemetry::KeyValue::from)); + self.observer.observe(value, &attributes); + } +} + +#[derive(Clone)] +pub struct OptionalObservableCounter { + _instrument: ::opentelemetry::metrics::ObservableCounter, +} + +impl OptionalObservableCounter { + pub fn new(meter: &super::meter::Meter, callback: impl Fn(OptionalCounterObserver<'_>) + Send + Sync + 'static) -> Self { + let instrument = meter.inner().u64_observable_counter("probe.optional") + .with_description("Measurements with optional numeric attributes.") + .with_unit("{measurement}") + .with_callback(move |observer| callback(OptionalCounterObserver { observer })) + .build(); + Self { _instrument: instrument } + } +} + +impl Default for OptionalObservableCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default(), |_| {}) } +} +/// Measurements with a required numeric attribute. +#[derive(Clone)] +pub struct RequiredCounter { + instrument: ::opentelemetry::metrics::Counter, +} + +impl RequiredCounter { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().u64_counter("probe.required") + .with_description("Measurements with a required numeric attribute.") + .with_unit("{measurement}") + .build(); + Self { instrument } + } + pub fn add(&self, value: u64, r#probe_required: super::probeattr::RequiredAttr) { + let attributes = [::opentelemetry::KeyValue::from(r#probe_required)]; + self.instrument.add(value, &attributes); + } +} + +impl Default for RequiredCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} +pub struct RequiredCounterObserver<'a> { + observer: &'a dyn ::opentelemetry::metrics::AsyncInstrument, +} + +impl RequiredCounterObserver<'_> { + pub fn observe(&self, value: u64, r#probe_required: super::probeattr::RequiredAttr) { + let attributes = [::opentelemetry::KeyValue::from(r#probe_required)]; + self.observer.observe(value, &attributes); + } +} + +#[derive(Clone)] +pub struct RequiredObservableCounter { + _instrument: ::opentelemetry::metrics::ObservableCounter, +} + +impl RequiredObservableCounter { + pub fn new(meter: &super::meter::Meter, callback: impl Fn(RequiredCounterObserver<'_>) + Send + Sync + 'static) -> Self { + let instrument = meter.inner().u64_observable_counter("probe.required") + .with_description("Measurements with a required numeric attribute.") + .with_unit("{measurement}") + .with_callback(move |observer| callback(RequiredCounterObserver { observer })) + .build(); + Self { _instrument: instrument } + } +} + +impl Default for RequiredObservableCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default(), |_| {}) } +} diff --git a/templates/code/rust/tests/allocations/expected/scope.rs b/templates/code/rust/tests/allocations/expected/scope.rs new file mode 100644 index 0000000..6f66e86 --- /dev/null +++ b/templates/code/rust/tests/allocations/expected/scope.rs @@ -0,0 +1,13 @@ +#[derive(Clone, Debug, Default)] +pub struct Scope { + pub attributes: Vec<::opentelemetry::KeyValue>, +} + +impl From for ::opentelemetry::InstrumentationScope { + fn from(value: Scope) -> Self { + Self::builder("generated-semconv") + .with_schema_url(super::SCHEMA_URL) + .with_attributes(value.attributes) + .build() + } +} diff --git a/templates/code/rust/tests/allocations/registry/manifest.yaml b/templates/code/rust/tests/allocations/registry/manifest.yaml new file mode 100644 index 0000000..8eb25e8 --- /dev/null +++ b/templates/code/rust/tests/allocations/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: synthetic-allocation-fixture +schema_url: https://example.com/schemas/allocations/1.0.0 diff --git a/templates/code/rust/tests/allocations/registry/model.yaml b/templates/code/rust/tests/allocations/registry/model.yaml new file mode 100644 index 0000000..abb4ceb --- /dev/null +++ b/templates/code/rust/tests/allocations/registry/model.yaml @@ -0,0 +1,70 @@ +file_format: definition/2 +attributes: + - key: probe.flag + type: boolean + stability: stable + brief: A synthetic boolean value. + examples: true + - key: probe.fraction + type: double + stability: stable + brief: A synthetic fractional value. + examples: 0.5 + - key: probe.label + type: string + stability: stable + brief: A synthetic text value. + examples: ["label"] + - key: probe.required + type: int + stability: stable + brief: Required numeric measurement identity. + examples: 1 + - key: probe.optional + type: int + stability: stable + brief: Optional numeric measurement identity. + examples: 2 + - key: probe.values + type: int[] + stability: stable + brief: Numeric values attached to an event. + examples: [1, 2] +metrics: + - name: probe.required + requirement_level: recommended + instrument: counter + unit: "{measurement}" + stability: stable + brief: Measurements with a required numeric attribute. + annotations: + code_generation: + metric_value_type: int + attributes: + - ref: probe.required + requirement_level: required + - name: probe.optional + requirement_level: recommended + instrument: counter + unit: "{measurement}" + stability: stable + brief: Measurements with optional numeric attributes. + annotations: + code_generation: + metric_value_type: int + attributes: + - ref: probe.required + requirement_level: required + - ref: probe.optional + requirement_level: recommended +events: + - name: probe.recorded + stability: stable + brief: A measurement was recorded. + attributes: + - ref: probe.required + requirement_level: required + - ref: probe.optional + requirement_level: recommended + - ref: probe.values + requirement_level: recommended diff --git a/templates/code/rust/tests/allocations/test.rs b/templates/code/rust/tests/allocations/test.rs new file mode 100644 index 0000000..9470ec4 --- /dev/null +++ b/templates/code/rust/tests/allocations/test.rs @@ -0,0 +1,493 @@ +use generated_semconv::options::InstrumentationOptions; +use generated_semconv::{logger, meter, probeattr, probeevent, probemetric, scope}; +use opentelemetry::logs::{AnyValue, LogRecord, Logger, LoggerProvider, Severity}; +use opentelemetry::metrics::{ + AsyncInstrument, AsyncInstrumentBuilder, Callback, Counter, InstrumentBuilder, + InstrumentProvider, Meter, MeterProvider, ObservableCounter, SyncInstrument, +}; +use opentelemetry::{Context, InstrumentationScope, Key, KeyValue}; +use std::alloc::{GlobalAlloc, Layout, System}; +use std::borrow::Cow; +use std::cell::Cell; +use std::hint::black_box; +use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +thread_local! { + static ALLOCATIONS: Cell> = const { Cell::new(None) }; +} + +struct AllocationCounter; + +unsafe impl GlobalAlloc for AllocationCounter { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let _ = ALLOCATIONS.try_with(|count| { + if let Some(value) = count.get() { + count.set(Some(value + 1)); + } + }); + unsafe { System.alloc(layout) } + } + + unsafe fn dealloc(&self, pointer: *mut u8, layout: Layout) { + unsafe { System.dealloc(pointer, layout) } + } + + unsafe fn realloc(&self, pointer: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + let _ = ALLOCATIONS.try_with(|count| { + if let Some(value) = count.get() { + count.set(Some(value + 1)); + } + }); + unsafe { System.realloc(pointer, layout, new_size) } + } +} + +#[global_allocator] +static ALLOCATOR: AllocationCounter = AllocationCounter; + +fn allocations(work: impl FnOnce()) -> usize { + ALLOCATIONS.with(|count| count.set(Some(0))); + work(); + ALLOCATIONS.with(|count| count.replace(None).unwrap()) +} + +#[derive(Default)] +struct MetricState { + attribute_count: AtomicUsize, + sum: AtomicI64, + order: AtomicU64, + callbacks: Mutex>>, +} + +struct MetricProvider(Arc); + +impl MeterProvider for MetricProvider { + fn meter_with_scope(&self, _: InstrumentationScope) -> Meter { + Meter::new(Arc::new(Self(self.0.clone()))) + } +} + +impl InstrumentProvider for MetricProvider { + fn u64_counter(&self, _: InstrumentBuilder<'_, Counter>) -> Counter { + Counter::new(Arc::new(Self(self.0.clone()))) + } + fn u64_observable_counter( + &self, + builder: AsyncInstrumentBuilder<'_, ObservableCounter, u64>, + ) -> ObservableCounter { + self.0.callbacks.lock().unwrap().extend(builder.callbacks); + ObservableCounter::new() + } +} + +impl AsyncInstrument for MetricProvider { + fn observe(&self, value: u64, attributes: &[KeyValue]) { + self.measure(value, attributes); + } +} + +struct BorrowedMeterProvider { + meter: Meter, + scope: Mutex>, +} + +impl MeterProvider for BorrowedMeterProvider { + fn meter_with_scope(&self, scope: InstrumentationScope) -> Meter { + *self.scope.lock().unwrap() = Some(scope); + self.meter.clone() + } +} + +impl SyncInstrument for MetricProvider { + fn measure(&self, _: u64, attributes: &[KeyValue]) { + black_box(attributes); + self.0 + .attribute_count + .store(attributes.len(), Ordering::Relaxed); + self.0.sum.store( + attributes + .iter() + .map(|attr| match attr.value { + opentelemetry::Value::I64(value) => value, + _ => 0, + }) + .sum(), + Ordering::Relaxed, + ); + self.0.order.store( + attributes + .iter() + .fold(0_u64, |hash, attr| match attr.value { + opentelemetry::Value::I64(value) => { + hash.wrapping_mul(31).wrapping_add(value as u64) + } + _ => hash, + }), + Ordering::Relaxed, + ); + } +} + +#[derive(Default)] +struct LogState { + enabled: AtomicBool, + attribute_count: AtomicUsize, + attribute_sum: AtomicI64, + severity: AtomicUsize, + context_attached_during_check: AtomicBool, + order: AtomicU64, + timestamp: AtomicU64, + scope: Mutex>, +} + +struct ExplicitContext; +struct LogProvider(Arc); +struct FakeLogger(Arc); + +impl LoggerProvider for LogProvider { + type Logger = FakeLogger; + + fn logger_with_scope(&self, scope: InstrumentationScope) -> Self::Logger { + *self.0.scope.lock().unwrap() = Some(scope); + FakeLogger(self.0.clone()) + } +} + +#[derive(Default)] +struct FakeRecord { + attributes: usize, + sum: i64, + severity: usize, + order: u64, + timestamp: u64, +} + +impl LogRecord for FakeRecord { + fn set_event_name(&mut self, _: &'static str) {} + fn set_target>>(&mut self, _: T) {} + fn set_timestamp(&mut self, value: SystemTime) { + self.timestamp = value.duration_since(UNIX_EPOCH).unwrap().as_secs(); + } + fn set_observed_timestamp(&mut self, _: SystemTime) {} + fn set_severity_text(&mut self, _: &'static str) {} + fn set_severity_number(&mut self, value: Severity) { + self.severity = value as usize; + } + fn set_body(&mut self, _: AnyValue) {} + fn add_attributes(&mut self, values: I) + where + I: IntoIterator, + K: Into, + V: Into, + { + for (key, value) in values { + self.add_attribute(key, value); + } + } + fn add_attribute, V: Into>(&mut self, key: K, value: V) { + black_box(key.into()); + self.attributes += 1; + if let AnyValue::Int(value) = value.into() { + self.sum += value; + self.order = self.order.wrapping_mul(31).wrapping_add(value as u64); + } + } +} + +impl Logger for FakeLogger { + type LogRecord = FakeRecord; + fn create_log_record(&self) -> FakeRecord { + FakeRecord::default() + } + fn emit(&self, record: FakeRecord) { + self.0 + .attribute_count + .store(record.attributes, Ordering::Relaxed); + self.0.attribute_sum.store(record.sum, Ordering::Relaxed); + self.0.severity.store(record.severity, Ordering::Relaxed); + self.0.order.store(record.order, Ordering::Relaxed); + self.0.timestamp.store(record.timestamp, Ordering::Relaxed); + } + fn event_enabled(&self, _: Severity, _: &str, _: Option<&str>) -> bool { + self.0.context_attached_during_check.store( + Context::current().get::().is_some(), + Ordering::Relaxed, + ); + self.0.enabled.load(Ordering::Relaxed) + } +} + +#[test] +fn metrics_use_no_generated_heap_for_required_or_typical_optional_attributes() { + let state = Arc::new(MetricState::default()); + let meter = meter::Meter::new( + &MetricProvider(state.clone()), + InstrumentationOptions::default(), + ); + let required = probemetric::RequiredCounter::new(&meter); + let optional = probemetric::OptionalCounter::new(&meter); + let required_count = allocations(|| required.add(1, probeattr::RequiredAttr::from(3))); + assert_eq!( + required_count, 0, + "required metric attribute buffer allocated" + ); + let optional_count = allocations(|| { + optional.add( + 1, + probeattr::RequiredAttr::from(3), + [probemetric::OptionalCounterAttr::Optional( + probeattr::OptionalAttr::from(5), + )], + ) + }); + assert_eq!( + optional_count, 0, + "ordinary optional metric attributes allocated" + ); + assert_eq!(state.attribute_count.load(Ordering::Relaxed), 2); + assert_eq!(state.sum.load(Ordering::Relaxed), 8); + optional.add( + 1, + probeattr::RequiredAttr::from(3), + (0..20).map(|value| { + probemetric::OptionalCounterAttr::Optional(probeattr::OptionalAttr::from(value)) + }), + ); + assert_eq!(state.attribute_count.load(Ordering::Relaxed), 21); + assert_eq!(state.sum.load(Ordering::Relaxed), 193); + assert_eq!( + state.order.load(Ordering::Relaxed), + (0..20_u64).fold(3_u64, |hash, value| hash + .wrapping_mul(31) + .wrapping_add(value)) + ); + println!("generated metric allocations: required={required_count}, optional={optional_count}"); +} + +#[test] +fn observable_measurements_use_stack_attribute_storage() { + let state = Arc::new(MetricState::default()); + let provider = MetricProvider(state.clone()); + let meter = meter::Meter::new(&provider, InstrumentationOptions::default()); + let _required = probemetric::RequiredObservableCounter::new(&meter, |observer| { + observer.observe(1, probeattr::RequiredAttr::from(3)); + }); + let _optional = probemetric::OptionalObservableCounter::new(&meter, |observer| { + observer.observe( + 1, + probeattr::RequiredAttr::from(3), + [probemetric::OptionalCounterAttr::Optional( + probeattr::OptionalAttr::from(5), + )], + ); + }); + let callbacks = state.callbacks.lock().unwrap(); + let count = allocations(|| { + for callback in callbacks.iter() { + callback(&provider); + } + }); + assert_eq!(count, 0); + assert_eq!(state.attribute_count.load(Ordering::Relaxed), 2); + assert_eq!(state.sum.load(Ordering::Relaxed), 8); + println!("generated observable metric allocations: {count}"); +} + +#[test] +fn fixed_scope_identity_uses_no_generated_heap() { + let metric_provider = BorrowedMeterProvider { + meter: Meter::new(Arc::new(MetricProvider(Arc::new(MetricState::default())))), + scope: Mutex::new(None), + }; + drop(metric_provider.scope.lock().unwrap()); + let metric_count = allocations(|| { + black_box(meter::Meter::new( + &metric_provider, + InstrumentationOptions::default(), + )); + }); + let log_provider = LogProvider(Arc::new(LogState::default())); + drop(log_provider.0.scope.lock().unwrap()); + let log_count = allocations(|| { + black_box(logger::Logger::new( + &log_provider, + InstrumentationOptions::default(), + )); + }); + assert_eq!(metric_count, 0); + assert_eq!(log_count, 0); + println!("generated static scope allocations: meter={metric_count}, logger={log_count}"); +} + +#[test] +fn scope_attributes_move_without_rebuilding_storage() { + let metric_provider = BorrowedMeterProvider { + meter: Meter::new(Arc::new(MetricProvider(Arc::new(MetricState::default())))), + scope: Mutex::new(None), + }; + let log_provider = LogProvider(Arc::new(LogState::default())); + drop(metric_provider.scope.lock().unwrap()); + drop(log_provider.0.scope.lock().unwrap()); + let scope = || scope::Scope { + attributes: vec![KeyValue::new("scope.identity", 7_i64)], + }; + let metric_scope = scope(); + let log_scope = scope(); + let original_metric = metric_scope.attributes.as_ptr(); + let original_log = log_scope.attributes.as_ptr(); + let metric_count = allocations(|| { + black_box(meter::Meter::new( + &metric_provider, + InstrumentationOptions { + scope: metric_scope, + }, + )); + }); + let log_count = allocations(|| { + black_box(logger::Logger::new( + &log_provider, + InstrumentationOptions { scope: log_scope }, + )); + }); + assert_eq!(metric_count, 0); + assert_eq!(log_count, 0); + for (stored, original) in [ + (metric_provider.scope.lock().unwrap(), original_metric), + (log_provider.0.scope.lock().unwrap(), original_log), + ] { + let scope = stored.as_ref().unwrap(); + assert_eq!(scope.name(), "generated-semconv"); + assert_eq!(scope.version(), None); + assert_eq!(scope.schema_url(), Some(generated_semconv::SCHEMA_URL)); + assert_eq!( + scope.attributes().next().unwrap() as *const KeyValue, + original + ); + assert_eq!( + scope.attributes().collect::>(), + [&KeyValue::new("scope.identity", 7_i64)] + ); + } + println!("generated configured scope allocations: meter={metric_count}, logger={log_count}"); +} + +#[test] +fn default_options_apply_generated_identity() { + let metric_provider = BorrowedMeterProvider { + meter: Meter::new(Arc::new(MetricProvider(Arc::new(MetricState::default())))), + scope: Mutex::new(None), + }; + let _meter = meter::Meter::new(&metric_provider, InstrumentationOptions::default()); + let stored = metric_provider.scope.lock().unwrap(); + let stored = stored.as_ref().unwrap(); + assert_eq!(stored.name(), "generated-semconv"); + assert_eq!(stored.version(), None); + assert_eq!(stored.attributes().count(), 0); + assert_eq!(stored.schema_url(), Some(generated_semconv::SCHEMA_URL)); +} + +#[test] +fn direct_scalar_log_values_move_without_heap_allocations() { + let owned = probeattr::LabelAttr::from(String::from("owned text")); + let count = allocations(|| { + black_box(AnyValue::from(probeattr::RequiredAttr::from(7))); + black_box(AnyValue::from(probeattr::FlagAttr::from(true))); + black_box(AnyValue::from(probeattr::FractionAttr::from(0.5))); + black_box(AnyValue::from(probeattr::LabelAttr::from("static text"))); + black_box(AnyValue::from(owned)); + }); + assert_eq!(count, 0); + println!("generated direct scalar log conversion allocations: {count}"); +} + +#[test] +fn enabled_events_use_no_generated_heap_and_preserve_duplicate_attributes() { + let state = Arc::new(LogState::default()); + state.enabled.store(true, Ordering::Relaxed); + let logger = logger::Logger::new( + &LogProvider(state.clone()), + InstrumentationOptions::default(), + ); + let context = Context::new(); + probeevent::emit_recorded(&context, &logger, probeattr::RequiredAttr::from(3), []); + let count = allocations(|| { + probeevent::emit_recorded( + &context, + &logger, + probeattr::RequiredAttr::from(3), + [probeevent::RecordedOption::Optional( + probeattr::OptionalAttr::from(5), + )], + ) + }); + assert_eq!(count, 0, "ordinary enabled event attributes allocated"); + assert_eq!(state.attribute_count.load(Ordering::Relaxed), 2); + assert_eq!(state.attribute_sum.load(Ordering::Relaxed), 8); + probeevent::emit_recorded( + &context, + &logger, + probeattr::RequiredAttr::from(3), + (0..20).map(|value| { + probeevent::RecordedOption::Optional(probeattr::OptionalAttr::from(value)) + }), + ); + assert_eq!(state.attribute_count.load(Ordering::Relaxed), 21); + assert_eq!(state.attribute_sum.load(Ordering::Relaxed), 193); + assert_eq!( + state.order.load(Ordering::Relaxed), + (0..20_u64).fold(3_u64, |hash, value| hash + .wrapping_mul(31) + .wrapping_add(value)) + ); + let configuration_count = allocations(|| { + probeevent::emit_recorded( + &context, + &logger, + probeattr::RequiredAttr::from(3), + [ + probeevent::RecordedOption::Severity(Severity::Warn), + probeevent::RecordedOption::Timestamp(UNIX_EPOCH + Duration::from_secs(10)), + probeevent::RecordedOption::Severity(Severity::Error), + probeevent::RecordedOption::Timestamp(UNIX_EPOCH + Duration::from_secs(20)), + ], + ) + }); + assert_eq!(configuration_count, 0); + assert_eq!( + state.severity.load(Ordering::Relaxed), + Severity::Error as usize + ); + assert_eq!(state.timestamp.load(Ordering::Relaxed), 20); + println!("generated enabled event allocations: attributes={count}, configuration={configuration_count}"); +} + +#[test] +fn disabled_events_defer_context_attachment_and_attribute_conversion() { + let state = Arc::new(LogState::default()); + let logger = logger::Logger::new( + &LogProvider(state.clone()), + InstrumentationOptions::default(), + ); + let context = Context::new().with_value(ExplicitContext); + let values = probeattr::ValuesAttr::from(vec![1, 2, 3]); + probeevent::emit_recorded( + &Context::new(), + &logger, + probeattr::RequiredAttr::from(0), + [], + ); + let count = allocations(|| { + probeevent::emit_recorded( + &context, + &logger, + probeattr::RequiredAttr::from(3), + [probeevent::RecordedOption::Values(values)], + ) + }); + assert_eq!(count, 0, "disabled event attributes allocated"); + assert!(!state.context_attached_during_check.load(Ordering::Relaxed)); + assert_eq!(state.attribute_count.load(Ordering::Relaxed), 0); + println!("generated disabled event allocations: {count}"); +} diff --git a/templates/code/rust/tests/attributes/compile-fail/dynamic_borrow.expected-error.txt b/templates/code/rust/tests/attributes/compile-fail/dynamic_borrow.expected-error.txt new file mode 100644 index 0000000..1a48ea1 --- /dev/null +++ b/templates/code/rust/tests/attributes/compile-fail/dynamic_borrow.expected-error.txt @@ -0,0 +1 @@ +does not live long enough diff --git a/templates/code/rust/tests/attributes/compile-fail/dynamic_borrow.rs b/templates/code/rust/tests/attributes/compile-fail/dynamic_borrow.rs new file mode 100644 index 0000000..0816e72 --- /dev/null +++ b/templates/code/rust/tests/attributes/compile-fail/dynamic_borrow.rs @@ -0,0 +1,4 @@ +fn main() { + let source = String::from("dynamic"); + let _ = generated_semconv::myappattr::TaskIdAttr::from(source.as_str()); +} diff --git a/templates/code/rust/tests/attributes/compile-fail/inherited_deprecation.expected-error.txt b/templates/code/rust/tests/attributes/compile-fail/inherited_deprecation.expected-error.txt new file mode 100644 index 0000000..56c12c8 --- /dev/null +++ b/templates/code/rust/tests/attributes/compile-fail/inherited_deprecation.expected-error.txt @@ -0,0 +1 @@ +use of deprecated unit variant diff --git a/templates/code/rust/tests/attributes/compile-fail/inherited_deprecation.rs b/templates/code/rust/tests/attributes/compile-fail/inherited_deprecation.rs new file mode 100644 index 0000000..2fdc518 --- /dev/null +++ b/templates/code/rust/tests/attributes/compile-fail/inherited_deprecation.rs @@ -0,0 +1,4 @@ +#![deny(deprecated)] +fn main() { + let _ = generated_semconv::myappattr::TaskLaneAttr::Fast; +} diff --git a/templates/code/rust/tests/attributes/compile-fail/unknown_enum.expected-error.txt b/templates/code/rust/tests/attributes/compile-fail/unknown_enum.expected-error.txt new file mode 100644 index 0000000..3f1caed --- /dev/null +++ b/templates/code/rust/tests/attributes/compile-fail/unknown_enum.expected-error.txt @@ -0,0 +1 @@ +no variant, associated function, or constant named `Unknown` diff --git a/templates/code/rust/tests/attributes/compile-fail/unknown_enum.rs b/templates/code/rust/tests/attributes/compile-fail/unknown_enum.rs new file mode 100644 index 0000000..fe8a0cc --- /dev/null +++ b/templates/code/rust/tests/attributes/compile-fail/unknown_enum.rs @@ -0,0 +1,3 @@ +fn main() { + let _ = generated_semconv::myappattr::TaskStateAttr::Unknown; +} diff --git a/templates/code/rust/tests/attributes/compile-fail/wrong_scalar.expected-error.txt b/templates/code/rust/tests/attributes/compile-fail/wrong_scalar.expected-error.txt new file mode 100644 index 0000000..d96698e --- /dev/null +++ b/templates/code/rust/tests/attributes/compile-fail/wrong_scalar.expected-error.txt @@ -0,0 +1 @@ +the trait bound `TaskRetriesAttr: From<&str>` is not satisfied diff --git a/templates/code/rust/tests/attributes/compile-fail/wrong_scalar.rs b/templates/code/rust/tests/attributes/compile-fail/wrong_scalar.rs new file mode 100644 index 0000000..1c753de --- /dev/null +++ b/templates/code/rust/tests/attributes/compile-fail/wrong_scalar.rs @@ -0,0 +1,3 @@ +fn main() { + let _ = generated_semconv::myappattr::TaskRetriesAttr::from("three"); +} diff --git a/templates/code/rust/tests/attributes/expected/authattr.rs b/templates/code/rust/tests/attributes/expected/authattr.rs new file mode 100644 index 0000000..5fba6c7 --- /dev/null +++ b/templates/code/rust/tests/attributes/expected/authattr.rs @@ -0,0 +1,172 @@ +// Generated by OpenTelemetry Weaver. +/// Assurance level of the authentication. +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum LevelAttr { + /// Single factor. + Low, + /// Multiple factors. + High, +} + +#[allow(deprecated)] +impl LevelAttr { + pub const KEY: &'static str = "auth.level"; +} + +#[allow(deprecated)] +impl ::std::fmt::Display for LevelAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + let value: i64 = match self { + LevelAttr::Low => 1, + LevelAttr::High => 2, + }; + ::std::fmt::Display::fmt(&value, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: LevelAttr) -> Self { + let value: i64 = match value { + LevelAttr::Low => 1, + LevelAttr::High => 2, + }; + ::opentelemetry::KeyValue::new(LevelAttr::KEY, value) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: LevelAttr) -> Self { + let value: i64 = match value { + LevelAttr::Low => 1, + LevelAttr::High => 2, + }; + value.into() + } +} + +/// How the user was authenticated. +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum MethodAttr { + /// A password was checked. + Password, + /// A bearer token was checked. + Token, +} + +#[allow(deprecated)] +impl MethodAttr { + pub const KEY: &'static str = "auth.method"; +} + +#[allow(deprecated)] +impl AsRef for MethodAttr { + #[inline] + fn as_ref(&self) -> &str { + match self { + MethodAttr::Password => "password", + MethodAttr::Token => "token", + } + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for MethodAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: MethodAttr) -> Self { + let value: &'static str = match value { + MethodAttr::Password => "password", + MethodAttr::Token => "token", + }; + ::opentelemetry::KeyValue::new(MethodAttr::KEY, value) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: MethodAttr) -> Self { + let value: &'static str = match value { + MethodAttr::Password => "password", + MethodAttr::Token => "token", + }; + value.into() + } +} + +/// Identifier of the authenticated user. +#[derive(Debug, Clone, PartialEq)] +pub struct UserIdAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl UserIdAttr { + pub const KEY: &'static str = "auth.user.id"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for UserIdAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for UserIdAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for UserIdAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for UserIdAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for UserIdAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: UserIdAttr) -> Self { + ::opentelemetry::KeyValue::new(UserIdAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: UserIdAttr) -> Self { + value.0.into() + } +} diff --git a/templates/code/rust/tests/attributes/expected/mod.rs b/templates/code/rust/tests/attributes/expected/mod.rs new file mode 100644 index 0000000..c487539 --- /dev/null +++ b/templates/code/rust/tests/attributes/expected/mod.rs @@ -0,0 +1,4 @@ +// Generated by OpenTelemetry Weaver. +pub const SCHEMA_URL: &str = "https://example.com/schemas/attributes/1.0.0"; +pub mod authattr; +pub mod myappattr; diff --git a/templates/code/rust/tests/attributes/expected/myappattr.rs b/templates/code/rust/tests/attributes/expected/myappattr.rs new file mode 100644 index 0000000..bd3bcf5 --- /dev/null +++ b/templates/code/rust/tests/attributes/expected/myappattr.rs @@ -0,0 +1,827 @@ +// Generated by OpenTelemetry Weaver. +/// Who assigned the task. +/// +/// The scheduler sets it on every task it passes along to `myapp.task.state`. Workers never set it themselves. +#[derive(Debug, Clone, PartialEq)] +pub struct TaskAssignedByAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl TaskAssignedByAttr { + pub const KEY: &'static str = "myapp.task.assigned_by"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for TaskAssignedByAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for TaskAssignedByAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for TaskAssignedByAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for TaskAssignedByAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskAssignedByAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskAssignedByAttr) -> Self { + ::opentelemetry::KeyValue::new(TaskAssignedByAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskAssignedByAttr) -> Self { + value.0.into() + } +} + +/// Whether the task was cancelled. +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct TaskCancelledAttr(bool); + +#[allow(deprecated)] +impl TaskCancelledAttr { + pub const KEY: &'static str = "myapp.task.cancelled"; +} + +#[allow(deprecated)] +impl From for TaskCancelledAttr { + #[inline] + fn from(value: bool) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskCancelledAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::std::fmt::Display::fmt(&self.0, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskCancelledAttr) -> Self { + ::opentelemetry::KeyValue::new(TaskCancelledAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskCancelledAttr) -> Self { + value.0.into() + } +} + +/// Tests Rust string literals. +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum TaskEscapedAttr { + /// Escaped text. + Quoted, +} + +#[allow(deprecated)] +impl TaskEscapedAttr { + pub const KEY: &'static str = "myapp.task.escaped"; +} + +#[allow(deprecated)] +impl AsRef for TaskEscapedAttr { + #[inline] + fn as_ref(&self) -> &str { + match self { + TaskEscapedAttr::Quoted => "quote\" slash\\ newline\n tab\t backspace\u{0008} formfeed\u{000c} null\u{0000} snowman☃", + } + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskEscapedAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskEscapedAttr) -> Self { + let value: &'static str = match value { + TaskEscapedAttr::Quoted => "quote\" slash\\ newline\n tab\t backspace\u{0008} formfeed\u{000c} null\u{0000} snowman☃", + }; + ::opentelemetry::KeyValue::new(TaskEscapedAttr::KEY, value) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskEscapedAttr) -> Self { + let value: &'static str = match value { + TaskEscapedAttr::Quoted => "quote\" slash\\ newline\n tab\t backspace\u{0008} formfeed\u{000c} null\u{0000} snowman☃", + }; + value.into() + } +} + +/// Feature flags evaluated for the task. +#[derive(Debug, Clone, PartialEq)] +pub struct TaskFlagsAttr(Vec); + +#[allow(deprecated)] +impl TaskFlagsAttr { + pub const KEY: &'static str = "myapp.task.flags"; +} + +#[allow(deprecated)] +impl From> for TaskFlagsAttr { + #[inline] + fn from(value: Vec) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl AsRef<[bool]> for TaskFlagsAttr { + #[inline] + fn as_ref(&self) -> &[bool] { + &self.0 + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskFlagsAttr { + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str("[")?; + for (index, value) in self.0.iter().enumerate() { + if index != 0 { + formatter.write_str(",")?; + } + ::std::fmt::Display::fmt(value, formatter)?; + } + formatter.write_str("]") + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskFlagsAttr) -> Self { + ::opentelemetry::KeyValue::new(TaskFlagsAttr::KEY, ::opentelemetry::Value::Array(::opentelemetry::Array::Bool(value.0))) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskFlagsAttr) -> Self { + value.0.into_iter().collect::<::opentelemetry::logs::AnyValue>() + } +} + +/// Unique identifier of the task. +/// +/// The identifier is assigned by the scheduler and stays stable across retries. +#[derive(Debug, Clone, PartialEq)] +pub struct TaskIdAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl TaskIdAttr { + pub const KEY: &'static str = "myapp.task.id"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for TaskIdAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for TaskIdAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for TaskIdAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for TaskIdAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskIdAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskIdAttr) -> Self { + ::opentelemetry::KeyValue::new(TaskIdAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskIdAttr) -> Self { + value.0.into() + } +} + +/// Lane the task was scheduled in. +#[deprecated(note = "Lanes were replaced by priorities.")] +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum TaskLaneAttr { + /// The fast lane. + #[deprecated(note = "Lanes were replaced by priorities.")] + Fast, + /// The slow lane. + #[deprecated(note = "Every task runs in the fast lane.")] + Slow, +} + +#[allow(deprecated)] +impl TaskLaneAttr { + pub const KEY: &'static str = "myapp.task.lane"; +} + +#[allow(deprecated)] +impl AsRef for TaskLaneAttr { + #[inline] + fn as_ref(&self) -> &str { + match self { + TaskLaneAttr::Fast => "fast", + TaskLaneAttr::Slow => "slow", + } + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskLaneAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskLaneAttr) -> Self { + let value: &'static str = match value { + TaskLaneAttr::Fast => "fast", + TaskLaneAttr::Slow => "slow", + }; + ::opentelemetry::KeyValue::new(TaskLaneAttr::KEY, value) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskLaneAttr) -> Self { + let value: &'static str = match value { + TaskLaneAttr::Fast => "fast", + TaskLaneAttr::Slow => "slow", + }; + value.into() + } +} + +/// Owner of the task. +#[deprecated(note = "Replaced by `auth.user.id`.")] +#[derive(Debug, Clone, PartialEq)] +pub struct TaskOwnerAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl TaskOwnerAttr { + pub const KEY: &'static str = "myapp.task.owner"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for TaskOwnerAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for TaskOwnerAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for TaskOwnerAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for TaskOwnerAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskOwnerAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskOwnerAttr) -> Self { + ::opentelemetry::KeyValue::new(TaskOwnerAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskOwnerAttr) -> Self { + value.0.into() + } +} + +/// Whether the scheduler may preempt the task. +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum TaskPreemptibleAttr { + /// The scheduler may preempt the task. + Allowed, + /// The task runs to completion. + Forbidden, +} + +#[allow(deprecated)] +impl TaskPreemptibleAttr { + pub const KEY: &'static str = "myapp.task.preemptible"; +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskPreemptibleAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + let value: bool = match self { + TaskPreemptibleAttr::Allowed => true, + TaskPreemptibleAttr::Forbidden => false, + }; + ::std::fmt::Display::fmt(&value, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskPreemptibleAttr) -> Self { + let value: bool = match value { + TaskPreemptibleAttr::Allowed => true, + TaskPreemptibleAttr::Forbidden => false, + }; + ::opentelemetry::KeyValue::new(TaskPreemptibleAttr::KEY, value) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskPreemptibleAttr) -> Self { + let value: bool = match value { + TaskPreemptibleAttr::Allowed => true, + TaskPreemptibleAttr::Forbidden => false, + }; + value.into() + } +} + +/// Fraction of the task that has completed, between 0 and 1. +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct TaskProgressAttr(f64); + +#[allow(deprecated)] +impl TaskProgressAttr { + pub const KEY: &'static str = "myapp.task.progress"; +} + +#[allow(deprecated)] +impl From for TaskProgressAttr { + #[inline] + fn from(value: f64) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskProgressAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::std::fmt::Display::fmt(&self.0, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskProgressAttr) -> Self { + ::opentelemetry::KeyValue::new(TaskProgressAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskProgressAttr) -> Self { + value.0.into() + } +} + +/// Number of times the task was retried. +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct TaskRetriesAttr(i64); + +#[allow(deprecated)] +impl TaskRetriesAttr { + pub const KEY: &'static str = "myapp.task.retries"; +} + +#[allow(deprecated)] +impl From for TaskRetriesAttr { + #[inline] + fn from(value: i64) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskRetriesAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::std::fmt::Display::fmt(&self.0, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskRetriesAttr) -> Self { + ::opentelemetry::KeyValue::new(TaskRetriesAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskRetriesAttr) -> Self { + value.0.into() + } +} + +/// Fraction of tasks whose telemetry is sampled. +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum TaskSampleRateAttr { + /// One task in ten is sampled. + Tenth, + /// Every task is sampled. + All, +} + +#[allow(deprecated)] +impl TaskSampleRateAttr { + pub const KEY: &'static str = "myapp.task.sample_rate"; +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskSampleRateAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + let value: f64 = match self { + TaskSampleRateAttr::Tenth => 0.1_f64, + TaskSampleRateAttr::All => 1_f64, + }; + ::std::fmt::Display::fmt(&value, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskSampleRateAttr) -> Self { + let value: f64 = match value { + TaskSampleRateAttr::Tenth => 0.1_f64, + TaskSampleRateAttr::All => 1_f64, + }; + ::opentelemetry::KeyValue::new(TaskSampleRateAttr::KEY, value) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskSampleRateAttr) -> Self { + let value: f64 = match value { + TaskSampleRateAttr::Tenth => 0.1_f64, + TaskSampleRateAttr::All => 1_f64, + }; + value.into() + } +} + +/// Shards the task touched. +#[derive(Debug, Clone, PartialEq)] +pub struct TaskShardIdsAttr(Vec); + +#[allow(deprecated)] +impl TaskShardIdsAttr { + pub const KEY: &'static str = "myapp.task.shard_ids"; +} + +#[allow(deprecated)] +impl From> for TaskShardIdsAttr { + #[inline] + fn from(value: Vec) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl AsRef<[i64]> for TaskShardIdsAttr { + #[inline] + fn as_ref(&self) -> &[i64] { + &self.0 + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskShardIdsAttr { + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str("[")?; + for (index, value) in self.0.iter().enumerate() { + if index != 0 { + formatter.write_str(",")?; + } + ::std::fmt::Display::fmt(value, formatter)?; + } + formatter.write_str("]") + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskShardIdsAttr) -> Self { + ::opentelemetry::KeyValue::new(TaskShardIdsAttr::KEY, ::opentelemetry::Value::Array(::opentelemetry::Array::I64(value.0))) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskShardIdsAttr) -> Self { + value.0.into_iter().collect::<::opentelemetry::logs::AnyValue>() + } +} + +/// Current state of the task. +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum TaskStateAttr { + /// The task is waiting to run. + Queued, + /// The task is running. + /// + /// A task stays running until it finishes or is cancelled. + Running, + /// The task was paused. + #[deprecated(note = "Paused tasks are reported as queued.")] + Paused, +} + +#[allow(deprecated)] +impl TaskStateAttr { + pub const KEY: &'static str = "myapp.task.state"; +} + +#[allow(deprecated)] +impl AsRef for TaskStateAttr { + #[inline] + fn as_ref(&self) -> &str { + match self { + TaskStateAttr::Queued => "queued", + TaskStateAttr::Running => "running", + TaskStateAttr::Paused => "paused", + } + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskStateAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskStateAttr) -> Self { + let value: &'static str = match value { + TaskStateAttr::Queued => "queued", + TaskStateAttr::Running => "running", + TaskStateAttr::Paused => "paused", + }; + ::opentelemetry::KeyValue::new(TaskStateAttr::KEY, value) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskStateAttr) -> Self { + let value: &'static str = match value { + TaskStateAttr::Queued => "queued", + TaskStateAttr::Running => "running", + TaskStateAttr::Paused => "paused", + }; + value.into() + } +} + +/// Tags attached to the task. +#[derive(Debug, Clone, PartialEq)] +pub struct TaskTagsAttr(Vec<::opentelemetry::StringValue>); + +#[allow(deprecated)] +impl TaskTagsAttr { + pub const KEY: &'static str = "myapp.task.tags"; +} + +#[allow(deprecated)] +impl From> for TaskTagsAttr { + #[inline] + fn from(value: Vec<::opentelemetry::StringValue>) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From> for TaskTagsAttr { + fn from(value: Vec) -> Self { + Self(value.into_iter().map(Into::into).collect()) + } +} + +#[allow(deprecated)] +impl AsRef<[::opentelemetry::StringValue]> for TaskTagsAttr { + #[inline] + fn as_ref(&self) -> &[::opentelemetry::StringValue] { + &self.0 + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskTagsAttr { + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str("[")?; + for (index, value) in self.0.iter().enumerate() { + if index != 0 { + formatter.write_str(",")?; + } + write!(formatter, "\"{value}\"")?; + } + formatter.write_str("]") + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskTagsAttr) -> Self { + ::opentelemetry::KeyValue::new(TaskTagsAttr::KEY, ::opentelemetry::Value::Array(::opentelemetry::Array::String(value.0))) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskTagsAttr) -> Self { + value.0.into_iter().collect::<::opentelemetry::logs::AnyValue>() + } +} + +/// Scheduling weights applied to the task. +#[derive(Debug, Clone, PartialEq)] +pub struct TaskWeightsAttr(Vec); + +#[allow(deprecated)] +impl TaskWeightsAttr { + pub const KEY: &'static str = "myapp.task.weights"; +} + +#[allow(deprecated)] +impl From> for TaskWeightsAttr { + #[inline] + fn from(value: Vec) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl AsRef<[f64]> for TaskWeightsAttr { + #[inline] + fn as_ref(&self) -> &[f64] { + &self.0 + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskWeightsAttr { + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str("[")?; + for (index, value) in self.0.iter().enumerate() { + if index != 0 { + formatter.write_str(",")?; + } + ::std::fmt::Display::fmt(value, formatter)?; + } + formatter.write_str("]") + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskWeightsAttr) -> Self { + ::opentelemetry::KeyValue::new(TaskWeightsAttr::KEY, ::opentelemetry::Value::Array(::opentelemetry::Array::F64(value.0))) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskWeightsAttr) -> Self { + value.0.into_iter().collect::<::opentelemetry::logs::AnyValue>() + } +} diff --git a/templates/code/rust/tests/attributes/registry/manifest.yaml b/templates/code/rust/tests/attributes/registry/manifest.yaml new file mode 100644 index 0000000..f168c1f --- /dev/null +++ b/templates/code/rust/tests/attributes/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: attributes +schema_url: https://example.com/schemas/attributes/1.0.0 diff --git a/templates/code/rust/tests/attributes/registry/model.yaml b/templates/code/rust/tests/attributes/registry/model.yaml new file mode 100644 index 0000000..74be0ec --- /dev/null +++ b/templates/code/rust/tests/attributes/registry/model.yaml @@ -0,0 +1,163 @@ +file_format: definition/2 +attributes: + - key: myapp.task.id + type: string + stability: stable + brief: Unique identifier of the task. + note: The identifier is assigned by the scheduler and stays stable across retries. + examples: ["task_0001"] + - key: myapp.task.retries + type: int + stability: stable + brief: Number of times the task was retried. + examples: 3 + - key: myapp.task.progress + type: double + stability: development + brief: Fraction of the task that has completed, between 0 and 1. + examples: 0.5 + - key: myapp.task.cancelled + type: boolean + stability: stable + brief: Whether the task was cancelled. + examples: false + - key: myapp.task.tags + type: string[] + stability: stable + brief: Tags attached to the task. + examples: [["nightly", "batch"]] + - key: myapp.task.shard_ids + type: int[] + stability: stable + brief: Shards the task touched. + examples: [[1, 2]] + - key: myapp.task.weights + type: double[] + stability: development + brief: Scheduling weights applied to the task. + examples: [[0.25, 0.75]] + - key: myapp.task.flags + type: boolean[] + stability: development + brief: Feature flags evaluated for the task. + examples: [[true, false]] + - key: myapp.task.state + type: + members: + - id: queued + value: queued + stability: stable + brief: The task is waiting to run. + - id: running + value: running + stability: stable + brief: The task is running. + note: A task stays running until it finishes or is cancelled. + - id: paused + value: paused + stability: development + brief: The task was paused. + deprecated: + reason: obsoleted + note: Paused tasks are reported as queued. + stability: stable + brief: Current state of the task. + - key: myapp.task.owner + type: string + stability: development + brief: Owner of the task. + examples: ["solo"] + deprecated: + reason: renamed + renamed_to: auth.user.id + - key: auth.user.id + type: string + stability: stable + brief: Identifier of the authenticated user. + examples: ["u_123"] + - key: auth.method + type: + members: + - id: password + value: password + stability: stable + brief: A password was checked. + - id: token + value: token + stability: stable + brief: A bearer token was checked. + stability: stable + brief: How the user was authenticated. + - key: auth.level + type: + members: + - id: low + value: 1 + stability: stable + brief: Single factor. + - id: high + value: 2 + stability: stable + brief: Multiple factors. + stability: development + brief: Assurance level of the authentication. + - key: myapp.task.preemptible + type: + members: + - id: allowed + value: true + stability: stable + brief: The scheduler may preempt the task. + - id: forbidden + value: false + stability: stable + brief: The task runs to completion. + stability: stable + brief: Whether the scheduler may preempt the task. + - key: myapp.task.sample_rate + type: + members: + - id: tenth + value: 0.1 + stability: stable + brief: One task in ten is sampled. + - id: all + value: 1 + stability: stable + brief: Every task is sampled. + stability: stable + brief: Fraction of tasks whose telemetry is sampled. + - key: myapp.task.assigned_by + type: string + stability: stable + brief: Who assigned the task. + note: The scheduler sets it on every task it passes along to `myapp.task.state`. Workers never set it themselves. + examples: ["scheduler"] + - key: myapp.task.lane + type: + members: + - id: fast + value: fast + stability: stable + brief: The fast lane. + - id: slow + value: slow + stability: stable + brief: The slow lane. + deprecated: + reason: obsoleted + note: Every task runs in the fast lane. + stability: development + brief: Lane the task was scheduled in. + deprecated: + reason: obsoleted + note: Lanes were replaced by priorities. + - key: myapp.task.escaped + type: + members: + - id: quoted + value: "quote\" slash\\ newline\n tab\t backspace\b formfeed\f null\u0000 snowman☃" + stability: stable + brief: Escaped text. + stability: stable + brief: Tests Rust string literals. diff --git a/templates/code/rust/tests/attributes/test.rs b/templates/code/rust/tests/attributes/test.rs new file mode 100644 index 0000000..bba8a21 --- /dev/null +++ b/templates/code/rust/tests/attributes/test.rs @@ -0,0 +1,144 @@ +use generated_semconv::{authattr, myappattr, SCHEMA_URL}; +use opentelemetry::{Array, KeyValue, Value}; + +#[test] +fn typed_attributes_preserve_keys_values_and_schema() { + assert_eq!(SCHEMA_URL, "https://example.com/schemas/attributes/1.0.0"); + let value: KeyValue = myappattr::TaskIdAttr::from("task").into(); + assert_eq!(value, KeyValue::new("myapp.task.id", "task")); + assert_eq!( + KeyValue::from(myappattr::TaskRetriesAttr::from(4)), + KeyValue::new("myapp.task.retries", 4_i64) + ); + assert_eq!( + KeyValue::from(myappattr::TaskCancelledAttr::from(true)).value, + Value::Bool(true) + ); + assert_eq!( + KeyValue::from(myappattr::TaskProgressAttr::from(0.5)).value, + Value::F64(0.5) + ); + assert_eq!( + KeyValue::from(myappattr::TaskTagsAttr::from(vec![String::from("tag")])).value, + Value::Array(Array::String(vec!["tag".into()])) + ); + assert_eq!( + KeyValue::from(myappattr::TaskShardIdsAttr::from(vec![1, 2])).value, + Value::Array(Array::I64(vec![1, 2])) + ); + assert_eq!( + KeyValue::from(myappattr::TaskWeightsAttr::from(vec![0.25])).value, + Value::Array(Array::F64(vec![0.25])) + ); + assert_eq!( + KeyValue::from(myappattr::TaskFlagsAttr::from(vec![false])).value, + Value::Array(Array::Bool(vec![false])) + ); + assert_eq!( + KeyValue::from(myappattr::TaskStateAttr::Running).value, + Value::String("running".into()) + ); + assert_eq!(KeyValue::from(myappattr::TaskEscapedAttr::Quoted).value, Value::String("quote\" slash\\ newline\n tab\t backspace\u{0008} formfeed\u{000c} null\u{0000} snowman☃".into())); + assert_eq!( + KeyValue::from(authattr::LevelAttr::High).value, + Value::I64(2) + ); + assert_eq!( + KeyValue::from(myappattr::TaskPreemptibleAttr::Allowed).value, + Value::Bool(true) + ); + assert_eq!( + KeyValue::from(myappattr::TaskSampleRateAttr::All).value, + Value::F64(1.0) + ); +} + +#[test] +fn string_attributes_accept_explicit_owned_and_inferred_conversions() { + let owned = String::from("owned"); + let borrowed: myappattr::TaskIdAttr = { + let source = String::from("borrowed"); + source.into() + }; + assert_eq!( + KeyValue::from(myappattr::TaskIdAttr::from(owned)), + KeyValue::new("myapp.task.id", "owned") + ); + assert_eq!( + KeyValue::from(borrowed), + KeyValue::new("myapp.task.id", "borrowed") + ); + let inferred: myappattr::TaskIdAttr = "inferred".into(); + assert_eq!( + KeyValue::from(inferred), + KeyValue::new("myapp.task.id", "inferred") + ); + let retries: myappattr::TaskRetriesAttr = 7_i64.into(); + assert_eq!(KeyValue::from(retries).value, Value::I64(7)); + let tags: myappattr::TaskTagsAttr = vec![String::from("tag")].into(); + assert_eq!( + KeyValue::from(tags).value, + Value::Array(Array::String(vec!["tag".into()])) + ); +} + +#[test] +fn static_and_native_strings_preserve_their_storage() { + const STATIC: &str = "static-task"; + let attribute = myappattr::TaskIdAttr::from(STATIC); + assert_eq!(attribute.as_ref().as_ptr(), STATIC.as_ptr()); + let Value::String(text) = KeyValue::from(attribute).value else { + panic!("expected string"); + }; + assert_eq!(text.as_str().as_ptr(), STATIC.as_ptr()); + let Value::String(text) = KeyValue::from(myappattr::TaskStateAttr::Running).value else { + panic!("expected string"); + }; + assert_eq!(text.as_str(), myappattr::TaskStateAttr::Running.as_ref()); + + let shared: std::sync::Arc = std::sync::Arc::from("shared-task"); + let native = opentelemetry::StringValue::from(shared.clone()); + let attribute = myappattr::TaskIdAttr::from(native); + assert_eq!(attribute.as_ref().as_ptr(), shared.as_ptr()); + let Value::String(text) = KeyValue::from(attribute).value else { + panic!("expected string"); + }; + assert_eq!(text.as_str().as_ptr(), shared.as_ptr()); + assert_eq!(std::sync::Arc::strong_count(&shared), 2); + + let native = vec![opentelemetry::StringValue::from("tag")]; + let original = native.as_ptr(); + let Value::Array(Array::String(values)) = + KeyValue::from(myappattr::TaskTagsAttr::from(native)).value + else { + panic!("expected string array"); + }; + assert_eq!(values.as_ptr(), original); +} + +#[test] +fn copy_scalars_and_borrowed_display_preserve_telemetry_formatting() { + fn assert_copy() {} + assert_copy::(); + assert_copy::(); + assert_copy::(); + assert_copy::(); + let retries = myappattr::TaskRetriesAttr::from(7); + let _: KeyValue = retries.into(); + assert_eq!(retries.to_string(), "7"); + assert_eq!(myappattr::TaskCancelledAttr::from(true).to_string(), "true"); + assert_eq!(myappattr::TaskProgressAttr::from(0.5).to_string(), "0.5"); + assert_eq!(myappattr::TaskStateAttr::Running.to_string(), "running"); + assert_eq!(authattr::LevelAttr::High.to_string(), "2"); + assert_eq!(myappattr::TaskSampleRateAttr::Tenth.to_string(), "0.1"); + let string = myappattr::TaskIdAttr::from(String::from("dynamic")); + assert_eq!(string.to_string(), string.as_ref()); + let array = myappattr::TaskTagsAttr::from(vec![String::from("a"), String::from("b")]); + assert_eq!(array.to_string(), KeyValue::from(array).value.to_string()); + let array = myappattr::TaskShardIdsAttr::from(vec![1, 2]); + assert_eq!(array.to_string(), "[1,2]"); + let array = myappattr::TaskWeightsAttr::from(vec![0.25, 0.5]); + assert_eq!(array.to_string(), "[0.25,0.5]"); + let array = myappattr::TaskFlagsAttr::from(vec![true, false]); + assert_eq!(array.to_string(), "[true,false]"); +} diff --git a/templates/code/rust/tests/attributes/tests/allocations.rs b/templates/code/rust/tests/attributes/tests/allocations.rs new file mode 100644 index 0000000..f415263 --- /dev/null +++ b/templates/code/rust/tests/attributes/tests/allocations.rs @@ -0,0 +1,74 @@ +use generated_semconv::myappattr; +use opentelemetry::{KeyValue, Value}; +use std::alloc::{GlobalAlloc, Layout, System}; +use std::cell::Cell; +use std::hint::black_box; + +struct CountingAllocator; +thread_local! { + static ALLOCATIONS: Cell> = const { Cell::new(None) }; +} +fn record_allocation() { + let _ = ALLOCATIONS.try_with(|count| { + if let Some(value) = count.get() { + count.set(Some(value + 1)); + } + }); +} +unsafe impl GlobalAlloc for CountingAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + record_allocation(); + unsafe { System.alloc(layout) } + } + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + unsafe { System.dealloc(ptr, layout) } + } + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, size: usize) -> *mut u8 { + record_allocation(); + unsafe { System.realloc(ptr, layout, size) } + } +} +#[global_allocator] +static ALLOCATOR: CountingAllocator = CountingAllocator; +fn allocations(operation: impl FnOnce() -> T) -> (T, usize) { + ALLOCATIONS.with(|count| count.set(Some(0))); + let value = operation(); + let count = ALLOCATIONS.with(|count| count.replace(None).unwrap()); + (value, count) +} + +#[test] +fn string_enum_conversion_has_no_heap_allocation() { + let (value, count) = + allocations(|| KeyValue::from(black_box(myappattr::TaskStateAttr::Running))); + assert_eq!(black_box(count), 0); + assert_eq!(value.value, Value::String("running".into())); +} + +#[test] +fn static_string_construction_and_conversion_have_no_heap_allocation() { + let (value, count) = allocations(|| { + let attribute = myappattr::TaskIdAttr::from(black_box("static-task")); + black_box(KeyValue::from(attribute)) + }); + assert_eq!(black_box(count), 0); + assert_eq!(value.value, Value::String("static-task".into())); +} + +#[test] +fn exact_capacity_owned_string_moves_without_another_allocation() { + let mut text = String::from("owned-task"); + text.shrink_to_fit(); + let (value, count) = + allocations(|| KeyValue::from(myappattr::TaskIdAttr::from(black_box(text)))); + assert_eq!(black_box(count), 0); + assert_eq!(value.value, Value::String("owned-task".into())); +} + +#[test] +fn native_string_array_moves_without_another_allocation() { + let values = vec![opentelemetry::StringValue::from("static-tag")]; + let (_, count) = + allocations(|| KeyValue::from(myappattr::TaskTagsAttr::from(black_box(values)))); + assert_eq!(black_box(count), 0); +} diff --git a/templates/code/rust/tests/attributes/tests/log_values.rs b/templates/code/rust/tests/attributes/tests/log_values.rs new file mode 100644 index 0000000..e009bc3 --- /dev/null +++ b/templates/code/rust/tests/attributes/tests/log_values.rs @@ -0,0 +1,88 @@ +use generated_semconv::{authattr, myappattr}; +use opentelemetry::logs::AnyValue; + +#[test] +fn typed_attributes_convert_directly_to_log_values() { + let retries: AnyValue = myappattr::TaskRetriesAttr::from(4).into(); + assert_eq!(retries, AnyValue::Int(4)); + assert_eq!( + AnyValue::from(myappattr::TaskProgressAttr::from(0.5)), + AnyValue::Double(0.5) + ); + assert_eq!( + AnyValue::from(myappattr::TaskCancelledAttr::from(true)), + AnyValue::Boolean(true) + ); + assert_eq!( + AnyValue::from(myappattr::TaskStateAttr::Running), + AnyValue::String("running".into()) + ); + assert_eq!(AnyValue::from(authattr::LevelAttr::High), AnyValue::Int(2)); + assert_eq!( + AnyValue::from(myappattr::TaskPreemptibleAttr::Allowed), + AnyValue::Boolean(true) + ); + assert_eq!( + AnyValue::from(myappattr::TaskSampleRateAttr::Tenth), + AnyValue::Double(0.1) + ); +} + +#[test] +fn log_conversion_preserves_string_storage() { + for attribute in [ + myappattr::TaskIdAttr::from("static-task"), + myappattr::TaskIdAttr::from(String::from("owned-task")), + myappattr::TaskIdAttr::from(opentelemetry::StringValue::from( + std::sync::Arc::::from("shared-task"), + )), + ] { + let pointer = attribute.as_ref().as_ptr(); + let AnyValue::String(value) = AnyValue::from(attribute) else { + panic!("expected string log value"); + }; + assert_eq!(value.as_str().as_ptr(), pointer); + } +} + +#[test] +fn typed_arrays_export_log_lists_with_order_and_duplicates() { + assert_eq!( + AnyValue::from(myappattr::TaskShardIdsAttr::from(vec![1, 1, 2])), + AnyValue::ListAny(Box::new(vec![ + AnyValue::Int(1), + AnyValue::Int(1), + AnyValue::Int(2), + ])) + ); + assert_eq!( + AnyValue::from(myappattr::TaskWeightsAttr::from(vec![0.25, 0.75])), + AnyValue::ListAny(Box::new(vec![ + AnyValue::Double(0.25), + AnyValue::Double(0.75), + ])) + ); + assert_eq!( + AnyValue::from(myappattr::TaskFlagsAttr::from(vec![true, false])), + AnyValue::ListAny(Box::new(vec![ + AnyValue::Boolean(true), + AnyValue::Boolean(false), + ])) + ); + let tag = opentelemetry::StringValue::from(String::from("owned-tag")); + let pointer = tag.as_str().as_ptr(); + let AnyValue::ListAny(values) = AnyValue::from(myappattr::TaskTagsAttr::from(vec![tag])) else { + panic!("expected string log list"); + }; + let AnyValue::String(value) = &values[0] else { + panic!("expected string list element"); + }; + assert_eq!(value.as_str(), "owned-tag"); + assert_eq!(value.as_str().as_ptr(), pointer); + assert_eq!( + AnyValue::from(myappattr::TaskTagsAttr::from(Vec::< + opentelemetry::StringValue, + >::new())), + AnyValue::ListAny(Box::default()) + ); +} diff --git a/templates/code/rust/tests/codegen_exclusion/compile-fail/excluded_attribute.expected-error.txt b/templates/code/rust/tests/codegen_exclusion/compile-fail/excluded_attribute.expected-error.txt new file mode 100644 index 0000000..a78250c --- /dev/null +++ b/templates/code/rust/tests/codegen_exclusion/compile-fail/excluded_attribute.expected-error.txt @@ -0,0 +1 @@ +could not find `HiddenAttr` diff --git a/templates/code/rust/tests/codegen_exclusion/compile-fail/excluded_attribute.rs b/templates/code/rust/tests/codegen_exclusion/compile-fail/excluded_attribute.rs new file mode 100644 index 0000000..85f82cf --- /dev/null +++ b/templates/code/rust/tests/codegen_exclusion/compile-fail/excluded_attribute.rs @@ -0,0 +1,3 @@ +fn main() { + let _ = generated_semconv::demoattr::HiddenAttr::from("value"); +} diff --git a/templates/code/rust/tests/codegen_exclusion/compile-fail/excluded_member.expected-error.txt b/templates/code/rust/tests/codegen_exclusion/compile-fail/excluded_member.expected-error.txt new file mode 100644 index 0000000..9527579 --- /dev/null +++ b/templates/code/rust/tests/codegen_exclusion/compile-fail/excluded_member.expected-error.txt @@ -0,0 +1 @@ +named `Hidden` diff --git a/templates/code/rust/tests/codegen_exclusion/compile-fail/excluded_member.rs b/templates/code/rust/tests/codegen_exclusion/compile-fail/excluded_member.rs new file mode 100644 index 0000000..0dfab52 --- /dev/null +++ b/templates/code/rust/tests/codegen_exclusion/compile-fail/excluded_member.rs @@ -0,0 +1,3 @@ +fn main() { + let _ = generated_semconv::demoattr::ModeAttr::Hidden; +} diff --git a/templates/code/rust/tests/codegen_exclusion/expected/demoattr.rs b/templates/code/rust/tests/codegen_exclusion/expected/demoattr.rs new file mode 100644 index 0000000..cfd8c00 --- /dev/null +++ b/templates/code/rust/tests/codegen_exclusion/expected/demoattr.rs @@ -0,0 +1,117 @@ +// Generated by OpenTelemetry Weaver. +/// Synthetic kept attribute. +#[derive(Debug, Clone, PartialEq)] +pub struct KeptAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl KeptAttr { + pub const KEY: &'static str = "demo.kept"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for KeptAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for KeptAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for KeptAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for KeptAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for KeptAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: KeptAttr) -> Self { + ::opentelemetry::KeyValue::new(KeptAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: KeptAttr) -> Self { + value.0.into() + } +} + +/// Synthetic mode. +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum ModeAttr { + /// Included mode. + Kept, +} + +#[allow(deprecated)] +impl ModeAttr { + pub const KEY: &'static str = "demo.mode"; +} + +#[allow(deprecated)] +impl AsRef for ModeAttr { + #[inline] + fn as_ref(&self) -> &str { + match self { + ModeAttr::Kept => "kept", + } + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for ModeAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: ModeAttr) -> Self { + let value: &'static str = match value { + ModeAttr::Kept => "kept", + }; + ::opentelemetry::KeyValue::new(ModeAttr::KEY, value) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: ModeAttr) -> Self { + let value: &'static str = match value { + ModeAttr::Kept => "kept", + }; + value.into() + } +} diff --git a/templates/code/rust/tests/codegen_exclusion/expected/mod.rs b/templates/code/rust/tests/codegen_exclusion/expected/mod.rs new file mode 100644 index 0000000..162e747 --- /dev/null +++ b/templates/code/rust/tests/codegen_exclusion/expected/mod.rs @@ -0,0 +1,3 @@ +// Generated by OpenTelemetry Weaver. +pub const SCHEMA_URL: &str = "https://example.com/schemas/exclusion/1.0.0"; +pub mod demoattr; diff --git a/templates/code/rust/tests/codegen_exclusion/registry/manifest.yaml b/templates/code/rust/tests/codegen_exclusion/registry/manifest.yaml new file mode 100644 index 0000000..a20da06 --- /dev/null +++ b/templates/code/rust/tests/codegen_exclusion/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: exclusion +schema_url: https://example.com/schemas/exclusion/1.0.0 diff --git a/templates/code/rust/tests/codegen_exclusion/registry/model.yaml b/templates/code/rust/tests/codegen_exclusion/registry/model.yaml new file mode 100644 index 0000000..81a9fdc --- /dev/null +++ b/templates/code/rust/tests/codegen_exclusion/registry/model.yaml @@ -0,0 +1,35 @@ +file_format: definition/2 +attributes: + - key: demo.kept + type: string + stability: stable + brief: Synthetic kept attribute. + annotations: + code_generation: + exclude: false + - key: demo.hidden + type: string + stability: stable + brief: Synthetic excluded attribute. + annotations: + code_generation: + exclude: true + - key: demo.mode + type: + members: + - id: kept + value: kept + stability: stable + brief: Included mode. + annotations: + code_generation: + exclude: false + - id: hidden + value: hidden + stability: stable + brief: Excluded mode. + annotations: + code_generation: + exclude: true + stability: stable + brief: Synthetic mode. diff --git a/templates/code/rust/tests/codegen_exclusion/test.rs b/templates/code/rust/tests/codegen_exclusion/test.rs new file mode 100644 index 0000000..9ba5c22 --- /dev/null +++ b/templates/code/rust/tests/codegen_exclusion/test.rs @@ -0,0 +1,13 @@ +use generated_semconv::demoattr::{KeptAttr, ModeAttr}; +use opentelemetry::KeyValue; +#[test] +fn explicit_false_keeps_attributes_and_enum_members() { + assert_eq!( + KeyValue::from(KeptAttr::from("value")), + KeyValue::new("demo.kept", "value") + ); + assert_eq!( + KeyValue::from(ModeAttr::Kept), + KeyValue::new("demo.mode", "kept") + ); +} diff --git a/templates/code/rust/tests/entities/compile-fail/missing_identity.expected-error.txt b/templates/code/rust/tests/entities/compile-fail/missing_identity.expected-error.txt new file mode 100644 index 0000000..129f1cd --- /dev/null +++ b/templates/code/rust/tests/entities/compile-fail/missing_identity.expected-error.txt @@ -0,0 +1 @@ +this function takes 2 arguments but 1 argument was supplied diff --git a/templates/code/rust/tests/entities/compile-fail/missing_identity.rs b/templates/code/rust/tests/entities/compile-fail/missing_identity.rs new file mode 100644 index 0000000..86bfeb8 --- /dev/null +++ b/templates/code/rust/tests/entities/compile-fail/missing_identity.rs @@ -0,0 +1,5 @@ +use generated_semconv::myappentity::HostEntity; + +fn main() { + let _ = HostEntity::new([]); +} diff --git a/templates/code/rust/tests/entities/compile-fail/required_default.expected-error.txt b/templates/code/rust/tests/entities/compile-fail/required_default.expected-error.txt new file mode 100644 index 0000000..c16c206 --- /dev/null +++ b/templates/code/rust/tests/entities/compile-fail/required_default.expected-error.txt @@ -0,0 +1 @@ +no associated function or constant named `default` diff --git a/templates/code/rust/tests/entities/compile-fail/required_default.rs b/templates/code/rust/tests/entities/compile-fail/required_default.rs new file mode 100644 index 0000000..648643c --- /dev/null +++ b/templates/code/rust/tests/entities/compile-fail/required_default.rs @@ -0,0 +1,3 @@ +fn main() { + let _ = generated_semconv::myappentity::HostEntity::default(); +} diff --git a/templates/code/rust/tests/entities/compile-fail/wrong_identity.expected-error.txt b/templates/code/rust/tests/entities/compile-fail/wrong_identity.expected-error.txt new file mode 100644 index 0000000..68cf9b6 --- /dev/null +++ b/templates/code/rust/tests/entities/compile-fail/wrong_identity.expected-error.txt @@ -0,0 +1 @@ +expected `HostNameAttr`, found `QueueNameAttr` diff --git a/templates/code/rust/tests/entities/compile-fail/wrong_identity.rs b/templates/code/rust/tests/entities/compile-fail/wrong_identity.rs new file mode 100644 index 0000000..96041aa --- /dev/null +++ b/templates/code/rust/tests/entities/compile-fail/wrong_identity.rs @@ -0,0 +1,5 @@ +use generated_semconv::{myappattr, myappentity}; + +fn main() { + let _ = myappentity::HostEntity::new(myappattr::QueueNameAttr::from("queue-1"), []); +} diff --git a/templates/code/rust/tests/entities/expected/mod.rs b/templates/code/rust/tests/entities/expected/mod.rs new file mode 100644 index 0000000..bab3903 --- /dev/null +++ b/templates/code/rust/tests/entities/expected/mod.rs @@ -0,0 +1,5 @@ +// Generated by OpenTelemetry Weaver. +pub const SCHEMA_URL: &str = "https://example.com/schemas/entities/1.0.0"; +pub mod myappattr; + +pub mod myappentity; diff --git a/templates/code/rust/tests/entities/expected/myappattr.rs b/templates/code/rust/tests/entities/expected/myappattr.rs new file mode 100644 index 0000000..67e72ed --- /dev/null +++ b/templates/code/rust/tests/entities/expected/myappattr.rs @@ -0,0 +1,335 @@ +// Generated by OpenTelemetry Weaver. +/// Number of CPUs the host has. +#[deprecated(note = "Use `myapp.host.cpu.logical_count` instead.")] +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct HostCpuCountAttr(i64); + +#[allow(deprecated)] +impl HostCpuCountAttr { + pub const KEY: &'static str = "myapp.host.cpu.count"; +} + +#[allow(deprecated)] +impl From for HostCpuCountAttr { + #[inline] + fn from(value: i64) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for HostCpuCountAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::std::fmt::Display::fmt(&self.0, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: HostCpuCountAttr) -> Self { + ::opentelemetry::KeyValue::new(HostCpuCountAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: HostCpuCountAttr) -> Self { + value.0.into() + } +} + +/// Name of the host. +#[derive(Debug, Clone, PartialEq)] +pub struct HostNameAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl HostNameAttr { + pub const KEY: &'static str = "myapp.host.name"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for HostNameAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for HostNameAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for HostNameAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for HostNameAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for HostNameAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: HostNameAttr) -> Self { + ::opentelemetry::KeyValue::new(HostNameAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: HostNameAttr) -> Self { + value.0.into() + } +} + +/// Role the host plays in the cluster. +#[derive(Debug, Clone, PartialEq)] +pub struct HostRoleAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl HostRoleAttr { + pub const KEY: &'static str = "myapp.host.role"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for HostRoleAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for HostRoleAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for HostRoleAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for HostRoleAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for HostRoleAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: HostRoleAttr) -> Self { + ::opentelemetry::KeyValue::new(HostRoleAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: HostRoleAttr) -> Self { + value.0.into() + } +} + +/// Type of the host. +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum HostTypeAttr { + /// A physical host. + Physical, + /// A virtual host. + Virtual, +} + +#[allow(deprecated)] +impl HostTypeAttr { + pub const KEY: &'static str = "myapp.host.type"; +} + +#[allow(deprecated)] +impl AsRef for HostTypeAttr { + #[inline] + fn as_ref(&self) -> &str { + match self { + HostTypeAttr::Physical => "physical", + HostTypeAttr::Virtual => "virtual", + } + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for HostTypeAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: HostTypeAttr) -> Self { + let value: &'static str = match value { + HostTypeAttr::Physical => "physical", + HostTypeAttr::Virtual => "virtual", + }; + ::opentelemetry::KeyValue::new(HostTypeAttr::KEY, value) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: HostTypeAttr) -> Self { + let value: &'static str = match value { + HostTypeAttr::Physical => "physical", + HostTypeAttr::Virtual => "virtual", + }; + value.into() + } +} + +/// Name of the queue. +#[derive(Debug, Clone, PartialEq)] +pub struct QueueNameAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl QueueNameAttr { + pub const KEY: &'static str = "myapp.queue.name"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for QueueNameAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for QueueNameAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for QueueNameAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for QueueNameAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for QueueNameAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: QueueNameAttr) -> Self { + ::opentelemetry::KeyValue::new(QueueNameAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: QueueNameAttr) -> Self { + value.0.into() + } +} + +/// Number of items currently in the queue. +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct QueueSizeAttr(i64); + +#[allow(deprecated)] +impl QueueSizeAttr { + pub const KEY: &'static str = "myapp.queue.size"; +} + +#[allow(deprecated)] +impl From for QueueSizeAttr { + #[inline] + fn from(value: i64) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for QueueSizeAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::std::fmt::Display::fmt(&self.0, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: QueueSizeAttr) -> Self { + ::opentelemetry::KeyValue::new(QueueSizeAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: QueueSizeAttr) -> Self { + value.0.into() + } +} diff --git a/templates/code/rust/tests/entities/expected/myappentity.rs b/templates/code/rust/tests/entities/expected/myappentity.rs new file mode 100644 index 0000000..4971fe9 --- /dev/null +++ b/templates/code/rust/tests/entities/expected/myappentity.rs @@ -0,0 +1,199 @@ +// Code generated by weaver. DO NOT EDIT. +#![allow(deprecated)] +#[derive(Clone, Debug)] +pub enum HostEntityAttr { + /// Opt-in: the convention records it only when a user asks for it. + HostCpuCount(super::myappattr::HostCpuCountAttr), + HostType(super::myappattr::HostTypeAttr), +} + +impl From for ::opentelemetry::KeyValue { + fn from(value: HostEntityAttr) -> Self { + match value { + HostEntityAttr::HostCpuCount(value) => ::opentelemetry::KeyValue::from(value), + HostEntityAttr::HostType(value) => ::opentelemetry::KeyValue::from(value), + } + } +} +/// A host running the application. +/// +/// Identified by its name across its lifetime. +/// Identifies the entity: `myapp.host.name`. +/// Describes the entity: `myapp.host.cpu.count`, `myapp.host.type`. +#[derive(Clone, Debug)] +pub struct HostEntity { + r#myapp_host_name: super::myappattr::HostNameAttr, + options: ::smallvec::SmallVec<[HostEntityAttr; 2]>, +} + +impl HostEntity { + pub fn new(r#myapp_host_name: super::myappattr::HostNameAttr, options: impl IntoIterator) -> Self { + Self { + r#myapp_host_name, + options: options.into_iter().collect(), + } + } + + pub fn attributes(&self) -> impl Iterator + '_ { + ::std::iter::empty() + .chain(::std::iter::once_with(move || ::opentelemetry::KeyValue::from(self.r#myapp_host_name.clone()))) + .chain(self.options.iter().cloned().map(::opentelemetry::KeyValue::from)) + } +} + +impl From for ::opentelemetry_sdk::Resource { + fn from(value: HostEntity) -> Self { + let attributes = [ + ::opentelemetry::KeyValue::from(value.r#myapp_host_name), + ].into_iter().chain(value.options.into_iter().map(::opentelemetry::KeyValue::from)); + ::opentelemetry_sdk::Resource::builder_empty() + .with_schema_url(attributes, super::SCHEMA_URL) + .build() + } +} +#[derive(Clone, Debug)] +pub enum HostWorkerEntityAttr { + /// Opt-in: the convention records it only when a user asks for it. + HostCpuCount(super::myappattr::HostCpuCountAttr), + HostType(super::myappattr::HostTypeAttr), +} + +impl From for ::opentelemetry::KeyValue { + fn from(value: HostWorkerEntityAttr) -> Self { + match value { + HostWorkerEntityAttr::HostCpuCount(value) => ::opentelemetry::KeyValue::from(value), + HostWorkerEntityAttr::HostType(value) => ::opentelemetry::KeyValue::from(value), + } + } +} +/// A host dedicated to running worker processes. +/// +/// Identified by its name across its lifetime. +/// Identifies the entity: `myapp.host.name`. +/// Describes the entity: `myapp.host.cpu.count`, `myapp.host.role`, `myapp.host.type`. +/// Refines `myapp.host`. +#[derive(Clone, Debug)] +pub struct HostWorkerEntity { + r#myapp_host_name: super::myappattr::HostNameAttr, + r#myapp_host_role: super::myappattr::HostRoleAttr, + options: ::smallvec::SmallVec<[HostWorkerEntityAttr; 2]>, +} + +impl HostWorkerEntity { + pub fn new(r#myapp_host_name: super::myappattr::HostNameAttr, r#myapp_host_role: super::myappattr::HostRoleAttr, options: impl IntoIterator) -> Self { + Self { + r#myapp_host_name, + r#myapp_host_role, + options: options.into_iter().collect(), + } + } + + pub fn attributes(&self) -> impl Iterator + '_ { + ::std::iter::empty() + .chain(::std::iter::once_with(move || ::opentelemetry::KeyValue::from(self.r#myapp_host_name.clone()))) + .chain(::std::iter::once_with(move || ::opentelemetry::KeyValue::from(self.r#myapp_host_role.clone()))) + .chain(self.options.iter().cloned().map(::opentelemetry::KeyValue::from)) + } +} + +impl From for ::opentelemetry_sdk::Resource { + fn from(value: HostWorkerEntity) -> Self { + let attributes = [ + ::opentelemetry::KeyValue::from(value.r#myapp_host_name), + ::opentelemetry::KeyValue::from(value.r#myapp_host_role), + ].into_iter().chain(value.options.into_iter().map(::opentelemetry::KeyValue::from)); + ::opentelemetry_sdk::Resource::builder_empty() + .with_schema_url(attributes, super::SCHEMA_URL) + .build() + } +} +#[derive(Clone, Debug)] +pub enum OptionalEntityAttr { + QueueName(super::myappattr::QueueNameAttr), + QueueSize(super::myappattr::QueueSizeAttr), +} + +impl From for ::opentelemetry::KeyValue { + fn from(value: OptionalEntityAttr) -> Self { + match value { + OptionalEntityAttr::QueueName(value) => ::opentelemetry::KeyValue::from(value), + OptionalEntityAttr::QueueSize(value) => ::opentelemetry::KeyValue::from(value), + } + } +} +/// An entity with optional description only. +/// Identifies the entity: `myapp.queue.name`. +/// Describes the entity: `myapp.queue.size`. +#[derive(Clone, Debug, Default)] +pub struct OptionalEntity { + options: ::smallvec::SmallVec<[OptionalEntityAttr; 2]>, +} + +impl OptionalEntity { + pub fn new(options: impl IntoIterator) -> Self { + Self { + options: options.into_iter().collect(), + } + } + + pub fn attributes(&self) -> impl Iterator + '_ { + ::std::iter::empty() + .chain(self.options.iter().cloned().map(::opentelemetry::KeyValue::from)) + } +} + +impl From for ::opentelemetry_sdk::Resource { + fn from(value: OptionalEntity) -> Self { + let attributes = [ + ].into_iter().chain(value.options.into_iter().map(::opentelemetry::KeyValue::from)); + ::opentelemetry_sdk::Resource::builder_empty() + .with_schema_url(attributes, super::SCHEMA_URL) + .build() + } +} +#[derive(Clone, Debug)] +pub enum QueueEntityAttr { + QueueSize(super::myappattr::QueueSizeAttr), +} + +impl From for ::opentelemetry::KeyValue { + fn from(value: QueueEntityAttr) -> Self { + match value { + QueueEntityAttr::QueueSize(value) => ::opentelemetry::KeyValue::from(value), + } + } +} +/// A queue of tasks. +/// Identifies the entity: `myapp.queue.name`. +/// Describes the entity: `myapp.queue.size`. +#[derive(Clone, Debug)] +pub struct QueueEntity { + r#myapp_queue_name: super::myappattr::QueueNameAttr, + options: ::smallvec::SmallVec<[QueueEntityAttr; 1]>, +} + +impl QueueEntity { + pub fn new(r#myapp_queue_name: super::myappattr::QueueNameAttr, options: impl IntoIterator) -> Self { + Self { + r#myapp_queue_name, + options: options.into_iter().collect(), + } + } + + pub fn attributes(&self) -> impl Iterator + '_ { + ::std::iter::empty() + .chain(::std::iter::once_with(move || ::opentelemetry::KeyValue::from(self.r#myapp_queue_name.clone()))) + .chain(self.options.iter().cloned().map(::opentelemetry::KeyValue::from)) + } +} + +impl From for ::opentelemetry_sdk::Resource { + fn from(value: QueueEntity) -> Self { + let attributes = [ + ::opentelemetry::KeyValue::from(value.r#myapp_queue_name), + ].into_iter().chain(value.options.into_iter().map(::opentelemetry::KeyValue::from)); + ::opentelemetry_sdk::Resource::builder_empty() + .with_schema_url(attributes, super::SCHEMA_URL) + .build() + } +} diff --git a/templates/code/rust/tests/entities/registry/manifest.yaml b/templates/code/rust/tests/entities/registry/manifest.yaml new file mode 100644 index 0000000..60bfc60 --- /dev/null +++ b/templates/code/rust/tests/entities/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: entities +schema_url: https://example.com/schemas/entities/1.0.0 diff --git a/templates/code/rust/tests/entities/registry/model.yaml b/templates/code/rust/tests/entities/registry/model.yaml new file mode 100644 index 0000000..a9e9417 --- /dev/null +++ b/templates/code/rust/tests/entities/registry/model.yaml @@ -0,0 +1,84 @@ +file_format: definition/2 +attributes: + - key: myapp.host.name + type: string + stability: stable + brief: Name of the host. + examples: ["host-1"] + - key: myapp.host.type + type: + members: + - id: physical + value: physical + stability: stable + brief: A physical host. + - id: virtual + value: virtual + stability: stable + brief: A virtual host. + stability: stable + brief: Type of the host. + - key: myapp.host.cpu.count + type: int + stability: development + brief: Number of CPUs the host has. + examples: 4 + deprecated: + reason: uncategorized + note: Use `myapp.host.cpu.logical_count` instead. + - key: myapp.host.role + type: string + stability: stable + brief: Role the host plays in the cluster. + examples: ["worker"] + - key: myapp.queue.name + type: string + stability: stable + brief: Name of the queue. + examples: ["reports"] + - key: myapp.queue.size + type: int + stability: stable + brief: Number of items currently in the queue. + examples: 3 +entities: + - type: myapp.host + requirement_level: recommended + stability: stable + brief: A host running the application. + note: Identified by its name across its lifetime. + identity: + - ref: myapp.host.name + requirement_level: required + description: + - ref: myapp.host.type + requirement_level: recommended + - ref: myapp.host.cpu.count + requirement_level: opt_in + - type: myapp.queue + requirement_level: recommended + stability: stable + brief: A queue of tasks. + identity: + - ref: myapp.queue.name + requirement_level: required + description: + - ref: myapp.queue.size + - type: myapp.optional + requirement_level: recommended + stability: stable + brief: An entity with optional description only. + identity: + - ref: myapp.queue.name + requirement_level: recommended + description: + - ref: myapp.queue.size + requirement_level: recommended +entity_refinements: + - id: myapp.host.worker + ref: myapp.host + stability: stable + brief: A host dedicated to running worker processes. + description: + - ref: myapp.host.role + requirement_level: required diff --git a/templates/code/rust/tests/entities/test.rs b/templates/code/rust/tests/entities/test.rs new file mode 100644 index 0000000..71ad6be --- /dev/null +++ b/templates/code/rust/tests/entities/test.rs @@ -0,0 +1,87 @@ +use generated_semconv::{myappattr, myappentity, SCHEMA_URL}; +use opentelemetry::{Key, Value}; +use opentelemetry_sdk::Resource; + +#[test] +fn entity_exports_identity_description_and_schema() { + let entity = myappentity::HostEntity::new( + myappattr::HostNameAttr::from("worker-1"), + [myappentity::HostEntityAttr::HostType( + myappattr::HostTypeAttr::Virtual, + )], + ); + let attributes: Vec<_> = entity.attributes().collect(); + assert_eq!(attributes.len(), 2); + assert_eq!(attributes[0].key.as_str(), "myapp.host.name"); + assert_eq!(attributes[0].value.as_str(), "worker-1"); + assert_eq!(attributes[1].value.as_str(), "virtual"); + assert_eq!(entity.attributes().count(), 2); + let resource = Resource::from(entity); + assert_eq!(resource.schema_url(), Some(SCHEMA_URL)); + assert_eq!(resource.len(), 2); +} + +#[test] +fn refinement_requires_its_description_attribute() { + let entity = myappentity::HostWorkerEntity::new( + myappattr::HostNameAttr::from("worker-2"), + myappattr::HostRoleAttr::from("worker"), + [], + ); + assert_eq!(entity.attributes().count(), 2); + assert_eq!( + entity.attributes().nth(1).unwrap().key.as_str(), + "myapp.host.role" + ); + assert_eq!(Resource::from(entity).len(), 2); +} + +#[test] +fn consuming_export_preserves_owned_string_storage() { + let mut owned = String::from("dynamic-worker"); + owned.shrink_to_fit(); + let original = owned.as_ptr(); + let entity = myappentity::HostEntity::new(myappattr::HostNameAttr::from(owned), []); + let resource: Resource = entity.into(); + let value = resource + .iter() + .find(|(key, _)| **key == Key::from("myapp.host.name")) + .unwrap() + .1; + let Value::String(text) = value else { + panic!("expected string"); + }; + assert_eq!(text.as_str().as_ptr(), original); +} + +#[test] +fn default_is_available_when_no_attributes_are_required() { + let empty = myappentity::OptionalEntity::default(); + assert_eq!(empty.attributes().count(), 0); + let resource = Resource::from(empty); + assert_eq!(resource.len(), 0); + assert_eq!(resource.schema_url(), Some(SCHEMA_URL)); +} + +#[test] +fn optional_attribute_overflow_preserves_values_and_order() { + let options = (0..15).map(|index| { + myappentity::HostEntityAttr::HostType(if index == 14 { + myappattr::HostTypeAttr::Physical + } else { + myappattr::HostTypeAttr::Virtual + }) + }); + let entity = myappentity::HostEntity::new(myappattr::HostNameAttr::from("overflow"), options); + let attributes: Vec<_> = entity.attributes().collect(); + assert_eq!(attributes.len(), 16); + assert_eq!( + attributes.last().unwrap().value, + Value::String("physical".into()) + ); + let resource: Resource = entity.into(); + assert_eq!( + resource.get(&Key::from("myapp.host.type")), + Some(Value::String("physical".into())) + ); +} diff --git a/templates/code/rust/tests/error_aggregation_counter/expected-error.txt b/templates/code/rust/tests/error_aggregation_counter/expected-error.txt new file mode 100644 index 0000000..2190d30 --- /dev/null +++ b/templates/code/rust/tests/error_aggregation_counter/expected-error.txt @@ -0,0 +1 @@ +metric myapp.task.started sets annotations.aggregation, which the Rust template supports only on a histogram diff --git a/templates/code/rust/tests/error_aggregation_counter/registry/manifest.yaml b/templates/code/rust/tests/error_aggregation_counter/registry/manifest.yaml new file mode 100644 index 0000000..8fdff1b --- /dev/null +++ b/templates/code/rust/tests/error_aggregation_counter/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: error_aggregation_counter +schema_url: https://example.com/schemas/error_aggregation_counter/1.0.0 diff --git a/templates/code/rust/tests/error_aggregation_counter/registry/myapp.yaml b/templates/code/rust/tests/error_aggregation_counter/registry/myapp.yaml new file mode 100644 index 0000000..900b1ec --- /dev/null +++ b/templates/code/rust/tests/error_aggregation_counter/registry/myapp.yaml @@ -0,0 +1,15 @@ +file_format: definition/2 +metrics: + - name: myapp.task.started + requirement_level: recommended + instrument: counter + unit: "{task}" + stability: stable + brief: Number of tasks started. + annotations: + code_generation: + metric_value_type: int + aggregation: + method: explicithistogram + parameters: + boundaries: [1, 10] diff --git a/templates/code/rust/tests/error_aggregation_method/expected-error.txt b/templates/code/rust/tests/error_aggregation_method/expected-error.txt new file mode 100644 index 0000000..3f55af1 --- /dev/null +++ b/templates/code/rust/tests/error_aggregation_method/expected-error.txt @@ -0,0 +1 @@ +metric myapp.task.duration sets annotations.aggregation.method to histogram, but the Rust template supports only explicithistogram diff --git a/templates/code/rust/tests/error_aggregation_method/registry/manifest.yaml b/templates/code/rust/tests/error_aggregation_method/registry/manifest.yaml new file mode 100644 index 0000000..462f08c --- /dev/null +++ b/templates/code/rust/tests/error_aggregation_method/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: error_aggregation_method +schema_url: https://example.com/schemas/error_aggregation_method/1.0.0 diff --git a/templates/code/rust/tests/error_aggregation_method/registry/myapp.yaml b/templates/code/rust/tests/error_aggregation_method/registry/myapp.yaml new file mode 100644 index 0000000..15edd9e --- /dev/null +++ b/templates/code/rust/tests/error_aggregation_method/registry/myapp.yaml @@ -0,0 +1,13 @@ +file_format: definition/2 +metrics: + - name: myapp.task.duration + requirement_level: recommended + instrument: histogram + unit: s + stability: stable + brief: Time a task took from start to finish. + annotations: + code_generation: + metric_value_type: double + aggregation: + method: histogram diff --git a/templates/code/rust/tests/error_aggregation_unsorted/expected-error.txt b/templates/code/rust/tests/error_aggregation_unsorted/expected-error.txt new file mode 100644 index 0000000..31ba0b3 --- /dev/null +++ b/templates/code/rust/tests/error_aggregation_unsorted/expected-error.txt @@ -0,0 +1 @@ +metric myapp.task.duration must list annotations.aggregation.parameters.boundaries in strictly increasing order diff --git a/templates/code/rust/tests/error_aggregation_unsorted/registry/manifest.yaml b/templates/code/rust/tests/error_aggregation_unsorted/registry/manifest.yaml new file mode 100644 index 0000000..3c055b4 --- /dev/null +++ b/templates/code/rust/tests/error_aggregation_unsorted/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: error_aggregation_unsorted +schema_url: https://example.com/schemas/error_aggregation_unsorted/1.0.0 diff --git a/templates/code/rust/tests/error_aggregation_unsorted/registry/myapp.yaml b/templates/code/rust/tests/error_aggregation_unsorted/registry/myapp.yaml new file mode 100644 index 0000000..243ba97 --- /dev/null +++ b/templates/code/rust/tests/error_aggregation_unsorted/registry/myapp.yaml @@ -0,0 +1,15 @@ +file_format: definition/2 +metrics: + - name: myapp.task.duration + requirement_level: recommended + instrument: histogram + unit: s + stability: stable + brief: Time a task took from start to finish. + annotations: + code_generation: + metric_value_type: double + aggregation: + method: explicithistogram + parameters: + boundaries: [0.1, 1, 1, 0.5] diff --git a/templates/code/rust/tests/error_attribute_collision/expected-error.txt b/templates/code/rust/tests/error_attribute_collision/expected-error.txt new file mode 100644 index 0000000..35ed613 --- /dev/null +++ b/templates/code/rust/tests/error_attribute_collision/expected-error.txt @@ -0,0 +1 @@ +attributes that render to the same Rust identifier diff --git a/templates/code/rust/tests/error_attribute_collision/registry/manifest.yaml b/templates/code/rust/tests/error_attribute_collision/registry/manifest.yaml new file mode 100644 index 0000000..5bddc29 --- /dev/null +++ b/templates/code/rust/tests/error_attribute_collision/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: demo +schema_url: https://example.com/schemas/1.0.0 diff --git a/templates/code/rust/tests/error_attribute_collision/registry/model.yaml b/templates/code/rust/tests/error_attribute_collision/registry/model.yaml new file mode 100644 index 0000000..8a0c9dd --- /dev/null +++ b/templates/code/rust/tests/error_attribute_collision/registry/model.yaml @@ -0,0 +1,10 @@ +file_format: definition/2 +attributes: + - key: demo.foo_bar + stability: stable + brief: Synthetic attribute. + type: string + - key: demo.foo.bar + stability: stable + brief: Synthetic attribute. + type: string diff --git a/templates/code/rust/tests/error_empty_name/expected-error.txt b/templates/code/rust/tests/error_empty_name/expected-error.txt new file mode 100644 index 0000000..e043b8a --- /dev/null +++ b/templates/code/rust/tests/error_empty_name/expected-error.txt @@ -0,0 +1 @@ +has no name after its namespace diff --git a/templates/code/rust/tests/error_empty_name/registry/manifest.yaml b/templates/code/rust/tests/error_empty_name/registry/manifest.yaml new file mode 100644 index 0000000..5bddc29 --- /dev/null +++ b/templates/code/rust/tests/error_empty_name/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: demo +schema_url: https://example.com/schemas/1.0.0 diff --git a/templates/code/rust/tests/error_empty_name/registry/model.yaml b/templates/code/rust/tests/error_empty_name/registry/model.yaml new file mode 100644 index 0000000..75c93c0 --- /dev/null +++ b/templates/code/rust/tests/error_empty_name/registry/model.yaml @@ -0,0 +1,6 @@ +file_format: definition/2 +attributes: + - key: demo + stability: stable + brief: Synthetic attribute. + type: string diff --git a/templates/code/rust/tests/error_enum_collision/expected-error.txt b/templates/code/rust/tests/error_enum_collision/expected-error.txt new file mode 100644 index 0000000..1263050 --- /dev/null +++ b/templates/code/rust/tests/error_enum_collision/expected-error.txt @@ -0,0 +1 @@ +enum members that render to the same Rust identifier diff --git a/templates/code/rust/tests/error_enum_collision/registry/manifest.yaml b/templates/code/rust/tests/error_enum_collision/registry/manifest.yaml new file mode 100644 index 0000000..5bddc29 --- /dev/null +++ b/templates/code/rust/tests/error_enum_collision/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: demo +schema_url: https://example.com/schemas/1.0.0 diff --git a/templates/code/rust/tests/error_enum_collision/registry/model.yaml b/templates/code/rust/tests/error_enum_collision/registry/model.yaml new file mode 100644 index 0000000..a133af8 --- /dev/null +++ b/templates/code/rust/tests/error_enum_collision/registry/model.yaml @@ -0,0 +1,13 @@ +file_format: definition/2 +attributes: + - key: demo.state + stability: stable + brief: Synthetic attribute. + type: + members: + - id: foo_bar + value: a + brief: First. + - id: foo.bar + value: b + brief: Next. diff --git a/templates/code/rust/tests/error_enum_keyword/expected-error.txt b/templates/code/rust/tests/error_enum_keyword/expected-error.txt new file mode 100644 index 0000000..57952d5 --- /dev/null +++ b/templates/code/rust/tests/error_enum_keyword/expected-error.txt @@ -0,0 +1 @@ +becomes a Rust keyword diff --git a/templates/code/rust/tests/error_enum_keyword/registry/manifest.yaml b/templates/code/rust/tests/error_enum_keyword/registry/manifest.yaml new file mode 100644 index 0000000..5bddc29 --- /dev/null +++ b/templates/code/rust/tests/error_enum_keyword/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: demo +schema_url: https://example.com/schemas/1.0.0 diff --git a/templates/code/rust/tests/error_enum_keyword/registry/model.yaml b/templates/code/rust/tests/error_enum_keyword/registry/model.yaml new file mode 100644 index 0000000..10d14e2 --- /dev/null +++ b/templates/code/rust/tests/error_enum_keyword/registry/model.yaml @@ -0,0 +1,10 @@ +file_format: definition/2 +attributes: + - key: demo.state + stability: stable + brief: Synthetic attribute. + type: + members: + - id: self + value: self + brief: Self. diff --git a/templates/code/rust/tests/error_enum_without_members/expected-error.txt b/templates/code/rust/tests/error_enum_without_members/expected-error.txt new file mode 100644 index 0000000..e7b5177 --- /dev/null +++ b/templates/code/rust/tests/error_enum_without_members/expected-error.txt @@ -0,0 +1 @@ +has no enum members left after filtering diff --git a/templates/code/rust/tests/error_enum_without_members/params.yaml b/templates/code/rust/tests/error_enum_without_members/params.yaml new file mode 100644 index 0000000..7606ce0 --- /dev/null +++ b/templates/code/rust/tests/error_enum_without_members/params.yaml @@ -0,0 +1,2 @@ +params: + stable_only: true diff --git a/templates/code/rust/tests/error_enum_without_members/registry/manifest.yaml b/templates/code/rust/tests/error_enum_without_members/registry/manifest.yaml new file mode 100644 index 0000000..5bddc29 --- /dev/null +++ b/templates/code/rust/tests/error_enum_without_members/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: demo +schema_url: https://example.com/schemas/1.0.0 diff --git a/templates/code/rust/tests/error_enum_without_members/registry/model.yaml b/templates/code/rust/tests/error_enum_without_members/registry/model.yaml new file mode 100644 index 0000000..33d5241 --- /dev/null +++ b/templates/code/rust/tests/error_enum_without_members/registry/model.yaml @@ -0,0 +1,11 @@ +file_format: definition/2 +attributes: + - key: myapp.task.state + type: + members: + - id: queued + value: queued + stability: development + brief: The task is waiting to run. + stability: stable + brief: Current state of the task. diff --git a/templates/code/rust/tests/error_event_collision/expected-error.txt b/templates/code/rust/tests/error_event_collision/expected-error.txt new file mode 100644 index 0000000..5fd8cbf --- /dev/null +++ b/templates/code/rust/tests/error_event_collision/expected-error.txt @@ -0,0 +1 @@ +package myappevent has events that render to the same Rust identifier: myapp.task.finished and myapp.task_finished diff --git a/templates/code/rust/tests/error_event_collision/registry/manifest.yaml b/templates/code/rust/tests/error_event_collision/registry/manifest.yaml new file mode 100644 index 0000000..ff89b1c --- /dev/null +++ b/templates/code/rust/tests/error_event_collision/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: error_event_collision +schema_url: https://example.com/schemas/error_event_collision/1.0.0 diff --git a/templates/code/rust/tests/error_event_collision/registry/myapp.yaml b/templates/code/rust/tests/error_event_collision/registry/myapp.yaml new file mode 100644 index 0000000..19927c1 --- /dev/null +++ b/templates/code/rust/tests/error_event_collision/registry/myapp.yaml @@ -0,0 +1,10 @@ +file_format: definition/2 +events: + - name: myapp.task.finished + requirement_level: recommended + stability: stable + brief: A task finished. + - name: myapp.task_finished + requirement_level: recommended + stability: stable + brief: A task finished, under another name. diff --git a/templates/code/rust/tests/error_event_option_collision/expected-error.txt b/templates/code/rust/tests/error_event_option_collision/expected-error.txt new file mode 100644 index 0000000..4cc16a3 --- /dev/null +++ b/templates/code/rust/tests/error_event_option_collision/expected-error.txt @@ -0,0 +1 @@ +package exceptionevent has events that render to the same Rust identifier diff --git a/templates/code/rust/tests/error_event_option_collision/registry/manifest.yaml b/templates/code/rust/tests/error_event_option_collision/registry/manifest.yaml new file mode 100644 index 0000000..9f878f4 --- /dev/null +++ b/templates/code/rust/tests/error_event_option_collision/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: event_option_collision +schema_url: https://example.com/schemas/event-option-collision/1.0.0 diff --git a/templates/code/rust/tests/error_event_option_collision/registry/model.yaml b/templates/code/rust/tests/error_event_option_collision/registry/model.yaml new file mode 100644 index 0000000..d4fb4a2 --- /dev/null +++ b/templates/code/rust/tests/error_event_option_collision/registry/model.yaml @@ -0,0 +1,10 @@ +file_format: definition/2 +events: + - name: exception + stability: stable + requirement_level: recommended + brief: A synthetic exception occurred. + - name: exception.event + stability: stable + requirement_level: recommended + brief: Another synthetic exception occurred. diff --git a/templates/code/rust/tests/error_event_required_collision/expected-error.txt b/templates/code/rust/tests/error_event_required_collision/expected-error.txt new file mode 100644 index 0000000..6fdf75b --- /dev/null +++ b/templates/code/rust/tests/error_event_required_collision/expected-error.txt @@ -0,0 +1 @@ +event myapp.task.finished has required attributes that render to the same Rust parameter: myapp.task.id and myapp.task_id diff --git a/templates/code/rust/tests/error_event_required_collision/registry/manifest.yaml b/templates/code/rust/tests/error_event_required_collision/registry/manifest.yaml new file mode 100644 index 0000000..1ff0d75 --- /dev/null +++ b/templates/code/rust/tests/error_event_required_collision/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: error_event_required_collision +schema_url: https://example.com/schemas/error_event_required_collision/1.0.0 diff --git a/templates/code/rust/tests/error_event_required_collision/registry/myapp.yaml b/templates/code/rust/tests/error_event_required_collision/registry/myapp.yaml new file mode 100644 index 0000000..bac7b4a --- /dev/null +++ b/templates/code/rust/tests/error_event_required_collision/registry/myapp.yaml @@ -0,0 +1,22 @@ +file_format: definition/2 +attributes: + - key: myapp.task.id + type: string + stability: stable + brief: Unique identifier of the task. + examples: ["task_0001"] + - key: myapp.task_id + type: string + stability: stable + brief: Unique identifier of the task, under another name. + examples: ["task_0001"] +events: + - name: myapp.task.finished + requirement_level: recommended + stability: stable + brief: A task finished. + attributes: + - ref: myapp.task.id + requirement_level: required + - ref: myapp.task_id + requirement_level: required diff --git a/templates/code/rust/tests/error_event_span_context_shadow/expected-error.txt b/templates/code/rust/tests/error_event_span_context_shadow/expected-error.txt new file mode 100644 index 0000000..26c139c --- /dev/null +++ b/templates/code/rust/tests/error_event_span_context_shadow/expected-error.txt @@ -0,0 +1 @@ +event probe.recorded has required attributes whose Rust parameter would shadow a name its Emit function declares: span.context diff --git a/templates/code/rust/tests/error_event_span_context_shadow/registry/manifest.yaml b/templates/code/rust/tests/error_event_span_context_shadow/registry/manifest.yaml new file mode 100644 index 0000000..8fc6c74 --- /dev/null +++ b/templates/code/rust/tests/error_event_span_context_shadow/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: synthetic-event-shadow +schema_url: https://example.com/schemas/event-shadow/1.0.0 diff --git a/templates/code/rust/tests/error_event_span_context_shadow/registry/model.yaml b/templates/code/rust/tests/error_event_span_context_shadow/registry/model.yaml new file mode 100644 index 0000000..abc08ac --- /dev/null +++ b/templates/code/rust/tests/error_event_span_context_shadow/registry/model.yaml @@ -0,0 +1,15 @@ +file_format: definition/2 +attributes: + - key: span.context + type: int + stability: stable + brief: A synthetic numeric span context attribute. + examples: 1 +events: + - name: probe.recorded + requirement_level: recommended + stability: stable + brief: A synthetic record event. + attributes: + - ref: span.context + requirement_level: required diff --git a/templates/code/rust/tests/error_event_span_shadow/dependency/manifest.yaml b/templates/code/rust/tests/error_event_span_shadow/dependency/manifest.yaml new file mode 100644 index 0000000..489cb50 --- /dev/null +++ b/templates/code/rust/tests/error_event_span_shadow/dependency/manifest.yaml @@ -0,0 +1,2 @@ +name: synthetic-upstream +schema_url: https://example.com/schemas/upstream/1.0.0 diff --git a/templates/code/rust/tests/error_event_span_shadow/dependency/model.yaml b/templates/code/rust/tests/error_event_span_shadow/dependency/model.yaml new file mode 100644 index 0000000..1a2c51e --- /dev/null +++ b/templates/code/rust/tests/error_event_span_shadow/dependency/model.yaml @@ -0,0 +1,7 @@ +file_format: definition/2 +attributes: + - key: span + type: int + stability: stable + brief: A synthetic numeric span attribute. + examples: 1 diff --git a/templates/code/rust/tests/error_event_span_shadow/expected-error.txt b/templates/code/rust/tests/error_event_span_shadow/expected-error.txt new file mode 100644 index 0000000..245b538 --- /dev/null +++ b/templates/code/rust/tests/error_event_span_shadow/expected-error.txt @@ -0,0 +1 @@ +event probe.recorded has required attributes whose Rust parameter would shadow a name its Emit function declares: span diff --git a/templates/code/rust/tests/error_event_span_shadow/registry/manifest.yaml b/templates/code/rust/tests/error_event_span_shadow/registry/manifest.yaml new file mode 100644 index 0000000..cd94a67 --- /dev/null +++ b/templates/code/rust/tests/error_event_span_shadow/registry/manifest.yaml @@ -0,0 +1,6 @@ +name: synthetic-event-shadow +schema_url: https://example.com/schemas/event-shadow/1.0.0 +dependencies: + - name: upstream + registry_path: dependency + schema_url: https://example.com/schemas/upstream/1.0.0 diff --git a/templates/code/rust/tests/error_event_span_shadow/registry/model.yaml b/templates/code/rust/tests/error_event_span_shadow/registry/model.yaml new file mode 100644 index 0000000..e0665ca --- /dev/null +++ b/templates/code/rust/tests/error_event_span_shadow/registry/model.yaml @@ -0,0 +1,9 @@ +file_format: definition/2 +events: + - name: probe.recorded + requirement_level: recommended + stability: stable + brief: A synthetic record event. + attributes: + - ref: span + requirement_level: required diff --git a/templates/code/rust/tests/error_histogram_boundaries_by_unit/expected-error.txt b/templates/code/rust/tests/error_histogram_boundaries_by_unit/expected-error.txt new file mode 100644 index 0000000..1f22d64 --- /dev/null +++ b/templates/code/rust/tests/error_histogram_boundaries_by_unit/expected-error.txt @@ -0,0 +1 @@ +metric myapp.task.queue_wait resolves histogram_boundaries_by_unit for unit min to boundaries that are not in strictly increasing order diff --git a/templates/code/rust/tests/error_histogram_boundaries_by_unit/params.yaml b/templates/code/rust/tests/error_histogram_boundaries_by_unit/params.yaml new file mode 100644 index 0000000..91e5edb --- /dev/null +++ b/templates/code/rust/tests/error_histogram_boundaries_by_unit/params.yaml @@ -0,0 +1,3 @@ +params: + histogram_boundaries_by_unit: + min: [10, 5, 1] diff --git a/templates/code/rust/tests/error_histogram_boundaries_by_unit/registry/manifest.yaml b/templates/code/rust/tests/error_histogram_boundaries_by_unit/registry/manifest.yaml new file mode 100644 index 0000000..50b6aed --- /dev/null +++ b/templates/code/rust/tests/error_histogram_boundaries_by_unit/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: error_histogram_boundaries_by_unit +schema_url: https://example.com/schemas/error_histogram_boundaries_by_unit/1.0.0 diff --git a/templates/code/rust/tests/error_histogram_boundaries_by_unit/registry/myapp.yaml b/templates/code/rust/tests/error_histogram_boundaries_by_unit/registry/myapp.yaml new file mode 100644 index 0000000..7dea0ce --- /dev/null +++ b/templates/code/rust/tests/error_histogram_boundaries_by_unit/registry/myapp.yaml @@ -0,0 +1,10 @@ +file_format: definition/2 +metrics: + - name: myapp.task.queue_wait + instrument: histogram + unit: min + stability: stable + brief: Time a task waited in the queue, in minutes. + annotations: + code_generation: + metric_value_type: double diff --git a/templates/code/rust/tests/error_instrumentation_name_empty/expected-error.txt b/templates/code/rust/tests/error_instrumentation_name_empty/expected-error.txt new file mode 100644 index 0000000..29bd62a --- /dev/null +++ b/templates/code/rust/tests/error_instrumentation_name_empty/expected-error.txt @@ -0,0 +1 @@ +instrumentation_name must be a nonempty string diff --git a/templates/code/rust/tests/error_instrumentation_name_empty/params.yaml b/templates/code/rust/tests/error_instrumentation_name_empty/params.yaml new file mode 100644 index 0000000..2e72054 --- /dev/null +++ b/templates/code/rust/tests/error_instrumentation_name_empty/params.yaml @@ -0,0 +1,2 @@ +params: + instrumentation_name: "" diff --git a/templates/code/rust/tests/error_instrumentation_name_empty/registry/manifest.yaml b/templates/code/rust/tests/error_instrumentation_name_empty/registry/manifest.yaml new file mode 100644 index 0000000..209c36a --- /dev/null +++ b/templates/code/rust/tests/error_instrumentation_name_empty/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: synthetic-empty-event-fixture +schema_url: https://example.com/schemas/event-no-attributes/1.0.0 diff --git a/templates/code/rust/tests/error_instrumentation_name_empty/registry/model.yaml b/templates/code/rust/tests/error_instrumentation_name_empty/registry/model.yaml new file mode 100644 index 0000000..b9120fb --- /dev/null +++ b/templates/code/rust/tests/error_instrumentation_name_empty/registry/model.yaml @@ -0,0 +1,5 @@ +file_format: definition/2 +events: + - name: probe.tick + stability: stable + brief: A synthetic tick occurred. diff --git a/templates/code/rust/tests/error_instrumentation_name_type/expected-error.txt b/templates/code/rust/tests/error_instrumentation_name_type/expected-error.txt new file mode 100644 index 0000000..29bd62a --- /dev/null +++ b/templates/code/rust/tests/error_instrumentation_name_type/expected-error.txt @@ -0,0 +1 @@ +instrumentation_name must be a nonempty string diff --git a/templates/code/rust/tests/error_instrumentation_name_type/params.yaml b/templates/code/rust/tests/error_instrumentation_name_type/params.yaml new file mode 100644 index 0000000..d1f6288 --- /dev/null +++ b/templates/code/rust/tests/error_instrumentation_name_type/params.yaml @@ -0,0 +1,2 @@ +params: + instrumentation_name: true diff --git a/templates/code/rust/tests/error_instrumentation_name_type/registry/manifest.yaml b/templates/code/rust/tests/error_instrumentation_name_type/registry/manifest.yaml new file mode 100644 index 0000000..209c36a --- /dev/null +++ b/templates/code/rust/tests/error_instrumentation_name_type/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: synthetic-empty-event-fixture +schema_url: https://example.com/schemas/event-no-attributes/1.0.0 diff --git a/templates/code/rust/tests/error_instrumentation_name_type/registry/model.yaml b/templates/code/rust/tests/error_instrumentation_name_type/registry/model.yaml new file mode 100644 index 0000000..b9120fb --- /dev/null +++ b/templates/code/rust/tests/error_instrumentation_name_type/registry/model.yaml @@ -0,0 +1,5 @@ +file_format: definition/2 +events: + - name: probe.tick + stability: stable + brief: A synthetic tick occurred. diff --git a/templates/code/rust/tests/error_instrumentation_version_type/expected-error.txt b/templates/code/rust/tests/error_instrumentation_version_type/expected-error.txt new file mode 100644 index 0000000..101ce01 --- /dev/null +++ b/templates/code/rust/tests/error_instrumentation_version_type/expected-error.txt @@ -0,0 +1 @@ +instrumentation_version must be a string diff --git a/templates/code/rust/tests/error_instrumentation_version_type/params.yaml b/templates/code/rust/tests/error_instrumentation_version_type/params.yaml new file mode 100644 index 0000000..e1f53ac --- /dev/null +++ b/templates/code/rust/tests/error_instrumentation_version_type/params.yaml @@ -0,0 +1,2 @@ +params: + instrumentation_version: false diff --git a/templates/code/rust/tests/error_instrumentation_version_type/registry/manifest.yaml b/templates/code/rust/tests/error_instrumentation_version_type/registry/manifest.yaml new file mode 100644 index 0000000..209c36a --- /dev/null +++ b/templates/code/rust/tests/error_instrumentation_version_type/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: synthetic-empty-event-fixture +schema_url: https://example.com/schemas/event-no-attributes/1.0.0 diff --git a/templates/code/rust/tests/error_instrumentation_version_type/registry/model.yaml b/templates/code/rust/tests/error_instrumentation_version_type/registry/model.yaml new file mode 100644 index 0000000..b9120fb --- /dev/null +++ b/templates/code/rust/tests/error_instrumentation_version_type/registry/model.yaml @@ -0,0 +1,5 @@ +file_format: definition/2 +events: + - name: probe.tick + stability: stable + brief: A synthetic tick occurred. diff --git a/templates/code/rust/tests/error_integer_enum_overflow/expected-error.txt b/templates/code/rust/tests/error_integer_enum_overflow/expected-error.txt new file mode 100644 index 0000000..7dddbb9 --- /dev/null +++ b/templates/code/rust/tests/error_integer_enum_overflow/expected-error.txt @@ -0,0 +1 @@ +cannot be represented by Rust i64 diff --git a/templates/code/rust/tests/error_integer_enum_overflow/registry/manifest.yaml b/templates/code/rust/tests/error_integer_enum_overflow/registry/manifest.yaml new file mode 100644 index 0000000..5bddc29 --- /dev/null +++ b/templates/code/rust/tests/error_integer_enum_overflow/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: demo +schema_url: https://example.com/schemas/1.0.0 diff --git a/templates/code/rust/tests/error_integer_enum_overflow/registry/model.yaml b/templates/code/rust/tests/error_integer_enum_overflow/registry/model.yaml new file mode 100644 index 0000000..8dfd87c --- /dev/null +++ b/templates/code/rust/tests/error_integer_enum_overflow/registry/model.yaml @@ -0,0 +1,11 @@ +file_format: definition/2 +attributes: + - key: demo.mode + stability: stable + brief: Synthetic mode. + type: + members: + - id: enormous + value: 9223372036854775808 + stability: stable + brief: Synthetic enum value. diff --git a/templates/code/rust/tests/error_invalid_name/expected-error.txt b/templates/code/rust/tests/error_invalid_name/expected-error.txt new file mode 100644 index 0000000..c343cbd --- /dev/null +++ b/templates/code/rust/tests/error_invalid_name/expected-error.txt @@ -0,0 +1 @@ +not a supported Rust attribute name diff --git a/templates/code/rust/tests/error_invalid_name/registry/manifest.yaml b/templates/code/rust/tests/error_invalid_name/registry/manifest.yaml new file mode 100644 index 0000000..5bddc29 --- /dev/null +++ b/templates/code/rust/tests/error_invalid_name/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: demo +schema_url: https://example.com/schemas/1.0.0 diff --git a/templates/code/rust/tests/error_invalid_name/registry/model.yaml b/templates/code/rust/tests/error_invalid_name/registry/model.yaml new file mode 100644 index 0000000..2c01271 --- /dev/null +++ b/templates/code/rust/tests/error_invalid_name/registry/model.yaml @@ -0,0 +1,6 @@ +file_format: definition/2 +attributes: + - key: demo.bad-name + stability: stable + brief: Synthetic attribute. + type: string diff --git a/templates/code/rust/tests/error_metric_collision/expected-error.txt b/templates/code/rust/tests/error_metric_collision/expected-error.txt new file mode 100644 index 0000000..de37c96 --- /dev/null +++ b/templates/code/rust/tests/error_metric_collision/expected-error.txt @@ -0,0 +1 @@ +package myappmetric has metrics that render to the same Rust identifier: myapp.task.started and myapp.task_started diff --git a/templates/code/rust/tests/error_metric_collision/registry/manifest.yaml b/templates/code/rust/tests/error_metric_collision/registry/manifest.yaml new file mode 100644 index 0000000..9fb94e3 --- /dev/null +++ b/templates/code/rust/tests/error_metric_collision/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: error_metric_collision +schema_url: https://example.com/schemas/error_metric_collision/1.0.0 diff --git a/templates/code/rust/tests/error_metric_collision/registry/model.yaml b/templates/code/rust/tests/error_metric_collision/registry/model.yaml new file mode 100644 index 0000000..c45a39c --- /dev/null +++ b/templates/code/rust/tests/error_metric_collision/registry/model.yaml @@ -0,0 +1,18 @@ +file_format: definition/2 +metrics: + - name: myapp.task.started + instrument: counter + unit: "{task}" + stability: stable + brief: Number of tasks started. + annotations: + code_generation: + metric_value_type: int + - name: myapp.task_started + instrument: counter + unit: "{task}" + stability: stable + brief: Number of tasks started, under another name. + annotations: + code_generation: + metric_value_type: int diff --git a/templates/code/rust/tests/error_metric_value_type/expected-error.txt b/templates/code/rust/tests/error_metric_value_type/expected-error.txt new file mode 100644 index 0000000..6043237 --- /dev/null +++ b/templates/code/rust/tests/error_metric_value_type/expected-error.txt @@ -0,0 +1 @@ +must set code_generation.metric_value_type to int or double diff --git a/templates/code/rust/tests/error_metric_value_type/registry/manifest.yaml b/templates/code/rust/tests/error_metric_value_type/registry/manifest.yaml new file mode 100644 index 0000000..d10001b --- /dev/null +++ b/templates/code/rust/tests/error_metric_value_type/registry/manifest.yaml @@ -0,0 +1,3 @@ +name: synthetic +semconv_version: 1.0.0 +schema_base_url: https://example.com/schemas/ diff --git a/templates/code/rust/tests/error_metric_value_type/registry/model.yaml b/templates/code/rust/tests/error_metric_value_type/registry/model.yaml new file mode 100644 index 0000000..91b80d7 --- /dev/null +++ b/templates/code/rust/tests/error_metric_value_type/registry/model.yaml @@ -0,0 +1,11 @@ +file_format: definition/2 +metrics: + - name: myapp.task.started + brief: A task count. + stability: stable + requirement_level: recommended + instrument: counter + unit: "{task}" + annotations: + code_generation: + metric_value_type: boolean diff --git a/templates/code/rust/tests/error_missing_metric_value_type/expected-error.txt b/templates/code/rust/tests/error_missing_metric_value_type/expected-error.txt new file mode 100644 index 0000000..6043237 --- /dev/null +++ b/templates/code/rust/tests/error_missing_metric_value_type/expected-error.txt @@ -0,0 +1 @@ +must set code_generation.metric_value_type to int or double diff --git a/templates/code/rust/tests/error_missing_metric_value_type/registry/manifest.yaml b/templates/code/rust/tests/error_missing_metric_value_type/registry/manifest.yaml new file mode 100644 index 0000000..d10001b --- /dev/null +++ b/templates/code/rust/tests/error_missing_metric_value_type/registry/manifest.yaml @@ -0,0 +1,3 @@ +name: synthetic +semconv_version: 1.0.0 +schema_base_url: https://example.com/schemas/ diff --git a/templates/code/rust/tests/error_missing_metric_value_type/registry/model.yaml b/templates/code/rust/tests/error_missing_metric_value_type/registry/model.yaml new file mode 100644 index 0000000..33e25d7 --- /dev/null +++ b/templates/code/rust/tests/error_missing_metric_value_type/registry/model.yaml @@ -0,0 +1,8 @@ +file_format: definition/2 +metrics: + - name: myapp.task.started + brief: A task count. + stability: stable + requirement_level: recommended + instrument: counter + unit: "{task}" diff --git a/templates/code/rust/tests/error_missing_schema/expected-error.txt b/templates/code/rust/tests/error_missing_schema/expected-error.txt new file mode 100644 index 0000000..a365ed6 --- /dev/null +++ b/templates/code/rust/tests/error_missing_schema/expected-error.txt @@ -0,0 +1 @@ +Either 'schema_url' or both 'schema_base_url' and 'semconv_version' must be provided diff --git a/templates/code/rust/tests/error_missing_schema/registry/manifest.yaml b/templates/code/rust/tests/error_missing_schema/registry/manifest.yaml new file mode 100644 index 0000000..bc69615 --- /dev/null +++ b/templates/code/rust/tests/error_missing_schema/registry/manifest.yaml @@ -0,0 +1 @@ +name: demo diff --git a/templates/code/rust/tests/error_missing_schema/registry/model.yaml b/templates/code/rust/tests/error_missing_schema/registry/model.yaml new file mode 100644 index 0000000..b281eab --- /dev/null +++ b/templates/code/rust/tests/error_missing_schema/registry/model.yaml @@ -0,0 +1,2 @@ +file_format: definition/2 +attributes: [] diff --git a/templates/code/rust/tests/error_mixed_enum/expected-error.txt b/templates/code/rust/tests/error_mixed_enum/expected-error.txt new file mode 100644 index 0000000..2201a96 --- /dev/null +++ b/templates/code/rust/tests/error_mixed_enum/expected-error.txt @@ -0,0 +1 @@ +has enum members of more than one value type diff --git a/templates/code/rust/tests/error_mixed_enum/registry/manifest.yaml b/templates/code/rust/tests/error_mixed_enum/registry/manifest.yaml new file mode 100644 index 0000000..5bddc29 --- /dev/null +++ b/templates/code/rust/tests/error_mixed_enum/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: demo +schema_url: https://example.com/schemas/1.0.0 diff --git a/templates/code/rust/tests/error_mixed_enum/registry/model.yaml b/templates/code/rust/tests/error_mixed_enum/registry/model.yaml new file mode 100644 index 0000000..e64d5e7 --- /dev/null +++ b/templates/code/rust/tests/error_mixed_enum/registry/model.yaml @@ -0,0 +1,15 @@ +file_format: definition/2 +attributes: + - key: demo.mode + stability: stable + brief: Synthetic mode. + type: + members: + - id: text + value: text + stability: stable + brief: Synthetic enum value. + - id: number + value: 42 + stability: stable + brief: Synthetic enum value. diff --git a/templates/code/rust/tests/error_namespace_uppercase/expected-error.txt b/templates/code/rust/tests/error_namespace_uppercase/expected-error.txt new file mode 100644 index 0000000..f061fcf --- /dev/null +++ b/templates/code/rust/tests/error_namespace_uppercase/expected-error.txt @@ -0,0 +1 @@ +namespace CAPS is not a supported Rust module name diff --git a/templates/code/rust/tests/error_namespace_uppercase/registry/manifest.yaml b/templates/code/rust/tests/error_namespace_uppercase/registry/manifest.yaml new file mode 100644 index 0000000..5bddc29 --- /dev/null +++ b/templates/code/rust/tests/error_namespace_uppercase/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: demo +schema_url: https://example.com/schemas/1.0.0 diff --git a/templates/code/rust/tests/error_namespace_uppercase/registry/model.yaml b/templates/code/rust/tests/error_namespace_uppercase/registry/model.yaml new file mode 100644 index 0000000..5fed346 --- /dev/null +++ b/templates/code/rust/tests/error_namespace_uppercase/registry/model.yaml @@ -0,0 +1,6 @@ +file_format: definition/2 +attributes: + - key: CAPS.id + type: string + stability: stable + brief: Synthetic identifier. diff --git a/templates/code/rust/tests/error_observable_collision/expected-error.txt b/templates/code/rust/tests/error_observable_collision/expected-error.txt new file mode 100644 index 0000000..3470b15 --- /dev/null +++ b/templates/code/rust/tests/error_observable_collision/expected-error.txt @@ -0,0 +1 @@ +package myappmetric has metrics that render to the same Rust identifier: myapp.task.started and myapp.task.started.observable diff --git a/templates/code/rust/tests/error_observable_collision/registry/manifest.yaml b/templates/code/rust/tests/error_observable_collision/registry/manifest.yaml new file mode 100644 index 0000000..fadb55b --- /dev/null +++ b/templates/code/rust/tests/error_observable_collision/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: error_observable_collision +schema_url: https://example.com/schemas/error_observable_collision/1.0.0 diff --git a/templates/code/rust/tests/error_observable_collision/registry/model.yaml b/templates/code/rust/tests/error_observable_collision/registry/model.yaml new file mode 100644 index 0000000..e5fbb0c --- /dev/null +++ b/templates/code/rust/tests/error_observable_collision/registry/model.yaml @@ -0,0 +1,18 @@ +file_format: definition/2 +metrics: + - name: myapp.task.started + instrument: counter + unit: "{task}" + stability: stable + brief: Number of tasks started. + annotations: + code_generation: + metric_value_type: int + - name: myapp.task.started.observable + instrument: counter + unit: "{task}" + stability: stable + brief: Number of tasks started, observed. + annotations: + code_generation: + metric_value_type: int diff --git a/templates/code/rust/tests/error_option_collision/expected-error.txt b/templates/code/rust/tests/error_option_collision/expected-error.txt new file mode 100644 index 0000000..b69d494 --- /dev/null +++ b/templates/code/rust/tests/error_option_collision/expected-error.txt @@ -0,0 +1 @@ +metric myapp.task.started has optional attributes that render to the same Rust identifier: myapp.task.id and queue.task.id diff --git a/templates/code/rust/tests/error_option_collision/registry/manifest.yaml b/templates/code/rust/tests/error_option_collision/registry/manifest.yaml new file mode 100644 index 0000000..8d9c13c --- /dev/null +++ b/templates/code/rust/tests/error_option_collision/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: error_option_collision +schema_url: https://example.com/schemas/error_option_collision/1.0.0 diff --git a/templates/code/rust/tests/error_option_collision/registry/myapp.yaml b/templates/code/rust/tests/error_option_collision/registry/myapp.yaml new file mode 100644 index 0000000..4bce764 --- /dev/null +++ b/templates/code/rust/tests/error_option_collision/registry/myapp.yaml @@ -0,0 +1,27 @@ +file_format: definition/2 +attributes: + - key: myapp.task.id + type: string + stability: stable + brief: Unique identifier of the task. + examples: ["task_0001"] + - key: queue.task.id + type: string + stability: stable + brief: Identifier the queue gave the task. + examples: ["task_0001"] +metrics: + - name: myapp.task.started + requirement_level: recommended + instrument: counter + unit: "{task}" + stability: stable + brief: Number of tasks started. + annotations: + code_generation: + metric_value_type: int + attributes: + - ref: myapp.task.id + requirement_level: recommended + - ref: queue.task.id + requirement_level: recommended diff --git a/templates/code/rust/tests/error_refinement_value_type/expected-error.txt b/templates/code/rust/tests/error_refinement_value_type/expected-error.txt new file mode 100644 index 0000000..b086b96 --- /dev/null +++ b/templates/code/rust/tests/error_refinement_value_type/expected-error.txt @@ -0,0 +1 @@ +metric refinement myapp.task.retry.duration sets a different code_generation.metric_value_type than myapp.task.duration diff --git a/templates/code/rust/tests/error_refinement_value_type/registry/manifest.yaml b/templates/code/rust/tests/error_refinement_value_type/registry/manifest.yaml new file mode 100644 index 0000000..4c4b425 --- /dev/null +++ b/templates/code/rust/tests/error_refinement_value_type/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: error_refinement_value_type +schema_url: https://example.com/schemas/error_refinement_value_type/1.0.0 diff --git a/templates/code/rust/tests/error_refinement_value_type/registry/model.yaml b/templates/code/rust/tests/error_refinement_value_type/registry/model.yaml new file mode 100644 index 0000000..cc22be3 --- /dev/null +++ b/templates/code/rust/tests/error_refinement_value_type/registry/model.yaml @@ -0,0 +1,18 @@ +file_format: definition/2 +metrics: + - name: myapp.task.duration + instrument: histogram + unit: s + stability: stable + brief: Time a task took from start to finish. + annotations: + code_generation: + metric_value_type: double +metric_refinements: + - id: myapp.task.retry.duration + ref: myapp.task.duration + stability: stable + brief: Time a retried task took from start to finish. + annotations: + code_generation: + metric_value_type: int diff --git a/templates/code/rust/tests/error_span_collision/expected-error.txt b/templates/code/rust/tests/error_span_collision/expected-error.txt new file mode 100644 index 0000000..15189b1 --- /dev/null +++ b/templates/code/rust/tests/error_span_collision/expected-error.txt @@ -0,0 +1 @@ +package myappspan has spans that render to the same Rust identifier: myapp.task.run and myapp.task_run diff --git a/templates/code/rust/tests/error_span_collision/registry/manifest.yaml b/templates/code/rust/tests/error_span_collision/registry/manifest.yaml new file mode 100644 index 0000000..a6edceb --- /dev/null +++ b/templates/code/rust/tests/error_span_collision/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: error_span_collision +schema_url: https://example.com/schemas/error_span_collision/1.0.0 diff --git a/templates/code/rust/tests/error_span_collision/registry/myapp.yaml b/templates/code/rust/tests/error_span_collision/registry/myapp.yaml new file mode 100644 index 0000000..e1d1aa4 --- /dev/null +++ b/templates/code/rust/tests/error_span_collision/registry/myapp.yaml @@ -0,0 +1,16 @@ +file_format: definition/2 +spans: + - type: myapp.task.run + kind: internal + name: + note: "" + requirement_level: recommended + stability: stable + brief: One run of a task. + - type: myapp.task_run + kind: internal + name: + note: "" + requirement_level: recommended + stability: stable + brief: One run of a task, under another name. diff --git a/templates/code/rust/tests/error_span_required_collision/expected-error.txt b/templates/code/rust/tests/error_span_required_collision/expected-error.txt new file mode 100644 index 0000000..ce98f61 --- /dev/null +++ b/templates/code/rust/tests/error_span_required_collision/expected-error.txt @@ -0,0 +1 @@ +span myapp.task.run has required attributes that render to the same Rust parameter: myapp.task.id and myapp.task_id diff --git a/templates/code/rust/tests/error_span_required_collision/registry/manifest.yaml b/templates/code/rust/tests/error_span_required_collision/registry/manifest.yaml new file mode 100644 index 0000000..6c9fa64 --- /dev/null +++ b/templates/code/rust/tests/error_span_required_collision/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: error_span_required_collision +schema_url: https://example.com/schemas/error_span_required_collision/1.0.0 diff --git a/templates/code/rust/tests/error_span_required_collision/registry/myapp.yaml b/templates/code/rust/tests/error_span_required_collision/registry/myapp.yaml new file mode 100644 index 0000000..292e89b --- /dev/null +++ b/templates/code/rust/tests/error_span_required_collision/registry/myapp.yaml @@ -0,0 +1,25 @@ +file_format: definition/2 +attributes: + - key: myapp.task.id + type: string + stability: stable + brief: Unique identifier of the task. + examples: ["task_0001"] + - key: myapp.task_id + type: string + stability: stable + brief: Unique identifier of the task, under another name. + examples: ["task_0001"] +spans: + - type: myapp.task.run + kind: internal + name: + note: "" + requirement_level: recommended + stability: stable + brief: One run of a task. + attributes: + - ref: myapp.task.id + requirement_level: required + - ref: myapp.task_id + requirement_level: required diff --git a/templates/code/rust/tests/error_stable_only_event_required_attribute/expected-error.txt b/templates/code/rust/tests/error_stable_only_event_required_attribute/expected-error.txt new file mode 100644 index 0000000..f988160 --- /dev/null +++ b/templates/code/rust/tests/error_stable_only_event_required_attribute/expected-error.txt @@ -0,0 +1 @@ +event myapp.task.finished requires myapp.task.id, which exclude_deprecated or stable_only leaves out diff --git a/templates/code/rust/tests/error_stable_only_event_required_attribute/params.yaml b/templates/code/rust/tests/error_stable_only_event_required_attribute/params.yaml new file mode 100644 index 0000000..7606ce0 --- /dev/null +++ b/templates/code/rust/tests/error_stable_only_event_required_attribute/params.yaml @@ -0,0 +1,2 @@ +params: + stable_only: true diff --git a/templates/code/rust/tests/error_stable_only_event_required_attribute/registry/manifest.yaml b/templates/code/rust/tests/error_stable_only_event_required_attribute/registry/manifest.yaml new file mode 100644 index 0000000..306d160 --- /dev/null +++ b/templates/code/rust/tests/error_stable_only_event_required_attribute/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: error_stable_only_event_required_attribute +schema_url: https://example.com/schemas/error_stable_only_event_required_attribute/1.0.0 diff --git a/templates/code/rust/tests/error_stable_only_event_required_attribute/registry/myapp.yaml b/templates/code/rust/tests/error_stable_only_event_required_attribute/registry/myapp.yaml new file mode 100644 index 0000000..31622d2 --- /dev/null +++ b/templates/code/rust/tests/error_stable_only_event_required_attribute/registry/myapp.yaml @@ -0,0 +1,15 @@ +file_format: definition/2 +attributes: + - key: myapp.task.id + type: string + stability: development + brief: Unique identifier of the task. + examples: ["task_0001"] +events: + - name: myapp.task.finished + requirement_level: recommended + stability: stable + brief: A task finished. + attributes: + - ref: myapp.task.id + requirement_level: required diff --git a/templates/code/rust/tests/error_stable_only_required_attribute/expected-error.txt b/templates/code/rust/tests/error_stable_only_required_attribute/expected-error.txt new file mode 100644 index 0000000..b91b482 --- /dev/null +++ b/templates/code/rust/tests/error_stable_only_required_attribute/expected-error.txt @@ -0,0 +1 @@ +metric myapp.task.started requires myapp.task.id, which exclude_deprecated or stable_only leaves out diff --git a/templates/code/rust/tests/error_stable_only_required_attribute/params.yaml b/templates/code/rust/tests/error_stable_only_required_attribute/params.yaml new file mode 100644 index 0000000..7606ce0 --- /dev/null +++ b/templates/code/rust/tests/error_stable_only_required_attribute/params.yaml @@ -0,0 +1,2 @@ +params: + stable_only: true diff --git a/templates/code/rust/tests/error_stable_only_required_attribute/registry/manifest.yaml b/templates/code/rust/tests/error_stable_only_required_attribute/registry/manifest.yaml new file mode 100644 index 0000000..7d4a2d9 --- /dev/null +++ b/templates/code/rust/tests/error_stable_only_required_attribute/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: error_stable_only_required_attribute +schema_url: https://example.com/schemas/error_stable_only_required_attribute/1.0.0 diff --git a/templates/code/rust/tests/error_stable_only_required_attribute/registry/myapp.yaml b/templates/code/rust/tests/error_stable_only_required_attribute/registry/myapp.yaml new file mode 100644 index 0000000..8b9004e --- /dev/null +++ b/templates/code/rust/tests/error_stable_only_required_attribute/registry/myapp.yaml @@ -0,0 +1,20 @@ +file_format: definition/2 +attributes: + - key: myapp.task.id + type: string + stability: development + brief: Unique identifier of the task. + examples: ["task_0001"] +metrics: + - name: myapp.task.started + requirement_level: recommended + instrument: counter + unit: "{task}" + stability: stable + brief: Number of tasks started. + annotations: + code_generation: + metric_value_type: int + attributes: + - ref: myapp.task.id + requirement_level: required diff --git a/templates/code/rust/tests/error_stable_only_span_required_attribute/expected-error.txt b/templates/code/rust/tests/error_stable_only_span_required_attribute/expected-error.txt new file mode 100644 index 0000000..055b71c --- /dev/null +++ b/templates/code/rust/tests/error_stable_only_span_required_attribute/expected-error.txt @@ -0,0 +1 @@ +span myapp.task.run requires myapp.task.id, which exclude_deprecated or stable_only leaves out diff --git a/templates/code/rust/tests/error_stable_only_span_required_attribute/params.yaml b/templates/code/rust/tests/error_stable_only_span_required_attribute/params.yaml new file mode 100644 index 0000000..7606ce0 --- /dev/null +++ b/templates/code/rust/tests/error_stable_only_span_required_attribute/params.yaml @@ -0,0 +1,2 @@ +params: + stable_only: true diff --git a/templates/code/rust/tests/error_stable_only_span_required_attribute/registry/manifest.yaml b/templates/code/rust/tests/error_stable_only_span_required_attribute/registry/manifest.yaml new file mode 100644 index 0000000..cc77b79 --- /dev/null +++ b/templates/code/rust/tests/error_stable_only_span_required_attribute/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: error_stable_only_span_required_attribute +schema_url: https://example.com/schemas/error_stable_only_span_required_attribute/1.0.0 diff --git a/templates/code/rust/tests/error_stable_only_span_required_attribute/registry/myapp.yaml b/templates/code/rust/tests/error_stable_only_span_required_attribute/registry/myapp.yaml new file mode 100644 index 0000000..0b0acfc --- /dev/null +++ b/templates/code/rust/tests/error_stable_only_span_required_attribute/registry/myapp.yaml @@ -0,0 +1,18 @@ +file_format: definition/2 +attributes: + - key: myapp.task.id + type: string + stability: development + brief: Unique identifier of the task. + examples: ["task_0001"] +spans: + - type: myapp.task.run + kind: internal + name: + note: "" + requirement_level: recommended + stability: stable + brief: One run of a task. + attributes: + - ref: myapp.task.id + requirement_level: required diff --git a/templates/code/rust/tests/error_unversioned_schema/expected-error.txt b/templates/code/rust/tests/error_unversioned_schema/expected-error.txt new file mode 100644 index 0000000..c2bdbcc --- /dev/null +++ b/templates/code/rust/tests/error_unversioned_schema/expected-error.txt @@ -0,0 +1 @@ +the registry schema_url must name a schema version diff --git a/templates/code/rust/tests/error_unversioned_schema/registry/manifest.yaml b/templates/code/rust/tests/error_unversioned_schema/registry/manifest.yaml new file mode 100644 index 0000000..078407f --- /dev/null +++ b/templates/code/rust/tests/error_unversioned_schema/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: demo +schema_url: https://example.com/schemas/demo diff --git a/templates/code/rust/tests/error_unversioned_schema/registry/model.yaml b/templates/code/rust/tests/error_unversioned_schema/registry/model.yaml new file mode 100644 index 0000000..b281eab --- /dev/null +++ b/templates/code/rust/tests/error_unversioned_schema/registry/model.yaml @@ -0,0 +1,2 @@ +file_format: definition/2 +attributes: [] diff --git a/templates/code/rust/tests/event_no_attributes/expected/logger.rs b/templates/code/rust/tests/event_no_attributes/expected/logger.rs new file mode 100644 index 0000000..78bcc02 --- /dev/null +++ b/templates/code/rust/tests/event_no_attributes/expected/logger.rs @@ -0,0 +1,16 @@ +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Logger::Logger>(L); + +impl Logger { + pub fn new>(provider: &P, options: InstrumentationOptions) -> Self { + let logger = provider.logger_with_scope(options.scope.into()); + Self(logger) + } + pub fn inner(&self) -> &L { &self.0 } +} + +impl Default for Logger { + fn default() -> Self { Self::new(&opentelemetry::logs::NoopLoggerProvider::new(), InstrumentationOptions::default()) } +} diff --git a/templates/code/rust/tests/event_no_attributes/expected/mod.rs b/templates/code/rust/tests/event_no_attributes/expected/mod.rs new file mode 100644 index 0000000..86001b2 --- /dev/null +++ b/templates/code/rust/tests/event_no_attributes/expected/mod.rs @@ -0,0 +1,7 @@ +// Generated by OpenTelemetry Weaver. +pub const SCHEMA_URL: &str = "https://example.com/schemas/event-no-attributes/1.0.0"; + +pub mod scope; +pub mod options; +pub mod logger; +pub mod probeevent; diff --git a/templates/code/rust/tests/event_no_attributes/expected/options.rs b/templates/code/rust/tests/event_no_attributes/expected/options.rs new file mode 100644 index 0000000..69a821a --- /dev/null +++ b/templates/code/rust/tests/event_no_attributes/expected/options.rs @@ -0,0 +1,6 @@ +use super::scope::Scope; + +#[derive(Clone, Debug, Default)] +pub struct InstrumentationOptions { + pub scope: Scope, +} diff --git a/templates/code/rust/tests/event_no_attributes/expected/probeevent.rs b/templates/code/rust/tests/event_no_attributes/expected/probeevent.rs new file mode 100644 index 0000000..873ac71 --- /dev/null +++ b/templates/code/rust/tests/event_no_attributes/expected/probeevent.rs @@ -0,0 +1,31 @@ +#![allow(deprecated)] +use ::opentelemetry::logs::LogRecord; +use ::opentelemetry::trace::TraceContextExt; + +#[derive(Clone, Debug)] +pub enum TickOption { + Severity(::opentelemetry::logs::Severity), + Timestamp(::std::time::SystemTime), +} +/// A synthetic tick occurred. +pub fn r#emit_tick(context: &::opentelemetry::Context, logger: &super::logger::Logger, options: impl IntoIterator) { + let mut severity = ::opentelemetry::logs::Severity::Info; + let mut timestamp = None; + for option in options { + match option { + TickOption::Severity(value) => severity = value, + TickOption::Timestamp(value) => timestamp = Some(value), + } + } + if !logger.inner().event_enabled(severity, "", Some("probe.tick")) { return; } + let _context_guard = context.clone().attach(); + let mut record = logger.inner().create_log_record(); + record.set_event_name("probe.tick"); + record.set_severity_number(severity); + record.set_observed_timestamp(::std::time::SystemTime::now()); + if let Some(timestamp) = timestamp { record.set_timestamp(timestamp); } + let span = context.span(); + let span_context = span.span_context(); + if span_context.is_valid() { record.set_trace_context(span_context.trace_id(), span_context.span_id(), Some(span_context.trace_flags())); } + logger.inner().emit(record); +} diff --git a/templates/code/rust/tests/event_no_attributes/expected/scope.rs b/templates/code/rust/tests/event_no_attributes/expected/scope.rs new file mode 100644 index 0000000..6f66e86 --- /dev/null +++ b/templates/code/rust/tests/event_no_attributes/expected/scope.rs @@ -0,0 +1,13 @@ +#[derive(Clone, Debug, Default)] +pub struct Scope { + pub attributes: Vec<::opentelemetry::KeyValue>, +} + +impl From for ::opentelemetry::InstrumentationScope { + fn from(value: Scope) -> Self { + Self::builder("generated-semconv") + .with_schema_url(super::SCHEMA_URL) + .with_attributes(value.attributes) + .build() + } +} diff --git a/templates/code/rust/tests/event_no_attributes/registry/manifest.yaml b/templates/code/rust/tests/event_no_attributes/registry/manifest.yaml new file mode 100644 index 0000000..209c36a --- /dev/null +++ b/templates/code/rust/tests/event_no_attributes/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: synthetic-empty-event-fixture +schema_url: https://example.com/schemas/event-no-attributes/1.0.0 diff --git a/templates/code/rust/tests/event_no_attributes/registry/model.yaml b/templates/code/rust/tests/event_no_attributes/registry/model.yaml new file mode 100644 index 0000000..b9120fb --- /dev/null +++ b/templates/code/rust/tests/event_no_attributes/registry/model.yaml @@ -0,0 +1,5 @@ +file_format: definition/2 +events: + - name: probe.tick + stability: stable + brief: A synthetic tick occurred. diff --git a/templates/code/rust/tests/event_no_attributes/test.rs b/templates/code/rust/tests/event_no_attributes/test.rs new file mode 100644 index 0000000..7483c37 --- /dev/null +++ b/templates/code/rust/tests/event_no_attributes/test.rs @@ -0,0 +1,30 @@ +use generated_semconv::options::InstrumentationOptions; +use generated_semconv::{logger, probeevent}; +use opentelemetry::logs::Severity; +use opentelemetry_sdk::logs::{InMemoryLogExporter, SdkLoggerProvider}; + +#[test] +fn events_without_attributes_export_cleanly() { + let exporter = InMemoryLogExporter::default(); + let provider = SdkLoggerProvider::builder() + .with_simple_exporter(exporter.clone()) + .build(); + let logger = logger::Logger::new(&provider, InstrumentationOptions::default()); + probeevent::emit_tick( + &opentelemetry::Context::new(), + &logger, + [probeevent::TickOption::Severity(Severity::Debug)], + ); + provider.force_flush().unwrap(); + let records = exporter.get_emitted_logs().unwrap(); + assert_eq!(records.len(), 1); + assert_eq!(records[0].instrumentation.name(), "generated-semconv"); + assert_eq!(records[0].instrumentation.version(), None); + assert_eq!(records[0].record.event_name(), Some("probe.tick")); + assert_eq!(records[0].record.severity_number(), Some(Severity::Debug)); + assert_eq!(records[0].record.attributes_iter().count(), 0); + assert_eq!( + records[0].instrumentation.schema_url(), + Some(generated_semconv::SCHEMA_URL) + ); +} diff --git a/templates/code/rust/tests/events/expected/logger.rs b/templates/code/rust/tests/events/expected/logger.rs new file mode 100644 index 0000000..78bcc02 --- /dev/null +++ b/templates/code/rust/tests/events/expected/logger.rs @@ -0,0 +1,16 @@ +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Logger::Logger>(L); + +impl Logger { + pub fn new>(provider: &P, options: InstrumentationOptions) -> Self { + let logger = provider.logger_with_scope(options.scope.into()); + Self(logger) + } + pub fn inner(&self) -> &L { &self.0 } +} + +impl Default for Logger { + fn default() -> Self { Self::new(&opentelemetry::logs::NoopLoggerProvider::new(), InstrumentationOptions::default()) } +} diff --git a/templates/code/rust/tests/events/expected/mod.rs b/templates/code/rust/tests/events/expected/mod.rs new file mode 100644 index 0000000..2f1689e --- /dev/null +++ b/templates/code/rust/tests/events/expected/mod.rs @@ -0,0 +1,10 @@ +// Generated by OpenTelemetry Weaver. +pub const SCHEMA_URL: &str = "https://example.com/schemas/events/1.0.0"; +pub mod myappattr; +pub mod workerattr; + +pub mod scope; +pub mod options; +pub mod logger; +pub mod myappevent; +pub mod workerevent; diff --git a/templates/code/rust/tests/events/expected/myappattr.rs b/templates/code/rust/tests/events/expected/myappattr.rs new file mode 100644 index 0000000..697c2d0 --- /dev/null +++ b/templates/code/rust/tests/events/expected/myappattr.rs @@ -0,0 +1,325 @@ +// Generated by OpenTelemetry Weaver. +/// Synthetic bool values. +#[derive(Debug, Clone, PartialEq)] +pub struct BoolValuesAttr(Vec); + +#[allow(deprecated)] +impl BoolValuesAttr { + pub const KEY: &'static str = "myapp.bool.values"; +} + +#[allow(deprecated)] +impl From> for BoolValuesAttr { + #[inline] + fn from(value: Vec) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl AsRef<[bool]> for BoolValuesAttr { + #[inline] + fn as_ref(&self) -> &[bool] { + &self.0 + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for BoolValuesAttr { + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str("[")?; + for (index, value) in self.0.iter().enumerate() { + if index != 0 { + formatter.write_str(",")?; + } + ::std::fmt::Display::fmt(value, formatter)?; + } + formatter.write_str("]") + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: BoolValuesAttr) -> Self { + ::opentelemetry::KeyValue::new(BoolValuesAttr::KEY, ::opentelemetry::Value::Array(::opentelemetry::Array::Bool(value.0))) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: BoolValuesAttr) -> Self { + value.0.into_iter().collect::<::opentelemetry::logs::AnyValue>() + } +} + +/// Synthetic double values. +#[derive(Debug, Clone, PartialEq)] +pub struct DoubleValuesAttr(Vec); + +#[allow(deprecated)] +impl DoubleValuesAttr { + pub const KEY: &'static str = "myapp.double.values"; +} + +#[allow(deprecated)] +impl From> for DoubleValuesAttr { + #[inline] + fn from(value: Vec) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl AsRef<[f64]> for DoubleValuesAttr { + #[inline] + fn as_ref(&self) -> &[f64] { + &self.0 + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for DoubleValuesAttr { + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str("[")?; + for (index, value) in self.0.iter().enumerate() { + if index != 0 { + formatter.write_str(",")?; + } + ::std::fmt::Display::fmt(value, formatter)?; + } + formatter.write_str("]") + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: DoubleValuesAttr) -> Self { + ::opentelemetry::KeyValue::new(DoubleValuesAttr::KEY, ::opentelemetry::Value::Array(::opentelemetry::Array::F64(value.0))) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: DoubleValuesAttr) -> Self { + value.0.into_iter().collect::<::opentelemetry::logs::AnyValue>() + } +} + +/// Synthetic int values. +#[derive(Debug, Clone, PartialEq)] +pub struct IntValuesAttr(Vec); + +#[allow(deprecated)] +impl IntValuesAttr { + pub const KEY: &'static str = "myapp.int.values"; +} + +#[allow(deprecated)] +impl From> for IntValuesAttr { + #[inline] + fn from(value: Vec) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl AsRef<[i64]> for IntValuesAttr { + #[inline] + fn as_ref(&self) -> &[i64] { + &self.0 + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for IntValuesAttr { + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str("[")?; + for (index, value) in self.0.iter().enumerate() { + if index != 0 { + formatter.write_str(",")?; + } + ::std::fmt::Display::fmt(value, formatter)?; + } + formatter.write_str("]") + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: IntValuesAttr) -> Self { + ::opentelemetry::KeyValue::new(IntValuesAttr::KEY, ::opentelemetry::Value::Array(::opentelemetry::Array::I64(value.0))) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: IntValuesAttr) -> Self { + value.0.into_iter().collect::<::opentelemetry::logs::AnyValue>() + } +} + +/// Synthetic string values. +#[derive(Debug, Clone, PartialEq)] +pub struct StringValuesAttr(Vec<::opentelemetry::StringValue>); + +#[allow(deprecated)] +impl StringValuesAttr { + pub const KEY: &'static str = "myapp.string.values"; +} + +#[allow(deprecated)] +impl From> for StringValuesAttr { + #[inline] + fn from(value: Vec<::opentelemetry::StringValue>) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From> for StringValuesAttr { + fn from(value: Vec) -> Self { + Self(value.into_iter().map(Into::into).collect()) + } +} + +#[allow(deprecated)] +impl AsRef<[::opentelemetry::StringValue]> for StringValuesAttr { + #[inline] + fn as_ref(&self) -> &[::opentelemetry::StringValue] { + &self.0 + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for StringValuesAttr { + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str("[")?; + for (index, value) in self.0.iter().enumerate() { + if index != 0 { + formatter.write_str(",")?; + } + write!(formatter, "\"{value}\"")?; + } + formatter.write_str("]") + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: StringValuesAttr) -> Self { + ::opentelemetry::KeyValue::new(StringValuesAttr::KEY, ::opentelemetry::Value::Array(::opentelemetry::Array::String(value.0))) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: StringValuesAttr) -> Self { + value.0.into_iter().collect::<::opentelemetry::logs::AnyValue>() + } +} + +/// Number of the attempt, starting at 1. +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct TaskAttemptAttr(i64); + +#[allow(deprecated)] +impl TaskAttemptAttr { + pub const KEY: &'static str = "myapp.task.attempt"; +} + +#[allow(deprecated)] +impl From for TaskAttemptAttr { + #[inline] + fn from(value: i64) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskAttemptAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::std::fmt::Display::fmt(&self.0, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskAttemptAttr) -> Self { + ::opentelemetry::KeyValue::new(TaskAttemptAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskAttemptAttr) -> Self { + value.0.into() + } +} + +/// Current state of the task. +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum TaskStateAttr { + /// The task finished. + Done, + /// The task gave up. + Failed, +} + +#[allow(deprecated)] +impl TaskStateAttr { + pub const KEY: &'static str = "myapp.task.state"; +} + +#[allow(deprecated)] +impl AsRef for TaskStateAttr { + #[inline] + fn as_ref(&self) -> &str { + match self { + TaskStateAttr::Done => "done", + TaskStateAttr::Failed => "failed", + } + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskStateAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskStateAttr) -> Self { + let value: &'static str = match value { + TaskStateAttr::Done => "done", + TaskStateAttr::Failed => "failed", + }; + ::opentelemetry::KeyValue::new(TaskStateAttr::KEY, value) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskStateAttr) -> Self { + let value: &'static str = match value { + TaskStateAttr::Done => "done", + TaskStateAttr::Failed => "failed", + }; + value.into() + } +} diff --git a/templates/code/rust/tests/events/expected/myappevent.rs b/templates/code/rust/tests/events/expected/myappevent.rs new file mode 100644 index 0000000..9138d5c --- /dev/null +++ b/templates/code/rust/tests/events/expected/myappevent.rs @@ -0,0 +1,85 @@ +#![allow(deprecated)] +use ::opentelemetry::logs::LogRecord; +use ::opentelemetry::trace::TraceContextExt; + +#[derive(Clone, Debug)] +pub enum TaskFinishedOption { + Severity(::opentelemetry::logs::Severity), + Timestamp(::std::time::SystemTime), + BoolValues(super::myappattr::BoolValuesAttr), + DoubleValues(super::myappattr::DoubleValuesAttr), + IntValues(super::myappattr::IntValuesAttr), + StringValues(super::myappattr::StringValuesAttr), + /// Recommended: When the task ran more than once. + TaskAttempt(super::myappattr::TaskAttemptAttr), + /// Opt-in: the convention records it only when a user asks for it. + HostName(super::workerattr::HostNameAttr), +} +/// A task reached a final state. +/// +/// Emitted once per task, after its last attempt. +pub fn r#emit_task_finished(context: &::opentelemetry::Context, logger: &super::logger::Logger, r#myapp_task_state: super::myappattr::TaskStateAttr, options: impl IntoIterator) { + let mut severity = ::opentelemetry::logs::Severity::Info; + let mut timestamp = None; + let mut attributes = ::smallvec::SmallVec::<[TaskFinishedOption; 6]>::new(); + for option in options { + match option { + TaskFinishedOption::Severity(value) => severity = value, + TaskFinishedOption::Timestamp(value) => timestamp = Some(value), + attribute => attributes.push(attribute), + } + } + if !logger.inner().event_enabled(severity, "", Some("myapp.task.finished")) { return; } + let _context_guard = context.clone().attach(); + let mut record = logger.inner().create_log_record(); + record.set_event_name("myapp.task.finished"); + record.set_severity_number(severity); + record.set_observed_timestamp(::std::time::SystemTime::now()); + if let Some(timestamp) = timestamp { record.set_timestamp(timestamp); } + let span = context.span(); + let span_context = span.span_context(); + if span_context.is_valid() { record.set_trace_context(span_context.trace_id(), span_context.span_id(), Some(span_context.trace_flags())); } + record.add_attribute(super::myappattr::TaskStateAttr::KEY, r#myapp_task_state); + for option in attributes { + match option { + TaskFinishedOption::BoolValues(value) => record.add_attribute(super::myappattr::BoolValuesAttr::KEY, value), + TaskFinishedOption::DoubleValues(value) => record.add_attribute(super::myappattr::DoubleValuesAttr::KEY, value), + TaskFinishedOption::IntValues(value) => record.add_attribute(super::myappattr::IntValuesAttr::KEY, value), + TaskFinishedOption::StringValues(value) => record.add_attribute(super::myappattr::StringValuesAttr::KEY, value), + TaskFinishedOption::TaskAttempt(value) => record.add_attribute(super::myappattr::TaskAttemptAttr::KEY, value), + TaskFinishedOption::HostName(value) => record.add_attribute(super::workerattr::HostNameAttr::KEY, value), + TaskFinishedOption::Severity(_) | TaskFinishedOption::Timestamp(_) => continue, + } + } + logger.inner().emit(record); +} +#[derive(Clone, Debug)] +pub enum TaskRetriedOption { + Severity(::opentelemetry::logs::Severity), + Timestamp(::std::time::SystemTime), +} +/// A task is about to run again. +/// Opt-in: record only when the user requests it. +#[deprecated(note = "Read the attempt from `myapp.task.finished` instead.")] +pub fn r#emit_task_retried(context: &::opentelemetry::Context, logger: &super::logger::Logger, r#myapp_task_attempt: super::myappattr::TaskAttemptAttr, options: impl IntoIterator) { + let mut severity = ::opentelemetry::logs::Severity::Info; + let mut timestamp = None; + for option in options { + match option { + TaskRetriedOption::Severity(value) => severity = value, + TaskRetriedOption::Timestamp(value) => timestamp = Some(value), + } + } + if !logger.inner().event_enabled(severity, "", Some("myapp.task.retried")) { return; } + let _context_guard = context.clone().attach(); + let mut record = logger.inner().create_log_record(); + record.set_event_name("myapp.task.retried"); + record.set_severity_number(severity); + record.set_observed_timestamp(::std::time::SystemTime::now()); + if let Some(timestamp) = timestamp { record.set_timestamp(timestamp); } + let span = context.span(); + let span_context = span.span_context(); + if span_context.is_valid() { record.set_trace_context(span_context.trace_id(), span_context.span_id(), Some(span_context.trace_flags())); } + record.add_attribute(super::myappattr::TaskAttemptAttr::KEY, r#myapp_task_attempt); + logger.inner().emit(record); +} diff --git a/templates/code/rust/tests/events/expected/options.rs b/templates/code/rust/tests/events/expected/options.rs new file mode 100644 index 0000000..69a821a --- /dev/null +++ b/templates/code/rust/tests/events/expected/options.rs @@ -0,0 +1,6 @@ +use super::scope::Scope; + +#[derive(Clone, Debug, Default)] +pub struct InstrumentationOptions { + pub scope: Scope, +} diff --git a/templates/code/rust/tests/events/expected/scope.rs b/templates/code/rust/tests/events/expected/scope.rs new file mode 100644 index 0000000..6f66e86 --- /dev/null +++ b/templates/code/rust/tests/events/expected/scope.rs @@ -0,0 +1,13 @@ +#[derive(Clone, Debug, Default)] +pub struct Scope { + pub attributes: Vec<::opentelemetry::KeyValue>, +} + +impl From for ::opentelemetry::InstrumentationScope { + fn from(value: Scope) -> Self { + Self::builder("generated-semconv") + .with_schema_url(super::SCHEMA_URL) + .with_attributes(value.attributes) + .build() + } +} diff --git a/templates/code/rust/tests/events/expected/workerattr.rs b/templates/code/rust/tests/events/expected/workerattr.rs new file mode 100644 index 0000000..50da699 --- /dev/null +++ b/templates/code/rust/tests/events/expected/workerattr.rs @@ -0,0 +1,65 @@ +// Generated by OpenTelemetry Weaver. +/// Name of the worker host. +#[derive(Debug, Clone, PartialEq)] +pub struct HostNameAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl HostNameAttr { + pub const KEY: &'static str = "worker.host.name"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for HostNameAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for HostNameAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for HostNameAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for HostNameAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for HostNameAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: HostNameAttr) -> Self { + ::opentelemetry::KeyValue::new(HostNameAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: HostNameAttr) -> Self { + value.0.into() + } +} diff --git a/templates/code/rust/tests/events/expected/workerevent.rs b/templates/code/rust/tests/events/expected/workerevent.rs new file mode 100644 index 0000000..69a4c0f --- /dev/null +++ b/templates/code/rust/tests/events/expected/workerevent.rs @@ -0,0 +1,80 @@ +#![allow(deprecated)] +use ::opentelemetry::logs::LogRecord; +use ::opentelemetry::trace::TraceContextExt; + +#[derive(Clone, Debug)] +pub enum HostStartedOption { + Severity(::opentelemetry::logs::Severity), + Timestamp(::std::time::SystemTime), +} +/// A worker host started taking tasks. +pub fn r#emit_host_started(context: &::opentelemetry::Context, logger: &super::logger::Logger, options: impl IntoIterator) { + let mut severity = ::opentelemetry::logs::Severity::Info; + let mut timestamp = None; + for option in options { + match option { + HostStartedOption::Severity(value) => severity = value, + HostStartedOption::Timestamp(value) => timestamp = Some(value), + } + } + if !logger.inner().event_enabled(severity, "", Some("worker.host.started")) { return; } + let _context_guard = context.clone().attach(); + let mut record = logger.inner().create_log_record(); + record.set_event_name("worker.host.started"); + record.set_severity_number(severity); + record.set_observed_timestamp(::std::time::SystemTime::now()); + if let Some(timestamp) = timestamp { record.set_timestamp(timestamp); } + let span = context.span(); + let span_context = span.span_context(); + if span_context.is_valid() { record.set_trace_context(span_context.trace_id(), span_context.span_id(), Some(span_context.trace_flags())); } + logger.inner().emit(record); +} +#[derive(Clone, Debug)] +pub enum TaskFinishedOption { + Severity(::opentelemetry::logs::Severity), + Timestamp(::std::time::SystemTime), + BoolValues(super::myappattr::BoolValuesAttr), + DoubleValues(super::myappattr::DoubleValuesAttr), + IntValues(super::myappattr::IntValuesAttr), + StringValues(super::myappattr::StringValuesAttr), + /// Recommended: When the task ran more than once. + TaskAttempt(super::myappattr::TaskAttemptAttr), +} +/// A task reached a final state on a worker. +/// +/// Emitted once per task, after its last attempt. +pub fn r#emit_task_finished(context: &::opentelemetry::Context, logger: &super::logger::Logger, r#myapp_task_state: super::myappattr::TaskStateAttr, r#worker_host_name: super::workerattr::HostNameAttr, options: impl IntoIterator) { + let mut severity = ::opentelemetry::logs::Severity::Info; + let mut timestamp = None; + let mut attributes = ::smallvec::SmallVec::<[TaskFinishedOption; 5]>::new(); + for option in options { + match option { + TaskFinishedOption::Severity(value) => severity = value, + TaskFinishedOption::Timestamp(value) => timestamp = Some(value), + attribute => attributes.push(attribute), + } + } + if !logger.inner().event_enabled(severity, "", Some("worker.task.finished")) { return; } + let _context_guard = context.clone().attach(); + let mut record = logger.inner().create_log_record(); + record.set_event_name("worker.task.finished"); + record.set_severity_number(severity); + record.set_observed_timestamp(::std::time::SystemTime::now()); + if let Some(timestamp) = timestamp { record.set_timestamp(timestamp); } + let span = context.span(); + let span_context = span.span_context(); + if span_context.is_valid() { record.set_trace_context(span_context.trace_id(), span_context.span_id(), Some(span_context.trace_flags())); } + record.add_attribute(super::myappattr::TaskStateAttr::KEY, r#myapp_task_state); + record.add_attribute(super::workerattr::HostNameAttr::KEY, r#worker_host_name); + for option in attributes { + match option { + TaskFinishedOption::BoolValues(value) => record.add_attribute(super::myappattr::BoolValuesAttr::KEY, value), + TaskFinishedOption::DoubleValues(value) => record.add_attribute(super::myappattr::DoubleValuesAttr::KEY, value), + TaskFinishedOption::IntValues(value) => record.add_attribute(super::myappattr::IntValuesAttr::KEY, value), + TaskFinishedOption::StringValues(value) => record.add_attribute(super::myappattr::StringValuesAttr::KEY, value), + TaskFinishedOption::TaskAttempt(value) => record.add_attribute(super::myappattr::TaskAttemptAttr::KEY, value), + TaskFinishedOption::Severity(_) | TaskFinishedOption::Timestamp(_) => continue, + } + } + logger.inner().emit(record); +} diff --git a/templates/code/rust/tests/events/registry/manifest.yaml b/templates/code/rust/tests/events/registry/manifest.yaml new file mode 100644 index 0000000..18c4f14 --- /dev/null +++ b/templates/code/rust/tests/events/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: events +schema_url: https://example.com/schemas/events/1.0.0 diff --git a/templates/code/rust/tests/events/registry/model.yaml b/templates/code/rust/tests/events/registry/model.yaml new file mode 100644 index 0000000..7280fa5 --- /dev/null +++ b/templates/code/rust/tests/events/registry/model.yaml @@ -0,0 +1,88 @@ +file_format: definition/2 +attributes: + - key: myapp.task.state + type: + members: + - id: done + value: done + stability: stable + brief: The task finished. + - id: failed + value: failed + stability: stable + brief: The task gave up. + stability: stable + brief: Current state of the task. + - key: myapp.task.attempt + type: int + stability: stable + brief: Number of the attempt, starting at 1. + examples: [1] + - key: worker.host.name + type: string + stability: stable + brief: Name of the worker host. + examples: ["worker-1"] + - key: myapp.bool.values + type: boolean[] + stability: stable + brief: Synthetic bool values. + examples: [[true]] + - key: myapp.int.values + type: int[] + stability: stable + brief: Synthetic int values. + examples: [[1]] + - key: myapp.double.values + type: double[] + stability: stable + brief: Synthetic double values. + examples: [[1.5]] + - key: myapp.string.values + type: string[] + stability: stable + brief: Synthetic string values. + examples: [["fetch"]] +events: + - name: myapp.task.finished + stability: development + requirement_level: recommended + brief: A task reached a final state. + note: Emitted once per task, after its last attempt. + attributes: + - ref: myapp.task.state + requirement_level: required + - ref: myapp.task.attempt + requirement_level: + recommended: When the task ran more than once. + - ref: worker.host.name + requirement_level: opt_in + - ref: myapp.bool.values + requirement_level: recommended + - ref: myapp.int.values + requirement_level: recommended + - ref: myapp.double.values + requirement_level: recommended + - ref: myapp.string.values + requirement_level: recommended + - name: myapp.task.retried + stability: development + requirement_level: opt_in + brief: A task is about to run again. + deprecated: + reason: obsoleted + note: Read the attempt from `myapp.task.finished` instead. + attributes: + - ref: myapp.task.attempt + requirement_level: required + - name: worker.host.started + stability: stable + requirement_level: recommended + brief: A worker host started taking tasks. +event_refinements: + - id: worker.task.finished + ref: myapp.task.finished + brief: A task reached a final state on a worker. + attributes: + - ref: worker.host.name + requirement_level: required diff --git a/templates/code/rust/tests/events/test.rs b/templates/code/rust/tests/events/test.rs new file mode 100644 index 0000000..9fc78a0 --- /dev/null +++ b/templates/code/rust/tests/events/test.rs @@ -0,0 +1,137 @@ +use generated_semconv::options::InstrumentationOptions; +use generated_semconv::{ + logger, myappattr, myappevent, scope, workerattr, workerevent, SCHEMA_URL, +}; +use opentelemetry::logs::{AnyValue, Severity}; +use opentelemetry::trace::{SpanContext, SpanId, TraceContextExt, TraceFlags, TraceId, TraceState}; +use opentelemetry_sdk::logs::{InMemoryLogExporter, SdkLoggerProvider}; + +#[test] +fn typed_events_have_schema_timestamps_severity_attributes_and_trace_context() { + let exporter = InMemoryLogExporter::default(); + let provider = SdkLoggerProvider::builder() + .with_simple_exporter(exporter.clone()) + .build(); + let logger = logger::Logger::new( + &provider, + InstrumentationOptions { + scope: scope::Scope { + attributes: vec![opentelemetry::KeyValue::new("scope.kind", "test")], + }, + }, + ); + let span = SpanContext::new( + TraceId::from(7), + SpanId::from(9), + TraceFlags::SAMPLED, + false, + TraceState::default(), + ); + let context = opentelemetry::Context::new().with_remote_span_context(span.clone()); + let timestamp = std::time::UNIX_EPOCH + std::time::Duration::from_secs(100); + myappevent::emit_task_finished( + &context, + &logger, + myappattr::TaskStateAttr::Done, + [ + myappevent::TaskFinishedOption::TaskAttempt(myappattr::TaskAttemptAttr::from(2)), + myappevent::TaskFinishedOption::HostName(workerattr::HostNameAttr::from("worker-1")), + myappevent::TaskFinishedOption::Severity(Severity::Warn), + myappevent::TaskFinishedOption::Timestamp(timestamp), + myappevent::TaskFinishedOption::BoolValues(myappattr::BoolValuesAttr::from(vec![true])), + myappevent::TaskFinishedOption::IntValues(myappattr::IntValuesAttr::from(vec![1])), + myappevent::TaskFinishedOption::DoubleValues(myappattr::DoubleValuesAttr::from(vec![ + 1.5, + ])), + myappevent::TaskFinishedOption::StringValues(myappattr::StringValuesAttr::from(vec![ + opentelemetry::StringValue::from("fetch"), + ])), + ], + ); + workerevent::emit_task_finished( + &context, + &logger, + myappattr::TaskStateAttr::Done, + workerattr::HostNameAttr::from("worker-1"), + [], + ); + provider.force_flush().unwrap(); + let logs = exporter.get_emitted_logs().unwrap(); + assert_eq!(logs.len(), 2); + let log = &logs[0]; + assert_eq!(log.record.event_name(), Some("myapp.task.finished")); + assert_eq!(log.record.severity_number(), Some(Severity::Warn)); + assert_eq!(log.record.timestamp(), Some(timestamp)); + assert!(log.record.observed_timestamp().is_some()); + assert!(log.record.body().is_none()); + assert_eq!(log.instrumentation.schema_url(), Some(SCHEMA_URL)); + assert_eq!(log.instrumentation.name(), "generated-semconv"); + assert_eq!(log.instrumentation.version(), None); + assert!(log + .instrumentation + .attributes() + .any(|kv| kv == &opentelemetry::KeyValue::new("scope.kind", "test"))); + assert_eq!( + log.record.trace_context().unwrap().trace_id, + span.trace_id() + ); + assert_eq!(log.record.trace_context().unwrap().span_id, span.span_id()); + assert!(log + .record + .attributes_iter() + .any(|(key, value)| key.as_str() == "myapp.task.attempt" && value == &AnyValue::Int(2))); + for (key, expected) in [ + ("myapp.bool.values", AnyValue::Boolean(true)), + ("myapp.int.values", AnyValue::Int(1)), + ("myapp.double.values", AnyValue::Double(1.5)), + ("myapp.string.values", AnyValue::String("fetch".into())), + ] { + assert!(log + .record + .attributes_iter() + .any(|(attr_key, value)| attr_key.as_str() == key + && value == &AnyValue::ListAny(Box::new(vec![expected.clone()])))); + } + assert_eq!(logs[1].record.event_name(), Some("worker.task.finished")); + assert_eq!(logs[1].record.severity_number(), Some(Severity::Info)); + assert!(logs[1].record.timestamp().is_none()); + myappevent::emit_task_finished( + &context, + &logger::Logger::default(), + myappattr::TaskStateAttr::Done, + [], + ); +} + +struct DisabledProvider; +struct DisabledLogger; +impl opentelemetry::logs::LoggerProvider for DisabledProvider { + type Logger = DisabledLogger; + fn logger_with_scope(&self, _: opentelemetry::InstrumentationScope) -> DisabledLogger { + DisabledLogger + } +} +impl opentelemetry::logs::Logger for DisabledLogger { + type LogRecord = opentelemetry_sdk::logs::SdkLogRecord; + fn create_log_record(&self) -> Self::LogRecord { + panic!("disabled events must not create a record") + } + fn emit(&self, _: Self::LogRecord) { + panic!("disabled events must not emit a record") + } + fn event_enabled(&self, severity: Severity, target: &str, name: Option<&str>) -> bool { + assert_eq!(severity, Severity::Error); + assert_eq!(target, ""); + assert_eq!(name, Some("myapp.task.finished")); + false + } +} +#[test] +fn disabled_events_are_checked_before_record_creation() { + myappevent::emit_task_finished( + &opentelemetry::Context::new(), + &logger::Logger::new(&DisabledProvider, InstrumentationOptions::default()), + myappattr::TaskStateAttr::Done, + [myappevent::TaskFinishedOption::Severity(Severity::Error)], + ); +} diff --git a/templates/code/rust/tests/filtering/expected/authattr.rs b/templates/code/rust/tests/filtering/expected/authattr.rs new file mode 100644 index 0000000..c2c1df1 --- /dev/null +++ b/templates/code/rust/tests/filtering/expected/authattr.rs @@ -0,0 +1,122 @@ +// Generated by OpenTelemetry Weaver. +/// How the user was authenticated. +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum MethodAttr { + /// A password was checked. + Password, + /// A bearer token was checked. + Token, +} + +#[allow(deprecated)] +impl MethodAttr { + pub const KEY: &'static str = "auth.method"; +} + +#[allow(deprecated)] +impl AsRef for MethodAttr { + #[inline] + fn as_ref(&self) -> &str { + match self { + MethodAttr::Password => "password", + MethodAttr::Token => "token", + } + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for MethodAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: MethodAttr) -> Self { + let value: &'static str = match value { + MethodAttr::Password => "password", + MethodAttr::Token => "token", + }; + ::opentelemetry::KeyValue::new(MethodAttr::KEY, value) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: MethodAttr) -> Self { + let value: &'static str = match value { + MethodAttr::Password => "password", + MethodAttr::Token => "token", + }; + value.into() + } +} + +/// Identifier of the authenticated user. +#[derive(Debug, Clone, PartialEq)] +pub struct UserIdAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl UserIdAttr { + pub const KEY: &'static str = "auth.user.id"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for UserIdAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for UserIdAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for UserIdAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for UserIdAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for UserIdAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: UserIdAttr) -> Self { + ::opentelemetry::KeyValue::new(UserIdAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: UserIdAttr) -> Self { + value.0.into() + } +} diff --git a/templates/code/rust/tests/filtering/expected/mod.rs b/templates/code/rust/tests/filtering/expected/mod.rs new file mode 100644 index 0000000..db0137c --- /dev/null +++ b/templates/code/rust/tests/filtering/expected/mod.rs @@ -0,0 +1,4 @@ +// Generated by OpenTelemetry Weaver. +pub const SCHEMA_URL: &str = "https://example.com/schemas/filtering/1.0.0"; +pub mod authattr; +pub mod taskattr; diff --git a/templates/code/rust/tests/filtering/expected/taskattr.rs b/templates/code/rust/tests/filtering/expected/taskattr.rs new file mode 100644 index 0000000..2d388b5 --- /dev/null +++ b/templates/code/rust/tests/filtering/expected/taskattr.rs @@ -0,0 +1,492 @@ +// Generated by OpenTelemetry Weaver. +/// Who assigned the task. +/// +/// The scheduler sets it on every task it passes along to `myapp.task.state`. Workers never set it themselves. +#[derive(Debug, Clone, PartialEq)] +pub struct AssignedByAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl AssignedByAttr { + pub const KEY: &'static str = "myapp.task.assigned_by"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for AssignedByAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for AssignedByAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for AssignedByAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for AssignedByAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for AssignedByAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: AssignedByAttr) -> Self { + ::opentelemetry::KeyValue::new(AssignedByAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: AssignedByAttr) -> Self { + value.0.into() + } +} + +/// Whether the task was cancelled. +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct CancelledAttr(bool); + +#[allow(deprecated)] +impl CancelledAttr { + pub const KEY: &'static str = "myapp.task.cancelled"; +} + +#[allow(deprecated)] +impl From for CancelledAttr { + #[inline] + fn from(value: bool) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for CancelledAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::std::fmt::Display::fmt(&self.0, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: CancelledAttr) -> Self { + ::opentelemetry::KeyValue::new(CancelledAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: CancelledAttr) -> Self { + value.0.into() + } +} + +/// Unique identifier of the task. +/// +/// The identifier is assigned by the scheduler and stays stable across retries. +#[derive(Debug, Clone, PartialEq)] +pub struct IdAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl IdAttr { + pub const KEY: &'static str = "myapp.task.id"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for IdAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for IdAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for IdAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for IdAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for IdAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: IdAttr) -> Self { + ::opentelemetry::KeyValue::new(IdAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: IdAttr) -> Self { + value.0.into() + } +} + +/// Whether the scheduler may preempt the task. +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum PreemptibleAttr { + /// The scheduler may preempt the task. + Allowed, + /// The task runs to completion. + Forbidden, +} + +#[allow(deprecated)] +impl PreemptibleAttr { + pub const KEY: &'static str = "myapp.task.preemptible"; +} + +#[allow(deprecated)] +impl ::std::fmt::Display for PreemptibleAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + let value: bool = match self { + PreemptibleAttr::Allowed => true, + PreemptibleAttr::Forbidden => false, + }; + ::std::fmt::Display::fmt(&value, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: PreemptibleAttr) -> Self { + let value: bool = match value { + PreemptibleAttr::Allowed => true, + PreemptibleAttr::Forbidden => false, + }; + ::opentelemetry::KeyValue::new(PreemptibleAttr::KEY, value) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: PreemptibleAttr) -> Self { + let value: bool = match value { + PreemptibleAttr::Allowed => true, + PreemptibleAttr::Forbidden => false, + }; + value.into() + } +} + +/// Number of times the task was retried. +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct RetriesAttr(i64); + +#[allow(deprecated)] +impl RetriesAttr { + pub const KEY: &'static str = "myapp.task.retries"; +} + +#[allow(deprecated)] +impl From for RetriesAttr { + #[inline] + fn from(value: i64) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for RetriesAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::std::fmt::Display::fmt(&self.0, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: RetriesAttr) -> Self { + ::opentelemetry::KeyValue::new(RetriesAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: RetriesAttr) -> Self { + value.0.into() + } +} + +/// Fraction of tasks whose telemetry is sampled. +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum SampleRateAttr { + /// One task in ten is sampled. + Tenth, + /// Every task is sampled. + All, +} + +#[allow(deprecated)] +impl SampleRateAttr { + pub const KEY: &'static str = "myapp.task.sample_rate"; +} + +#[allow(deprecated)] +impl ::std::fmt::Display for SampleRateAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + let value: f64 = match self { + SampleRateAttr::Tenth => 0.1_f64, + SampleRateAttr::All => 1_f64, + }; + ::std::fmt::Display::fmt(&value, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: SampleRateAttr) -> Self { + let value: f64 = match value { + SampleRateAttr::Tenth => 0.1_f64, + SampleRateAttr::All => 1_f64, + }; + ::opentelemetry::KeyValue::new(SampleRateAttr::KEY, value) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: SampleRateAttr) -> Self { + let value: f64 = match value { + SampleRateAttr::Tenth => 0.1_f64, + SampleRateAttr::All => 1_f64, + }; + value.into() + } +} + +/// Shards the task touched. +#[derive(Debug, Clone, PartialEq)] +pub struct ShardIdsAttr(Vec); + +#[allow(deprecated)] +impl ShardIdsAttr { + pub const KEY: &'static str = "myapp.task.shard_ids"; +} + +#[allow(deprecated)] +impl From> for ShardIdsAttr { + #[inline] + fn from(value: Vec) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl AsRef<[i64]> for ShardIdsAttr { + #[inline] + fn as_ref(&self) -> &[i64] { + &self.0 + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for ShardIdsAttr { + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str("[")?; + for (index, value) in self.0.iter().enumerate() { + if index != 0 { + formatter.write_str(",")?; + } + ::std::fmt::Display::fmt(value, formatter)?; + } + formatter.write_str("]") + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: ShardIdsAttr) -> Self { + ::opentelemetry::KeyValue::new(ShardIdsAttr::KEY, ::opentelemetry::Value::Array(::opentelemetry::Array::I64(value.0))) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: ShardIdsAttr) -> Self { + value.0.into_iter().collect::<::opentelemetry::logs::AnyValue>() + } +} + +/// Current state of the task. +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum StateAttr { + /// The task is waiting to run. + Queued, + /// The task is running. + /// + /// A task stays running until it finishes or is cancelled. + Running, +} + +#[allow(deprecated)] +impl StateAttr { + pub const KEY: &'static str = "myapp.task.state"; +} + +#[allow(deprecated)] +impl AsRef for StateAttr { + #[inline] + fn as_ref(&self) -> &str { + match self { + StateAttr::Queued => "queued", + StateAttr::Running => "running", + } + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for StateAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: StateAttr) -> Self { + let value: &'static str = match value { + StateAttr::Queued => "queued", + StateAttr::Running => "running", + }; + ::opentelemetry::KeyValue::new(StateAttr::KEY, value) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: StateAttr) -> Self { + let value: &'static str = match value { + StateAttr::Queued => "queued", + StateAttr::Running => "running", + }; + value.into() + } +} + +/// Tags attached to the task. +#[derive(Debug, Clone, PartialEq)] +pub struct TagsAttr(Vec<::opentelemetry::StringValue>); + +#[allow(deprecated)] +impl TagsAttr { + pub const KEY: &'static str = "myapp.task.tags"; +} + +#[allow(deprecated)] +impl From> for TagsAttr { + #[inline] + fn from(value: Vec<::opentelemetry::StringValue>) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From> for TagsAttr { + fn from(value: Vec) -> Self { + Self(value.into_iter().map(Into::into).collect()) + } +} + +#[allow(deprecated)] +impl AsRef<[::opentelemetry::StringValue]> for TagsAttr { + #[inline] + fn as_ref(&self) -> &[::opentelemetry::StringValue] { + &self.0 + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TagsAttr { + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str("[")?; + for (index, value) in self.0.iter().enumerate() { + if index != 0 { + formatter.write_str(",")?; + } + write!(formatter, "\"{value}\"")?; + } + formatter.write_str("]") + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TagsAttr) -> Self { + ::opentelemetry::KeyValue::new(TagsAttr::KEY, ::opentelemetry::Value::Array(::opentelemetry::Array::String(value.0))) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TagsAttr) -> Self { + value.0.into_iter().collect::<::opentelemetry::logs::AnyValue>() + } +} diff --git a/templates/code/rust/tests/filtering/params.yaml b/templates/code/rust/tests/filtering/params.yaml new file mode 100644 index 0000000..5ccc45f --- /dev/null +++ b/templates/code/rust/tests/filtering/params.yaml @@ -0,0 +1,4 @@ +params: + stable_only: true + exclude_deprecated: true + vendor_prefixes: [myapp] diff --git a/templates/code/rust/tests/filtering/registry/manifest.yaml b/templates/code/rust/tests/filtering/registry/manifest.yaml new file mode 100644 index 0000000..b9f7519 --- /dev/null +++ b/templates/code/rust/tests/filtering/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: filtering +schema_url: https://example.com/schemas/filtering/1.0.0 diff --git a/templates/code/rust/tests/filtering/registry/model.yaml b/templates/code/rust/tests/filtering/registry/model.yaml new file mode 100644 index 0000000..e43af4d --- /dev/null +++ b/templates/code/rust/tests/filtering/registry/model.yaml @@ -0,0 +1,154 @@ +file_format: definition/2 +attributes: + - key: myapp.task.id + type: string + stability: stable + brief: Unique identifier of the task. + note: The identifier is assigned by the scheduler and stays stable across retries. + examples: ["task_0001"] + - key: myapp.task.retries + type: int + stability: stable + brief: Number of times the task was retried. + examples: 3 + - key: myapp.task.progress + type: double + stability: development + brief: Fraction of the task that has completed, between 0 and 1. + examples: 0.5 + - key: myapp.task.cancelled + type: boolean + stability: stable + brief: Whether the task was cancelled. + examples: false + - key: myapp.task.tags + type: string[] + stability: stable + brief: Tags attached to the task. + examples: [["nightly", "batch"]] + - key: myapp.task.shard_ids + type: int[] + stability: stable + brief: Shards the task touched. + examples: [[1, 2]] + - key: myapp.task.weights + type: double[] + stability: development + brief: Scheduling weights applied to the task. + examples: [[0.25, 0.75]] + - key: myapp.task.flags + type: boolean[] + stability: development + brief: Feature flags evaluated for the task. + examples: [[true, false]] + - key: myapp.task.state + type: + members: + - id: queued + value: queued + stability: stable + brief: The task is waiting to run. + - id: running + value: running + stability: stable + brief: The task is running. + note: A task stays running until it finishes or is cancelled. + - id: paused + value: paused + stability: development + brief: The task was paused. + deprecated: + reason: obsoleted + note: Paused tasks are reported as queued. + stability: stable + brief: Current state of the task. + - key: myapp.task.owner + type: string + stability: development + brief: Owner of the task. + examples: ["solo"] + deprecated: + reason: renamed + renamed_to: auth.user.id + - key: auth.user.id + type: string + stability: stable + brief: Identifier of the authenticated user. + examples: ["u_123"] + - key: auth.method + type: + members: + - id: password + value: password + stability: stable + brief: A password was checked. + - id: token + value: token + stability: stable + brief: A bearer token was checked. + stability: stable + brief: How the user was authenticated. + - key: auth.level + type: + members: + - id: low + value: 1 + stability: stable + brief: Single factor. + - id: high + value: 2 + stability: stable + brief: Multiple factors. + stability: development + brief: Assurance level of the authentication. + - key: myapp.task.preemptible + type: + members: + - id: allowed + value: true + stability: stable + brief: The scheduler may preempt the task. + - id: forbidden + value: false + stability: stable + brief: The task runs to completion. + stability: stable + brief: Whether the scheduler may preempt the task. + - key: myapp.task.sample_rate + type: + members: + - id: tenth + value: 0.1 + stability: stable + brief: One task in ten is sampled. + - id: all + value: 1 + stability: stable + brief: Every task is sampled. + stability: stable + brief: Fraction of tasks whose telemetry is sampled. + - key: myapp.task.assigned_by + type: string + stability: stable + brief: Who assigned the task. + note: The scheduler sets it on every task it passes along to `myapp.task.state`. Workers never set it themselves. + examples: ["scheduler"] + - key: myapp.task.lane + type: + members: + - id: fast + value: fast + stability: stable + brief: The fast lane. + - id: slow + value: slow + stability: stable + brief: The slow lane. + deprecated: + reason: obsoleted + note: Every task runs in the fast lane. + stability: development + brief: Lane the task was scheduled in. + deprecated: + reason: obsoleted + note: Lanes were replaced by priorities. diff --git a/templates/code/rust/tests/filtering/test.rs b/templates/code/rust/tests/filtering/test.rs new file mode 100644 index 0000000..8ee0762 --- /dev/null +++ b/templates/code/rust/tests/filtering/test.rs @@ -0,0 +1,14 @@ +use generated_semconv::taskattr::{IdAttr, StateAttr}; +use opentelemetry::{KeyValue, Value}; + +#[test] +fn vendor_prefix_changes_types_but_preserves_telemetry_keys() { + assert_eq!( + ::opentelemetry::KeyValue::from(IdAttr::from("task")), + KeyValue::new("myapp.task.id", "task") + ); + assert_eq!( + ::opentelemetry::KeyValue::from(StateAttr::Queued).value, + Value::String("queued".into()) + ); +} diff --git a/templates/code/rust/tests/histogram_boundaries_by_unit/expected/meter.rs b/templates/code/rust/tests/histogram_boundaries_by_unit/expected/meter.rs new file mode 100644 index 0000000..a330bd2 --- /dev/null +++ b/templates/code/rust/tests/histogram_boundaries_by_unit/expected/meter.rs @@ -0,0 +1,16 @@ +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Meter(opentelemetry::metrics::Meter); + +impl Meter { + pub fn new(provider: &impl opentelemetry::metrics::MeterProvider, options: InstrumentationOptions) -> Self { + let meter = provider.meter_with_scope(options.scope.into()); + Self(meter) + } + pub fn inner(&self) -> &opentelemetry::metrics::Meter { &self.0 } +} + +impl Default for Meter { + fn default() -> Self { Self::new(&opentelemetry::metrics::noop::NoopMeterProvider::new(), InstrumentationOptions::default()) } +} diff --git a/templates/code/rust/tests/histogram_boundaries_by_unit/expected/mod.rs b/templates/code/rust/tests/histogram_boundaries_by_unit/expected/mod.rs new file mode 100644 index 0000000..834ced1 --- /dev/null +++ b/templates/code/rust/tests/histogram_boundaries_by_unit/expected/mod.rs @@ -0,0 +1,8 @@ +// Generated by OpenTelemetry Weaver. +pub const SCHEMA_URL: &str = "https://example.com/schemas/histogram_boundaries_by_unit/1.0.0"; +pub mod myappattr; + +pub mod scope; +pub mod options; +pub mod meter; +pub mod myappmetric; diff --git a/templates/code/rust/tests/histogram_boundaries_by_unit/expected/myappattr.rs b/templates/code/rust/tests/histogram_boundaries_by_unit/expected/myappattr.rs new file mode 100644 index 0000000..c2a50dd --- /dev/null +++ b/templates/code/rust/tests/histogram_boundaries_by_unit/expected/myappattr.rs @@ -0,0 +1,65 @@ +// Generated by OpenTelemetry Weaver. +/// Unique identifier of the task. +#[derive(Debug, Clone, PartialEq)] +pub struct TaskIdAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl TaskIdAttr { + pub const KEY: &'static str = "myapp.task.id"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for TaskIdAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for TaskIdAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for TaskIdAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for TaskIdAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskIdAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskIdAttr) -> Self { + ::opentelemetry::KeyValue::new(TaskIdAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskIdAttr) -> Self { + value.0.into() + } +} diff --git a/templates/code/rust/tests/histogram_boundaries_by_unit/expected/myappmetric.rs b/templates/code/rust/tests/histogram_boundaries_by_unit/expected/myappmetric.rs new file mode 100644 index 0000000..8d23184 --- /dev/null +++ b/templates/code/rust/tests/histogram_boundaries_by_unit/expected/myappmetric.rs @@ -0,0 +1,96 @@ +#![allow(deprecated)] +/// Time a task took from start to finish. +#[derive(Clone)] +pub struct TaskDurationHistogram { + instrument: ::opentelemetry::metrics::Histogram, +} + +impl TaskDurationHistogram { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().f64_histogram("myapp.task.duration") + .with_description("Time a task took from start to finish.") + .with_unit("s") + .with_boundaries(vec![0.005_f64, 0.01_f64, 0.025_f64, 0.05_f64, 0.075_f64, 0.1_f64, 0.25_f64, 0.5_f64, 0.75_f64, 1_f64, 2.5_f64, 5_f64, 7.5_f64, 10_f64]) + .build(); + Self { instrument } + } + pub fn record(&self, value: f64, r#myapp_task_id: super::myappattr::TaskIdAttr) { + let attributes = [::opentelemetry::KeyValue::from(r#myapp_task_id)]; + self.instrument.record(value, &attributes); + } +} + +impl Default for TaskDurationHistogram { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} +/// Time between a task being queued and started. +#[derive(Clone)] +pub struct TaskLatencyHistogram { + instrument: ::opentelemetry::metrics::Histogram, +} + +impl TaskLatencyHistogram { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().f64_histogram("myapp.task.latency") + .with_description("Time between a task being queued and started.") + .with_unit("s") + .with_boundaries(vec![1_f64, 10_f64, 100_f64]) + .build(); + Self { instrument } + } + pub fn record(&self, value: f64, r#myapp_task_id: super::myappattr::TaskIdAttr) { + let attributes = [::opentelemetry::KeyValue::from(r#myapp_task_id)]; + self.instrument.record(value, &attributes); + } +} + +impl Default for TaskLatencyHistogram { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} +/// Time a retried task took from start to finish. +#[derive(Clone)] +pub struct TaskRetryDurationHistogram { + instrument: ::opentelemetry::metrics::Histogram, +} + +impl TaskRetryDurationHistogram { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().f64_histogram("myapp.task.duration") + .with_description("Time a task took from start to finish.") + .with_unit("s") + .with_boundaries(vec![0.005_f64, 0.01_f64, 0.025_f64, 0.05_f64, 0.075_f64, 0.1_f64, 0.25_f64, 0.5_f64, 0.75_f64, 1_f64, 2.5_f64, 5_f64, 7.5_f64, 10_f64]) + .build(); + Self { instrument } + } + pub fn record(&self, value: f64, r#myapp_task_id: super::myappattr::TaskIdAttr) { + let attributes = [::opentelemetry::KeyValue::from(r#myapp_task_id)]; + self.instrument.record(value, &attributes); + } +} + +impl Default for TaskRetryDurationHistogram { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} +/// Size of a task's payload. +#[derive(Clone)] +pub struct TaskSizeHistogram { + instrument: ::opentelemetry::metrics::Histogram, +} + +impl TaskSizeHistogram { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().f64_histogram("myapp.task.size") + .with_description("Size of a task\u{0027}s payload.") + .with_unit("By") + .build(); + Self { instrument } + } + pub fn record(&self, value: f64, r#myapp_task_id: super::myappattr::TaskIdAttr) { + let attributes = [::opentelemetry::KeyValue::from(r#myapp_task_id)]; + self.instrument.record(value, &attributes); + } +} + +impl Default for TaskSizeHistogram { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} diff --git a/templates/code/rust/tests/histogram_boundaries_by_unit/expected/options.rs b/templates/code/rust/tests/histogram_boundaries_by_unit/expected/options.rs new file mode 100644 index 0000000..69a821a --- /dev/null +++ b/templates/code/rust/tests/histogram_boundaries_by_unit/expected/options.rs @@ -0,0 +1,6 @@ +use super::scope::Scope; + +#[derive(Clone, Debug, Default)] +pub struct InstrumentationOptions { + pub scope: Scope, +} diff --git a/templates/code/rust/tests/histogram_boundaries_by_unit/expected/scope.rs b/templates/code/rust/tests/histogram_boundaries_by_unit/expected/scope.rs new file mode 100644 index 0000000..6f66e86 --- /dev/null +++ b/templates/code/rust/tests/histogram_boundaries_by_unit/expected/scope.rs @@ -0,0 +1,13 @@ +#[derive(Clone, Debug, Default)] +pub struct Scope { + pub attributes: Vec<::opentelemetry::KeyValue>, +} + +impl From for ::opentelemetry::InstrumentationScope { + fn from(value: Scope) -> Self { + Self::builder("generated-semconv") + .with_schema_url(super::SCHEMA_URL) + .with_attributes(value.attributes) + .build() + } +} diff --git a/templates/code/rust/tests/histogram_boundaries_by_unit/registry/manifest.yaml b/templates/code/rust/tests/histogram_boundaries_by_unit/registry/manifest.yaml new file mode 100644 index 0000000..437da86 --- /dev/null +++ b/templates/code/rust/tests/histogram_boundaries_by_unit/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: histogram_boundaries_by_unit +schema_url: https://example.com/schemas/histogram_boundaries_by_unit/1.0.0 diff --git a/templates/code/rust/tests/histogram_boundaries_by_unit/registry/myapp.yaml b/templates/code/rust/tests/histogram_boundaries_by_unit/registry/myapp.yaml new file mode 100644 index 0000000..6d1f779 --- /dev/null +++ b/templates/code/rust/tests/histogram_boundaries_by_unit/registry/myapp.yaml @@ -0,0 +1,53 @@ +file_format: definition/2 +attributes: + - key: myapp.task.id + type: string + stability: stable + brief: Unique identifier of the task. + examples: ["task_0001"] +metrics: + - name: myapp.task.duration + instrument: histogram + unit: s + stability: stable + brief: Time a task took from start to finish. + annotations: + code_generation: + metric_value_type: double + attributes: + - ref: myapp.task.id + requirement_level: required + - name: myapp.task.latency + instrument: histogram + unit: s + stability: stable + brief: Time between a task being queued and started. + annotations: + code_generation: + metric_value_type: double + aggregation: + method: explicithistogram + parameters: + boundaries: [1, 10, 100] + attributes: + - ref: myapp.task.id + requirement_level: required + - name: myapp.task.size + instrument: histogram + unit: By + stability: stable + brief: Size of a task's payload. + annotations: + code_generation: + metric_value_type: double + attributes: + - ref: myapp.task.id + requirement_level: required +metric_refinements: + - id: myapp.task.retry.duration + ref: myapp.task.duration + stability: stable + brief: Time a retried task took from start to finish. + attributes: + - ref: myapp.task.id + requirement_level: required diff --git a/templates/code/rust/tests/histogram_boundaries_by_unit_params/expected/meter.rs b/templates/code/rust/tests/histogram_boundaries_by_unit_params/expected/meter.rs new file mode 100644 index 0000000..a330bd2 --- /dev/null +++ b/templates/code/rust/tests/histogram_boundaries_by_unit_params/expected/meter.rs @@ -0,0 +1,16 @@ +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Meter(opentelemetry::metrics::Meter); + +impl Meter { + pub fn new(provider: &impl opentelemetry::metrics::MeterProvider, options: InstrumentationOptions) -> Self { + let meter = provider.meter_with_scope(options.scope.into()); + Self(meter) + } + pub fn inner(&self) -> &opentelemetry::metrics::Meter { &self.0 } +} + +impl Default for Meter { + fn default() -> Self { Self::new(&opentelemetry::metrics::noop::NoopMeterProvider::new(), InstrumentationOptions::default()) } +} diff --git a/templates/code/rust/tests/histogram_boundaries_by_unit_params/expected/mod.rs b/templates/code/rust/tests/histogram_boundaries_by_unit_params/expected/mod.rs new file mode 100644 index 0000000..bf2567f --- /dev/null +++ b/templates/code/rust/tests/histogram_boundaries_by_unit_params/expected/mod.rs @@ -0,0 +1,8 @@ +// Generated by OpenTelemetry Weaver. +pub const SCHEMA_URL: &str = "https://example.com/schemas/histogram_boundaries_by_unit_params/1.0.0"; +pub mod myappattr; + +pub mod scope; +pub mod options; +pub mod meter; +pub mod myappmetric; diff --git a/templates/code/rust/tests/histogram_boundaries_by_unit_params/expected/myappattr.rs b/templates/code/rust/tests/histogram_boundaries_by_unit_params/expected/myappattr.rs new file mode 100644 index 0000000..c2a50dd --- /dev/null +++ b/templates/code/rust/tests/histogram_boundaries_by_unit_params/expected/myappattr.rs @@ -0,0 +1,65 @@ +// Generated by OpenTelemetry Weaver. +/// Unique identifier of the task. +#[derive(Debug, Clone, PartialEq)] +pub struct TaskIdAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl TaskIdAttr { + pub const KEY: &'static str = "myapp.task.id"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for TaskIdAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for TaskIdAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for TaskIdAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for TaskIdAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskIdAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskIdAttr) -> Self { + ::opentelemetry::KeyValue::new(TaskIdAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskIdAttr) -> Self { + value.0.into() + } +} diff --git a/templates/code/rust/tests/histogram_boundaries_by_unit_params/expected/myappmetric.rs b/templates/code/rust/tests/histogram_boundaries_by_unit_params/expected/myappmetric.rs new file mode 100644 index 0000000..bd6280a --- /dev/null +++ b/templates/code/rust/tests/histogram_boundaries_by_unit_params/expected/myappmetric.rs @@ -0,0 +1,48 @@ +#![allow(deprecated)] +/// Time a task took from start to finish. +#[derive(Clone)] +pub struct TaskDurationHistogram { + instrument: ::opentelemetry::metrics::Histogram, +} + +impl TaskDurationHistogram { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().f64_histogram("myapp.task.duration") + .with_description("Time a task took from start to finish.") + .with_unit("s") + .build(); + Self { instrument } + } + pub fn record(&self, value: f64, r#myapp_task_id: super::myappattr::TaskIdAttr) { + let attributes = [::opentelemetry::KeyValue::from(r#myapp_task_id)]; + self.instrument.record(value, &attributes); + } +} + +impl Default for TaskDurationHistogram { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} +/// Time a task waited in the queue, in minutes. +#[derive(Clone)] +pub struct TaskQueueWaitHistogram { + instrument: ::opentelemetry::metrics::Histogram, +} + +impl TaskQueueWaitHistogram { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().f64_histogram("myapp.task.queue_wait") + .with_description("Time a task waited in the queue, in minutes.") + .with_unit("min") + .with_boundaries(vec![1_f64, 5_f64, 10_f64, 30_f64, 60_f64]) + .build(); + Self { instrument } + } + pub fn record(&self, value: f64, r#myapp_task_id: super::myappattr::TaskIdAttr) { + let attributes = [::opentelemetry::KeyValue::from(r#myapp_task_id)]; + self.instrument.record(value, &attributes); + } +} + +impl Default for TaskQueueWaitHistogram { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} diff --git a/templates/code/rust/tests/histogram_boundaries_by_unit_params/expected/options.rs b/templates/code/rust/tests/histogram_boundaries_by_unit_params/expected/options.rs new file mode 100644 index 0000000..69a821a --- /dev/null +++ b/templates/code/rust/tests/histogram_boundaries_by_unit_params/expected/options.rs @@ -0,0 +1,6 @@ +use super::scope::Scope; + +#[derive(Clone, Debug, Default)] +pub struct InstrumentationOptions { + pub scope: Scope, +} diff --git a/templates/code/rust/tests/histogram_boundaries_by_unit_params/expected/scope.rs b/templates/code/rust/tests/histogram_boundaries_by_unit_params/expected/scope.rs new file mode 100644 index 0000000..6f66e86 --- /dev/null +++ b/templates/code/rust/tests/histogram_boundaries_by_unit_params/expected/scope.rs @@ -0,0 +1,13 @@ +#[derive(Clone, Debug, Default)] +pub struct Scope { + pub attributes: Vec<::opentelemetry::KeyValue>, +} + +impl From for ::opentelemetry::InstrumentationScope { + fn from(value: Scope) -> Self { + Self::builder("generated-semconv") + .with_schema_url(super::SCHEMA_URL) + .with_attributes(value.attributes) + .build() + } +} diff --git a/templates/code/rust/tests/histogram_boundaries_by_unit_params/params.yaml b/templates/code/rust/tests/histogram_boundaries_by_unit_params/params.yaml new file mode 100644 index 0000000..a4d15ed --- /dev/null +++ b/templates/code/rust/tests/histogram_boundaries_by_unit_params/params.yaml @@ -0,0 +1,3 @@ +params: + histogram_boundaries_by_unit: + min: [1, 5, 10, 30, 60] diff --git a/templates/code/rust/tests/histogram_boundaries_by_unit_params/registry/manifest.yaml b/templates/code/rust/tests/histogram_boundaries_by_unit_params/registry/manifest.yaml new file mode 100644 index 0000000..22ea470 --- /dev/null +++ b/templates/code/rust/tests/histogram_boundaries_by_unit_params/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: histogram_boundaries_by_unit_params +schema_url: https://example.com/schemas/histogram_boundaries_by_unit_params/1.0.0 diff --git a/templates/code/rust/tests/histogram_boundaries_by_unit_params/registry/myapp.yaml b/templates/code/rust/tests/histogram_boundaries_by_unit_params/registry/myapp.yaml new file mode 100644 index 0000000..cda8262 --- /dev/null +++ b/templates/code/rust/tests/histogram_boundaries_by_unit_params/registry/myapp.yaml @@ -0,0 +1,30 @@ +file_format: definition/2 +attributes: + - key: myapp.task.id + type: string + stability: stable + brief: Unique identifier of the task. + examples: ["task_0001"] +metrics: + - name: myapp.task.duration + instrument: histogram + unit: s + stability: stable + brief: Time a task took from start to finish. + annotations: + code_generation: + metric_value_type: double + attributes: + - ref: myapp.task.id + requirement_level: required + - name: myapp.task.queue_wait + instrument: histogram + unit: min + stability: stable + brief: Time a task waited in the queue, in minutes. + annotations: + code_generation: + metric_value_type: double + attributes: + - ref: myapp.task.id + requirement_level: required diff --git a/templates/code/rust/tests/imported/dependency/manifest.yaml b/templates/code/rust/tests/imported/dependency/manifest.yaml new file mode 100644 index 0000000..6fe2cd0 --- /dev/null +++ b/templates/code/rust/tests/imported/dependency/manifest.yaml @@ -0,0 +1,2 @@ +name: upstream +schema_url: https://example.com/schemas/upstream/1.0.0 diff --git a/templates/code/rust/tests/imported/dependency/model.yaml b/templates/code/rust/tests/imported/dependency/model.yaml new file mode 100644 index 0000000..109e05e --- /dev/null +++ b/templates/code/rust/tests/imported/dependency/model.yaml @@ -0,0 +1,30 @@ +file_format: definition/2 +attributes: + - key: error.type + type: + members: + - id: timeout + value: timeout + stability: stable + brief: The operation timed out. + - id: other + value: _OTHER + stability: stable + brief: Any other error. + stability: stable + brief: Describes a class of error. + - key: server.address + type: string + stability: stable + brief: Server domain name or IP address. + examples: ["example.com"] + - key: server.port + type: int + stability: stable + brief: Server port number. + examples: [443] + - key: http.request.method_list + type: string[] + stability: stable + brief: Methods attempted, in order. + examples: [["GET", "POST"]] diff --git a/templates/code/rust/tests/imported/expected/edgeevent.rs b/templates/code/rust/tests/imported/expected/edgeevent.rs new file mode 100644 index 0000000..0bcd626 --- /dev/null +++ b/templates/code/rust/tests/imported/expected/edgeevent.rs @@ -0,0 +1,43 @@ +#![allow(deprecated)] +use ::opentelemetry::logs::LogRecord; +use ::opentelemetry::trace::TraceContextExt; + +#[derive(Clone, Debug)] +pub enum UpstreamFailedOption { + Severity(::opentelemetry::logs::Severity), + Timestamp(::std::time::SystemTime), + HttpRequestMethodList(Vec<::opentelemetry::StringValue>), + ServerAddress(::opentelemetry::StringValue), +} +/// A request to an upstream server failed. +pub fn r#emit_upstream_failed(context: &::opentelemetry::Context, logger: &super::logger::Logger, r#error_type: ::opentelemetry::StringValue, options: impl IntoIterator) { + let mut severity = ::opentelemetry::logs::Severity::Info; + let mut timestamp = None; + let mut attributes = ::smallvec::SmallVec::<[UpstreamFailedOption; 2]>::new(); + for option in options { + match option { + UpstreamFailedOption::Severity(value) => severity = value, + UpstreamFailedOption::Timestamp(value) => timestamp = Some(value), + attribute => attributes.push(attribute), + } + } + if !logger.inner().event_enabled(severity, "", Some("edge.upstream.failed")) { return; } + let _context_guard = context.clone().attach(); + let mut record = logger.inner().create_log_record(); + record.set_event_name("edge.upstream.failed"); + record.set_severity_number(severity); + record.set_observed_timestamp(::std::time::SystemTime::now()); + if let Some(timestamp) = timestamp { record.set_timestamp(timestamp); } + let span = context.span(); + let span_context = span.span_context(); + if span_context.is_valid() { record.set_trace_context(span_context.trace_id(), span_context.span_id(), Some(span_context.trace_flags())); } + record.add_attribute("error.type", r#error_type); + for option in attributes { + match option { + UpstreamFailedOption::HttpRequestMethodList(value) => record.add_attribute("http.request.method_list", value.into_iter().collect::<::opentelemetry::logs::AnyValue>()), + UpstreamFailedOption::ServerAddress(value) => record.add_attribute("server.address", value), + UpstreamFailedOption::Severity(_) | UpstreamFailedOption::Timestamp(_) => continue, + } + } + logger.inner().emit(record); +} diff --git a/templates/code/rust/tests/imported/expected/edgemetric.rs b/templates/code/rust/tests/imported/expected/edgemetric.rs new file mode 100644 index 0000000..0df00c7 --- /dev/null +++ b/templates/code/rust/tests/imported/expected/edgemetric.rs @@ -0,0 +1,39 @@ +#![allow(deprecated)] +#[derive(Clone, Debug)] +pub enum UpstreamDurationHistogramAttr { + ServerPort(i64), +} + +impl From for ::opentelemetry::KeyValue { + fn from(value: UpstreamDurationHistogramAttr) -> Self { + match value { + UpstreamDurationHistogramAttr::ServerPort(value) => ::opentelemetry::KeyValue::new("server.port", value), + } + } +} +/// Time spent waiting on an upstream server. +#[derive(Clone)] +pub struct UpstreamDurationHistogram { + instrument: ::opentelemetry::metrics::Histogram, +} + +impl UpstreamDurationHistogram { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().f64_histogram("edge.upstream.duration") + .with_description("Time spent waiting on an upstream server.") + .with_unit("s") + .with_boundaries(vec![0.005_f64, 0.01_f64, 0.025_f64, 0.05_f64, 0.075_f64, 0.1_f64, 0.25_f64, 0.5_f64, 0.75_f64, 1_f64, 2.5_f64, 5_f64, 7.5_f64, 10_f64]) + .build(); + Self { instrument } + } + pub fn record(&self, value: f64, r#server_address: ::opentelemetry::StringValue, options: impl IntoIterator) { + let mut attributes = ::smallvec::SmallVec::<[::opentelemetry::KeyValue; 2]>::new(); + attributes.extend([::opentelemetry::KeyValue::new("server.address", r#server_address)]); + attributes.extend(options.into_iter().map(::opentelemetry::KeyValue::from)); + self.instrument.record(value, &attributes); + } +} + +impl Default for UpstreamDurationHistogram { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} diff --git a/templates/code/rust/tests/imported/expected/edgespan.rs b/templates/code/rust/tests/imported/expected/edgespan.rs new file mode 100644 index 0000000..a7ba98f --- /dev/null +++ b/templates/code/rust/tests/imported/expected/edgespan.rs @@ -0,0 +1,109 @@ +#![allow(deprecated)] +use ::opentelemetry::trace::{TraceContextExt, Tracer}; +use ::std::fmt::Write; + +/// A request named from attempted methods. +pub struct UpstreamMethodsSpan { + span: S, +} + +impl UpstreamMethodsSpan { + pub fn into_context(self, parent: &::opentelemetry::Context) -> ::opentelemetry::Context + where S: Send + Sync + 'static, + { + parent.with_span(self.span) + } + pub fn span(&mut self) -> &mut S { &mut self.span } + pub fn end(&mut self) { ::opentelemetry::trace::Span::end(&mut self.span); } + pub fn end_with_timestamp(&mut self, timestamp: ::std::time::SystemTime) { ::opentelemetry::trace::Span::end_with_timestamp(&mut self.span, timestamp); } + pub fn record_error(&mut self, error: &dyn ::std::error::Error) { ::opentelemetry::trace::Span::record_error(&mut self.span, error); } + pub fn set_status(&mut self, status: ::opentelemetry::trace::Status) { ::opentelemetry::trace::Span::set_status(&mut self.span, status); } +} + +impl Default for UpstreamMethodsSpan { + fn default() -> Self { + Self { span: super::tracer::Tracer::default().inner().start("edge.upstream.methods") } + } +} + +pub fn r#start_upstream_methods(context: &::opentelemetry::Context, tracer: &super::tracer::Tracer, r#http_request_method_list: Vec<::opentelemetry::StringValue>) -> UpstreamMethodsSpan { + let mut name = String::with_capacity(r#http_request_method_list.iter().map(|value| value.as_str().len() + 3).sum::() + 2); + name.push('['); + for (index, value) in r#http_request_method_list.iter().enumerate() { + if index > 0 { name.push(','); } + write!(&mut name, "\"{}\"", value).expect("writing a span name to String cannot fail"); + } + name.push(']'); + let attributes: [::opentelemetry::KeyValue; 1] = [::opentelemetry::KeyValue::new("http.request.method_list", ::opentelemetry::Value::Array(::opentelemetry::Array::String(r#http_request_method_list)))]; + let span = tracer.inner().span_builder(name) + .with_kind(::opentelemetry::trace::SpanKind::Client) + .with_attributes(attributes) + .start_with_context(tracer.inner(), context); + UpstreamMethodsSpan { span } +} +#[derive(Clone, Debug)] +pub enum UpstreamRequestStartAttr { + /// Conditionally required: If the request failed. + ErrorType(::opentelemetry::StringValue), + HttpRequestMethodList(Vec<::opentelemetry::StringValue>), +} + +impl From for ::opentelemetry::KeyValue { + fn from(value: UpstreamRequestStartAttr) -> Self { + match value { + UpstreamRequestStartAttr::ErrorType(value) => ::opentelemetry::KeyValue::new("error.type", value), + UpstreamRequestStartAttr::HttpRequestMethodList(value) => ::opentelemetry::KeyValue::new("http.request.method_list", ::opentelemetry::Value::Array(::opentelemetry::Array::String(value))), + } + } +} +#[derive(Clone, Debug)] +pub enum UpstreamRequestAttr { + HttpRequestMethodList(Vec<::opentelemetry::StringValue>), +} + +impl From for UpstreamRequestStartAttr { + fn from(value: UpstreamRequestAttr) -> Self { + match value { + UpstreamRequestAttr::HttpRequestMethodList(value) => Self::HttpRequestMethodList(value), + } + } +} +/// A request to an upstream server. +pub struct UpstreamRequestSpan { + span: S, +} + +impl UpstreamRequestSpan { + pub fn into_context(self, parent: &::opentelemetry::Context) -> ::opentelemetry::Context + where S: Send + Sync + 'static, + { + parent.with_span(self.span) + } + pub fn span(&mut self) -> &mut S { &mut self.span } + pub fn end(&mut self) { ::opentelemetry::trace::Span::end(&mut self.span); } + pub fn end_with_timestamp(&mut self, timestamp: ::std::time::SystemTime) { ::opentelemetry::trace::Span::end_with_timestamp(&mut self.span, timestamp); } + pub fn record_error(&mut self, error: &dyn ::std::error::Error) { ::opentelemetry::trace::Span::record_error(&mut self.span, error); } + pub fn set_status(&mut self, status: ::opentelemetry::trace::Status) { ::opentelemetry::trace::Span::set_status(&mut self.span, status); } + pub fn set_attributes(&mut self, attributes: impl IntoIterator) { + for attribute in attributes { ::opentelemetry::trace::Span::set_attribute(&mut self.span, ::opentelemetry::KeyValue::from(UpstreamRequestStartAttr::from(attribute))); } + } +} + +impl Default for UpstreamRequestSpan { + fn default() -> Self { + Self { span: super::tracer::Tracer::default().inner().start("edge.upstream.request") } + } +} + +pub fn r#start_upstream_request(context: &::opentelemetry::Context, tracer: &super::tracer::Tracer, r#server_address: ::opentelemetry::StringValue, r#server_port: i64, options: impl IntoIterator) -> UpstreamRequestSpan { + let mut name = String::with_capacity(r#server_address.as_str().len() + ":".len() + 20); + write!(&mut name, "{}", r#server_address).expect("writing a span name to String cannot fail"); + name.push(':'); + write!(&mut name, "{}", r#server_port).expect("writing a span name to String cannot fail"); + let attributes: [::opentelemetry::KeyValue; 2] = [::opentelemetry::KeyValue::new("server.address", r#server_address), ::opentelemetry::KeyValue::new("server.port", r#server_port)]; + let span = tracer.inner().span_builder(name) + .with_kind(::opentelemetry::trace::SpanKind::Client) + .with_attributes(attributes.into_iter().chain(options.into_iter().map(::opentelemetry::KeyValue::from))) + .start_with_context(tracer.inner(), context); + UpstreamRequestSpan { span } +} diff --git a/templates/code/rust/tests/imported/expected/logger.rs b/templates/code/rust/tests/imported/expected/logger.rs new file mode 100644 index 0000000..78bcc02 --- /dev/null +++ b/templates/code/rust/tests/imported/expected/logger.rs @@ -0,0 +1,16 @@ +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Logger::Logger>(L); + +impl Logger { + pub fn new>(provider: &P, options: InstrumentationOptions) -> Self { + let logger = provider.logger_with_scope(options.scope.into()); + Self(logger) + } + pub fn inner(&self) -> &L { &self.0 } +} + +impl Default for Logger { + fn default() -> Self { Self::new(&opentelemetry::logs::NoopLoggerProvider::new(), InstrumentationOptions::default()) } +} diff --git a/templates/code/rust/tests/imported/expected/meter.rs b/templates/code/rust/tests/imported/expected/meter.rs new file mode 100644 index 0000000..a330bd2 --- /dev/null +++ b/templates/code/rust/tests/imported/expected/meter.rs @@ -0,0 +1,16 @@ +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Meter(opentelemetry::metrics::Meter); + +impl Meter { + pub fn new(provider: &impl opentelemetry::metrics::MeterProvider, options: InstrumentationOptions) -> Self { + let meter = provider.meter_with_scope(options.scope.into()); + Self(meter) + } + pub fn inner(&self) -> &opentelemetry::metrics::Meter { &self.0 } +} + +impl Default for Meter { + fn default() -> Self { Self::new(&opentelemetry::metrics::noop::NoopMeterProvider::new(), InstrumentationOptions::default()) } +} diff --git a/templates/code/rust/tests/imported/expected/mod.rs b/templates/code/rust/tests/imported/expected/mod.rs new file mode 100644 index 0000000..c1aaad3 --- /dev/null +++ b/templates/code/rust/tests/imported/expected/mod.rs @@ -0,0 +1,13 @@ +// Generated by OpenTelemetry Weaver. +pub const SCHEMA_URL: &str = "https://example.com/schemas/myapp/1.0.0"; +pub mod myappattr; + +pub mod scope; +pub mod options; +pub mod meter; +pub mod tracer; +pub mod logger; +pub mod edgeevent; +pub mod edgemetric; +pub mod edgespan; +pub mod myappmetric; diff --git a/templates/code/rust/tests/imported/expected/myappattr.rs b/templates/code/rust/tests/imported/expected/myappattr.rs new file mode 100644 index 0000000..c2a50dd --- /dev/null +++ b/templates/code/rust/tests/imported/expected/myappattr.rs @@ -0,0 +1,65 @@ +// Generated by OpenTelemetry Weaver. +/// Unique identifier of the task. +#[derive(Debug, Clone, PartialEq)] +pub struct TaskIdAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl TaskIdAttr { + pub const KEY: &'static str = "myapp.task.id"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for TaskIdAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for TaskIdAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for TaskIdAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for TaskIdAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskIdAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskIdAttr) -> Self { + ::opentelemetry::KeyValue::new(TaskIdAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskIdAttr) -> Self { + value.0.into() + } +} diff --git a/templates/code/rust/tests/imported/expected/myappmetric.rs b/templates/code/rust/tests/imported/expected/myappmetric.rs new file mode 100644 index 0000000..cf876c8 --- /dev/null +++ b/templates/code/rust/tests/imported/expected/myappmetric.rs @@ -0,0 +1,70 @@ +#![allow(deprecated)] +#[derive(Clone, Debug)] +pub enum TaskFailedCounterAttr { + HttpRequestMethodList(Vec<::opentelemetry::StringValue>), +} + +impl From for ::opentelemetry::KeyValue { + fn from(value: TaskFailedCounterAttr) -> Self { + match value { + TaskFailedCounterAttr::HttpRequestMethodList(value) => ::opentelemetry::KeyValue::new("http.request.method_list", ::opentelemetry::Value::Array(::opentelemetry::Array::String(value))), + } + } +} +/// Number of tasks that failed. +#[derive(Clone)] +pub struct TaskFailedCounter { + instrument: ::opentelemetry::metrics::Counter, +} + +impl TaskFailedCounter { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().u64_counter("myapp.task.failed") + .with_description("Number of tasks that failed.") + .with_unit("{task}") + .build(); + Self { instrument } + } + pub fn add(&self, value: u64, r#error_type: ::opentelemetry::StringValue, r#myapp_task_id: super::myappattr::TaskIdAttr, options: impl IntoIterator) { + let mut attributes = ::smallvec::SmallVec::<[::opentelemetry::KeyValue; 3]>::new(); + attributes.extend([::opentelemetry::KeyValue::new("error.type", r#error_type), ::opentelemetry::KeyValue::from(r#myapp_task_id)]); + attributes.extend(options.into_iter().map(::opentelemetry::KeyValue::from)); + self.instrument.add(value, &attributes); + } +} + +impl Default for TaskFailedCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} +pub struct TaskFailedCounterObserver<'a> { + observer: &'a dyn ::opentelemetry::metrics::AsyncInstrument, +} + +impl TaskFailedCounterObserver<'_> { + pub fn observe(&self, value: u64, r#error_type: ::opentelemetry::StringValue, r#myapp_task_id: super::myappattr::TaskIdAttr, options: impl IntoIterator) { + let mut attributes = ::smallvec::SmallVec::<[::opentelemetry::KeyValue; 3]>::new(); + attributes.extend([::opentelemetry::KeyValue::new("error.type", r#error_type), ::opentelemetry::KeyValue::from(r#myapp_task_id)]); + attributes.extend(options.into_iter().map(::opentelemetry::KeyValue::from)); + self.observer.observe(value, &attributes); + } +} + +#[derive(Clone)] +pub struct TaskFailedObservableCounter { + _instrument: ::opentelemetry::metrics::ObservableCounter, +} + +impl TaskFailedObservableCounter { + pub fn new(meter: &super::meter::Meter, callback: impl Fn(TaskFailedCounterObserver<'_>) + Send + Sync + 'static) -> Self { + let instrument = meter.inner().u64_observable_counter("myapp.task.failed") + .with_description("Number of tasks that failed.") + .with_unit("{task}") + .with_callback(move |observer| callback(TaskFailedCounterObserver { observer })) + .build(); + Self { _instrument: instrument } + } +} + +impl Default for TaskFailedObservableCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default(), |_| {}) } +} diff --git a/templates/code/rust/tests/imported/expected/options.rs b/templates/code/rust/tests/imported/expected/options.rs new file mode 100644 index 0000000..69a821a --- /dev/null +++ b/templates/code/rust/tests/imported/expected/options.rs @@ -0,0 +1,6 @@ +use super::scope::Scope; + +#[derive(Clone, Debug, Default)] +pub struct InstrumentationOptions { + pub scope: Scope, +} diff --git a/templates/code/rust/tests/imported/expected/scope.rs b/templates/code/rust/tests/imported/expected/scope.rs new file mode 100644 index 0000000..6f66e86 --- /dev/null +++ b/templates/code/rust/tests/imported/expected/scope.rs @@ -0,0 +1,13 @@ +#[derive(Clone, Debug, Default)] +pub struct Scope { + pub attributes: Vec<::opentelemetry::KeyValue>, +} + +impl From for ::opentelemetry::InstrumentationScope { + fn from(value: Scope) -> Self { + Self::builder("generated-semconv") + .with_schema_url(super::SCHEMA_URL) + .with_attributes(value.attributes) + .build() + } +} diff --git a/templates/code/rust/tests/imported/expected/tracer.rs b/templates/code/rust/tests/imported/expected/tracer.rs new file mode 100644 index 0000000..d684a8d --- /dev/null +++ b/templates/code/rust/tests/imported/expected/tracer.rs @@ -0,0 +1,16 @@ +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Tracer(T); + +impl Tracer { + pub fn new>(provider: &P, options: InstrumentationOptions) -> Self { + let tracer = provider.tracer_with_scope(options.scope.into()); + Self(tracer) + } + pub fn inner(&self) -> &T { &self.0 } +} + +impl Default for Tracer { + fn default() -> Self { Self::new(&opentelemetry::trace::noop::NoopTracerProvider::new(), InstrumentationOptions::default()) } +} diff --git a/templates/code/rust/tests/imported/registry/manifest.yaml b/templates/code/rust/tests/imported/registry/manifest.yaml new file mode 100644 index 0000000..d1bbe08 --- /dev/null +++ b/templates/code/rust/tests/imported/registry/manifest.yaml @@ -0,0 +1,6 @@ +name: myapp +schema_url: https://example.com/schemas/myapp/1.0.0 +dependencies: + - name: upstream + registry_path: dependency + schema_url: https://example.com/schemas/upstream/1.0.0 diff --git a/templates/code/rust/tests/imported/registry/model.yaml b/templates/code/rust/tests/imported/registry/model.yaml new file mode 100644 index 0000000..3fc83d6 --- /dev/null +++ b/templates/code/rust/tests/imported/registry/model.yaml @@ -0,0 +1,77 @@ +file_format: definition/2 +attributes: + - key: myapp.task.id + type: string + stability: stable + brief: Unique identifier of the task. + examples: ["task_0001"] +metrics: + - name: myapp.task.failed + instrument: counter + unit: "{task}" + stability: stable + brief: Number of tasks that failed. + annotations: + code_generation: + metric_value_type: int + attributes: + - ref: myapp.task.id + requirement_level: required + - ref: error.type + requirement_level: required + - ref: http.request.method_list + requirement_level: recommended + - name: edge.upstream.duration + instrument: histogram + unit: s + stability: stable + brief: Time spent waiting on an upstream server. + annotations: + code_generation: + metric_value_type: double + attributes: + - ref: server.address + requirement_level: required + - ref: server.port + requirement_level: recommended +spans: + - type: edge.upstream.request + kind: client + requirement_level: recommended + stability: stable + brief: A request to an upstream server. + name: + note: "`{server.address}:{server.port}`" + attributes: + - ref: server.address + requirement_level: required + - ref: server.port + requirement_level: required + - ref: error.type + requirement_level: + conditionally_required: If the request failed. + sampling_relevant: true + - ref: http.request.method_list + requirement_level: recommended + - type: edge.upstream.methods + kind: client + requirement_level: recommended + stability: stable + brief: A request named from attempted methods. + name: + note: "`{http.request.method_list}`" + attributes: + - ref: http.request.method_list + requirement_level: required +events: + - name: edge.upstream.failed + requirement_level: recommended + stability: stable + brief: A request to an upstream server failed. + attributes: + - ref: error.type + requirement_level: required + - ref: server.address + requirement_level: recommended + - ref: http.request.method_list + requirement_level: recommended diff --git a/templates/code/rust/tests/imported/test.rs b/templates/code/rust/tests/imported/test.rs new file mode 100644 index 0000000..e1647e9 --- /dev/null +++ b/templates/code/rust/tests/imported/test.rs @@ -0,0 +1,107 @@ +use generated_semconv::options::InstrumentationOptions; +use generated_semconv::{edgeevent, edgespan, logger, myappattr, myappmetric, tracer}; +use opentelemetry::{Array, KeyValue, StringValue, Value}; + +#[test] +fn imported_native_strings_and_arrays_keep_their_sdk_storage() { + let methods = vec![StringValue::from("GET"), StringValue::from("POST")]; + let pointer = methods.as_ptr(); + let attribute = KeyValue::from(myappmetric::TaskFailedCounterAttr::HttpRequestMethodList( + methods, + )); + assert_eq!(attribute.key.as_str(), "http.request.method_list"); + let Value::Array(Array::String(methods)) = attribute.value else { + panic!("imported string arrays must keep native SDK storage"); + }; + assert_eq!(methods.as_ptr(), pointer); + assert_eq!( + methods.iter().map(StringValue::as_str).collect::>(), + ["GET", "POST"] + ); + myappmetric::TaskFailedCounter::default().add( + 1, + StringValue::from("timeout"), + myappattr::TaskIdAttr::from("task-1"), + [myappmetric::TaskFailedCounterAttr::HttpRequestMethodList( + methods, + )], + ); + let context = opentelemetry::Context::new(); + edgeevent::emit_upstream_failed( + &context, + &logger::Logger::default(), + StringValue::from("timeout"), + [edgeevent::UpstreamFailedOption::ServerAddress( + StringValue::from("example.com"), + )], + ); + let mut span = edgespan::start_upstream_request( + &context, + &tracer::Tracer::default(), + StringValue::from("example.com"), + 443, + [], + ); + span.end(); +} + +#[test] +fn imported_native_string_arrays_format_derived_span_names() { + let exporter = opentelemetry_sdk::trace::InMemorySpanExporter::default(); + let provider = opentelemetry_sdk::trace::SdkTracerProvider::builder() + .with_simple_exporter(exporter.clone()) + .build(); + let tracer = tracer::Tracer::new(&provider, InstrumentationOptions::default()); + let mut span = edgespan::start_upstream_methods( + &opentelemetry::Context::new(), + &tracer, + vec![StringValue::from("GET"), StringValue::from("POST")], + ); + span.end(); + provider.force_flush().unwrap(); + let spans = exporter.get_finished_spans().unwrap(); + assert_eq!(spans.len(), 1); + assert_eq!(spans[0].instrumentation_scope.name(), "generated-semconv"); + assert_eq!(spans[0].instrumentation_scope.version(), None); + assert_eq!( + spans[0].instrumentation_scope.schema_url(), + Some(generated_semconv::SCHEMA_URL) + ); + assert_eq!(spans[0].name, "[\"GET\",\"POST\"]"); +} + +#[test] +fn imported_native_string_array_events_preserve_order_and_duplicates() { + let exporter = opentelemetry_sdk::logs::InMemoryLogExporter::default(); + let provider = opentelemetry_sdk::logs::SdkLoggerProvider::builder() + .with_simple_exporter(exporter.clone()) + .build(); + edgeevent::emit_upstream_failed( + &opentelemetry::Context::new(), + &logger::Logger::new(&provider, InstrumentationOptions::default()), + StringValue::from("timeout"), + [edgeevent::UpstreamFailedOption::HttpRequestMethodList( + vec![ + StringValue::from("GET"), + StringValue::from("GET"), + StringValue::from("POST"), + ], + )], + ); + provider.force_flush().unwrap(); + let records = exporter.get_emitted_logs().unwrap(); + let value = &records[0] + .record + .attributes_iter() + .find(|(key, _)| key.as_str() == "http.request.method_list") + .unwrap() + .1; + assert_eq!( + value, + &opentelemetry::logs::AnyValue::ListAny(Box::new(vec![ + "GET".into(), + "GET".into(), + "POST".into() + ])) + ); +} diff --git a/templates/code/rust/tests/imported_typed/dependency-params.yaml b/templates/code/rust/tests/imported_typed/dependency-params.yaml new file mode 100644 index 0000000..7957adf --- /dev/null +++ b/templates/code/rust/tests/imported_typed/dependency-params.yaml @@ -0,0 +1 @@ +params: diff --git a/templates/code/rust/tests/imported_typed/dependency/manifest.yaml b/templates/code/rust/tests/imported_typed/dependency/manifest.yaml new file mode 100644 index 0000000..6fe2cd0 --- /dev/null +++ b/templates/code/rust/tests/imported_typed/dependency/manifest.yaml @@ -0,0 +1,2 @@ +name: upstream +schema_url: https://example.com/schemas/upstream/1.0.0 diff --git a/templates/code/rust/tests/imported_typed/dependency/model.yaml b/templates/code/rust/tests/imported_typed/dependency/model.yaml new file mode 100644 index 0000000..c550f39 --- /dev/null +++ b/templates/code/rust/tests/imported_typed/dependency/model.yaml @@ -0,0 +1,20 @@ +file_format: definition/2 +attributes: + - key: error.type + type: + members: + - id: timeout + value: timeout + stability: stable + brief: The operation timed out. + - id: other + value: _OTHER + stability: stable + brief: Any other error. + stability: stable + brief: Describes a class of error. + - key: server.port + type: int + stability: stable + brief: Server port number. + examples: [443] diff --git a/templates/code/rust/tests/imported_typed/expected/logger.rs b/templates/code/rust/tests/imported_typed/expected/logger.rs new file mode 100644 index 0000000..78bcc02 --- /dev/null +++ b/templates/code/rust/tests/imported_typed/expected/logger.rs @@ -0,0 +1,16 @@ +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Logger::Logger>(L); + +impl Logger { + pub fn new>(provider: &P, options: InstrumentationOptions) -> Self { + let logger = provider.logger_with_scope(options.scope.into()); + Self(logger) + } + pub fn inner(&self) -> &L { &self.0 } +} + +impl Default for Logger { + fn default() -> Self { Self::new(&opentelemetry::logs::NoopLoggerProvider::new(), InstrumentationOptions::default()) } +} diff --git a/templates/code/rust/tests/imported_typed/expected/meter.rs b/templates/code/rust/tests/imported_typed/expected/meter.rs new file mode 100644 index 0000000..a330bd2 --- /dev/null +++ b/templates/code/rust/tests/imported_typed/expected/meter.rs @@ -0,0 +1,16 @@ +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Meter(opentelemetry::metrics::Meter); + +impl Meter { + pub fn new(provider: &impl opentelemetry::metrics::MeterProvider, options: InstrumentationOptions) -> Self { + let meter = provider.meter_with_scope(options.scope.into()); + Self(meter) + } + pub fn inner(&self) -> &opentelemetry::metrics::Meter { &self.0 } +} + +impl Default for Meter { + fn default() -> Self { Self::new(&opentelemetry::metrics::noop::NoopMeterProvider::new(), InstrumentationOptions::default()) } +} diff --git a/templates/code/rust/tests/imported_typed/expected/mod.rs b/templates/code/rust/tests/imported_typed/expected/mod.rs new file mode 100644 index 0000000..11c3f63 --- /dev/null +++ b/templates/code/rust/tests/imported_typed/expected/mod.rs @@ -0,0 +1,12 @@ +// Generated by OpenTelemetry Weaver. +pub const SCHEMA_URL: &str = "https://example.com/schemas/myapp/1.0.0"; +pub mod myappattr; + +pub mod scope; +pub mod options; +pub mod meter; +pub mod tracer; +pub mod logger; +pub mod myappevent; +pub mod myappmetric; +pub mod myappspan; diff --git a/templates/code/rust/tests/imported_typed/expected/myappattr.rs b/templates/code/rust/tests/imported_typed/expected/myappattr.rs new file mode 100644 index 0000000..c2a50dd --- /dev/null +++ b/templates/code/rust/tests/imported_typed/expected/myappattr.rs @@ -0,0 +1,65 @@ +// Generated by OpenTelemetry Weaver. +/// Unique identifier of the task. +#[derive(Debug, Clone, PartialEq)] +pub struct TaskIdAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl TaskIdAttr { + pub const KEY: &'static str = "myapp.task.id"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for TaskIdAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for TaskIdAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for TaskIdAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for TaskIdAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskIdAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskIdAttr) -> Self { + ::opentelemetry::KeyValue::new(TaskIdAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskIdAttr) -> Self { + value.0.into() + } +} diff --git a/templates/code/rust/tests/imported_typed/expected/myappevent.rs b/templates/code/rust/tests/imported_typed/expected/myappevent.rs new file mode 100644 index 0000000..832f6bd --- /dev/null +++ b/templates/code/rust/tests/imported_typed/expected/myappevent.rs @@ -0,0 +1,41 @@ +#![allow(deprecated)] +use ::opentelemetry::logs::LogRecord; +use ::opentelemetry::trace::TraceContextExt; + +#[derive(Clone, Debug)] +pub enum TaskErroredOption { + Severity(::opentelemetry::logs::Severity), + Timestamp(::std::time::SystemTime), + ServerPort(i64), +} +/// A task failed with an error. +pub fn r#emit_task_errored(context: &::opentelemetry::Context, logger: &super::logger::Logger, r#error_type: upstream::errorattr::TypeAttr, options: impl IntoIterator) { + let mut severity = ::opentelemetry::logs::Severity::Info; + let mut timestamp = None; + let mut attributes = ::smallvec::SmallVec::<[TaskErroredOption; 1]>::new(); + for option in options { + match option { + TaskErroredOption::Severity(value) => severity = value, + TaskErroredOption::Timestamp(value) => timestamp = Some(value), + attribute => attributes.push(attribute), + } + } + if !logger.inner().event_enabled(severity, "", Some("myapp.task.errored")) { return; } + let _context_guard = context.clone().attach(); + let mut record = logger.inner().create_log_record(); + record.set_event_name("myapp.task.errored"); + record.set_severity_number(severity); + record.set_observed_timestamp(::std::time::SystemTime::now()); + if let Some(timestamp) = timestamp { record.set_timestamp(timestamp); } + let span = context.span(); + let span_context = span.span_context(); + if span_context.is_valid() { record.set_trace_context(span_context.trace_id(), span_context.span_id(), Some(span_context.trace_flags())); } + record.add_attribute(upstream::errorattr::TypeAttr::KEY, r#error_type); + for option in attributes { + match option { + TaskErroredOption::ServerPort(value) => record.add_attribute("server.port", value), + TaskErroredOption::Severity(_) | TaskErroredOption::Timestamp(_) => continue, + } + } + logger.inner().emit(record); +} diff --git a/templates/code/rust/tests/imported_typed/expected/myappmetric.rs b/templates/code/rust/tests/imported_typed/expected/myappmetric.rs new file mode 100644 index 0000000..cfc1605 --- /dev/null +++ b/templates/code/rust/tests/imported_typed/expected/myappmetric.rs @@ -0,0 +1,70 @@ +#![allow(deprecated)] +#[derive(Clone, Debug)] +pub enum TaskFailedCounterAttr { + ServerPort(i64), +} + +impl From for ::opentelemetry::KeyValue { + fn from(value: TaskFailedCounterAttr) -> Self { + match value { + TaskFailedCounterAttr::ServerPort(value) => ::opentelemetry::KeyValue::new("server.port", value), + } + } +} +/// Number of tasks that failed. +#[derive(Clone)] +pub struct TaskFailedCounter { + instrument: ::opentelemetry::metrics::Counter, +} + +impl TaskFailedCounter { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().u64_counter("myapp.task.failed") + .with_description("Number of tasks that failed.") + .with_unit("{task}") + .build(); + Self { instrument } + } + pub fn add(&self, value: u64, r#error_type: upstream::errorattr::TypeAttr, r#myapp_task_id: super::myappattr::TaskIdAttr, options: impl IntoIterator) { + let mut attributes = ::smallvec::SmallVec::<[::opentelemetry::KeyValue; 3]>::new(); + attributes.extend([::opentelemetry::KeyValue::from(r#error_type), ::opentelemetry::KeyValue::from(r#myapp_task_id)]); + attributes.extend(options.into_iter().map(::opentelemetry::KeyValue::from)); + self.instrument.add(value, &attributes); + } +} + +impl Default for TaskFailedCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} +pub struct TaskFailedCounterObserver<'a> { + observer: &'a dyn ::opentelemetry::metrics::AsyncInstrument, +} + +impl TaskFailedCounterObserver<'_> { + pub fn observe(&self, value: u64, r#error_type: upstream::errorattr::TypeAttr, r#myapp_task_id: super::myappattr::TaskIdAttr, options: impl IntoIterator) { + let mut attributes = ::smallvec::SmallVec::<[::opentelemetry::KeyValue; 3]>::new(); + attributes.extend([::opentelemetry::KeyValue::from(r#error_type), ::opentelemetry::KeyValue::from(r#myapp_task_id)]); + attributes.extend(options.into_iter().map(::opentelemetry::KeyValue::from)); + self.observer.observe(value, &attributes); + } +} + +#[derive(Clone)] +pub struct TaskFailedObservableCounter { + _instrument: ::opentelemetry::metrics::ObservableCounter, +} + +impl TaskFailedObservableCounter { + pub fn new(meter: &super::meter::Meter, callback: impl Fn(TaskFailedCounterObserver<'_>) + Send + Sync + 'static) -> Self { + let instrument = meter.inner().u64_observable_counter("myapp.task.failed") + .with_description("Number of tasks that failed.") + .with_unit("{task}") + .with_callback(move |observer| callback(TaskFailedCounterObserver { observer })) + .build(); + Self { _instrument: instrument } + } +} + +impl Default for TaskFailedObservableCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default(), |_| {}) } +} diff --git a/templates/code/rust/tests/imported_typed/expected/myappspan.rs b/templates/code/rust/tests/imported_typed/expected/myappspan.rs new file mode 100644 index 0000000..abd5d30 --- /dev/null +++ b/templates/code/rust/tests/imported_typed/expected/myappspan.rs @@ -0,0 +1,68 @@ +#![allow(deprecated)] +use ::opentelemetry::trace::{TraceContextExt, Tracer}; +use ::std::fmt::Write; + +#[derive(Clone, Debug)] +pub enum TaskDispatchStartAttr { + ServerPort(i64), +} + +impl From for ::opentelemetry::KeyValue { + fn from(value: TaskDispatchStartAttr) -> Self { + match value { + TaskDispatchStartAttr::ServerPort(value) => ::opentelemetry::KeyValue::new("server.port", value), + } + } +} +#[derive(Clone, Debug)] +pub enum TaskDispatchAttr { + ServerPort(i64), +} + +impl From for TaskDispatchStartAttr { + fn from(value: TaskDispatchAttr) -> Self { + match value { + TaskDispatchAttr::ServerPort(value) => Self::ServerPort(value), + } + } +} +/// Dispatch of a task. +pub struct TaskDispatchSpan { + span: S, +} + +impl TaskDispatchSpan { + pub fn into_context(self, parent: &::opentelemetry::Context) -> ::opentelemetry::Context + where S: Send + Sync + 'static, + { + parent.with_span(self.span) + } + pub fn span(&mut self) -> &mut S { &mut self.span } + pub fn end(&mut self) { ::opentelemetry::trace::Span::end(&mut self.span); } + pub fn end_with_timestamp(&mut self, timestamp: ::std::time::SystemTime) { ::opentelemetry::trace::Span::end_with_timestamp(&mut self.span, timestamp); } + pub fn record_error(&mut self, error: &dyn ::std::error::Error) { ::opentelemetry::trace::Span::record_error(&mut self.span, error); } + pub fn set_status(&mut self, status: ::opentelemetry::trace::Status) { ::opentelemetry::trace::Span::set_status(&mut self.span, status); } + pub fn set_attributes(&mut self, attributes: impl IntoIterator) { + for attribute in attributes { ::opentelemetry::trace::Span::set_attribute(&mut self.span, ::opentelemetry::KeyValue::from(TaskDispatchStartAttr::from(attribute))); } + } +} + +impl Default for TaskDispatchSpan { + fn default() -> Self { + Self { span: super::tracer::Tracer::default().inner().start("myapp.task.dispatch") } + } +} + +pub fn r#start_task_dispatch(context: &::opentelemetry::Context, tracer: &super::tracer::Tracer, r#error_type: upstream::errorattr::TypeAttr, r#myapp_task_id: super::myappattr::TaskIdAttr, options: impl IntoIterator) -> TaskDispatchSpan { + let mut name = String::with_capacity("task ".len() + ::std::convert::AsRef::::as_ref(&r#myapp_task_id).len() + " failed with ".len() + ::std::convert::AsRef::::as_ref(&r#error_type).len()); + name.push_str("task "); + write!(&mut name, "{}", r#myapp_task_id).expect("writing a span name to String cannot fail"); + name.push_str(" failed with "); + write!(&mut name, "{}", r#error_type).expect("writing a span name to String cannot fail"); + let attributes: [::opentelemetry::KeyValue; 2] = [::opentelemetry::KeyValue::from(r#error_type), ::opentelemetry::KeyValue::from(r#myapp_task_id)]; + let span = tracer.inner().span_builder(name) + .with_kind(::opentelemetry::trace::SpanKind::Internal) + .with_attributes(attributes.into_iter().chain(options.into_iter().map(::opentelemetry::KeyValue::from))) + .start_with_context(tracer.inner(), context); + TaskDispatchSpan { span } +} diff --git a/templates/code/rust/tests/imported_typed/expected/options.rs b/templates/code/rust/tests/imported_typed/expected/options.rs new file mode 100644 index 0000000..69a821a --- /dev/null +++ b/templates/code/rust/tests/imported_typed/expected/options.rs @@ -0,0 +1,6 @@ +use super::scope::Scope; + +#[derive(Clone, Debug, Default)] +pub struct InstrumentationOptions { + pub scope: Scope, +} diff --git a/templates/code/rust/tests/imported_typed/expected/scope.rs b/templates/code/rust/tests/imported_typed/expected/scope.rs new file mode 100644 index 0000000..6f66e86 --- /dev/null +++ b/templates/code/rust/tests/imported_typed/expected/scope.rs @@ -0,0 +1,13 @@ +#[derive(Clone, Debug, Default)] +pub struct Scope { + pub attributes: Vec<::opentelemetry::KeyValue>, +} + +impl From for ::opentelemetry::InstrumentationScope { + fn from(value: Scope) -> Self { + Self::builder("generated-semconv") + .with_schema_url(super::SCHEMA_URL) + .with_attributes(value.attributes) + .build() + } +} diff --git a/templates/code/rust/tests/imported_typed/expected/tracer.rs b/templates/code/rust/tests/imported_typed/expected/tracer.rs new file mode 100644 index 0000000..d684a8d --- /dev/null +++ b/templates/code/rust/tests/imported_typed/expected/tracer.rs @@ -0,0 +1,16 @@ +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Tracer(T); + +impl Tracer { + pub fn new>(provider: &P, options: InstrumentationOptions) -> Self { + let tracer = provider.tracer_with_scope(options.scope.into()); + Self(tracer) + } + pub fn inner(&self) -> &T { &self.0 } +} + +impl Default for Tracer { + fn default() -> Self { Self::new(&opentelemetry::trace::noop::NoopTracerProvider::new(), InstrumentationOptions::default()) } +} diff --git a/templates/code/rust/tests/imported_typed/params.yaml b/templates/code/rust/tests/imported_typed/params.yaml new file mode 100644 index 0000000..960ae05 --- /dev/null +++ b/templates/code/rust/tests/imported_typed/params.yaml @@ -0,0 +1,3 @@ +params: + imported_crate_paths: + error: upstream diff --git a/templates/code/rust/tests/imported_typed/registry/manifest.yaml b/templates/code/rust/tests/imported_typed/registry/manifest.yaml new file mode 100644 index 0000000..d1bbe08 --- /dev/null +++ b/templates/code/rust/tests/imported_typed/registry/manifest.yaml @@ -0,0 +1,6 @@ +name: myapp +schema_url: https://example.com/schemas/myapp/1.0.0 +dependencies: + - name: upstream + registry_path: dependency + schema_url: https://example.com/schemas/upstream/1.0.0 diff --git a/templates/code/rust/tests/imported_typed/registry/model.yaml b/templates/code/rust/tests/imported_typed/registry/model.yaml new file mode 100644 index 0000000..b3c0f63 --- /dev/null +++ b/templates/code/rust/tests/imported_typed/registry/model.yaml @@ -0,0 +1,48 @@ +file_format: definition/2 +attributes: + - key: myapp.task.id + type: string + stability: stable + brief: Unique identifier of the task. + examples: ["task_0001"] +metrics: + - name: myapp.task.failed + instrument: counter + unit: "{task}" + stability: stable + brief: Number of tasks that failed. + annotations: + code_generation: + metric_value_type: int + attributes: + - ref: myapp.task.id + requirement_level: required + - ref: error.type + requirement_level: required + - ref: server.port + requirement_level: recommended +spans: + - type: myapp.task.dispatch + kind: internal + requirement_level: recommended + stability: stable + brief: Dispatch of a task. + name: + note: "`task {myapp.task.id} failed with {error.type}`" + attributes: + - ref: myapp.task.id + requirement_level: required + - ref: error.type + requirement_level: required + - ref: server.port + requirement_level: recommended +events: + - name: myapp.task.errored + requirement_level: recommended + stability: stable + brief: A task failed with an error. + attributes: + - ref: error.type + requirement_level: required + - ref: server.port + requirement_level: recommended diff --git a/templates/code/rust/tests/imported_typed/test.rs b/templates/code/rust/tests/imported_typed/test.rs new file mode 100644 index 0000000..54a62ed --- /dev/null +++ b/templates/code/rust/tests/imported_typed/test.rs @@ -0,0 +1,32 @@ +use generated_semconv::{logger, myappattr, myappevent, myappmetric, myappspan, tracer}; +use opentelemetry::KeyValue; + +#[test] +fn typed_dependencies_and_plain_imported_values_work_together() { + let error = upstream::errorattr::TypeAttr::Timeout; + assert_eq!( + KeyValue::from(error), + KeyValue::new("error.type", "timeout") + ); + myappmetric::TaskFailedCounter::default().add( + 1, + error, + myappattr::TaskIdAttr::from("task-1"), + [myappmetric::TaskFailedCounterAttr::ServerPort(443)], + ); + let context = opentelemetry::Context::new(); + myappevent::emit_task_errored( + &context, + &logger::Logger::default(), + error, + [myappevent::TaskErroredOption::ServerPort(443)], + ); + let mut span = myappspan::start_task_dispatch( + &context, + &tracer::Tracer::default(), + error, + myappattr::TaskIdAttr::from("task-1"), + [myappspan::TaskDispatchStartAttr::ServerPort(443)], + ); + span.end(); +} diff --git a/templates/code/rust/tests/integer_boundaries/expected/demoattr.rs b/templates/code/rust/tests/integer_boundaries/expected/demoattr.rs new file mode 100644 index 0000000..9ab9533 --- /dev/null +++ b/templates/code/rust/tests/integer_boundaries/expected/demoattr.rs @@ -0,0 +1,50 @@ +// Generated by OpenTelemetry Weaver. +/// Synthetic integer limits. +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum BoundaryAttr { + /// Lowest signed 64-bit integer. + Minimum, + /// Highest signed 64-bit integer. + Maximum, +} + +#[allow(deprecated)] +impl BoundaryAttr { + pub const KEY: &'static str = "demo.boundary"; +} + +#[allow(deprecated)] +impl ::std::fmt::Display for BoundaryAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + let value: i64 = match self { + BoundaryAttr::Minimum => -9223372036854775808, + BoundaryAttr::Maximum => 9223372036854775807, + }; + ::std::fmt::Display::fmt(&value, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: BoundaryAttr) -> Self { + let value: i64 = match value { + BoundaryAttr::Minimum => -9223372036854775808, + BoundaryAttr::Maximum => 9223372036854775807, + }; + ::opentelemetry::KeyValue::new(BoundaryAttr::KEY, value) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: BoundaryAttr) -> Self { + let value: i64 = match value { + BoundaryAttr::Minimum => -9223372036854775808, + BoundaryAttr::Maximum => 9223372036854775807, + }; + value.into() + } +} diff --git a/templates/code/rust/tests/integer_boundaries/expected/mod.rs b/templates/code/rust/tests/integer_boundaries/expected/mod.rs new file mode 100644 index 0000000..19656c6 --- /dev/null +++ b/templates/code/rust/tests/integer_boundaries/expected/mod.rs @@ -0,0 +1,3 @@ +// Generated by OpenTelemetry Weaver. +pub const SCHEMA_URL: &str = "https://example.com/schemas/1.0.0"; +pub mod demoattr; diff --git a/templates/code/rust/tests/integer_boundaries/registry/manifest.yaml b/templates/code/rust/tests/integer_boundaries/registry/manifest.yaml new file mode 100644 index 0000000..7e0695c --- /dev/null +++ b/templates/code/rust/tests/integer_boundaries/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: boundaries +schema_url: https://example.com/schemas/1.0.0 diff --git a/templates/code/rust/tests/integer_boundaries/registry/model.yaml b/templates/code/rust/tests/integer_boundaries/registry/model.yaml new file mode 100644 index 0000000..e434316 --- /dev/null +++ b/templates/code/rust/tests/integer_boundaries/registry/model.yaml @@ -0,0 +1,15 @@ +file_format: definition/2 +attributes: + - key: demo.boundary + stability: stable + brief: Synthetic integer limits. + type: + members: + - id: minimum + value: -9223372036854775808 + stability: stable + brief: Lowest signed 64-bit integer. + - id: maximum + value: 9223372036854775807 + stability: stable + brief: Highest signed 64-bit integer. diff --git a/templates/code/rust/tests/integer_boundaries/test.rs b/templates/code/rust/tests/integer_boundaries/test.rs new file mode 100644 index 0000000..c320747 --- /dev/null +++ b/templates/code/rust/tests/integer_boundaries/test.rs @@ -0,0 +1,13 @@ +use generated_semconv::demoattr::BoundaryAttr; +use opentelemetry::Value; +#[test] +fn integer_enum_preserves_signed_64_bit_limits() { + assert_eq!( + ::opentelemetry::KeyValue::from(BoundaryAttr::Minimum).value, + Value::I64(i64::MIN) + ); + assert_eq!( + ::opentelemetry::KeyValue::from(BoundaryAttr::Maximum).value, + Value::I64(i64::MAX) + ); +} diff --git a/templates/code/rust/tests/literal_escaping/expected/demoattr.rs b/templates/code/rust/tests/literal_escaping/expected/demoattr.rs new file mode 100644 index 0000000..1a5a546 --- /dev/null +++ b/templates/code/rust/tests/literal_escaping/expected/demoattr.rs @@ -0,0 +1,57 @@ +// Generated by OpenTelemetry Weaver. +/// Literal escape sequences. +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum LiteralAttr { + /// Literal escape sequences. + Literal, + /// Actual controls and escaped text. + Controls, +} + +#[allow(deprecated)] +impl LiteralAttr { + pub const KEY: &'static str = "demo.literal"; +} + +#[allow(deprecated)] +impl AsRef for LiteralAttr { + #[inline] + fn as_ref(&self) -> &str { + match self { + LiteralAttr::Literal => "backspace\\b formfeed\\f unicode\\u1234 doubled\\\\b tripled\\\\\\u0000", + LiteralAttr::Controls => "backspace\u{0008} formfeed\u{000c} unicode\u{0001} slash\\b", + } + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for LiteralAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: LiteralAttr) -> Self { + let value: &'static str = match value { + LiteralAttr::Literal => "backspace\\b formfeed\\f unicode\\u1234 doubled\\\\b tripled\\\\\\u0000", + LiteralAttr::Controls => "backspace\u{0008} formfeed\u{000c} unicode\u{0001} slash\\b", + }; + ::opentelemetry::KeyValue::new(LiteralAttr::KEY, value) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: LiteralAttr) -> Self { + let value: &'static str = match value { + LiteralAttr::Literal => "backspace\\b formfeed\\f unicode\\u1234 doubled\\\\b tripled\\\\\\u0000", + LiteralAttr::Controls => "backspace\u{0008} formfeed\u{000c} unicode\u{0001} slash\\b", + }; + value.into() + } +} diff --git a/templates/code/rust/tests/literal_escaping/expected/demometric.rs b/templates/code/rust/tests/literal_escaping/expected/demometric.rs new file mode 100644 index 0000000..1b3a78b --- /dev/null +++ b/templates/code/rust/tests/literal_escaping/expected/demometric.rs @@ -0,0 +1,54 @@ +#![allow(deprecated)] +/// literal \b \f \u1234 +#[derive(Clone)] +pub struct LiteralCountCounter { + instrument: ::opentelemetry::metrics::Counter, +} + +impl LiteralCountCounter { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().u64_counter("demo.literal.count") + .with_description("literal \\b \\f \\u1234") + .with_unit("literal\\b\\u1234") + .build(); + Self { instrument } + } + pub fn add(&self, value: u64, r#demo_literal: super::demoattr::LiteralAttr) { + let attributes = [::opentelemetry::KeyValue::from(r#demo_literal)]; + self.instrument.add(value, &attributes); + } +} + +impl Default for LiteralCountCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} +pub struct LiteralCountCounterObserver<'a> { + observer: &'a dyn ::opentelemetry::metrics::AsyncInstrument, +} + +impl LiteralCountCounterObserver<'_> { + pub fn observe(&self, value: u64, r#demo_literal: super::demoattr::LiteralAttr) { + let attributes = [::opentelemetry::KeyValue::from(r#demo_literal)]; + self.observer.observe(value, &attributes); + } +} + +#[derive(Clone)] +pub struct LiteralCountObservableCounter { + _instrument: ::opentelemetry::metrics::ObservableCounter, +} + +impl LiteralCountObservableCounter { + pub fn new(meter: &super::meter::Meter, callback: impl Fn(LiteralCountCounterObserver<'_>) + Send + Sync + 'static) -> Self { + let instrument = meter.inner().u64_observable_counter("demo.literal.count") + .with_description("literal \\b \\f \\u1234") + .with_unit("literal\\b\\u1234") + .with_callback(move |observer| callback(LiteralCountCounterObserver { observer })) + .build(); + Self { _instrument: instrument } + } +} + +impl Default for LiteralCountObservableCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default(), |_| {}) } +} diff --git a/templates/code/rust/tests/literal_escaping/expected/meter.rs b/templates/code/rust/tests/literal_escaping/expected/meter.rs new file mode 100644 index 0000000..a330bd2 --- /dev/null +++ b/templates/code/rust/tests/literal_escaping/expected/meter.rs @@ -0,0 +1,16 @@ +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Meter(opentelemetry::metrics::Meter); + +impl Meter { + pub fn new(provider: &impl opentelemetry::metrics::MeterProvider, options: InstrumentationOptions) -> Self { + let meter = provider.meter_with_scope(options.scope.into()); + Self(meter) + } + pub fn inner(&self) -> &opentelemetry::metrics::Meter { &self.0 } +} + +impl Default for Meter { + fn default() -> Self { Self::new(&opentelemetry::metrics::noop::NoopMeterProvider::new(), InstrumentationOptions::default()) } +} diff --git a/templates/code/rust/tests/literal_escaping/expected/mod.rs b/templates/code/rust/tests/literal_escaping/expected/mod.rs new file mode 100644 index 0000000..86079ee --- /dev/null +++ b/templates/code/rust/tests/literal_escaping/expected/mod.rs @@ -0,0 +1,8 @@ +// Generated by OpenTelemetry Weaver. +pub const SCHEMA_URL: &str = "https://example.com/schemas/escaping/1.0.0"; +pub mod demoattr; + +pub mod scope; +pub mod options; +pub mod meter; +pub mod demometric; diff --git a/templates/code/rust/tests/literal_escaping/expected/options.rs b/templates/code/rust/tests/literal_escaping/expected/options.rs new file mode 100644 index 0000000..69a821a --- /dev/null +++ b/templates/code/rust/tests/literal_escaping/expected/options.rs @@ -0,0 +1,6 @@ +use super::scope::Scope; + +#[derive(Clone, Debug, Default)] +pub struct InstrumentationOptions { + pub scope: Scope, +} diff --git a/templates/code/rust/tests/literal_escaping/expected/scope.rs b/templates/code/rust/tests/literal_escaping/expected/scope.rs new file mode 100644 index 0000000..6eb6ff7 --- /dev/null +++ b/templates/code/rust/tests/literal_escaping/expected/scope.rs @@ -0,0 +1,14 @@ +#[derive(Clone, Debug, Default)] +pub struct Scope { + pub attributes: Vec<::opentelemetry::KeyValue>, +} + +impl From for ::opentelemetry::InstrumentationScope { + fn from(value: Scope) -> Self { + Self::builder("literal \\b \\f \\u1234 quote\" double\\\\slash") + .with_version("controls \u{0008} \u{000c} \u{0001} literal\\b quote\"") + .with_schema_url(super::SCHEMA_URL) + .with_attributes(value.attributes) + .build() + } +} diff --git a/templates/code/rust/tests/literal_escaping/params.yaml b/templates/code/rust/tests/literal_escaping/params.yaml new file mode 100644 index 0000000..2e2ca96 --- /dev/null +++ b/templates/code/rust/tests/literal_escaping/params.yaml @@ -0,0 +1,3 @@ +params: + instrumentation_name: 'literal \b \f \u1234 quote" double\\slash' + instrumentation_version: "controls \b \f \u0001 literal\\b quote\"" diff --git a/templates/code/rust/tests/literal_escaping/registry/manifest.yaml b/templates/code/rust/tests/literal_escaping/registry/manifest.yaml new file mode 100644 index 0000000..b77d792 --- /dev/null +++ b/templates/code/rust/tests/literal_escaping/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: escaping +schema_url: https://example.com/schemas/escaping/1.0.0 diff --git a/templates/code/rust/tests/literal_escaping/registry/model.yaml b/templates/code/rust/tests/literal_escaping/registry/model.yaml new file mode 100644 index 0000000..b30eb30 --- /dev/null +++ b/templates/code/rust/tests/literal_escaping/registry/model.yaml @@ -0,0 +1,28 @@ +file_format: definition/2 +attributes: + - key: demo.literal + stability: stable + brief: Literal escape sequences. + type: + members: + - id: literal + value: 'backspace\b formfeed\f unicode\u1234 doubled\\b tripled\\\u0000' + stability: stable + brief: Literal escape sequences. + - id: controls + value: "backspace\b formfeed\f unicode\u0001 slash\\b" + stability: stable + brief: Actual controls and escaped text. +metrics: + - name: demo.literal.count + instrument: counter + unit: 'literal\b\u1234' + stability: stable + requirement_level: recommended + brief: 'literal \b \f \u1234' + annotations: + code_generation: + metric_value_type: int + attributes: + - ref: demo.literal + requirement_level: required diff --git a/templates/code/rust/tests/literal_escaping/test.rs b/templates/code/rust/tests/literal_escaping/test.rs new file mode 100644 index 0000000..a8cecfc --- /dev/null +++ b/templates/code/rust/tests/literal_escaping/test.rs @@ -0,0 +1,44 @@ +use generated_semconv::demoattr::LiteralAttr; +use generated_semconv::options::InstrumentationOptions; +use generated_semconv::{demometric::LiteralCountCounter, meter::Meter}; +use opentelemetry::Value; +use opentelemetry_sdk::metrics::{InMemoryMetricExporter, PeriodicReader, SdkMeterProvider}; +#[test] +fn literal_backslashes_and_actual_control_characters_are_distinct() { + assert_eq!( + ::opentelemetry::KeyValue::from(LiteralAttr::Literal).value, + Value::String(r"backspace\b formfeed\f unicode\u1234 doubled\\b tripled\\\u0000".into()) + ); + assert_eq!( + ::opentelemetry::KeyValue::from(LiteralAttr::Controls).value, + Value::String("backspace\u{0008} formfeed\u{000c} unicode\u{0001} slash\\b".into()) + ); +} + +#[test] +fn signal_metadata_preserves_literal_backslash_sequences() { + let exporter = InMemoryMetricExporter::default(); + let provider = SdkMeterProvider::builder() + .with_reader(PeriodicReader::builder(exporter.clone()).build()) + .build(); + LiteralCountCounter::new(&Meter::new(&provider, InstrumentationOptions::default())) + .add(1, LiteralAttr::Literal); + provider.force_flush().unwrap(); + let exported = exporter.get_finished_metrics().unwrap(); + let scope = exported[0].scope_metrics().next().unwrap(); + assert_eq!( + scope.scope().name(), + r#"literal \b \f \u1234 quote" double\\slash"# + ); + assert_eq!( + scope.scope().version(), + Some("controls \u{0008} \u{000c} \u{0001} literal\\b quote\"") + ); + assert_eq!( + scope.scope().schema_url(), + Some(generated_semconv::SCHEMA_URL) + ); + let metric = scope.metrics().next().unwrap(); + assert_eq!(metric.description(), r"literal \b \f \u1234"); + assert_eq!(metric.unit(), r"literal\b\u1234"); +} diff --git a/templates/code/rust/tests/metrics/compile-fail/missing_required.expected-error.txt b/templates/code/rust/tests/metrics/compile-fail/missing_required.expected-error.txt new file mode 100644 index 0000000..2fb3720 --- /dev/null +++ b/templates/code/rust/tests/metrics/compile-fail/missing_required.expected-error.txt @@ -0,0 +1 @@ +this method takes 3 arguments but 2 arguments were supplied diff --git a/templates/code/rust/tests/metrics/compile-fail/missing_required.rs b/templates/code/rust/tests/metrics/compile-fail/missing_required.rs new file mode 100644 index 0000000..a9918a9 --- /dev/null +++ b/templates/code/rust/tests/metrics/compile-fail/missing_required.rs @@ -0,0 +1,5 @@ +use generated_semconv::{authattr, authmetric}; + +fn main() { + authmetric::AttemptsCounter::default().add(1, authattr::MethodAttr::Password); +} diff --git a/templates/code/rust/tests/metrics/compile-fail/signed_counter.expected-error.txt b/templates/code/rust/tests/metrics/compile-fail/signed_counter.expected-error.txt new file mode 100644 index 0000000..e79632e --- /dev/null +++ b/templates/code/rust/tests/metrics/compile-fail/signed_counter.expected-error.txt @@ -0,0 +1 @@ +expected `u64`, found `i64` diff --git a/templates/code/rust/tests/metrics/compile-fail/signed_counter.rs b/templates/code/rust/tests/metrics/compile-fail/signed_counter.rs new file mode 100644 index 0000000..6826e64 --- /dev/null +++ b/templates/code/rust/tests/metrics/compile-fail/signed_counter.rs @@ -0,0 +1,5 @@ +use generated_semconv::myappmetric; + +fn main() { + myappmetric::TaskStartedCounter::default().add(-1_i64, []); +} diff --git a/templates/code/rust/tests/metrics/compile-fail/unrelated_optional.expected-error.txt b/templates/code/rust/tests/metrics/compile-fail/unrelated_optional.expected-error.txt new file mode 100644 index 0000000..77b0d3f --- /dev/null +++ b/templates/code/rust/tests/metrics/compile-fail/unrelated_optional.expected-error.txt @@ -0,0 +1 @@ +named `Success` diff --git a/templates/code/rust/tests/metrics/compile-fail/unrelated_optional.rs b/templates/code/rust/tests/metrics/compile-fail/unrelated_optional.rs new file mode 100644 index 0000000..667cb94 --- /dev/null +++ b/templates/code/rust/tests/metrics/compile-fail/unrelated_optional.rs @@ -0,0 +1,5 @@ +use generated_semconv::{authattr, myappmetric}; + +fn main() { + let _ = myappmetric::TaskDurationHistogramAttr::Success(authattr::SuccessAttr::from(true)); +} diff --git a/templates/code/rust/tests/metrics/compile-fail/wrong_required_type.expected-error.txt b/templates/code/rust/tests/metrics/compile-fail/wrong_required_type.expected-error.txt new file mode 100644 index 0000000..f8fceb8 --- /dev/null +++ b/templates/code/rust/tests/metrics/compile-fail/wrong_required_type.expected-error.txt @@ -0,0 +1 @@ +expected `TaskIdAttr`, found `TaskStateAttr` diff --git a/templates/code/rust/tests/metrics/compile-fail/wrong_required_type.rs b/templates/code/rust/tests/metrics/compile-fail/wrong_required_type.rs new file mode 100644 index 0000000..dfcf2cd --- /dev/null +++ b/templates/code/rust/tests/metrics/compile-fail/wrong_required_type.rs @@ -0,0 +1,5 @@ +use generated_semconv::{myappattr, myappmetric}; + +fn main() { + myappmetric::TaskDurationHistogram::default().record(0.1, myappattr::TaskStateAttr::Done, []); +} diff --git a/templates/code/rust/tests/metrics/expected/authattr.rs b/templates/code/rust/tests/metrics/expected/authattr.rs new file mode 100644 index 0000000..ff9bba6 --- /dev/null +++ b/templates/code/rust/tests/metrics/expected/authattr.rs @@ -0,0 +1,98 @@ +// Generated by OpenTelemetry Weaver. +/// How the user was authenticated. +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum MethodAttr { + /// A password was checked. + Password, + /// A bearer token was checked. + Token, +} + +#[allow(deprecated)] +impl MethodAttr { + pub const KEY: &'static str = "auth.method"; +} + +#[allow(deprecated)] +impl AsRef for MethodAttr { + #[inline] + fn as_ref(&self) -> &str { + match self { + MethodAttr::Password => "password", + MethodAttr::Token => "token", + } + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for MethodAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: MethodAttr) -> Self { + let value: &'static str = match value { + MethodAttr::Password => "password", + MethodAttr::Token => "token", + }; + ::opentelemetry::KeyValue::new(MethodAttr::KEY, value) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: MethodAttr) -> Self { + let value: &'static str = match value { + MethodAttr::Password => "password", + MethodAttr::Token => "token", + }; + value.into() + } +} + +/// Whether the attempt succeeded. +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct SuccessAttr(bool); + +#[allow(deprecated)] +impl SuccessAttr { + pub const KEY: &'static str = "auth.success"; +} + +#[allow(deprecated)] +impl From for SuccessAttr { + #[inline] + fn from(value: bool) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for SuccessAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::std::fmt::Display::fmt(&self.0, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: SuccessAttr) -> Self { + ::opentelemetry::KeyValue::new(SuccessAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: SuccessAttr) -> Self { + value.0.into() + } +} diff --git a/templates/code/rust/tests/metrics/expected/authmetric.rs b/templates/code/rust/tests/metrics/expected/authmetric.rs new file mode 100644 index 0000000..7a2580e --- /dev/null +++ b/templates/code/rust/tests/metrics/expected/authmetric.rs @@ -0,0 +1,54 @@ +#![allow(deprecated)] +/// Number of authentication attempts. +#[derive(Clone)] +pub struct AttemptsCounter { + instrument: ::opentelemetry::metrics::Counter, +} + +impl AttemptsCounter { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().u64_counter("auth.attempts") + .with_description("Number of authentication attempts.") + .with_unit("{attempt}") + .build(); + Self { instrument } + } + pub fn add(&self, value: u64, r#auth_method: super::authattr::MethodAttr, r#auth_success: super::authattr::SuccessAttr) { + let attributes = [::opentelemetry::KeyValue::from(r#auth_method), ::opentelemetry::KeyValue::from(r#auth_success)]; + self.instrument.add(value, &attributes); + } +} + +impl Default for AttemptsCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} +pub struct AttemptsCounterObserver<'a> { + observer: &'a dyn ::opentelemetry::metrics::AsyncInstrument, +} + +impl AttemptsCounterObserver<'_> { + pub fn observe(&self, value: u64, r#auth_method: super::authattr::MethodAttr, r#auth_success: super::authattr::SuccessAttr) { + let attributes = [::opentelemetry::KeyValue::from(r#auth_method), ::opentelemetry::KeyValue::from(r#auth_success)]; + self.observer.observe(value, &attributes); + } +} + +#[derive(Clone)] +pub struct AttemptsObservableCounter { + _instrument: ::opentelemetry::metrics::ObservableCounter, +} + +impl AttemptsObservableCounter { + pub fn new(meter: &super::meter::Meter, callback: impl Fn(AttemptsCounterObserver<'_>) + Send + Sync + 'static) -> Self { + let instrument = meter.inner().u64_observable_counter("auth.attempts") + .with_description("Number of authentication attempts.") + .with_unit("{attempt}") + .with_callback(move |observer| callback(AttemptsCounterObserver { observer })) + .build(); + Self { _instrument: instrument } + } +} + +impl Default for AttemptsObservableCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default(), |_| {}) } +} diff --git a/templates/code/rust/tests/metrics/expected/meter.rs b/templates/code/rust/tests/metrics/expected/meter.rs new file mode 100644 index 0000000..a330bd2 --- /dev/null +++ b/templates/code/rust/tests/metrics/expected/meter.rs @@ -0,0 +1,16 @@ +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Meter(opentelemetry::metrics::Meter); + +impl Meter { + pub fn new(provider: &impl opentelemetry::metrics::MeterProvider, options: InstrumentationOptions) -> Self { + let meter = provider.meter_with_scope(options.scope.into()); + Self(meter) + } + pub fn inner(&self) -> &opentelemetry::metrics::Meter { &self.0 } +} + +impl Default for Meter { + fn default() -> Self { Self::new(&opentelemetry::metrics::noop::NoopMeterProvider::new(), InstrumentationOptions::default()) } +} diff --git a/templates/code/rust/tests/metrics/expected/mod.rs b/templates/code/rust/tests/metrics/expected/mod.rs new file mode 100644 index 0000000..f85e525 --- /dev/null +++ b/templates/code/rust/tests/metrics/expected/mod.rs @@ -0,0 +1,11 @@ +// Generated by OpenTelemetry Weaver. +pub const SCHEMA_URL: &str = "https://example.com/schemas/metrics/1.0.0"; +pub mod authattr; +pub mod myappattr; + +pub mod scope; +pub mod options; +pub mod meter; +pub mod authmetric; +pub mod myappmetric; +pub mod workermetric; diff --git a/templates/code/rust/tests/metrics/expected/myappattr.rs b/templates/code/rust/tests/metrics/expected/myappattr.rs new file mode 100644 index 0000000..8928c88 --- /dev/null +++ b/templates/code/rust/tests/metrics/expected/myappattr.rs @@ -0,0 +1,122 @@ +// Generated by OpenTelemetry Weaver. +/// Unique identifier of the task. +#[derive(Debug, Clone, PartialEq)] +pub struct TaskIdAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl TaskIdAttr { + pub const KEY: &'static str = "myapp.task.id"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for TaskIdAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for TaskIdAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for TaskIdAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for TaskIdAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskIdAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskIdAttr) -> Self { + ::opentelemetry::KeyValue::new(TaskIdAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskIdAttr) -> Self { + value.0.into() + } +} + +/// Current state of the task. +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum TaskStateAttr { + /// The task is waiting to run. + Queued, + /// The task finished. + Done, +} + +#[allow(deprecated)] +impl TaskStateAttr { + pub const KEY: &'static str = "myapp.task.state"; +} + +#[allow(deprecated)] +impl AsRef for TaskStateAttr { + #[inline] + fn as_ref(&self) -> &str { + match self { + TaskStateAttr::Queued => "queued", + TaskStateAttr::Done => "done", + } + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskStateAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskStateAttr) -> Self { + let value: &'static str = match value { + TaskStateAttr::Queued => "queued", + TaskStateAttr::Done => "done", + }; + ::opentelemetry::KeyValue::new(TaskStateAttr::KEY, value) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskStateAttr) -> Self { + let value: &'static str = match value { + TaskStateAttr::Queued => "queued", + TaskStateAttr::Done => "done", + }; + value.into() + } +} diff --git a/templates/code/rust/tests/metrics/expected/myappmetric.rs b/templates/code/rust/tests/metrics/expected/myappmetric.rs new file mode 100644 index 0000000..8a269e4 --- /dev/null +++ b/templates/code/rust/tests/metrics/expected/myappmetric.rs @@ -0,0 +1,445 @@ +#![allow(deprecated)] +/// Number of tasks waiting in the queue. +#[deprecated(note = "Replaced by `myapp.task.active`.")] +#[derive(Clone)] +pub struct QueueDepthGauge { + instrument: ::opentelemetry::metrics::Gauge, +} + +impl QueueDepthGauge { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().i64_gauge("myapp.queue.depth") + .with_description("Number of tasks waiting in the queue.") + .with_unit("{task}") + .build(); + Self { instrument } + } + pub fn record(&self, value: i64) { + let attributes = []; + self.instrument.record(value, &attributes); + } +} + +impl Default for QueueDepthGauge { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} +pub struct QueueDepthGaugeObserver<'a> { + observer: &'a dyn ::opentelemetry::metrics::AsyncInstrument, +} + +impl QueueDepthGaugeObserver<'_> { + pub fn observe(&self, value: i64) { + let attributes = []; + self.observer.observe(value, &attributes); + } +} +#[deprecated(note = "Replaced by `myapp.task.active`.")] +#[derive(Clone)] +pub struct QueueDepthObservableGauge { + _instrument: ::opentelemetry::metrics::ObservableGauge, +} + +impl QueueDepthObservableGauge { + pub fn new(meter: &super::meter::Meter, callback: impl Fn(QueueDepthGaugeObserver<'_>) + Send + Sync + 'static) -> Self { + let instrument = meter.inner().i64_observable_gauge("myapp.queue.depth") + .with_description("Number of tasks waiting in the queue.") + .with_unit("{task}") + .with_callback(move |observer| callback(QueueDepthGaugeObserver { observer })) + .build(); + Self { _instrument: instrument } + } +} + +impl Default for QueueDepthObservableGauge { + fn default() -> Self { Self::new(&super::meter::Meter::default(), |_| {}) } +} +/// Number of tasks currently running. +#[derive(Clone)] +pub struct TaskActiveUpDownCounter { + instrument: ::opentelemetry::metrics::UpDownCounter, +} + +impl TaskActiveUpDownCounter { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().i64_up_down_counter("myapp.task.active") + .with_description("Number of tasks currently running.") + .with_unit("{task}") + .build(); + Self { instrument } + } + pub fn add(&self, value: i64, r#myapp_task_state: super::myappattr::TaskStateAttr) { + let attributes = [::opentelemetry::KeyValue::from(r#myapp_task_state)]; + self.instrument.add(value, &attributes); + } +} + +impl Default for TaskActiveUpDownCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} +pub struct TaskActiveUpDownCounterObserver<'a> { + observer: &'a dyn ::opentelemetry::metrics::AsyncInstrument, +} + +impl TaskActiveUpDownCounterObserver<'_> { + pub fn observe(&self, value: i64, r#myapp_task_state: super::myappattr::TaskStateAttr) { + let attributes = [::opentelemetry::KeyValue::from(r#myapp_task_state)]; + self.observer.observe(value, &attributes); + } +} + +#[derive(Clone)] +pub struct TaskActiveObservableUpDownCounter { + _instrument: ::opentelemetry::metrics::ObservableUpDownCounter, +} + +impl TaskActiveObservableUpDownCounter { + pub fn new(meter: &super::meter::Meter, callback: impl Fn(TaskActiveUpDownCounterObserver<'_>) + Send + Sync + 'static) -> Self { + let instrument = meter.inner().i64_observable_up_down_counter("myapp.task.active") + .with_description("Number of tasks currently running.") + .with_unit("{task}") + .with_callback(move |observer| callback(TaskActiveUpDownCounterObserver { observer })) + .build(); + Self { _instrument: instrument } + } +} + +impl Default for TaskActiveObservableUpDownCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default(), |_| {}) } +} +#[derive(Clone, Debug)] +pub enum TaskDurationHistogramAttr { + /// Opt-in: the convention records it only when a user asks for it. + Method(super::authattr::MethodAttr), + /// Conditionally required: If the task reached a final state. + TaskState(super::myappattr::TaskStateAttr), +} + +impl From for ::opentelemetry::KeyValue { + fn from(value: TaskDurationHistogramAttr) -> Self { + match value { + TaskDurationHistogramAttr::Method(value) => ::opentelemetry::KeyValue::from(value), + TaskDurationHistogramAttr::TaskState(value) => ::opentelemetry::KeyValue::from(value), + } + } +} +/// Time a task took from start to finish. +/// +/// Measured by the worker that ran the task. +#[derive(Clone)] +pub struct TaskDurationHistogram { + instrument: ::opentelemetry::metrics::Histogram, +} + +impl TaskDurationHistogram { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().f64_histogram("myapp.task.duration") + .with_description("Time a task took from start to finish.") + .with_unit("s") + .with_boundaries(vec![0_f64, 0.005_f64, 0.01_f64, 0.1_f64, 1_f64, 10_f64]) + .build(); + Self { instrument } + } + pub fn record(&self, value: f64, r#myapp_task_id: super::myappattr::TaskIdAttr, options: impl IntoIterator) { + let mut attributes = ::smallvec::SmallVec::<[::opentelemetry::KeyValue; 3]>::new(); + attributes.extend([::opentelemetry::KeyValue::from(r#myapp_task_id)]); + attributes.extend(options.into_iter().map(::opentelemetry::KeyValue::from)); + self.instrument.record(value, &attributes); + } +} + +impl Default for TaskDurationHistogram { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} +/// Fractional active task adjustment. +#[derive(Clone)] +pub struct TaskFloatActiveUpDownCounter { + instrument: ::opentelemetry::metrics::UpDownCounter, +} + +impl TaskFloatActiveUpDownCounter { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().f64_up_down_counter("myapp.task.float.active") + .with_description("Fractional active task adjustment.") + .with_unit("{task}") + .build(); + Self { instrument } + } + pub fn add(&self, value: f64) { + let attributes = []; + self.instrument.add(value, &attributes); + } +} + +impl Default for TaskFloatActiveUpDownCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} +pub struct TaskFloatActiveUpDownCounterObserver<'a> { + observer: &'a dyn ::opentelemetry::metrics::AsyncInstrument, +} + +impl TaskFloatActiveUpDownCounterObserver<'_> { + pub fn observe(&self, value: f64) { + let attributes = []; + self.observer.observe(value, &attributes); + } +} + +#[derive(Clone)] +pub struct TaskFloatActiveObservableUpDownCounter { + _instrument: ::opentelemetry::metrics::ObservableUpDownCounter, +} + +impl TaskFloatActiveObservableUpDownCounter { + pub fn new(meter: &super::meter::Meter, callback: impl Fn(TaskFloatActiveUpDownCounterObserver<'_>) + Send + Sync + 'static) -> Self { + let instrument = meter.inner().f64_observable_up_down_counter("myapp.task.float.active") + .with_description("Fractional active task adjustment.") + .with_unit("{task}") + .with_callback(move |observer| callback(TaskFloatActiveUpDownCounterObserver { observer })) + .build(); + Self { _instrument: instrument } + } +} + +impl Default for TaskFloatActiveObservableUpDownCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default(), |_| {}) } +} +/// Fractional queue depth. +#[derive(Clone)] +pub struct TaskFloatDepthGauge { + instrument: ::opentelemetry::metrics::Gauge, +} + +impl TaskFloatDepthGauge { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().f64_gauge("myapp.task.float.depth") + .with_description("Fractional queue depth.") + .with_unit("{task}") + .build(); + Self { instrument } + } + pub fn record(&self, value: f64) { + let attributes = []; + self.instrument.record(value, &attributes); + } +} + +impl Default for TaskFloatDepthGauge { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} +pub struct TaskFloatDepthGaugeObserver<'a> { + observer: &'a dyn ::opentelemetry::metrics::AsyncInstrument, +} + +impl TaskFloatDepthGaugeObserver<'_> { + pub fn observe(&self, value: f64) { + let attributes = []; + self.observer.observe(value, &attributes); + } +} + +#[derive(Clone)] +pub struct TaskFloatDepthObservableGauge { + _instrument: ::opentelemetry::metrics::ObservableGauge, +} + +impl TaskFloatDepthObservableGauge { + pub fn new(meter: &super::meter::Meter, callback: impl Fn(TaskFloatDepthGaugeObserver<'_>) + Send + Sync + 'static) -> Self { + let instrument = meter.inner().f64_observable_gauge("myapp.task.float.depth") + .with_description("Fractional queue depth.") + .with_unit("{task}") + .with_callback(move |observer| callback(TaskFloatDepthGaugeObserver { observer })) + .build(); + Self { _instrument: instrument } + } +} + +impl Default for TaskFloatDepthObservableGauge { + fn default() -> Self { Self::new(&super::meter::Meter::default(), |_| {}) } +} +/// Fractional tasks started. +#[derive(Clone)] +pub struct TaskFloatStartedCounter { + instrument: ::opentelemetry::metrics::Counter, +} + +impl TaskFloatStartedCounter { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().f64_counter("myapp.task.float.started") + .with_description("Fractional tasks started.") + .with_unit("{task}") + .build(); + Self { instrument } + } + pub fn add(&self, value: f64) { + let attributes = []; + self.instrument.add(value, &attributes); + } +} + +impl Default for TaskFloatStartedCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} +pub struct TaskFloatStartedCounterObserver<'a> { + observer: &'a dyn ::opentelemetry::metrics::AsyncInstrument, +} + +impl TaskFloatStartedCounterObserver<'_> { + pub fn observe(&self, value: f64) { + let attributes = []; + self.observer.observe(value, &attributes); + } +} + +#[derive(Clone)] +pub struct TaskFloatStartedObservableCounter { + _instrument: ::opentelemetry::metrics::ObservableCounter, +} + +impl TaskFloatStartedObservableCounter { + pub fn new(meter: &super::meter::Meter, callback: impl Fn(TaskFloatStartedCounterObserver<'_>) + Send + Sync + 'static) -> Self { + let instrument = meter.inner().f64_observable_counter("myapp.task.float.started") + .with_description("Fractional tasks started.") + .with_unit("{task}") + .with_callback(move |observer| callback(TaskFloatStartedCounterObserver { observer })) + .build(); + Self { _instrument: instrument } + } +} + +impl Default for TaskFloatStartedObservableCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default(), |_| {}) } +} +/// Task payload size. +#[derive(Clone)] +pub struct TaskPayloadHistogram { + instrument: ::opentelemetry::metrics::Histogram, +} + +impl TaskPayloadHistogram { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().u64_histogram("myapp.task.payload") + .with_description("Task payload size.") + .with_unit("By") + .with_boundaries(vec![128_f64, 256_f64, 1024_f64]) + .build(); + Self { instrument } + } + pub fn record(&self, value: u64) { + let attributes = []; + self.instrument.record(value, &attributes); + } +} + +impl Default for TaskPayloadHistogram { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} +#[derive(Clone, Debug)] +pub enum TaskRetryDurationHistogramAttr { + /// Opt-in: the convention records it only when a user asks for it. + Method(super::authattr::MethodAttr), +} + +impl From for ::opentelemetry::KeyValue { + fn from(value: TaskRetryDurationHistogramAttr) -> Self { + match value { + TaskRetryDurationHistogramAttr::Method(value) => ::opentelemetry::KeyValue::from(value), + } + } +} +/// Time a retried task took from start to finish. +/// +/// Recorded only for tasks that ran more than once. +#[derive(Clone)] +pub struct TaskRetryDurationHistogram { + instrument: ::opentelemetry::metrics::Histogram, +} + +impl TaskRetryDurationHistogram { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().f64_histogram("myapp.task.duration") + .with_description("Time a task took from start to finish.") + .with_unit("s") + .with_boundaries(vec![0_f64, 0.005_f64, 0.01_f64, 0.1_f64, 1_f64, 10_f64]) + .build(); + Self { instrument } + } + pub fn record(&self, value: f64, r#myapp_task_id: super::myappattr::TaskIdAttr, r#myapp_task_state: super::myappattr::TaskStateAttr, options: impl IntoIterator) { + let mut attributes = ::smallvec::SmallVec::<[::opentelemetry::KeyValue; 3]>::new(); + attributes.extend([::opentelemetry::KeyValue::from(r#myapp_task_id), ::opentelemetry::KeyValue::from(r#myapp_task_state)]); + attributes.extend(options.into_iter().map(::opentelemetry::KeyValue::from)); + self.instrument.record(value, &attributes); + } +} + +impl Default for TaskRetryDurationHistogram { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} +#[derive(Clone, Debug)] +pub enum TaskStartedCounterAttr { + /// Recommended: When the scheduler assigned an id before the task started. + TaskId(super::myappattr::TaskIdAttr), +} + +impl From for ::opentelemetry::KeyValue { + fn from(value: TaskStartedCounterAttr) -> Self { + match value { + TaskStartedCounterAttr::TaskId(value) => ::opentelemetry::KeyValue::from(value), + } + } +} +/// Number of tasks started. +/// Opt-in: record only when the user requests it. +#[derive(Clone)] +pub struct TaskStartedCounter { + instrument: ::opentelemetry::metrics::Counter, +} + +impl TaskStartedCounter { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().u64_counter("myapp.task.started") + .with_description("Number of tasks started.") + .with_unit("{task}") + .build(); + Self { instrument } + } + pub fn add(&self, value: u64, options: impl IntoIterator) { + let mut attributes = ::smallvec::SmallVec::<[::opentelemetry::KeyValue; 1]>::new(); + attributes.extend(options.into_iter().map(::opentelemetry::KeyValue::from)); + self.instrument.add(value, &attributes); + } +} + +impl Default for TaskStartedCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} +pub struct TaskStartedCounterObserver<'a> { + observer: &'a dyn ::opentelemetry::metrics::AsyncInstrument, +} + +impl TaskStartedCounterObserver<'_> { + pub fn observe(&self, value: u64, options: impl IntoIterator) { + let mut attributes = ::smallvec::SmallVec::<[::opentelemetry::KeyValue; 1]>::new(); + attributes.extend(options.into_iter().map(::opentelemetry::KeyValue::from)); + self.observer.observe(value, &attributes); + } +} + +#[derive(Clone)] +pub struct TaskStartedObservableCounter { + _instrument: ::opentelemetry::metrics::ObservableCounter, +} + +impl TaskStartedObservableCounter { + pub fn new(meter: &super::meter::Meter, callback: impl Fn(TaskStartedCounterObserver<'_>) + Send + Sync + 'static) -> Self { + let instrument = meter.inner().u64_observable_counter("myapp.task.started") + .with_description("Number of tasks started.") + .with_unit("{task}") + .with_callback(move |observer| callback(TaskStartedCounterObserver { observer })) + .build(); + Self { _instrument: instrument } + } +} + +impl Default for TaskStartedObservableCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default(), |_| {}) } +} diff --git a/templates/code/rust/tests/metrics/expected/options.rs b/templates/code/rust/tests/metrics/expected/options.rs new file mode 100644 index 0000000..69a821a --- /dev/null +++ b/templates/code/rust/tests/metrics/expected/options.rs @@ -0,0 +1,6 @@ +use super::scope::Scope; + +#[derive(Clone, Debug, Default)] +pub struct InstrumentationOptions { + pub scope: Scope, +} diff --git a/templates/code/rust/tests/metrics/expected/scope.rs b/templates/code/rust/tests/metrics/expected/scope.rs new file mode 100644 index 0000000..6f66e86 --- /dev/null +++ b/templates/code/rust/tests/metrics/expected/scope.rs @@ -0,0 +1,13 @@ +#[derive(Clone, Debug, Default)] +pub struct Scope { + pub attributes: Vec<::opentelemetry::KeyValue>, +} + +impl From for ::opentelemetry::InstrumentationScope { + fn from(value: Scope) -> Self { + Self::builder("generated-semconv") + .with_schema_url(super::SCHEMA_URL) + .with_attributes(value.attributes) + .build() + } +} diff --git a/templates/code/rust/tests/metrics/expected/workermetric.rs b/templates/code/rust/tests/metrics/expected/workermetric.rs new file mode 100644 index 0000000..21f03e7 --- /dev/null +++ b/templates/code/rust/tests/metrics/expected/workermetric.rs @@ -0,0 +1,45 @@ +#![allow(deprecated)] +#[derive(Clone, Debug)] +pub enum TaskDurationHistogramAttr { + /// Opt-in: the convention records it only when a user asks for it. + Method(super::authattr::MethodAttr), + /// Conditionally required: If the task reached a final state. + TaskState(super::myappattr::TaskStateAttr), +} + +impl From for ::opentelemetry::KeyValue { + fn from(value: TaskDurationHistogramAttr) -> Self { + match value { + TaskDurationHistogramAttr::Method(value) => ::opentelemetry::KeyValue::from(value), + TaskDurationHistogramAttr::TaskState(value) => ::opentelemetry::KeyValue::from(value), + } + } +} +/// Time a task took on the worker that ran it. +/// +/// Measured by the worker that ran the task. +#[derive(Clone)] +pub struct TaskDurationHistogram { + instrument: ::opentelemetry::metrics::Histogram, +} + +impl TaskDurationHistogram { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().f64_histogram("myapp.task.duration") + .with_description("Time a task took from start to finish.") + .with_unit("s") + .with_boundaries(vec![0_f64, 0.005_f64, 0.01_f64, 0.1_f64, 1_f64, 10_f64]) + .build(); + Self { instrument } + } + pub fn record(&self, value: f64, r#myapp_task_id: super::myappattr::TaskIdAttr, options: impl IntoIterator) { + let mut attributes = ::smallvec::SmallVec::<[::opentelemetry::KeyValue; 3]>::new(); + attributes.extend([::opentelemetry::KeyValue::from(r#myapp_task_id)]); + attributes.extend(options.into_iter().map(::opentelemetry::KeyValue::from)); + self.instrument.record(value, &attributes); + } +} + +impl Default for TaskDurationHistogram { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} diff --git a/templates/code/rust/tests/metrics/registry/manifest.yaml b/templates/code/rust/tests/metrics/registry/manifest.yaml new file mode 100644 index 0000000..6a5676e --- /dev/null +++ b/templates/code/rust/tests/metrics/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: metrics +schema_url: https://example.com/schemas/metrics/1.0.0 diff --git a/templates/code/rust/tests/metrics/registry/model.yaml b/templates/code/rust/tests/metrics/registry/model.yaml new file mode 100644 index 0000000..12a099b --- /dev/null +++ b/templates/code/rust/tests/metrics/registry/model.yaml @@ -0,0 +1,170 @@ +file_format: definition/2 +attributes: + - key: myapp.task.id + type: string + stability: stable + brief: Unique identifier of the task. + examples: ["task_0001"] + - key: myapp.task.state + type: + members: + - id: queued + value: queued + stability: stable + brief: The task is waiting to run. + - id: done + value: done + stability: stable + brief: The task finished. + stability: stable + brief: Current state of the task. + - key: auth.method + type: + members: + - id: password + value: password + stability: stable + brief: A password was checked. + - id: token + value: token + stability: stable + brief: A bearer token was checked. + stability: stable + brief: How the user was authenticated. + - key: auth.success + type: boolean + stability: stable + brief: Whether the attempt succeeded. + examples: true +metrics: + - name: myapp.task.duration + requirement_level: recommended + instrument: histogram + unit: s + stability: stable + brief: Time a task took from start to finish. + note: Measured by the worker that ran the task. + annotations: + code_generation: + metric_value_type: double + aggregation: + method: explicithistogram + parameters: + boundaries: [0, 0.005, 0.01, 0.1, 1, 10] + attributes: + - ref: myapp.task.id + requirement_level: required + - ref: myapp.task.state + requirement_level: + conditionally_required: If the task reached a final state. + - ref: auth.method + requirement_level: opt_in + - name: myapp.task.started + requirement_level: opt_in + instrument: counter + unit: "{task}" + stability: stable + brief: Number of tasks started. + annotations: + code_generation: + metric_value_type: int + attributes: + - ref: myapp.task.id + requirement_level: + recommended: When the scheduler assigned an id before the task started. + - name: myapp.task.active + requirement_level: recommended + instrument: updowncounter + unit: "{task}" + stability: development + brief: Number of tasks currently running. + annotations: + code_generation: + metric_value_type: int + attributes: + - ref: myapp.task.state + requirement_level: required + - name: myapp.queue.depth + requirement_level: recommended + instrument: gauge + unit: "{task}" + stability: development + brief: Number of tasks waiting in the queue. + annotations: + code_generation: + metric_value_type: int + deprecated: + reason: renamed + renamed_to: myapp.task.active + - name: auth.attempts + requirement_level: recommended + instrument: counter + unit: "{attempt}" + stability: stable + brief: Number of authentication attempts. + annotations: + code_generation: + metric_value_type: int + attributes: + - ref: auth.method + requirement_level: required + - ref: auth.success + requirement_level: required + - name: myapp.task.float.started + requirement_level: recommended + instrument: counter + unit: "{task}" + stability: stable + brief: Fractional tasks started. + annotations: + code_generation: + metric_value_type: double + - name: myapp.task.float.active + requirement_level: recommended + instrument: updowncounter + unit: "{task}" + stability: stable + brief: Fractional active task adjustment. + annotations: + code_generation: + metric_value_type: double + - name: myapp.task.float.depth + requirement_level: recommended + instrument: gauge + unit: "{task}" + stability: stable + brief: Fractional queue depth. + annotations: + code_generation: + metric_value_type: double + - name: myapp.task.payload + requirement_level: recommended + instrument: histogram + unit: By + stability: stable + brief: Task payload size. + annotations: + code_generation: + metric_value_type: int + aggregation: + method: explicithistogram + parameters: + boundaries: [128, 256, 1024] +metric_refinements: + - id: myapp.task.retry.duration + ref: myapp.task.duration + stability: stable + brief: Time a retried task took from start to finish. + note: Recorded only for tasks that ran more than once. + attributes: + - ref: myapp.task.id + requirement_level: required + - ref: myapp.task.state + requirement_level: required + - id: worker.task.duration + ref: myapp.task.duration + stability: stable + brief: Time a task took on the worker that ran it. + attributes: + - ref: myapp.task.id + requirement_level: required diff --git a/templates/code/rust/tests/metrics/test.rs b/templates/code/rust/tests/metrics/test.rs new file mode 100644 index 0000000..35b5a17 --- /dev/null +++ b/templates/code/rust/tests/metrics/test.rs @@ -0,0 +1,349 @@ +use generated_semconv::options::InstrumentationOptions; +use generated_semconv::{authattr, authmetric, meter, myappattr, myappmetric, scope, workermetric}; +use opentelemetry::KeyValue; +use opentelemetry_sdk::metrics::data::{AggregatedMetrics, MetricData}; +use opentelemetry_sdk::metrics::{InMemoryMetricExporter, PeriodicReader, SdkMeterProvider}; +use std::time::Duration; + +fn provider() -> (SdkMeterProvider, InMemoryMetricExporter) { + let exporter = InMemoryMetricExporter::default(); + let provider = SdkMeterProvider::builder() + .with_reader( + PeriodicReader::builder(exporter.clone()) + .with_interval(Duration::from_secs(3600)) + .build(), + ) + .build(); + (provider, exporter) +} + +#[test] +#[allow(deprecated)] +fn synchronous_metrics_export_types_values_attributes_and_refinements() { + let (provider, exporter) = provider(); + let meter = meter::Meter::new( + &provider, + InstrumentationOptions { + scope: scope::Scope { + attributes: vec![KeyValue::new("scope.kind", "worker")], + }, + }, + ); + myappmetric::TaskDurationHistogram::new(&meter).record( + 0.1, + myappattr::TaskIdAttr::from("base"), + [ + myappmetric::TaskDurationHistogramAttr::TaskState(myappattr::TaskStateAttr::Queued), + myappmetric::TaskDurationHistogramAttr::Method(authattr::MethodAttr::Password), + ], + ); + myappmetric::TaskRetryDurationHistogram::new(&meter).record( + 0.2, + myappattr::TaskIdAttr::from("retry"), + myappattr::TaskStateAttr::Done, + [myappmetric::TaskRetryDurationHistogramAttr::Method( + authattr::MethodAttr::Token, + )], + ); + workermetric::TaskDurationHistogram::new(&meter).record( + 0.3, + myappattr::TaskIdAttr::from("worker"), + [], + ); + myappmetric::TaskStartedCounter::new(&meter).add( + 7, + [myappmetric::TaskStartedCounterAttr::TaskId( + myappattr::TaskIdAttr::from("started"), + )], + ); + myappmetric::TaskActiveUpDownCounter::new(&meter).add(-2, myappattr::TaskStateAttr::Queued); + myappmetric::QueueDepthGauge::new(&meter).record(5); + authmetric::AttemptsCounter::new(&meter).add( + 3, + authattr::MethodAttr::Password, + authattr::SuccessAttr::from(true), + ); + + provider.force_flush().unwrap(); + let exported = exporter.get_finished_metrics().unwrap(); + let scope = exported[0].scope_metrics().next().unwrap(); + assert_eq!(scope.scope().version(), None); + assert!(scope + .scope() + .attributes() + .any(|attribute| attribute.key.as_str() == "scope.kind" + && attribute.value.as_str() == "worker")); + assert_eq!(scope.scope().name(), "generated-semconv"); + assert_eq!( + scope.scope().schema_url(), + Some(generated_semconv::SCHEMA_URL) + ); + let find = |name: &str| { + scope + .metrics() + .find(|metric| metric.name() == name) + .unwrap() + }; + + let duration = find("myapp.task.duration"); + assert_eq!( + duration.description(), + "Time a task took from start to finish." + ); + assert_eq!(duration.unit(), "s"); + let AggregatedMetrics::F64(MetricData::Histogram(histogram)) = duration.data() else { + panic!("duration must be an f64 histogram"); + }; + assert_eq!(histogram.data_points().count(), 3); + for (id, value, expected_attrs) in [ + ( + "base", + 0.1, + vec![ + KeyValue::new("myapp.task.id", "base"), + KeyValue::new("myapp.task.state", "queued"), + KeyValue::new("auth.method", "password"), + ], + ), + ( + "retry", + 0.2, + vec![ + KeyValue::new("myapp.task.id", "retry"), + KeyValue::new("myapp.task.state", "done"), + KeyValue::new("auth.method", "token"), + ], + ), + ( + "worker", + 0.3, + vec![KeyValue::new("myapp.task.id", "worker")], + ), + ] { + let point = histogram + .data_points() + .find(|point| { + point + .attributes() + .any(|attr| *attr == KeyValue::new("myapp.task.id", id)) + }) + .unwrap(); + assert_eq!(point.count(), 1); + assert_eq!(point.sum(), value); + assert_eq!( + point.bounds().collect::>(), + [0.0, 0.005, 0.01, 0.1, 1.0, 10.0] + ); + assert_eq!(point.attributes().count(), expected_attrs.len()); + for expected in expected_attrs { + assert!(point.attributes().any(|actual| *actual == expected)); + } + } + + let started = find("myapp.task.started"); + assert_eq!(started.unit(), "{task}"); + let AggregatedMetrics::U64(MetricData::Sum(sum)) = started.data() else { + panic!("counter must be an unsigned sum"); + }; + let point = sum.data_points().next().unwrap(); + assert_eq!(point.value(), 7); + assert_eq!( + point.attributes().collect::>(), + [&KeyValue::new("myapp.task.id", "started")] + ); + + let AggregatedMetrics::I64(MetricData::Sum(sum)) = find("myapp.task.active").data() else { + panic!("up-down counter must preserve signed values"); + }; + let point = sum.data_points().next().unwrap(); + assert_eq!(point.value(), -2); + assert_eq!( + point.attributes().collect::>(), + [&KeyValue::new("myapp.task.state", "queued")] + ); + + let AggregatedMetrics::I64(MetricData::Gauge(gauge)) = find("myapp.queue.depth").data() else { + panic!("queue depth must be a signed gauge"); + }; + let point = gauge.data_points().next().unwrap(); + assert_eq!(point.value(), 5); + assert_eq!(point.attributes().count(), 0); + + let attempts = find("auth.attempts"); + assert_eq!(attempts.unit(), "{attempt}"); + let AggregatedMetrics::U64(MetricData::Sum(sum)) = attempts.data() else { + panic!("attempts must be an unsigned sum"); + }; + let point = sum.data_points().next().unwrap(); + assert_eq!(point.value(), 3); + assert!(point + .attributes() + .any(|attr| *attr == KeyValue::new("auth.method", "password"))); + assert!(point + .attributes() + .any(|attr| *attr == KeyValue::new("auth.success", true))); + provider.shutdown().unwrap(); +} + +#[test] +#[allow(deprecated)] +fn observable_callbacks_export_values_and_typed_attributes() { + let (provider, exporter) = provider(); + let meter = meter::Meter::new(&provider, InstrumentationOptions::default()); + let _started = myappmetric::TaskStartedObservableCounter::new(&meter, |observer| { + observer.observe( + 11, + [myappmetric::TaskStartedCounterAttr::TaskId( + myappattr::TaskIdAttr::from("observed"), + )], + ); + }); + let _active = myappmetric::TaskActiveObservableUpDownCounter::new(&meter, |observer| { + observer.observe(-4, myappattr::TaskStateAttr::Queued); + }); + let _depth = myappmetric::QueueDepthObservableGauge::new(&meter, |observer| { + observer.observe(9); + }); + let _attempts = authmetric::AttemptsObservableCounter::new(&meter, |observer| { + observer.observe( + 13, + authattr::MethodAttr::Token, + authattr::SuccessAttr::from(false), + ); + }); + provider.force_flush().unwrap(); + let exported = exporter.get_finished_metrics().unwrap(); + let scope = exported[0].scope_metrics().next().unwrap(); + assert_eq!(scope.scope().name(), "generated-semconv"); + assert_eq!(scope.scope().version(), None); + assert_eq!( + scope.scope().schema_url(), + Some(generated_semconv::SCHEMA_URL) + ); + let find = |name: &str| { + scope + .metrics() + .find(|metric| metric.name() == name) + .unwrap() + }; + let AggregatedMetrics::U64(MetricData::Sum(started)) = find("myapp.task.started").data() else { + panic!("observable counter must export an unsigned sum"); + }; + let point = started.data_points().next().unwrap(); + assert_eq!(point.value(), 11); + assert!(point + .attributes() + .any(|attr| *attr == KeyValue::new("myapp.task.id", "observed"))); + let AggregatedMetrics::I64(MetricData::Sum(active)) = find("myapp.task.active").data() else { + panic!("observable up-down counter must preserve signed values"); + }; + assert_eq!(active.data_points().next().unwrap().value(), -4); + let AggregatedMetrics::I64(MetricData::Gauge(depth)) = find("myapp.queue.depth").data() else { + panic!("observable gauge must export a gauge"); + }; + assert_eq!(depth.data_points().next().unwrap().value(), 9); + let AggregatedMetrics::U64(MetricData::Sum(attempts)) = find("auth.attempts").data() else { + panic!("observable counter must export an unsigned sum"); + }; + let point = attempts.data_points().next().unwrap(); + assert_eq!(point.value(), 13); + assert!(point + .attributes() + .any(|attr| *attr == KeyValue::new("auth.method", "token"))); + assert!(point + .attributes() + .any(|attr| *attr == KeyValue::new("auth.success", false))); + provider.shutdown().unwrap(); +} + +#[test] +fn alternate_numeric_types_export_sync_and_observable_measurements() { + let (provider, exporter) = provider(); + let sync_meter = meter::Meter::new( + &provider, + InstrumentationOptions { + scope: scope::Scope { + attributes: vec![KeyValue::new("measurement.mode", "sync")], + }, + }, + ); + myappmetric::TaskFloatStartedCounter::new(&sync_meter).add(0.5); + myappmetric::TaskFloatActiveUpDownCounter::new(&sync_meter).add(-1.25); + myappmetric::TaskFloatDepthGauge::new(&sync_meter).record(2.5); + myappmetric::TaskPayloadHistogram::new(&sync_meter).record(512); + let async_meter = meter::Meter::new( + &provider, + InstrumentationOptions { + scope: scope::Scope { + attributes: vec![KeyValue::new("measurement.mode", "async")], + }, + }, + ); + let _started = myappmetric::TaskFloatStartedObservableCounter::new(&async_meter, |observer| { + observer.observe(0.75); + }); + let _active = + myappmetric::TaskFloatActiveObservableUpDownCounter::new(&async_meter, |observer| { + observer.observe(-2.5); + }); + let _depth = myappmetric::TaskFloatDepthObservableGauge::new(&async_meter, |observer| { + observer.observe(3.75); + }); + provider.force_flush().unwrap(); + let exported = exporter.get_finished_metrics().unwrap(); + for (mode, started_value, active_value, depth_value) in + [("sync", 0.5, -1.25, 2.5), ("async", 0.75, -2.5, 3.75)] + { + let scope = exported[0] + .scope_metrics() + .find(|scope| { + scope + .scope() + .attributes() + .any(|attribute| attribute == &KeyValue::new("measurement.mode", mode)) + }) + .unwrap(); + assert_eq!(scope.scope().name(), "generated-semconv"); + assert_eq!(scope.scope().version(), None); + assert_eq!( + scope.scope().schema_url(), + Some(generated_semconv::SCHEMA_URL) + ); + let find = |metric_name: &str| { + scope + .metrics() + .find(|metric| metric.name() == metric_name) + .unwrap() + }; + let AggregatedMetrics::F64(MetricData::Sum(started)) = + find("myapp.task.float.started").data() + else { + panic!("double counter must export a floating sum"); + }; + assert_eq!(started.data_points().next().unwrap().value(), started_value); + let AggregatedMetrics::F64(MetricData::Sum(active)) = + find("myapp.task.float.active").data() + else { + panic!("double up-down counter must export a floating sum"); + }; + assert_eq!(active.data_points().next().unwrap().value(), active_value); + let AggregatedMetrics::F64(MetricData::Gauge(depth)) = + find("myapp.task.float.depth").data() + else { + panic!("double gauge must export a floating gauge"); + }; + assert_eq!(depth.data_points().next().unwrap().value(), depth_value); + if mode == "sync" { + let payload = find("myapp.task.payload"); + assert_eq!(payload.unit(), "By"); + let AggregatedMetrics::U64(MetricData::Histogram(histogram)) = payload.data() else { + panic!("integer histogram must preserve unsigned measurements"); + }; + let point = histogram.data_points().next().unwrap(); + assert_eq!(point.count(), 1); + assert_eq!(point.sum(), 512); + assert_eq!(point.bounds().collect::>(), [128.0, 256.0, 1024.0]); + } + } + provider.shutdown().unwrap(); +} diff --git a/templates/code/rust/tests/naming/expected/capsattr.rs b/templates/code/rust/tests/naming/expected/capsattr.rs new file mode 100644 index 0000000..66d2edf --- /dev/null +++ b/templates/code/rust/tests/naming/expected/capsattr.rs @@ -0,0 +1,117 @@ +// Generated by OpenTelemetry Weaver. +/// Synthetic identifier. +#[derive(Debug, Clone, PartialEq)] +pub struct Http2STATUSApiIDAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl Http2STATUSApiIDAttr { + pub const KEY: &'static str = "caps.http2_STATUS.api_ID"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for Http2STATUSApiIDAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for Http2STATUSApiIDAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for Http2STATUSApiIDAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for Http2STATUSApiIDAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for Http2STATUSApiIDAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: Http2STATUSApiIDAttr) -> Self { + ::opentelemetry::KeyValue::new(Http2STATUSApiIDAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: Http2STATUSApiIDAttr) -> Self { + value.0.into() + } +} + +/// Synthetic mode. +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum Mode2STATEAttr { + /// Synthetic state. + HTTP2ReadySTATE, +} + +#[allow(deprecated)] +impl Mode2STATEAttr { + pub const KEY: &'static str = "caps.mode2_STATE"; +} + +#[allow(deprecated)] +impl AsRef for Mode2STATEAttr { + #[inline] + fn as_ref(&self) -> &str { + match self { + Mode2STATEAttr::HTTP2ReadySTATE => "ready", + } + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for Mode2STATEAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: Mode2STATEAttr) -> Self { + let value: &'static str = match value { + Mode2STATEAttr::HTTP2ReadySTATE => "ready", + }; + ::opentelemetry::KeyValue::new(Mode2STATEAttr::KEY, value) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: Mode2STATEAttr) -> Self { + let value: &'static str = match value { + Mode2STATEAttr::HTTP2ReadySTATE => "ready", + }; + value.into() + } +} diff --git a/templates/code/rust/tests/naming/expected/capsmetric.rs b/templates/code/rust/tests/naming/expected/capsmetric.rs new file mode 100644 index 0000000..eac6967 --- /dev/null +++ b/templates/code/rust/tests/naming/expected/capsmetric.rs @@ -0,0 +1,54 @@ +#![allow(deprecated)] +/// Synthetic count. +#[derive(Clone)] +pub struct Http2STATUSCountCounter { + instrument: ::opentelemetry::metrics::Counter, +} + +impl Http2STATUSCountCounter { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().u64_counter("caps.http2_STATUS.count") + .with_description("Synthetic count.") + .with_unit("{item}") + .build(); + Self { instrument } + } + pub fn add(&self, value: u64, r#caps_http2_status_api_id: super::capsattr::Http2STATUSApiIDAttr, r#caps_mode2_state: super::capsattr::Mode2STATEAttr) { + let attributes = [::opentelemetry::KeyValue::from(r#caps_http2_status_api_id), ::opentelemetry::KeyValue::from(r#caps_mode2_state)]; + self.instrument.add(value, &attributes); + } +} + +impl Default for Http2STATUSCountCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} +pub struct Http2STATUSCountCounterObserver<'a> { + observer: &'a dyn ::opentelemetry::metrics::AsyncInstrument, +} + +impl Http2STATUSCountCounterObserver<'_> { + pub fn observe(&self, value: u64, r#caps_http2_status_api_id: super::capsattr::Http2STATUSApiIDAttr, r#caps_mode2_state: super::capsattr::Mode2STATEAttr) { + let attributes = [::opentelemetry::KeyValue::from(r#caps_http2_status_api_id), ::opentelemetry::KeyValue::from(r#caps_mode2_state)]; + self.observer.observe(value, &attributes); + } +} + +#[derive(Clone)] +pub struct Http2STATUSCountObservableCounter { + _instrument: ::opentelemetry::metrics::ObservableCounter, +} + +impl Http2STATUSCountObservableCounter { + pub fn new(meter: &super::meter::Meter, callback: impl Fn(Http2STATUSCountCounterObserver<'_>) + Send + Sync + 'static) -> Self { + let instrument = meter.inner().u64_observable_counter("caps.http2_STATUS.count") + .with_description("Synthetic count.") + .with_unit("{item}") + .with_callback(move |observer| callback(Http2STATUSCountCounterObserver { observer })) + .build(); + Self { _instrument: instrument } + } +} + +impl Default for Http2STATUSCountObservableCounter { + fn default() -> Self { Self::new(&super::meter::Meter::default(), |_| {}) } +} diff --git a/templates/code/rust/tests/naming/expected/meter.rs b/templates/code/rust/tests/naming/expected/meter.rs new file mode 100644 index 0000000..a330bd2 --- /dev/null +++ b/templates/code/rust/tests/naming/expected/meter.rs @@ -0,0 +1,16 @@ +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Meter(opentelemetry::metrics::Meter); + +impl Meter { + pub fn new(provider: &impl opentelemetry::metrics::MeterProvider, options: InstrumentationOptions) -> Self { + let meter = provider.meter_with_scope(options.scope.into()); + Self(meter) + } + pub fn inner(&self) -> &opentelemetry::metrics::Meter { &self.0 } +} + +impl Default for Meter { + fn default() -> Self { Self::new(&opentelemetry::metrics::noop::NoopMeterProvider::new(), InstrumentationOptions::default()) } +} diff --git a/templates/code/rust/tests/naming/expected/mod.rs b/templates/code/rust/tests/naming/expected/mod.rs new file mode 100644 index 0000000..dc64fde --- /dev/null +++ b/templates/code/rust/tests/naming/expected/mod.rs @@ -0,0 +1,8 @@ +// Generated by OpenTelemetry Weaver. +pub const SCHEMA_URL: &str = "https://example.com/schemas/naming/1.0.0"; +pub mod capsattr; + +pub mod scope; +pub mod options; +pub mod meter; +pub mod capsmetric; diff --git a/templates/code/rust/tests/naming/expected/options.rs b/templates/code/rust/tests/naming/expected/options.rs new file mode 100644 index 0000000..69a821a --- /dev/null +++ b/templates/code/rust/tests/naming/expected/options.rs @@ -0,0 +1,6 @@ +use super::scope::Scope; + +#[derive(Clone, Debug, Default)] +pub struct InstrumentationOptions { + pub scope: Scope, +} diff --git a/templates/code/rust/tests/naming/expected/scope.rs b/templates/code/rust/tests/naming/expected/scope.rs new file mode 100644 index 0000000..6f66e86 --- /dev/null +++ b/templates/code/rust/tests/naming/expected/scope.rs @@ -0,0 +1,13 @@ +#[derive(Clone, Debug, Default)] +pub struct Scope { + pub attributes: Vec<::opentelemetry::KeyValue>, +} + +impl From for ::opentelemetry::InstrumentationScope { + fn from(value: Scope) -> Self { + Self::builder("generated-semconv") + .with_schema_url(super::SCHEMA_URL) + .with_attributes(value.attributes) + .build() + } +} diff --git a/templates/code/rust/tests/naming/registry/manifest.yaml b/templates/code/rust/tests/naming/registry/manifest.yaml new file mode 100644 index 0000000..b9a8901 --- /dev/null +++ b/templates/code/rust/tests/naming/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: naming +schema_url: https://example.com/schemas/naming/1.0.0 diff --git a/templates/code/rust/tests/naming/registry/model.yaml b/templates/code/rust/tests/naming/registry/model.yaml new file mode 100644 index 0000000..6824a23 --- /dev/null +++ b/templates/code/rust/tests/naming/registry/model.yaml @@ -0,0 +1,30 @@ +file_format: definition/2 +attributes: + - key: caps.http2_STATUS.api_ID + type: string + stability: stable + brief: Synthetic identifier. + - key: caps.mode2_STATE + type: + members: + - id: HTTP2_ready_STATE + value: ready + stability: stable + brief: Synthetic state. + stability: stable + brief: Synthetic mode. +metrics: + - name: caps.http2_STATUS.count + instrument: counter + unit: "{item}" + requirement_level: recommended + stability: stable + brief: Synthetic count. + annotations: + code_generation: + metric_value_type: int + attributes: + - ref: caps.http2_STATUS.api_ID + requirement_level: required + - ref: caps.mode2_STATE + requirement_level: required diff --git a/templates/code/rust/tests/naming/test.rs b/templates/code/rust/tests/naming/test.rs new file mode 100644 index 0000000..2d31a00 --- /dev/null +++ b/templates/code/rust/tests/naming/test.rs @@ -0,0 +1,15 @@ +#![allow(non_camel_case_types, non_snake_case)] +use generated_semconv::{capsattr, capsmetric, meter}; +use opentelemetry::KeyValue; + +#[test] +fn names_preserve_capitalization_and_digits_in_attributes_and_signals() { + let identifier = capsattr::Http2STATUSApiIDAttr::from("value"); + assert_eq!( + KeyValue::from(identifier.clone()), + KeyValue::new("caps.http2_STATUS.api_ID", "value") + ); + let mode = capsattr::Mode2STATEAttr::HTTP2ReadySTATE; + let metric = capsmetric::Http2STATUSCountCounter::new(&meter::Meter::default()); + metric.add(1, identifier, mode); +} diff --git a/templates/code/rust/tests/no_namespace/expected/exceptionattr.rs b/templates/code/rust/tests/no_namespace/expected/exceptionattr.rs new file mode 100644 index 0000000..35411bd --- /dev/null +++ b/templates/code/rust/tests/no_namespace/expected/exceptionattr.rs @@ -0,0 +1,130 @@ +// Generated by OpenTelemetry Weaver. +/// The exception message. +#[derive(Debug, Clone, PartialEq)] +pub struct MessageAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl MessageAttr { + pub const KEY: &'static str = "exception.message"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for MessageAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for MessageAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for MessageAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for MessageAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for MessageAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: MessageAttr) -> Self { + ::opentelemetry::KeyValue::new(MessageAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: MessageAttr) -> Self { + value.0.into() + } +} + +/// The type of the exception. +#[derive(Debug, Clone, PartialEq)] +pub struct TypeAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl TypeAttr { + pub const KEY: &'static str = "exception.type"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for TypeAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for TypeAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for TypeAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for TypeAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TypeAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TypeAttr) -> Self { + ::opentelemetry::KeyValue::new(TypeAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TypeAttr) -> Self { + value.0.into() + } +} diff --git a/templates/code/rust/tests/no_namespace/expected/exceptionevent.rs b/templates/code/rust/tests/no_namespace/expected/exceptionevent.rs new file mode 100644 index 0000000..4123d2c --- /dev/null +++ b/templates/code/rust/tests/no_namespace/expected/exceptionevent.rs @@ -0,0 +1,41 @@ +#![allow(deprecated)] +use ::opentelemetry::logs::LogRecord; +use ::opentelemetry::trace::TraceContextExt; + +#[derive(Clone, Debug)] +pub enum EventOption { + Severity(::opentelemetry::logs::Severity), + Timestamp(::std::time::SystemTime), + Message(super::exceptionattr::MessageAttr), +} +/// A noteworthy exception occurred. +pub fn r#emit(context: &::opentelemetry::Context, logger: &super::logger::Logger, r#exception_type: super::exceptionattr::TypeAttr, options: impl IntoIterator) { + let mut severity = ::opentelemetry::logs::Severity::Info; + let mut timestamp = None; + let mut attributes = ::smallvec::SmallVec::<[EventOption; 1]>::new(); + for option in options { + match option { + EventOption::Severity(value) => severity = value, + EventOption::Timestamp(value) => timestamp = Some(value), + attribute => attributes.push(attribute), + } + } + if !logger.inner().event_enabled(severity, "", Some("exception")) { return; } + let _context_guard = context.clone().attach(); + let mut record = logger.inner().create_log_record(); + record.set_event_name("exception"); + record.set_severity_number(severity); + record.set_observed_timestamp(::std::time::SystemTime::now()); + if let Some(timestamp) = timestamp { record.set_timestamp(timestamp); } + let span = context.span(); + let span_context = span.span_context(); + if span_context.is_valid() { record.set_trace_context(span_context.trace_id(), span_context.span_id(), Some(span_context.trace_flags())); } + record.add_attribute(super::exceptionattr::TypeAttr::KEY, r#exception_type); + for option in attributes { + match option { + EventOption::Message(value) => record.add_attribute(super::exceptionattr::MessageAttr::KEY, value), + EventOption::Severity(_) | EventOption::Timestamp(_) => continue, + } + } + logger.inner().emit(record); +} diff --git a/templates/code/rust/tests/no_namespace/expected/heartbeatattr.rs b/templates/code/rust/tests/no_namespace/expected/heartbeatattr.rs new file mode 100644 index 0000000..b43d822 --- /dev/null +++ b/templates/code/rust/tests/no_namespace/expected/heartbeatattr.rs @@ -0,0 +1,106 @@ +// Generated by OpenTelemetry Weaver. +/// Sequence number of the heartbeat. +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct SequenceAttr(i64); + +#[allow(deprecated)] +impl SequenceAttr { + pub const KEY: &'static str = "heartbeat.sequence"; +} + +#[allow(deprecated)] +impl From for SequenceAttr { + #[inline] + fn from(value: i64) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for SequenceAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::std::fmt::Display::fmt(&self.0, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: SequenceAttr) -> Self { + ::opentelemetry::KeyValue::new(SequenceAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: SequenceAttr) -> Self { + value.0.into() + } +} + +/// Name of the component that sent the heartbeat. +#[derive(Debug, Clone, PartialEq)] +pub struct SourceAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl SourceAttr { + pub const KEY: &'static str = "heartbeat.source"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for SourceAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for SourceAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for SourceAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for SourceAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for SourceAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: SourceAttr) -> Self { + ::opentelemetry::KeyValue::new(SourceAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: SourceAttr) -> Self { + value.0.into() + } +} diff --git a/templates/code/rust/tests/no_namespace/expected/heartbeatspan.rs b/templates/code/rust/tests/no_namespace/expected/heartbeatspan.rs new file mode 100644 index 0000000..4b05f1f --- /dev/null +++ b/templates/code/rust/tests/no_namespace/expected/heartbeatspan.rs @@ -0,0 +1,77 @@ +#![allow(deprecated)] +use ::opentelemetry::trace::{TraceContextExt, Tracer}; + +#[derive(Clone, Debug)] +pub enum StartAttr { + Source(super::heartbeatattr::SourceAttr), +} + +impl From for ::opentelemetry::KeyValue { + fn from(value: StartAttr) -> Self { + match value { + StartAttr::Source(value) => ::opentelemetry::KeyValue::from(value), + } + } +} +#[derive(Clone, Debug)] +pub enum Attr { + Source(super::heartbeatattr::SourceAttr), +} + +impl From for StartAttr { + fn from(value: Attr) -> Self { + match value { + Attr::Source(value) => Self::Source(value), + } + } +} +#[derive(Clone, Debug, Default)] +pub struct Name(::std::borrow::Cow<'static, str>); + +impl From<&'static str> for Name { + fn from(value: &'static str) -> Self { Self(::std::borrow::Cow::Borrowed(value)) } +} + +impl From for Name { + fn from(value: String) -> Self { Self(::std::borrow::Cow::Owned(value)) } +} + +impl From<::std::borrow::Cow<'static, str>> for Name { + fn from(value: ::std::borrow::Cow<'static, str>) -> Self { Self(value) } +} +/// A periodic liveness signal. +pub struct Span { + span: S, +} + +impl Span { + pub fn into_context(self, parent: &::opentelemetry::Context) -> ::opentelemetry::Context + where S: Send + Sync + 'static, + { + parent.with_span(self.span) + } + pub fn span(&mut self) -> &mut S { &mut self.span } + pub fn end(&mut self) { ::opentelemetry::trace::Span::end(&mut self.span); } + pub fn end_with_timestamp(&mut self, timestamp: ::std::time::SystemTime) { ::opentelemetry::trace::Span::end_with_timestamp(&mut self.span, timestamp); } + pub fn record_error(&mut self, error: &dyn ::std::error::Error) { ::opentelemetry::trace::Span::record_error(&mut self.span, error); } + pub fn set_status(&mut self, status: ::opentelemetry::trace::Status) { ::opentelemetry::trace::Span::set_status(&mut self.span, status); } + pub fn set_attributes(&mut self, attributes: impl IntoIterator) { + for attribute in attributes { ::opentelemetry::trace::Span::set_attribute(&mut self.span, ::opentelemetry::KeyValue::from(StartAttr::from(attribute))); } + } +} + +impl Default for Span { + fn default() -> Self { + Self { span: super::tracer::Tracer::default().inner().start("heartbeat") } + } +} + +pub fn r#start(context: &::opentelemetry::Context, tracer: &super::tracer::Tracer, name: Name, r#heartbeat_sequence: super::heartbeatattr::SequenceAttr, options: impl IntoIterator) -> Span { + let name = if name.0.is_empty() { ::std::borrow::Cow::Borrowed("heartbeat") } else { name.0 }; + let attributes: [::opentelemetry::KeyValue; 1] = [::opentelemetry::KeyValue::from(r#heartbeat_sequence)]; + let span = tracer.inner().span_builder(name) + .with_kind(::opentelemetry::trace::SpanKind::Internal) + .with_attributes(attributes.into_iter().chain(options.into_iter().map(::opentelemetry::KeyValue::from))) + .start_with_context(tracer.inner(), context); + Span { span } +} diff --git a/templates/code/rust/tests/no_namespace/expected/hostattr.rs b/templates/code/rust/tests/no_namespace/expected/hostattr.rs new file mode 100644 index 0000000..b8c90eb --- /dev/null +++ b/templates/code/rust/tests/no_namespace/expected/hostattr.rs @@ -0,0 +1,130 @@ +// Generated by OpenTelemetry Weaver. +/// Unique identifier of the host. +#[derive(Debug, Clone, PartialEq)] +pub struct IdAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl IdAttr { + pub const KEY: &'static str = "host.id"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for IdAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for IdAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for IdAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for IdAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for IdAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: IdAttr) -> Self { + ::opentelemetry::KeyValue::new(IdAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: IdAttr) -> Self { + value.0.into() + } +} + +/// Name of the host. +#[derive(Debug, Clone, PartialEq)] +pub struct NameAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl NameAttr { + pub const KEY: &'static str = "host.name"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for NameAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for NameAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for NameAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for NameAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for NameAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: NameAttr) -> Self { + ::opentelemetry::KeyValue::new(NameAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: NameAttr) -> Self { + value.0.into() + } +} diff --git a/templates/code/rust/tests/no_namespace/expected/hostentity.rs b/templates/code/rust/tests/no_namespace/expected/hostentity.rs new file mode 100644 index 0000000..49100c6 --- /dev/null +++ b/templates/code/rust/tests/no_namespace/expected/hostentity.rs @@ -0,0 +1,48 @@ +// Code generated by weaver. DO NOT EDIT. +#![allow(deprecated)] +#[derive(Clone, Debug)] +pub enum EntityAttr { + Name(super::hostattr::NameAttr), +} + +impl From for ::opentelemetry::KeyValue { + fn from(value: EntityAttr) -> Self { + match value { + EntityAttr::Name(value) => ::opentelemetry::KeyValue::from(value), + } + } +} +/// A physical or virtual machine. +/// Identifies the entity: `host.id`. +/// Describes the entity: `host.name`. +#[derive(Clone, Debug)] +pub struct Entity { + r#host_id: super::hostattr::IdAttr, + options: ::smallvec::SmallVec<[EntityAttr; 1]>, +} + +impl Entity { + pub fn new(r#host_id: super::hostattr::IdAttr, options: impl IntoIterator) -> Self { + Self { + r#host_id, + options: options.into_iter().collect(), + } + } + + pub fn attributes(&self) -> impl Iterator + '_ { + ::std::iter::empty() + .chain(::std::iter::once_with(move || ::opentelemetry::KeyValue::from(self.r#host_id.clone()))) + .chain(self.options.iter().cloned().map(::opentelemetry::KeyValue::from)) + } +} + +impl From for ::opentelemetry_sdk::Resource { + fn from(value: Entity) -> Self { + let attributes = [ + ::opentelemetry::KeyValue::from(value.r#host_id), + ].into_iter().chain(value.options.into_iter().map(::opentelemetry::KeyValue::from)); + ::opentelemetry_sdk::Resource::builder_empty() + .with_schema_url(attributes, super::SCHEMA_URL) + .build() + } +} diff --git a/templates/code/rust/tests/no_namespace/expected/logger.rs b/templates/code/rust/tests/no_namespace/expected/logger.rs new file mode 100644 index 0000000..78bcc02 --- /dev/null +++ b/templates/code/rust/tests/no_namespace/expected/logger.rs @@ -0,0 +1,16 @@ +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Logger::Logger>(L); + +impl Logger { + pub fn new>(provider: &P, options: InstrumentationOptions) -> Self { + let logger = provider.logger_with_scope(options.scope.into()); + Self(logger) + } + pub fn inner(&self) -> &L { &self.0 } +} + +impl Default for Logger { + fn default() -> Self { Self::new(&opentelemetry::logs::NoopLoggerProvider::new(), InstrumentationOptions::default()) } +} diff --git a/templates/code/rust/tests/no_namespace/expected/mod.rs b/templates/code/rust/tests/no_namespace/expected/mod.rs new file mode 100644 index 0000000..fc6773d --- /dev/null +++ b/templates/code/rust/tests/no_namespace/expected/mod.rs @@ -0,0 +1,13 @@ +// Generated by OpenTelemetry Weaver. +pub const SCHEMA_URL: &str = "https://example.com/schemas/no_namespace/1.0.0"; +pub mod exceptionattr; +pub mod heartbeatattr; +pub mod hostattr; + +pub mod scope; +pub mod options; +pub mod tracer; +pub mod logger; +pub mod exceptionevent; +pub mod heartbeatspan; +pub mod hostentity; diff --git a/templates/code/rust/tests/no_namespace/expected/options.rs b/templates/code/rust/tests/no_namespace/expected/options.rs new file mode 100644 index 0000000..69a821a --- /dev/null +++ b/templates/code/rust/tests/no_namespace/expected/options.rs @@ -0,0 +1,6 @@ +use super::scope::Scope; + +#[derive(Clone, Debug, Default)] +pub struct InstrumentationOptions { + pub scope: Scope, +} diff --git a/templates/code/rust/tests/no_namespace/expected/scope.rs b/templates/code/rust/tests/no_namespace/expected/scope.rs new file mode 100644 index 0000000..6f66e86 --- /dev/null +++ b/templates/code/rust/tests/no_namespace/expected/scope.rs @@ -0,0 +1,13 @@ +#[derive(Clone, Debug, Default)] +pub struct Scope { + pub attributes: Vec<::opentelemetry::KeyValue>, +} + +impl From for ::opentelemetry::InstrumentationScope { + fn from(value: Scope) -> Self { + Self::builder("generated-semconv") + .with_schema_url(super::SCHEMA_URL) + .with_attributes(value.attributes) + .build() + } +} diff --git a/templates/code/rust/tests/no_namespace/expected/tracer.rs b/templates/code/rust/tests/no_namespace/expected/tracer.rs new file mode 100644 index 0000000..d684a8d --- /dev/null +++ b/templates/code/rust/tests/no_namespace/expected/tracer.rs @@ -0,0 +1,16 @@ +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Tracer(T); + +impl Tracer { + pub fn new>(provider: &P, options: InstrumentationOptions) -> Self { + let tracer = provider.tracer_with_scope(options.scope.into()); + Self(tracer) + } + pub fn inner(&self) -> &T { &self.0 } +} + +impl Default for Tracer { + fn default() -> Self { Self::new(&opentelemetry::trace::noop::NoopTracerProvider::new(), InstrumentationOptions::default()) } +} diff --git a/templates/code/rust/tests/no_namespace/registry/manifest.yaml b/templates/code/rust/tests/no_namespace/registry/manifest.yaml new file mode 100644 index 0000000..af4761c --- /dev/null +++ b/templates/code/rust/tests/no_namespace/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: no_namespace +schema_url: https://example.com/schemas/no_namespace/1.0.0 diff --git a/templates/code/rust/tests/no_namespace/registry/model.yaml b/templates/code/rust/tests/no_namespace/registry/model.yaml new file mode 100644 index 0000000..d9cc883 --- /dev/null +++ b/templates/code/rust/tests/no_namespace/registry/model.yaml @@ -0,0 +1,65 @@ +file_format: definition/2 +attributes: + - key: exception.type + type: string + stability: stable + brief: The type of the exception. + examples: ["ValueError"] + - key: exception.message + type: string + stability: stable + brief: The exception message. + examples: ["division by zero"] + - key: heartbeat.sequence + type: int + stability: stable + brief: Sequence number of the heartbeat. + examples: 1 + - key: heartbeat.source + type: string + stability: stable + brief: Name of the component that sent the heartbeat. + examples: ["worker-1"] + - key: host.id + type: string + stability: stable + brief: Unique identifier of the host. + examples: ["h-1"] + - key: host.name + type: string + stability: stable + brief: Name of the host. + examples: ["worker-1"] +entities: + - type: host + requirement_level: recommended + stability: stable + brief: A physical or virtual machine. + identity: + - ref: host.id + requirement_level: required + description: + - ref: host.name +events: + - name: exception + requirement_level: recommended + stability: stable + brief: A noteworthy exception occurred. + attributes: + - ref: exception.type + requirement_level: required + - ref: exception.message + requirement_level: recommended +spans: + - type: heartbeat + kind: internal + requirement_level: recommended + stability: stable + brief: A periodic liveness signal. + name: + note: "`heartbeat`" + attributes: + - ref: heartbeat.sequence + requirement_level: required + - ref: heartbeat.source + requirement_level: recommended diff --git a/templates/code/rust/tests/no_namespace/test.rs b/templates/code/rust/tests/no_namespace/test.rs new file mode 100644 index 0000000..cbc0731 --- /dev/null +++ b/templates/code/rust/tests/no_namespace/test.rs @@ -0,0 +1,60 @@ +use generated_semconv::{ + exceptionattr, exceptionevent, heartbeatattr, heartbeatspan, logger, + options::InstrumentationOptions, tracer, +}; +use opentelemetry::logs::{AnyValue, Severity}; +use opentelemetry_sdk::logs::{InMemoryLogExporter, SdkLoggerProvider}; +use opentelemetry_sdk::trace::{InMemorySpanExporter, SdkTracerProvider}; + +#[test] +fn empty_short_names_expose_bare_functions_and_unambiguous_options() { + let context = opentelemetry::Context::new(); + let logs = InMemoryLogExporter::default(); + let logger_provider = SdkLoggerProvider::builder() + .with_simple_exporter(logs.clone()) + .build(); + let logger = logger::Logger::new(&logger_provider, InstrumentationOptions::default()); + exceptionevent::emit( + &context, + &logger, + exceptionattr::TypeAttr::from("ValueError"), + [ + exceptionevent::EventOption::Message(exceptionattr::MessageAttr::from("failed")), + exceptionevent::EventOption::Severity(Severity::Warn), + ], + ); + logger_provider.force_flush().unwrap(); + let records = logs.get_emitted_logs().unwrap(); + assert_eq!(records.len(), 1); + assert_eq!(records[0].record.event_name(), Some("exception")); + assert_eq!(records[0].record.severity_number(), Some(Severity::Warn)); + assert!(records[0].record.attributes_iter().any(|(key, value)| { + key.as_str() == "exception.message" && value == &AnyValue::String("failed".into()) + })); + + let spans = InMemorySpanExporter::default(); + let tracer_provider = SdkTracerProvider::builder() + .with_simple_exporter(spans.clone()) + .build(); + let tracer = tracer::Tracer::new(&tracer_provider, InstrumentationOptions::default()); + let mut span = heartbeatspan::start( + &context, + &tracer, + heartbeatspan::Name::default(), + heartbeatattr::SequenceAttr::from(7), + [heartbeatspan::StartAttr::Source( + heartbeatattr::SourceAttr::from("worker"), + )], + ); + span.end(); + tracer_provider.force_flush().unwrap(); + let spans = spans.get_finished_spans().unwrap(); + assert_eq!(spans.len(), 1); + assert_eq!(spans[0].name, "heartbeat"); + assert!(spans[0] + .attributes + .contains(&opentelemetry::KeyValue::new("heartbeat.sequence", 7))); + assert!(spans[0] + .attributes + .contains(&opentelemetry::KeyValue::new("heartbeat.source", "worker"))); +} diff --git a/templates/code/rust/tests/params/expected/billingattr.rs b/templates/code/rust/tests/params/expected/billingattr.rs new file mode 100644 index 0000000..c00f2e5 --- /dev/null +++ b/templates/code/rust/tests/params/expected/billingattr.rs @@ -0,0 +1,130 @@ +// Generated by OpenTelemetry Weaver. +/// Identifier of the invoice. +#[derive(Debug, Clone, PartialEq)] +pub struct InvoiceIdAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl InvoiceIdAttr { + pub const KEY: &'static str = "acme.billing.invoice.id"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for InvoiceIdAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for InvoiceIdAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for InvoiceIdAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for InvoiceIdAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for InvoiceIdAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: InvoiceIdAttr) -> Self { + ::opentelemetry::KeyValue::new(InvoiceIdAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: InvoiceIdAttr) -> Self { + value.0.into() + } +} + +/// ISO 4217 currency code of the amount. +#[derive(Debug, Clone, PartialEq)] +pub struct CurrencyAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl CurrencyAttr { + pub const KEY: &'static str = "billing.currency"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for CurrencyAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for CurrencyAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for CurrencyAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for CurrencyAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for CurrencyAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: CurrencyAttr) -> Self { + ::opentelemetry::KeyValue::new(CurrencyAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: CurrencyAttr) -> Self { + value.0.into() + } +} diff --git a/templates/code/rust/tests/params/expected/billingevent.rs b/templates/code/rust/tests/params/expected/billingevent.rs new file mode 100644 index 0000000..d8ecae9 --- /dev/null +++ b/templates/code/rust/tests/params/expected/billingevent.rs @@ -0,0 +1,32 @@ +#![allow(deprecated)] +use ::opentelemetry::logs::LogRecord; +use ::opentelemetry::trace::TraceContextExt; + +#[derive(Clone, Debug)] +pub enum InvoiceIssuedOption { + Severity(::opentelemetry::logs::Severity), + Timestamp(::std::time::SystemTime), +} +/// An invoice was issued. +pub fn r#emit_invoice_issued(context: &::opentelemetry::Context, logger: &super::logger::Logger, r#billing_invoice_id: super::billingattr::InvoiceIdAttr, options: impl IntoIterator) { + let mut severity = ::opentelemetry::logs::Severity::Info; + let mut timestamp = None; + for option in options { + match option { + InvoiceIssuedOption::Severity(value) => severity = value, + InvoiceIssuedOption::Timestamp(value) => timestamp = Some(value), + } + } + if !logger.inner().event_enabled(severity, "", Some("acme.billing.invoice.issued")) { return; } + let _context_guard = context.clone().attach(); + let mut record = logger.inner().create_log_record(); + record.set_event_name("acme.billing.invoice.issued"); + record.set_severity_number(severity); + record.set_observed_timestamp(::std::time::SystemTime::now()); + if let Some(timestamp) = timestamp { record.set_timestamp(timestamp); } + let span = context.span(); + let span_context = span.span_context(); + if span_context.is_valid() { record.set_trace_context(span_context.trace_id(), span_context.span_id(), Some(span_context.trace_flags())); } + record.add_attribute(super::billingattr::InvoiceIdAttr::KEY, r#billing_invoice_id); + logger.inner().emit(record); +} diff --git a/templates/code/rust/tests/params/expected/billingmetric.rs b/templates/code/rust/tests/params/expected/billingmetric.rs new file mode 100644 index 0000000..7b20973 --- /dev/null +++ b/templates/code/rust/tests/params/expected/billingmetric.rs @@ -0,0 +1,40 @@ +#![allow(deprecated)] +#[derive(Clone, Debug)] +pub enum InvoiceAmountHistogramAttr { + Currency(super::billingattr::CurrencyAttr), + RequestUrl(super::myappattr::RequestUrlAttr), +} + +impl From for ::opentelemetry::KeyValue { + fn from(value: InvoiceAmountHistogramAttr) -> Self { + match value { + InvoiceAmountHistogramAttr::Currency(value) => ::opentelemetry::KeyValue::from(value), + InvoiceAmountHistogramAttr::RequestUrl(value) => ::opentelemetry::KeyValue::from(value), + } + } +} +/// Amount of each issued invoice. +#[derive(Clone)] +pub struct InvoiceAmountHistogram { + instrument: ::opentelemetry::metrics::Histogram, +} + +impl InvoiceAmountHistogram { + pub fn new(meter: &super::meter::Meter) -> Self { + let instrument = meter.inner().f64_histogram("acme.billing.invoice.amount") + .with_description("Amount of each issued invoice.") + .with_unit("{currency_unit}") + .build(); + Self { instrument } + } + pub fn record(&self, value: f64, r#billing_invoice_id: super::billingattr::InvoiceIdAttr, options: impl IntoIterator) { + let mut attributes = ::smallvec::SmallVec::<[::opentelemetry::KeyValue; 3]>::new(); + attributes.extend([::opentelemetry::KeyValue::from(r#billing_invoice_id)]); + attributes.extend(options.into_iter().map(::opentelemetry::KeyValue::from)); + self.instrument.record(value, &attributes); + } +} + +impl Default for InvoiceAmountHistogram { + fn default() -> Self { Self::new(&super::meter::Meter::default()) } +} diff --git a/templates/code/rust/tests/params/expected/billingspan.rs b/templates/code/rust/tests/params/expected/billingspan.rs new file mode 100644 index 0000000..d745290 --- /dev/null +++ b/templates/code/rust/tests/params/expected/billingspan.rs @@ -0,0 +1,39 @@ +#![allow(deprecated)] +use ::opentelemetry::trace::{TraceContextExt, Tracer}; +use ::std::fmt::Write; + +/// An invoice is being issued. +pub struct InvoiceIssueSpan { + span: S, +} + +impl InvoiceIssueSpan { + pub fn into_context(self, parent: &::opentelemetry::Context) -> ::opentelemetry::Context + where S: Send + Sync + 'static, + { + parent.with_span(self.span) + } + pub fn span(&mut self) -> &mut S { &mut self.span } + pub fn end(&mut self) { ::opentelemetry::trace::Span::end(&mut self.span); } + pub fn end_with_timestamp(&mut self, timestamp: ::std::time::SystemTime) { ::opentelemetry::trace::Span::end_with_timestamp(&mut self.span, timestamp); } + pub fn record_error(&mut self, error: &dyn ::std::error::Error) { ::opentelemetry::trace::Span::record_error(&mut self.span, error); } + pub fn set_status(&mut self, status: ::opentelemetry::trace::Status) { ::opentelemetry::trace::Span::set_status(&mut self.span, status); } +} + +impl Default for InvoiceIssueSpan { + fn default() -> Self { + Self { span: super::tracer::Tracer::default().inner().start("acme.billing.invoice.issue") } + } +} + +pub fn r#start_invoice_issue(context: &::opentelemetry::Context, tracer: &super::tracer::Tracer, r#billing_invoice_id: super::billingattr::InvoiceIdAttr) -> InvoiceIssueSpan { + let mut name = String::with_capacity("invoice ".len() + ::std::convert::AsRef::::as_ref(&r#billing_invoice_id).len()); + name.push_str("invoice "); + write!(&mut name, "{}", r#billing_invoice_id).expect("writing a span name to String cannot fail"); + let attributes: [::opentelemetry::KeyValue; 1] = [::opentelemetry::KeyValue::from(r#billing_invoice_id)]; + let span = tracer.inner().span_builder(name) + .with_kind(::opentelemetry::trace::SpanKind::Internal) + .with_attributes(attributes) + .start_with_context(tracer.inner(), context); + InvoiceIssueSpan { span } +} diff --git a/templates/code/rust/tests/params/expected/logger.rs b/templates/code/rust/tests/params/expected/logger.rs new file mode 100644 index 0000000..78bcc02 --- /dev/null +++ b/templates/code/rust/tests/params/expected/logger.rs @@ -0,0 +1,16 @@ +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Logger::Logger>(L); + +impl Logger { + pub fn new>(provider: &P, options: InstrumentationOptions) -> Self { + let logger = provider.logger_with_scope(options.scope.into()); + Self(logger) + } + pub fn inner(&self) -> &L { &self.0 } +} + +impl Default for Logger { + fn default() -> Self { Self::new(&opentelemetry::logs::NoopLoggerProvider::new(), InstrumentationOptions::default()) } +} diff --git a/templates/code/rust/tests/params/expected/meter.rs b/templates/code/rust/tests/params/expected/meter.rs new file mode 100644 index 0000000..a330bd2 --- /dev/null +++ b/templates/code/rust/tests/params/expected/meter.rs @@ -0,0 +1,16 @@ +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Meter(opentelemetry::metrics::Meter); + +impl Meter { + pub fn new(provider: &impl opentelemetry::metrics::MeterProvider, options: InstrumentationOptions) -> Self { + let meter = provider.meter_with_scope(options.scope.into()); + Self(meter) + } + pub fn inner(&self) -> &opentelemetry::metrics::Meter { &self.0 } +} + +impl Default for Meter { + fn default() -> Self { Self::new(&opentelemetry::metrics::noop::NoopMeterProvider::new(), InstrumentationOptions::default()) } +} diff --git a/templates/code/rust/tests/params/expected/mod.rs b/templates/code/rust/tests/params/expected/mod.rs new file mode 100644 index 0000000..bc8ad5f --- /dev/null +++ b/templates/code/rust/tests/params/expected/mod.rs @@ -0,0 +1,16 @@ +//! Typed telemetry for a synthetic registry. +// Generated by OpenTelemetry Weaver. +// Source: model/ +// Regenerate: mise run generate +pub const SCHEMA_URL: &str = "https://example.com/schemas/params/1.0.0"; +pub mod billingattr; +pub mod myappattr; + +pub mod scope; +pub mod options; +pub mod meter; +pub mod tracer; +pub mod logger; +pub mod billingevent; +pub mod billingmetric; +pub mod billingspan; diff --git a/templates/code/rust/tests/params/expected/myappattr.rs b/templates/code/rust/tests/params/expected/myappattr.rs new file mode 100644 index 0000000..a771ccb --- /dev/null +++ b/templates/code/rust/tests/params/expected/myappattr.rs @@ -0,0 +1,65 @@ +// Generated by OpenTelemetry Weaver. +/// URL the request was sent to. +#[derive(Debug, Clone, PartialEq)] +pub struct RequestUrlAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl RequestUrlAttr { + pub const KEY: &'static str = "myapp.request.url"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for RequestUrlAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for RequestUrlAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for RequestUrlAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for RequestUrlAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for RequestUrlAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: RequestUrlAttr) -> Self { + ::opentelemetry::KeyValue::new(RequestUrlAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: RequestUrlAttr) -> Self { + value.0.into() + } +} diff --git a/templates/code/rust/tests/params/expected/options.rs b/templates/code/rust/tests/params/expected/options.rs new file mode 100644 index 0000000..69a821a --- /dev/null +++ b/templates/code/rust/tests/params/expected/options.rs @@ -0,0 +1,6 @@ +use super::scope::Scope; + +#[derive(Clone, Debug, Default)] +pub struct InstrumentationOptions { + pub scope: Scope, +} diff --git a/templates/code/rust/tests/params/expected/scope.rs b/templates/code/rust/tests/params/expected/scope.rs new file mode 100644 index 0000000..bb5d98e --- /dev/null +++ b/templates/code/rust/tests/params/expected/scope.rs @@ -0,0 +1,14 @@ +#[derive(Clone, Debug, Default)] +pub struct Scope { + pub attributes: Vec<::opentelemetry::KeyValue>, +} + +impl From for ::opentelemetry::InstrumentationScope { + fn from(value: Scope) -> Self { + Self::builder("synthetic-billing") + .with_version("2.3.4") + .with_schema_url(super::SCHEMA_URL) + .with_attributes(value.attributes) + .build() + } +} diff --git a/templates/code/rust/tests/params/expected/tracer.rs b/templates/code/rust/tests/params/expected/tracer.rs new file mode 100644 index 0000000..d684a8d --- /dev/null +++ b/templates/code/rust/tests/params/expected/tracer.rs @@ -0,0 +1,16 @@ +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Tracer(T); + +impl Tracer { + pub fn new>(provider: &P, options: InstrumentationOptions) -> Self { + let tracer = provider.tracer_with_scope(options.scope.into()); + Self(tracer) + } + pub fn inner(&self) -> &T { &self.0 } +} + +impl Default for Tracer { + fn default() -> Self { Self::new(&opentelemetry::trace::noop::NoopTracerProvider::new(), InstrumentationOptions::default()) } +} diff --git a/templates/code/rust/tests/params/params.yaml b/templates/code/rust/tests/params/params.yaml new file mode 100644 index 0000000..7a7679d --- /dev/null +++ b/templates/code/rust/tests/params/params.yaml @@ -0,0 +1,8 @@ +params: + instrumentation_name: synthetic-billing + instrumentation_version: "2.3.4" + module_description: Typed telemetry for a synthetic registry. + header_source: model/ + regenerate_command: mise run generate + vendor_prefixes: [acme] + exclude_deprecated: true diff --git a/templates/code/rust/tests/params/registry/manifest.yaml b/templates/code/rust/tests/params/registry/manifest.yaml new file mode 100644 index 0000000..afaa82c --- /dev/null +++ b/templates/code/rust/tests/params/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: params +schema_url: https://example.com/schemas/params/1.0.0 diff --git a/templates/code/rust/tests/params/registry/model.yaml b/templates/code/rust/tests/params/registry/model.yaml new file mode 100644 index 0000000..889205a --- /dev/null +++ b/templates/code/rust/tests/params/registry/model.yaml @@ -0,0 +1,61 @@ +file_format: definition/2 +attributes: + - key: acme.billing.invoice.id + type: string + stability: stable + brief: Identifier of the invoice. + examples: ["inv_1"] + - key: billing.currency + type: string + stability: stable + brief: ISO 4217 currency code of the amount. + examples: ["EUR"] + - key: acme.billing.legacy_code + type: string + stability: development + brief: Code used before invoices had identifiers. + examples: ["X1"] + deprecated: + reason: renamed + renamed_to: acme.billing.invoice.id + - key: myapp.request.url + type: string + stability: stable + brief: URL the request was sent to. + examples: ["https://example.com/"] +metrics: + - name: acme.billing.invoice.amount + requirement_level: recommended + instrument: histogram + unit: "{currency_unit}" + stability: stable + brief: Amount of each issued invoice. + annotations: + code_generation: + metric_value_type: double + attributes: + - ref: acme.billing.invoice.id + requirement_level: required + - ref: billing.currency + requirement_level: recommended + - ref: myapp.request.url + requirement_level: recommended +spans: + - type: acme.billing.invoice.issue + kind: internal + stability: stable + requirement_level: recommended + brief: An invoice is being issued. + name: + note: "`invoice {acme.billing.invoice.id}`" + attributes: + - ref: acme.billing.invoice.id + requirement_level: required +events: + - name: acme.billing.invoice.issued + stability: stable + requirement_level: recommended + brief: An invoice was issued. + attributes: + - ref: acme.billing.invoice.id + requirement_level: required diff --git a/templates/code/rust/tests/params/test.rs b/templates/code/rust/tests/params/test.rs new file mode 100644 index 0000000..b6ada57 --- /dev/null +++ b/templates/code/rust/tests/params/test.rs @@ -0,0 +1,79 @@ +use generated_semconv::{ + billingattr, billingevent, billingmetric, billingspan, logger, meter, + options::InstrumentationOptions, scope::Scope, tracer, SCHEMA_URL, +}; +use opentelemetry::{InstrumentationScope, KeyValue}; +use opentelemetry_sdk::logs::{InMemoryLogExporter, SdkLoggerProvider}; +use opentelemetry_sdk::metrics::{InMemoryMetricExporter, PeriodicReader, SdkMeterProvider}; +use opentelemetry_sdk::trace::{InMemorySpanExporter, SdkTracerProvider}; +use std::time::Duration; + +fn options() -> InstrumentationOptions { + InstrumentationOptions { + scope: Scope { + attributes: vec![KeyValue::new("scope.kind", "configured")], + }, + } +} + +fn configured_identity(scope: &InstrumentationScope) { + assert_eq!(scope.name(), "synthetic-billing"); + assert_eq!(scope.version(), Some("2.3.4")); + assert_eq!(scope.schema_url(), Some(SCHEMA_URL)); + assert_eq!( + scope.attributes().collect::>(), + [&KeyValue::new("scope.kind", "configured")] + ); +} + +#[test] +fn configured_identity_is_shared_by_metrics_spans_and_events() { + let metric_exporter = InMemoryMetricExporter::default(); + let metric_provider = SdkMeterProvider::builder() + .with_reader( + PeriodicReader::builder(metric_exporter.clone()) + .with_interval(Duration::from_secs(3600)) + .build(), + ) + .build(); + billingmetric::InvoiceAmountHistogram::new(&meter::Meter::new(&metric_provider, options())) + .record(12.5, billingattr::InvoiceIdAttr::from("invoice-1"), []); + metric_provider.force_flush().unwrap(); + let metrics = metric_exporter.get_finished_metrics().unwrap(); + configured_identity(metrics[0].scope_metrics().next().unwrap().scope()); + + let span_exporter = InMemorySpanExporter::default(); + let span_provider = SdkTracerProvider::builder() + .with_simple_exporter(span_exporter.clone()) + .build(); + let tracer = tracer::Tracer::new(&span_provider, options()); + let context = opentelemetry::Context::new(); + let mut span = billingspan::start_invoice_issue( + &context, + &tracer, + billingattr::InvoiceIdAttr::from("invoice-1"), + ); + span.end(); + span_provider.force_flush().unwrap(); + let spans = span_exporter.get_finished_spans().unwrap(); + assert_eq!(spans[0].name, "invoice invoice-1"); + configured_identity(&spans[0].instrumentation_scope); + + let log_exporter = InMemoryLogExporter::default(); + let log_provider = SdkLoggerProvider::builder() + .with_simple_exporter(log_exporter.clone()) + .build(); + billingevent::emit_invoice_issued( + &context, + &logger::Logger::new(&log_provider, options()), + billingattr::InvoiceIdAttr::from("invoice-1"), + [], + ); + log_provider.force_flush().unwrap(); + let logs = log_exporter.get_emitted_logs().unwrap(); + assert_eq!( + logs[0].record.event_name(), + Some("acme.billing.invoice.issued") + ); + configured_identity(&logs[0].instrumentation); +} diff --git a/templates/code/rust/tests/spans/compile-fail/missing_required.expected-error.txt b/templates/code/rust/tests/spans/compile-fail/missing_required.expected-error.txt new file mode 100644 index 0000000..3d7bf5d --- /dev/null +++ b/templates/code/rust/tests/spans/compile-fail/missing_required.expected-error.txt @@ -0,0 +1 @@ +this function takes 5 arguments but 3 arguments were supplied diff --git a/templates/code/rust/tests/spans/compile-fail/missing_required.rs b/templates/code/rust/tests/spans/compile-fail/missing_required.rs new file mode 100644 index 0000000..8cb0571 --- /dev/null +++ b/templates/code/rust/tests/spans/compile-fail/missing_required.rs @@ -0,0 +1,8 @@ +use generated_semconv::{myappspan, tracer}; +fn main() { + myappspan::start_task_dispatch( + &opentelemetry::Context::new(), + &tracer::Tracer::default(), + [], + ); +} diff --git a/templates/code/rust/tests/spans/compile-fail/sampling_attribute_late.expected-error.txt b/templates/code/rust/tests/spans/compile-fail/sampling_attribute_late.expected-error.txt new file mode 100644 index 0000000..ab8b948 --- /dev/null +++ b/templates/code/rust/tests/spans/compile-fail/sampling_attribute_late.expected-error.txt @@ -0,0 +1 @@ +expected `TaskDispatchAttr`, found `TaskDispatchStartAttr` diff --git a/templates/code/rust/tests/spans/compile-fail/sampling_attribute_late.rs b/templates/code/rust/tests/spans/compile-fail/sampling_attribute_late.rs new file mode 100644 index 0000000..7a50d5b --- /dev/null +++ b/templates/code/rust/tests/spans/compile-fail/sampling_attribute_late.rs @@ -0,0 +1,7 @@ +use generated_semconv::{myappattr, myappspan}; +fn main() { + let mut span = myappspan::TaskDispatchSpan::default(); + span.set_attributes([myappspan::TaskDispatchStartAttr::TaskId( + myappattr::TaskIdAttr::from("task-1"), + )]); +} diff --git a/templates/code/rust/tests/spans/expected/mod.rs b/templates/code/rust/tests/spans/expected/mod.rs new file mode 100644 index 0000000..2a8aae8 --- /dev/null +++ b/templates/code/rust/tests/spans/expected/mod.rs @@ -0,0 +1,10 @@ +// Generated by OpenTelemetry Weaver. +pub const SCHEMA_URL: &str = "https://example.com/schemas/spans/1.0.0"; +pub mod myappattr; +pub mod workerattr; + +pub mod scope; +pub mod options; +pub mod tracer; +pub mod myappspan; +pub mod workerspan; diff --git a/templates/code/rust/tests/spans/expected/myappattr.rs b/templates/code/rust/tests/spans/expected/myappattr.rs new file mode 100644 index 0000000..f7c83c4 --- /dev/null +++ b/templates/code/rust/tests/spans/expected/myappattr.rs @@ -0,0 +1,496 @@ +// Generated by OpenTelemetry Weaver. +/// Synthetic naming value. +#[derive(Debug, Clone, PartialEq)] +pub struct ArrayFlagsAttr(Vec); + +#[allow(deprecated)] +impl ArrayFlagsAttr { + pub const KEY: &'static str = "myapp.array.flags"; +} + +#[allow(deprecated)] +impl From> for ArrayFlagsAttr { + #[inline] + fn from(value: Vec) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl AsRef<[bool]> for ArrayFlagsAttr { + #[inline] + fn as_ref(&self) -> &[bool] { + &self.0 + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for ArrayFlagsAttr { + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str("[")?; + for (index, value) in self.0.iter().enumerate() { + if index != 0 { + formatter.write_str(",")?; + } + ::std::fmt::Display::fmt(value, formatter)?; + } + formatter.write_str("]") + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: ArrayFlagsAttr) -> Self { + ::opentelemetry::KeyValue::new(ArrayFlagsAttr::KEY, ::opentelemetry::Value::Array(::opentelemetry::Array::Bool(value.0))) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: ArrayFlagsAttr) -> Self { + value.0.into_iter().collect::<::opentelemetry::logs::AnyValue>() + } +} + +/// Synthetic naming value. +#[derive(Debug, Clone, PartialEq)] +pub struct ArrayIndicesAttr(Vec); + +#[allow(deprecated)] +impl ArrayIndicesAttr { + pub const KEY: &'static str = "myapp.array.indices"; +} + +#[allow(deprecated)] +impl From> for ArrayIndicesAttr { + #[inline] + fn from(value: Vec) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl AsRef<[i64]> for ArrayIndicesAttr { + #[inline] + fn as_ref(&self) -> &[i64] { + &self.0 + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for ArrayIndicesAttr { + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str("[")?; + for (index, value) in self.0.iter().enumerate() { + if index != 0 { + formatter.write_str(",")?; + } + ::std::fmt::Display::fmt(value, formatter)?; + } + formatter.write_str("]") + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: ArrayIndicesAttr) -> Self { + ::opentelemetry::KeyValue::new(ArrayIndicesAttr::KEY, ::opentelemetry::Value::Array(::opentelemetry::Array::I64(value.0))) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: ArrayIndicesAttr) -> Self { + value.0.into_iter().collect::<::opentelemetry::logs::AnyValue>() + } +} + +/// Synthetic naming value. +#[derive(Debug, Clone, PartialEq)] +pub struct ArraySamplesAttr(Vec); + +#[allow(deprecated)] +impl ArraySamplesAttr { + pub const KEY: &'static str = "myapp.array.samples"; +} + +#[allow(deprecated)] +impl From> for ArraySamplesAttr { + #[inline] + fn from(value: Vec) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl AsRef<[f64]> for ArraySamplesAttr { + #[inline] + fn as_ref(&self) -> &[f64] { + &self.0 + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for ArraySamplesAttr { + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str("[")?; + for (index, value) in self.0.iter().enumerate() { + if index != 0 { + formatter.write_str(",")?; + } + ::std::fmt::Display::fmt(value, formatter)?; + } + formatter.write_str("]") + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: ArraySamplesAttr) -> Self { + ::opentelemetry::KeyValue::new(ArraySamplesAttr::KEY, ::opentelemetry::Value::Array(::opentelemetry::Array::F64(value.0))) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: ArraySamplesAttr) -> Self { + value.0.into_iter().collect::<::opentelemetry::logs::AnyValue>() + } +} + +/// Synthetic naming value. +#[derive(Debug, Clone, PartialEq)] +pub struct ArrayTagsAttr(Vec<::opentelemetry::StringValue>); + +#[allow(deprecated)] +impl ArrayTagsAttr { + pub const KEY: &'static str = "myapp.array.tags"; +} + +#[allow(deprecated)] +impl From> for ArrayTagsAttr { + #[inline] + fn from(value: Vec<::opentelemetry::StringValue>) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From> for ArrayTagsAttr { + fn from(value: Vec) -> Self { + Self(value.into_iter().map(Into::into).collect()) + } +} + +#[allow(deprecated)] +impl AsRef<[::opentelemetry::StringValue]> for ArrayTagsAttr { + #[inline] + fn as_ref(&self) -> &[::opentelemetry::StringValue] { + &self.0 + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for ArrayTagsAttr { + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str("[")?; + for (index, value) in self.0.iter().enumerate() { + if index != 0 { + formatter.write_str(",")?; + } + write!(formatter, "\"{value}\"")?; + } + formatter.write_str("]") + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: ArrayTagsAttr) -> Self { + ::opentelemetry::KeyValue::new(ArrayTagsAttr::KEY, ::opentelemetry::Value::Array(::opentelemetry::Array::String(value.0))) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: ArrayTagsAttr) -> Self { + value.0.into_iter().collect::<::opentelemetry::logs::AnyValue>() + } +} + +/// Synthetic naming value. +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct NumberValueAttr(f64); + +#[allow(deprecated)] +impl NumberValueAttr { + pub const KEY: &'static str = "myapp.number.value"; +} + +#[allow(deprecated)] +impl From for NumberValueAttr { + #[inline] + fn from(value: f64) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for NumberValueAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::std::fmt::Display::fmt(&self.0, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: NumberValueAttr) -> Self { + ::opentelemetry::KeyValue::new(NumberValueAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: NumberValueAttr) -> Self { + value.0.into() + } +} + +/// Name of the queue the task came from. +#[derive(Debug, Clone, PartialEq)] +pub struct QueueNameAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl QueueNameAttr { + pub const KEY: &'static str = "myapp.queue.name"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for QueueNameAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for QueueNameAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for QueueNameAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for QueueNameAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for QueueNameAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: QueueNameAttr) -> Self { + ::opentelemetry::KeyValue::new(QueueNameAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: QueueNameAttr) -> Self { + value.0.into() + } +} + +/// Number of the attempt, starting at one. +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct TaskAttemptAttr(i64); + +#[allow(deprecated)] +impl TaskAttemptAttr { + pub const KEY: &'static str = "myapp.task.attempt"; +} + +#[allow(deprecated)] +impl From for TaskAttemptAttr { + #[inline] + fn from(value: i64) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskAttemptAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::std::fmt::Display::fmt(&self.0, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskAttemptAttr) -> Self { + ::opentelemetry::KeyValue::new(TaskAttemptAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskAttemptAttr) -> Self { + value.0.into() + } +} + +/// Unique identifier of the task. +#[derive(Debug, Clone, PartialEq)] +pub struct TaskIdAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl TaskIdAttr { + pub const KEY: &'static str = "myapp.task.id"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for TaskIdAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for TaskIdAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for TaskIdAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for TaskIdAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskIdAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskIdAttr) -> Self { + ::opentelemetry::KeyValue::new(TaskIdAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskIdAttr) -> Self { + value.0.into() + } +} + +/// Current state of the task. +#[derive(Debug, Clone, PartialEq, Copy)] +pub enum TaskStateAttr { + /// The task is waiting to run. + Queued, + /// The task finished. + Done, +} + +#[allow(deprecated)] +impl TaskStateAttr { + pub const KEY: &'static str = "myapp.task.state"; +} + +#[allow(deprecated)] +impl AsRef for TaskStateAttr { + #[inline] + fn as_ref(&self) -> &str { + match self { + TaskStateAttr::Queued => "queued", + TaskStateAttr::Done => "done", + } + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for TaskStateAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: TaskStateAttr) -> Self { + let value: &'static str = match value { + TaskStateAttr::Queued => "queued", + TaskStateAttr::Done => "done", + }; + ::opentelemetry::KeyValue::new(TaskStateAttr::KEY, value) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: TaskStateAttr) -> Self { + let value: &'static str = match value { + TaskStateAttr::Queued => "queued", + TaskStateAttr::Done => "done", + }; + value.into() + } +} diff --git a/templates/code/rust/tests/spans/expected/myappspan.rs b/templates/code/rust/tests/spans/expected/myappspan.rs new file mode 100644 index 0000000..f144d17 --- /dev/null +++ b/templates/code/rust/tests/spans/expected/myappspan.rs @@ -0,0 +1,462 @@ +#![allow(deprecated)] +use ::opentelemetry::trace::{TraceContextExt, Tracer}; +use ::std::fmt::Write; + +/// Synthetic escaped name. +pub struct NameApostropheSpan { + span: S, +} + +impl NameApostropheSpan { + pub fn into_context(self, parent: &::opentelemetry::Context) -> ::opentelemetry::Context + where S: Send + Sync + 'static, + { + parent.with_span(self.span) + } + pub fn span(&mut self) -> &mut S { &mut self.span } + pub fn end(&mut self) { ::opentelemetry::trace::Span::end(&mut self.span); } + pub fn end_with_timestamp(&mut self, timestamp: ::std::time::SystemTime) { ::opentelemetry::trace::Span::end_with_timestamp(&mut self.span, timestamp); } + pub fn record_error(&mut self, error: &dyn ::std::error::Error) { ::opentelemetry::trace::Span::record_error(&mut self.span, error); } + pub fn set_status(&mut self, status: ::opentelemetry::trace::Status) { ::opentelemetry::trace::Span::set_status(&mut self.span, status); } +} + +impl Default for NameApostropheSpan { + fn default() -> Self { + Self { span: super::tracer::Tracer::default().inner().start("myapp.name.apostrophe") } + } +} + +pub fn r#start_name_apostrophe(context: &::opentelemetry::Context, tracer: &super::tracer::Tracer, r#worker_host_port: super::workerattr::HostPortAttr) -> NameApostropheSpan { + let mut name = String::with_capacity("\u{0027}".len() + 20); + name.push('\u{0027}'); + write!(&mut name, "{}", r#worker_host_port).expect("writing a span name to String cannot fail"); + let attributes: [::opentelemetry::KeyValue; 1] = [::opentelemetry::KeyValue::from(r#worker_host_port)]; + let span = tracer.inner().span_builder(name) + .with_kind(::opentelemetry::trace::SpanKind::Internal) + .with_attributes(attributes) + .start_with_context(tracer.inner(), context); + NameApostropheSpan { span } +} +/// A span named from array values. +pub struct NameArraysSpan { + span: S, +} + +impl NameArraysSpan { + pub fn into_context(self, parent: &::opentelemetry::Context) -> ::opentelemetry::Context + where S: Send + Sync + 'static, + { + parent.with_span(self.span) + } + pub fn span(&mut self) -> &mut S { &mut self.span } + pub fn end(&mut self) { ::opentelemetry::trace::Span::end(&mut self.span); } + pub fn end_with_timestamp(&mut self, timestamp: ::std::time::SystemTime) { ::opentelemetry::trace::Span::end_with_timestamp(&mut self.span, timestamp); } + pub fn record_error(&mut self, error: &dyn ::std::error::Error) { ::opentelemetry::trace::Span::record_error(&mut self.span, error); } + pub fn set_status(&mut self, status: ::opentelemetry::trace::Status) { ::opentelemetry::trace::Span::set_status(&mut self.span, status); } +} + +impl Default for NameArraysSpan { + fn default() -> Self { + Self { span: super::tracer::Tracer::default().inner().start("myapp.name.arrays") } + } +} + +pub fn r#start_name_arrays(context: &::opentelemetry::Context, tracer: &super::tracer::Tracer, r#myapp_array_flags: super::myappattr::ArrayFlagsAttr, r#myapp_array_indices: super::myappattr::ArrayIndicesAttr, r#myapp_array_samples: super::myappattr::ArraySamplesAttr, r#myapp_array_tags: super::myappattr::ArrayTagsAttr) -> NameArraysSpan { + let mut name = String::with_capacity(::std::convert::AsRef::<[::opentelemetry::StringValue]>::as_ref(&r#myapp_array_tags).iter().map(|value| value.as_str().len() + 3).sum::() + 2 + ":".len() + ::std::convert::AsRef::<[i64]>::as_ref(&r#myapp_array_indices).len() * 21 + 2 + ":".len() + ::std::convert::AsRef::<[f64]>::as_ref(&r#myapp_array_samples).len() * 328 + 2 + ":".len() + ::std::convert::AsRef::<[bool]>::as_ref(&r#myapp_array_flags).len() * 6 + 2); + write!(&mut name, "{}", r#myapp_array_tags).expect("writing a span name to String cannot fail"); + name.push(':'); + write!(&mut name, "{}", r#myapp_array_indices).expect("writing a span name to String cannot fail"); + name.push(':'); + write!(&mut name, "{}", r#myapp_array_samples).expect("writing a span name to String cannot fail"); + name.push(':'); + write!(&mut name, "{}", r#myapp_array_flags).expect("writing a span name to String cannot fail"); + let attributes: [::opentelemetry::KeyValue; 4] = [::opentelemetry::KeyValue::from(r#myapp_array_flags), ::opentelemetry::KeyValue::from(r#myapp_array_indices), ::opentelemetry::KeyValue::from(r#myapp_array_samples), ::opentelemetry::KeyValue::from(r#myapp_array_tags)]; + let span = tracer.inner().span_builder(name) + .with_kind(::opentelemetry::trace::SpanKind::Internal) + .with_attributes(attributes) + .start_with_context(tracer.inner(), context); + NameArraysSpan { span } +} +/// Synthetic escaped name. +pub struct NameBackslashSpan { + span: S, +} + +impl NameBackslashSpan { + pub fn into_context(self, parent: &::opentelemetry::Context) -> ::opentelemetry::Context + where S: Send + Sync + 'static, + { + parent.with_span(self.span) + } + pub fn span(&mut self) -> &mut S { &mut self.span } + pub fn end(&mut self) { ::opentelemetry::trace::Span::end(&mut self.span); } + pub fn end_with_timestamp(&mut self, timestamp: ::std::time::SystemTime) { ::opentelemetry::trace::Span::end_with_timestamp(&mut self.span, timestamp); } + pub fn record_error(&mut self, error: &dyn ::std::error::Error) { ::opentelemetry::trace::Span::record_error(&mut self.span, error); } + pub fn set_status(&mut self, status: ::opentelemetry::trace::Status) { ::opentelemetry::trace::Span::set_status(&mut self.span, status); } +} + +impl Default for NameBackslashSpan { + fn default() -> Self { + Self { span: super::tracer::Tracer::default().inner().start("myapp.name.backslash") } + } +} + +pub fn r#start_name_backslash(context: &::opentelemetry::Context, tracer: &super::tracer::Tracer, r#worker_host_port: super::workerattr::HostPortAttr) -> NameBackslashSpan { + let mut name = String::with_capacity("\\".len() + 20); + name.push('\\'); + write!(&mut name, "{}", r#worker_host_port).expect("writing a span name to String cannot fail"); + let attributes: [::opentelemetry::KeyValue; 1] = [::opentelemetry::KeyValue::from(r#worker_host_port)]; + let span = tracer.inner().span_builder(name) + .with_kind(::opentelemetry::trace::SpanKind::Internal) + .with_attributes(attributes) + .start_with_context(tracer.inner(), context); + NameBackslashSpan { span } +} +/// Synthetic escaped name. +pub struct NameControlSpan { + span: S, +} + +impl NameControlSpan { + pub fn into_context(self, parent: &::opentelemetry::Context) -> ::opentelemetry::Context + where S: Send + Sync + 'static, + { + parent.with_span(self.span) + } + pub fn span(&mut self) -> &mut S { &mut self.span } + pub fn end(&mut self) { ::opentelemetry::trace::Span::end(&mut self.span); } + pub fn end_with_timestamp(&mut self, timestamp: ::std::time::SystemTime) { ::opentelemetry::trace::Span::end_with_timestamp(&mut self.span, timestamp); } + pub fn record_error(&mut self, error: &dyn ::std::error::Error) { ::opentelemetry::trace::Span::record_error(&mut self.span, error); } + pub fn set_status(&mut self, status: ::opentelemetry::trace::Status) { ::opentelemetry::trace::Span::set_status(&mut self.span, status); } +} + +impl Default for NameControlSpan { + fn default() -> Self { + Self { span: super::tracer::Tracer::default().inner().start("myapp.name.control") } + } +} + +pub fn r#start_name_control(context: &::opentelemetry::Context, tracer: &super::tracer::Tracer, r#worker_host_port: super::workerattr::HostPortAttr) -> NameControlSpan { + let mut name = String::with_capacity("\u{0008}".len() + 20); + name.push('\u{0008}'); + write!(&mut name, "{}", r#worker_host_port).expect("writing a span name to String cannot fail"); + let attributes: [::opentelemetry::KeyValue; 1] = [::opentelemetry::KeyValue::from(r#worker_host_port)]; + let span = tracer.inner().span_builder(name) + .with_kind(::opentelemetry::trace::SpanKind::Internal) + .with_attributes(attributes) + .start_with_context(tracer.inner(), context); + NameControlSpan { span } +} +/// A span named from a floating point value. +pub struct NameFloatSpan { + span: S, +} + +impl NameFloatSpan { + pub fn into_context(self, parent: &::opentelemetry::Context) -> ::opentelemetry::Context + where S: Send + Sync + 'static, + { + parent.with_span(self.span) + } + pub fn span(&mut self) -> &mut S { &mut self.span } + pub fn end(&mut self) { ::opentelemetry::trace::Span::end(&mut self.span); } + pub fn end_with_timestamp(&mut self, timestamp: ::std::time::SystemTime) { ::opentelemetry::trace::Span::end_with_timestamp(&mut self.span, timestamp); } + pub fn record_error(&mut self, error: &dyn ::std::error::Error) { ::opentelemetry::trace::Span::record_error(&mut self.span, error); } + pub fn set_status(&mut self, status: ::opentelemetry::trace::Status) { ::opentelemetry::trace::Span::set_status(&mut self.span, status); } +} + +impl Default for NameFloatSpan { + fn default() -> Self { + Self { span: super::tracer::Tracer::default().inner().start("myapp.name.float") } + } +} + +pub fn r#start_name_float(context: &::opentelemetry::Context, tracer: &super::tracer::Tracer, r#myapp_number_value: super::myappattr::NumberValueAttr) -> NameFloatSpan { + let mut name = String::with_capacity(328); + write!(&mut name, "{}", r#myapp_number_value).expect("writing a span name to String cannot fail"); + let attributes: [::opentelemetry::KeyValue; 1] = [::opentelemetry::KeyValue::from(r#myapp_number_value)]; + let span = tracer.inner().span_builder(name) + .with_kind(::opentelemetry::trace::SpanKind::Internal) + .with_attributes(attributes) + .start_with_context(tracer.inner(), context); + NameFloatSpan { span } +} +#[derive(Clone, Debug, Default)] +pub struct NameStaticName(::std::borrow::Cow<'static, str>); + +impl From<&'static str> for NameStaticName { + fn from(value: &'static str) -> Self { Self(::std::borrow::Cow::Borrowed(value)) } +} + +impl From for NameStaticName { + fn from(value: String) -> Self { Self(::std::borrow::Cow::Owned(value)) } +} + +impl From<::std::borrow::Cow<'static, str>> for NameStaticName { + fn from(value: ::std::borrow::Cow<'static, str>) -> Self { Self(value) } +} +/// A span with an unconstrained caller supplied name. +pub struct NameStaticSpan { + span: S, +} + +impl NameStaticSpan { + pub fn into_context(self, parent: &::opentelemetry::Context) -> ::opentelemetry::Context + where S: Send + Sync + 'static, + { + parent.with_span(self.span) + } + pub fn span(&mut self) -> &mut S { &mut self.span } + pub fn end(&mut self) { ::opentelemetry::trace::Span::end(&mut self.span); } + pub fn end_with_timestamp(&mut self, timestamp: ::std::time::SystemTime) { ::opentelemetry::trace::Span::end_with_timestamp(&mut self.span, timestamp); } + pub fn record_error(&mut self, error: &dyn ::std::error::Error) { ::opentelemetry::trace::Span::record_error(&mut self.span, error); } + pub fn set_status(&mut self, status: ::opentelemetry::trace::Status) { ::opentelemetry::trace::Span::set_status(&mut self.span, status); } +} + +impl Default for NameStaticSpan { + fn default() -> Self { + Self { span: super::tracer::Tracer::default().inner().start("myapp.name.static") } + } +} + +pub fn r#start_name_static(context: &::opentelemetry::Context, tracer: &super::tracer::Tracer, name: NameStaticName) -> NameStaticSpan { + let name = if name.0.is_empty() { ::std::borrow::Cow::Borrowed("myapp.name.static") } else { name.0 }; + let attributes: [::opentelemetry::KeyValue; 0] = []; + let span = tracer.inner().span_builder(name) + .with_kind(::opentelemetry::trace::SpanKind::Internal) + .with_attributes(attributes) + .start_with_context(tracer.inner(), context); + NameStaticSpan { span } +} +/// Synthetic escaped name. +pub struct NameUnicodeSpan { + span: S, +} + +impl NameUnicodeSpan { + pub fn into_context(self, parent: &::opentelemetry::Context) -> ::opentelemetry::Context + where S: Send + Sync + 'static, + { + parent.with_span(self.span) + } + pub fn span(&mut self) -> &mut S { &mut self.span } + pub fn end(&mut self) { ::opentelemetry::trace::Span::end(&mut self.span); } + pub fn end_with_timestamp(&mut self, timestamp: ::std::time::SystemTime) { ::opentelemetry::trace::Span::end_with_timestamp(&mut self.span, timestamp); } + pub fn record_error(&mut self, error: &dyn ::std::error::Error) { ::opentelemetry::trace::Span::record_error(&mut self.span, error); } + pub fn set_status(&mut self, status: ::opentelemetry::trace::Status) { ::opentelemetry::trace::Span::set_status(&mut self.span, status); } +} + +impl Default for NameUnicodeSpan { + fn default() -> Self { + Self { span: super::tracer::Tracer::default().inner().start("myapp.name.unicode") } + } +} + +pub fn r#start_name_unicode(context: &::opentelemetry::Context, tracer: &super::tracer::Tracer, r#worker_host_port: super::workerattr::HostPortAttr) -> NameUnicodeSpan { + let mut name = String::with_capacity("é".len() + 20); + name.push('é'); + write!(&mut name, "{}", r#worker_host_port).expect("writing a span name to String cannot fail"); + let attributes: [::opentelemetry::KeyValue; 1] = [::opentelemetry::KeyValue::from(r#worker_host_port)]; + let span = tracer.inner().span_builder(name) + .with_kind(::opentelemetry::trace::SpanKind::Internal) + .with_attributes(attributes) + .start_with_context(tracer.inner(), context); + NameUnicodeSpan { span } +} +#[derive(Clone, Debug, Default)] +pub struct QueueDrainName(::std::borrow::Cow<'static, str>); + +impl From<&'static str> for QueueDrainName { + fn from(value: &'static str) -> Self { Self(::std::borrow::Cow::Borrowed(value)) } +} + +impl From for QueueDrainName { + fn from(value: String) -> Self { Self(::std::borrow::Cow::Owned(value)) } +} + +impl From<::std::borrow::Cow<'static, str>> for QueueDrainName { + fn from(value: ::std::borrow::Cow<'static, str>) -> Self { Self(value) } +} +/// Removing every task from a queue. +/// Opt-in: record only when the user requests it. +#[deprecated(note = "Drain queues by dispatching their tasks instead.")] +pub struct QueueDrainSpan { + span: S, +} + +impl QueueDrainSpan { + pub fn into_context(self, parent: &::opentelemetry::Context) -> ::opentelemetry::Context + where S: Send + Sync + 'static, + { + parent.with_span(self.span) + } + pub fn span(&mut self) -> &mut S { &mut self.span } + pub fn end(&mut self) { ::opentelemetry::trace::Span::end(&mut self.span); } + pub fn end_with_timestamp(&mut self, timestamp: ::std::time::SystemTime) { ::opentelemetry::trace::Span::end_with_timestamp(&mut self.span, timestamp); } + pub fn record_error(&mut self, error: &dyn ::std::error::Error) { ::opentelemetry::trace::Span::record_error(&mut self.span, error); } + pub fn set_status(&mut self, status: ::opentelemetry::trace::Status) { ::opentelemetry::trace::Span::set_status(&mut self.span, status); } +} + +impl Default for QueueDrainSpan { + fn default() -> Self { + Self { span: super::tracer::Tracer::default().inner().start("myapp.queue.drain") } + } +} +#[deprecated(note = "Drain queues by dispatching their tasks instead.")] +pub fn r#start_queue_drain(context: &::opentelemetry::Context, tracer: &super::tracer::Tracer, name: QueueDrainName) -> QueueDrainSpan { + let name = if name.0.is_empty() { ::std::borrow::Cow::Borrowed("myapp.queue.drain") } else { name.0 }; + let attributes: [::opentelemetry::KeyValue; 0] = []; + let span = tracer.inner().span_builder(name) + .with_kind(::opentelemetry::trace::SpanKind::Consumer) + .with_attributes(attributes) + .start_with_context(tracer.inner(), context); + QueueDrainSpan { span } +} +#[derive(Clone, Debug)] +pub enum TaskDispatchStartAttr { + /// Recommended: When the task came from a named queue. + QueueName(super::myappattr::QueueNameAttr), + /// Opt-in: the convention records it only when a user asks for it. + TaskAttempt(super::myappattr::TaskAttemptAttr), + TaskId(super::myappattr::TaskIdAttr), +} + +impl From for ::opentelemetry::KeyValue { + fn from(value: TaskDispatchStartAttr) -> Self { + match value { + TaskDispatchStartAttr::QueueName(value) => ::opentelemetry::KeyValue::from(value), + TaskDispatchStartAttr::TaskAttempt(value) => ::opentelemetry::KeyValue::from(value), + TaskDispatchStartAttr::TaskId(value) => ::opentelemetry::KeyValue::from(value), + } + } +} +#[derive(Clone, Debug)] +pub enum TaskDispatchAttr { + QueueName(super::myappattr::QueueNameAttr), + TaskAttempt(super::myappattr::TaskAttemptAttr), +} + +impl From for TaskDispatchStartAttr { + fn from(value: TaskDispatchAttr) -> Self { + match value { + TaskDispatchAttr::QueueName(value) => Self::QueueName(value), + TaskDispatchAttr::TaskAttempt(value) => Self::TaskAttempt(value), + } + } +} +/// The scheduler handing a task to a worker. +/// +/// Ends when the worker accepts the task. +pub struct TaskDispatchSpan { + span: S, +} + +impl TaskDispatchSpan { + pub fn into_context(self, parent: &::opentelemetry::Context) -> ::opentelemetry::Context + where S: Send + Sync + 'static, + { + parent.with_span(self.span) + } + pub fn span(&mut self) -> &mut S { &mut self.span } + pub fn end(&mut self) { ::opentelemetry::trace::Span::end(&mut self.span); } + pub fn end_with_timestamp(&mut self, timestamp: ::std::time::SystemTime) { ::opentelemetry::trace::Span::end_with_timestamp(&mut self.span, timestamp); } + pub fn record_error(&mut self, error: &dyn ::std::error::Error) { ::opentelemetry::trace::Span::record_error(&mut self.span, error); } + pub fn set_status(&mut self, status: ::opentelemetry::trace::Status) { ::opentelemetry::trace::Span::set_status(&mut self.span, status); } + pub fn set_attributes(&mut self, attributes: impl IntoIterator) { + for attribute in attributes { ::opentelemetry::trace::Span::set_attribute(&mut self.span, ::opentelemetry::KeyValue::from(TaskDispatchStartAttr::from(attribute))); } + } +} + +impl Default for TaskDispatchSpan { + fn default() -> Self { + Self { span: super::tracer::Tracer::default().inner().start("myapp.task.dispatch") } + } +} + +pub fn r#start_task_dispatch(context: &::opentelemetry::Context, tracer: &super::tracer::Tracer, r#worker_host_name: super::workerattr::HostNameAttr, r#worker_host_port: super::workerattr::HostPortAttr, options: impl IntoIterator) -> TaskDispatchSpan { + let mut name = String::with_capacity("dispatch ".len() + ::std::convert::AsRef::::as_ref(&r#worker_host_name).len() + ":".len() + 20); + name.push_str("dispatch "); + write!(&mut name, "{}", r#worker_host_name).expect("writing a span name to String cannot fail"); + name.push(':'); + write!(&mut name, "{}", r#worker_host_port).expect("writing a span name to String cannot fail"); + let attributes: [::opentelemetry::KeyValue; 2] = [::opentelemetry::KeyValue::from(r#worker_host_name), ::opentelemetry::KeyValue::from(r#worker_host_port)]; + let span = tracer.inner().span_builder(name) + .with_kind(::opentelemetry::trace::SpanKind::Client) + .with_attributes(attributes.into_iter().chain(options.into_iter().map(::opentelemetry::KeyValue::from))) + .start_with_context(tracer.inner(), context); + TaskDispatchSpan { span } +} +#[derive(Clone, Debug)] +pub enum TaskRunStartAttr { + TaskId(super::myappattr::TaskIdAttr), +} + +impl From for ::opentelemetry::KeyValue { + fn from(value: TaskRunStartAttr) -> Self { + match value { + TaskRunStartAttr::TaskId(value) => ::opentelemetry::KeyValue::from(value), + } + } +} +#[derive(Clone, Debug)] +pub enum TaskRunAttr { + TaskId(super::myappattr::TaskIdAttr), +} + +impl From for TaskRunStartAttr { + fn from(value: TaskRunAttr) -> Self { + match value { + TaskRunAttr::TaskId(value) => Self::TaskId(value), + } + } +} +#[derive(Clone, Debug, Default)] +pub struct TaskRunName(::std::borrow::Cow<'static, str>); + +impl From<&'static str> for TaskRunName { + fn from(value: &'static str) -> Self { Self(::std::borrow::Cow::Borrowed(value)) } +} + +impl From for TaskRunName { + fn from(value: String) -> Self { Self(::std::borrow::Cow::Owned(value)) } +} + +impl From<::std::borrow::Cow<'static, str>> for TaskRunName { + fn from(value: ::std::borrow::Cow<'static, str>) -> Self { Self(value) } +} +/// One run of a task. +pub struct TaskRunSpan { + span: S, +} + +impl TaskRunSpan { + pub fn into_context(self, parent: &::opentelemetry::Context) -> ::opentelemetry::Context + where S: Send + Sync + 'static, + { + parent.with_span(self.span) + } + pub fn span(&mut self) -> &mut S { &mut self.span } + pub fn end(&mut self) { ::opentelemetry::trace::Span::end(&mut self.span); } + pub fn end_with_timestamp(&mut self, timestamp: ::std::time::SystemTime) { ::opentelemetry::trace::Span::end_with_timestamp(&mut self.span, timestamp); } + pub fn record_error(&mut self, error: &dyn ::std::error::Error) { ::opentelemetry::trace::Span::record_error(&mut self.span, error); } + pub fn set_status(&mut self, status: ::opentelemetry::trace::Status) { ::opentelemetry::trace::Span::set_status(&mut self.span, status); } + pub fn set_attributes(&mut self, attributes: impl IntoIterator) { + for attribute in attributes { ::opentelemetry::trace::Span::set_attribute(&mut self.span, ::opentelemetry::KeyValue::from(TaskRunStartAttr::from(attribute))); } + } +} + +impl Default for TaskRunSpan { + fn default() -> Self { + Self { span: super::tracer::Tracer::default().inner().start("myapp.task.run") } + } +} + +pub fn r#start_task_run(context: &::opentelemetry::Context, tracer: &super::tracer::Tracer, name: TaskRunName, r#myapp_task_state: super::myappattr::TaskStateAttr, options: impl IntoIterator) -> TaskRunSpan { + let name = if name.0.is_empty() { ::std::borrow::Cow::Borrowed("myapp.task.run") } else { name.0 }; + let attributes: [::opentelemetry::KeyValue; 1] = [::opentelemetry::KeyValue::from(r#myapp_task_state)]; + let span = tracer.inner().span_builder(name) + .with_kind(::opentelemetry::trace::SpanKind::Internal) + .with_attributes(attributes.into_iter().chain(options.into_iter().map(::opentelemetry::KeyValue::from))) + .start_with_context(tracer.inner(), context); + TaskRunSpan { span } +} diff --git a/templates/code/rust/tests/spans/expected/options.rs b/templates/code/rust/tests/spans/expected/options.rs new file mode 100644 index 0000000..69a821a --- /dev/null +++ b/templates/code/rust/tests/spans/expected/options.rs @@ -0,0 +1,6 @@ +use super::scope::Scope; + +#[derive(Clone, Debug, Default)] +pub struct InstrumentationOptions { + pub scope: Scope, +} diff --git a/templates/code/rust/tests/spans/expected/scope.rs b/templates/code/rust/tests/spans/expected/scope.rs new file mode 100644 index 0000000..6f66e86 --- /dev/null +++ b/templates/code/rust/tests/spans/expected/scope.rs @@ -0,0 +1,13 @@ +#[derive(Clone, Debug, Default)] +pub struct Scope { + pub attributes: Vec<::opentelemetry::KeyValue>, +} + +impl From for ::opentelemetry::InstrumentationScope { + fn from(value: Scope) -> Self { + Self::builder("generated-semconv") + .with_schema_url(super::SCHEMA_URL) + .with_attributes(value.attributes) + .build() + } +} diff --git a/templates/code/rust/tests/spans/expected/tracer.rs b/templates/code/rust/tests/spans/expected/tracer.rs new file mode 100644 index 0000000..d684a8d --- /dev/null +++ b/templates/code/rust/tests/spans/expected/tracer.rs @@ -0,0 +1,16 @@ +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Tracer(T); + +impl Tracer { + pub fn new>(provider: &P, options: InstrumentationOptions) -> Self { + let tracer = provider.tracer_with_scope(options.scope.into()); + Self(tracer) + } + pub fn inner(&self) -> &T { &self.0 } +} + +impl Default for Tracer { + fn default() -> Self { Self::new(&opentelemetry::trace::noop::NoopTracerProvider::new(), InstrumentationOptions::default()) } +} diff --git a/templates/code/rust/tests/spans/expected/workerattr.rs b/templates/code/rust/tests/spans/expected/workerattr.rs new file mode 100644 index 0000000..431c6c0 --- /dev/null +++ b/templates/code/rust/tests/spans/expected/workerattr.rs @@ -0,0 +1,106 @@ +// Generated by OpenTelemetry Weaver. +/// Host name of the worker. +#[derive(Debug, Clone, PartialEq)] +pub struct HostNameAttr(::opentelemetry::StringValue); + +#[allow(deprecated)] +impl HostNameAttr { + pub const KEY: &'static str = "worker.host.name"; +} + +#[allow(deprecated)] +impl From<::opentelemetry::StringValue> for HostNameAttr { + #[inline] + fn from(value: ::opentelemetry::StringValue) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl From for HostNameAttr { + #[inline] + fn from(value: String) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl From<&'static str> for HostNameAttr { + #[inline] + fn from(value: &'static str) -> Self { + Self(value.into()) + } +} + +#[allow(deprecated)] +impl AsRef for HostNameAttr { + #[inline] + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for HostNameAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + formatter.write_str(self.as_ref()) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: HostNameAttr) -> Self { + ::opentelemetry::KeyValue::new(HostNameAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: HostNameAttr) -> Self { + value.0.into() + } +} + +/// Port the worker listens on. +#[derive(Debug, Clone, PartialEq, Copy)] +pub struct HostPortAttr(i64); + +#[allow(deprecated)] +impl HostPortAttr { + pub const KEY: &'static str = "worker.host.port"; +} + +#[allow(deprecated)] +impl From for HostPortAttr { + #[inline] + fn from(value: i64) -> Self { + Self(value) + } +} + +#[allow(deprecated)] +impl ::std::fmt::Display for HostPortAttr { + #[inline] + fn fmt(&self, formatter: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::std::fmt::Display::fmt(&self.0, formatter) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::KeyValue { + #[inline] + fn from(value: HostPortAttr) -> Self { + ::opentelemetry::KeyValue::new(HostPortAttr::KEY, value.0) + } +} + +#[allow(deprecated)] +impl From for ::opentelemetry::logs::AnyValue { + #[inline] + fn from(value: HostPortAttr) -> Self { + value.0.into() + } +} diff --git a/templates/code/rust/tests/spans/expected/workerspan.rs b/templates/code/rust/tests/spans/expected/workerspan.rs new file mode 100644 index 0000000..7fc031f --- /dev/null +++ b/templates/code/rust/tests/spans/expected/workerspan.rs @@ -0,0 +1,73 @@ +#![allow(deprecated)] +use ::opentelemetry::trace::{TraceContextExt, Tracer}; +use ::std::fmt::Write; + +#[derive(Clone, Debug)] +pub enum TaskDispatchStartAttr { + /// Recommended: When the task came from a named queue. + QueueName(super::myappattr::QueueNameAttr), + TaskId(super::myappattr::TaskIdAttr), +} + +impl From for ::opentelemetry::KeyValue { + fn from(value: TaskDispatchStartAttr) -> Self { + match value { + TaskDispatchStartAttr::QueueName(value) => ::opentelemetry::KeyValue::from(value), + TaskDispatchStartAttr::TaskId(value) => ::opentelemetry::KeyValue::from(value), + } + } +} +#[derive(Clone, Debug)] +pub enum TaskDispatchAttr { + QueueName(super::myappattr::QueueNameAttr), +} + +impl From for TaskDispatchStartAttr { + fn from(value: TaskDispatchAttr) -> Self { + match value { + TaskDispatchAttr::QueueName(value) => Self::QueueName(value), + } + } +} +/// The scheduler handing a task to a worker. +/// +/// Ends when the worker accepts the task. +pub struct TaskDispatchSpan { + span: S, +} + +impl TaskDispatchSpan { + pub fn into_context(self, parent: &::opentelemetry::Context) -> ::opentelemetry::Context + where S: Send + Sync + 'static, + { + parent.with_span(self.span) + } + pub fn span(&mut self) -> &mut S { &mut self.span } + pub fn end(&mut self) { ::opentelemetry::trace::Span::end(&mut self.span); } + pub fn end_with_timestamp(&mut self, timestamp: ::std::time::SystemTime) { ::opentelemetry::trace::Span::end_with_timestamp(&mut self.span, timestamp); } + pub fn record_error(&mut self, error: &dyn ::std::error::Error) { ::opentelemetry::trace::Span::record_error(&mut self.span, error); } + pub fn set_status(&mut self, status: ::opentelemetry::trace::Status) { ::opentelemetry::trace::Span::set_status(&mut self.span, status); } + pub fn set_attributes(&mut self, attributes: impl IntoIterator) { + for attribute in attributes { ::opentelemetry::trace::Span::set_attribute(&mut self.span, ::opentelemetry::KeyValue::from(TaskDispatchStartAttr::from(attribute))); } + } +} + +impl Default for TaskDispatchSpan { + fn default() -> Self { + Self { span: super::tracer::Tracer::default().inner().start("myapp.task.dispatch") } + } +} + +pub fn r#start_task_dispatch(context: &::opentelemetry::Context, tracer: &super::tracer::Tracer, r#myapp_task_attempt: super::myappattr::TaskAttemptAttr, r#worker_host_name: super::workerattr::HostNameAttr, r#worker_host_port: super::workerattr::HostPortAttr, options: impl IntoIterator) -> TaskDispatchSpan { + let mut name = String::with_capacity("dispatch ".len() + ::std::convert::AsRef::::as_ref(&r#worker_host_name).len() + ":".len() + 20); + name.push_str("dispatch "); + write!(&mut name, "{}", r#worker_host_name).expect("writing a span name to String cannot fail"); + name.push(':'); + write!(&mut name, "{}", r#worker_host_port).expect("writing a span name to String cannot fail"); + let attributes: [::opentelemetry::KeyValue; 3] = [::opentelemetry::KeyValue::from(r#myapp_task_attempt), ::opentelemetry::KeyValue::from(r#worker_host_name), ::opentelemetry::KeyValue::from(r#worker_host_port)]; + let span = tracer.inner().span_builder(name) + .with_kind(::opentelemetry::trace::SpanKind::Client) + .with_attributes(attributes.into_iter().chain(options.into_iter().map(::opentelemetry::KeyValue::from))) + .start_with_context(tracer.inner(), context); + TaskDispatchSpan { span } +} diff --git a/templates/code/rust/tests/spans/registry/manifest.yaml b/templates/code/rust/tests/spans/registry/manifest.yaml new file mode 100644 index 0000000..1520442 --- /dev/null +++ b/templates/code/rust/tests/spans/registry/manifest.yaml @@ -0,0 +1,2 @@ +name: spans +schema_url: https://example.com/schemas/spans/1.0.0 diff --git a/templates/code/rust/tests/spans/registry/model.yaml b/templates/code/rust/tests/spans/registry/model.yaml new file mode 100644 index 0000000..d84d1f5 --- /dev/null +++ b/templates/code/rust/tests/spans/registry/model.yaml @@ -0,0 +1,188 @@ +file_format: definition/2 +attributes: + - key: myapp.task.id + type: string + stability: stable + brief: Unique identifier of the task. + examples: ["task_0001"] + - key: myapp.task.state + type: + members: + - id: queued + value: queued + stability: stable + brief: The task is waiting to run. + - id: done + value: done + stability: stable + brief: The task finished. + stability: stable + brief: Current state of the task. + - key: myapp.task.attempt + type: int + stability: stable + brief: Number of the attempt, starting at one. + examples: 1 + - key: myapp.queue.name + type: string + stability: stable + brief: Name of the queue the task came from. + examples: ["reports"] + - key: worker.host.name + type: string + stability: stable + brief: Host name of the worker. + examples: ["worker-1"] + - key: worker.host.port + type: int + stability: stable + brief: Port the worker listens on. + examples: 8080 + - key: myapp.number.value + type: double + stability: stable + brief: Synthetic naming value. + examples: [1.5] + - key: myapp.array.flags + type: boolean[] + stability: stable + brief: Synthetic naming value. + examples: [[true]] + - key: myapp.array.indices + type: int[] + stability: stable + brief: Synthetic naming value. + examples: [[1]] + - key: myapp.array.samples + type: double[] + stability: stable + brief: Synthetic naming value. + examples: [[1.5]] + - key: myapp.array.tags + type: string[] + stability: stable + brief: Synthetic naming value. + examples: [["a"]] +spans: + - type: myapp.task.dispatch + kind: client + requirement_level: recommended + stability: stable + brief: The scheduler handing a task to a worker. + note: Ends when the worker accepts the task. + name: + note: "`dispatch {worker.host.name}:{worker.host.port}`" + attributes: + - ref: worker.host.name + requirement_level: required + - ref: worker.host.port + requirement_level: required + - ref: myapp.task.id + requirement_level: recommended + sampling_relevant: true + - ref: myapp.queue.name + requirement_level: + recommended: When the task came from a named queue. + - ref: myapp.task.attempt + requirement_level: opt_in + - type: myapp.task.run + kind: internal + requirement_level: recommended + stability: stable + brief: One run of a task. + name: + note: "`run {myapp.task.id}`" + attributes: + - ref: myapp.task.state + requirement_level: required + - ref: myapp.task.id + requirement_level: recommended + - type: myapp.queue.drain + kind: consumer + requirement_level: opt_in + stability: development + brief: Removing every task from a queue. + name: + note: "`drain` followed by the queue's name, such as `drain reports`." + deprecated: + reason: obsoleted + note: Drain queues by dispatching their tasks instead. + - type: myapp.name.apostrophe + kind: internal + requirement_level: recommended + stability: stable + brief: Synthetic escaped name. + name: + note: "`'{worker.host.port}`" + attributes: + - ref: worker.host.port + requirement_level: required + - type: myapp.name.backslash + kind: internal + requirement_level: recommended + stability: stable + brief: Synthetic escaped name. + name: + note: "`\\{worker.host.port}`" + attributes: + - ref: worker.host.port + requirement_level: required + - type: myapp.name.unicode + kind: internal + requirement_level: recommended + stability: stable + brief: Synthetic escaped name. + name: + note: "`é{worker.host.port}`" + attributes: + - ref: worker.host.port + requirement_level: required + - type: myapp.name.control + kind: internal + requirement_level: recommended + stability: stable + brief: Synthetic escaped name. + name: + note: "`\b{worker.host.port}`" + attributes: + - ref: worker.host.port + requirement_level: required + - type: myapp.name.float + kind: internal + requirement_level: recommended + stability: stable + brief: A span named from a floating point value. + name: + note: "`{myapp.number.value}`" + attributes: + - ref: myapp.number.value + requirement_level: required + - type: myapp.name.arrays + kind: internal + requirement_level: recommended + stability: stable + brief: A span named from array values. + name: + note: "`{myapp.array.tags}:{myapp.array.indices}:{myapp.array.samples}:{myapp.array.flags}`" + attributes: + - ref: myapp.array.tags + requirement_level: required + - ref: myapp.array.indices + requirement_level: required + - ref: myapp.array.samples + requirement_level: required + - ref: myapp.array.flags + requirement_level: required + - type: myapp.name.static + kind: internal + requirement_level: recommended + stability: stable + brief: A span with an unconstrained caller supplied name. + name: + note: "`static`" +span_refinements: + - id: worker.task.dispatch + ref: myapp.task.dispatch + attributes: + - ref: myapp.task.attempt + requirement_level: required diff --git a/templates/code/rust/tests/spans/test.rs b/templates/code/rust/tests/spans/test.rs new file mode 100644 index 0000000..6941404 --- /dev/null +++ b/templates/code/rust/tests/spans/test.rs @@ -0,0 +1,160 @@ +use generated_semconv::{myappattr, myappspan, tracer, workerattr, workerspan, SCHEMA_URL}; +use opentelemetry::trace::{Span, TraceContextExt}; +use opentelemetry_sdk::trace::{InMemorySpanExporter, SdkTracerProvider}; + +#[test] +fn derived_names_refinements_parent_context_and_late_attributes() { + let exporter = InMemorySpanExporter::default(); + let provider = SdkTracerProvider::builder() + .with_simple_exporter(exporter.clone()) + .build(); + let tracer = tracer::Tracer::new( + &provider, + generated_semconv::options::InstrumentationOptions { + scope: generated_semconv::scope::Scope { + attributes: vec![opentelemetry::KeyValue::new("scope.kind", "test")], + }, + }, + ); + let _: &opentelemetry_sdk::trace::SdkTracer = tracer.inner(); + let mut parent = myappspan::start_task_dispatch( + &opentelemetry::Context::new(), + &tracer, + workerattr::HostNameAttr::from("worker-1"), + workerattr::HostPortAttr::from(8080), + [myappspan::TaskDispatchStartAttr::TaskId( + myappattr::TaskIdAttr::from("task-1"), + )], + ); + let _: &mut opentelemetry_sdk::trace::Span = parent.span(); + let context = parent.into_context(&opentelemetry::Context::new()); + let mut child = myappspan::start_task_run( + &context, + &tracer, + myappspan::TaskRunName::from("run task-1"), + myappattr::TaskStateAttr::Done, + [], + ); + child.set_attributes([myappspan::TaskRunAttr::TaskId(myappattr::TaskIdAttr::from( + "task-1", + ))]); + child.set_status(opentelemetry::trace::Status::Ok); + child.end(); + context.span().end(); + let mut refined = workerspan::start_task_dispatch( + &opentelemetry::Context::new(), + &tracer, + myappattr::TaskAttemptAttr::from(3), + workerattr::HostNameAttr::from("worker-2"), + workerattr::HostPortAttr::from(9090), + [], + ); + refined.end(); + let mut fallback = myappspan::start_task_run( + &opentelemetry::Context::new(), + &tracer, + myappspan::TaskRunName::default(), + myappattr::TaskStateAttr::Done, + [], + ); + fallback.end(); + myappspan::start_name_apostrophe( + &opentelemetry::Context::new(), + &tracer, + workerattr::HostPortAttr::from(1), + ) + .end(); + myappspan::start_name_backslash( + &opentelemetry::Context::new(), + &tracer, + workerattr::HostPortAttr::from(1), + ) + .end(); + myappspan::start_name_unicode( + &opentelemetry::Context::new(), + &tracer, + workerattr::HostPortAttr::from(1), + ) + .end(); + myappspan::start_name_control( + &opentelemetry::Context::new(), + &tracer, + workerattr::HostPortAttr::from(1), + ) + .end(); + for value in [f64::MAX, -f64::from_bits(1)] { + myappspan::start_name_float( + &opentelemetry::Context::new(), + &tracer, + myappattr::NumberValueAttr::from(value), + ) + .end(); + } + myappspan::start_name_arrays( + &opentelemetry::Context::new(), + &tracer, + myappattr::ArrayFlagsAttr::from(vec![true, false]), + myappattr::ArrayIndicesAttr::from(vec![-1, 2]), + myappattr::ArraySamplesAttr::from(vec![1.5, 2.5]), + myappattr::ArrayTagsAttr::from(vec![ + opentelemetry::StringValue::from("a"), + opentelemetry::StringValue::from("b"), + ]), + ) + .end(); + for name in [ + myappspan::NameStaticName::default(), + myappspan::NameStaticName::from("explicit static"), + myappspan::NameStaticName::from(String::from("owned dynamic")), + ] { + myappspan::start_name_static(&opentelemetry::Context::new(), &tracer, name).end(); + } + provider.force_flush().unwrap(); + let spans = exporter.get_finished_spans().unwrap(); + assert_eq!(spans.len(), 14); + let child = spans.iter().find(|span| span.name == "run task-1").unwrap(); + let parent = spans + .iter() + .find(|span| span.name == "dispatch worker-1:8080") + .unwrap(); + assert_eq!(child.parent_span_id, parent.span_context.span_id()); + assert_eq!(parent.span_kind, opentelemetry::trace::SpanKind::Client); + assert_eq!(child.status, opentelemetry::trace::Status::Ok); + assert!(child + .attributes + .iter() + .any(|kv| kv.key.as_str() == "myapp.task.id" && kv.value.as_str() == "task-1")); + assert_eq!(parent.instrumentation_scope.schema_url(), Some(SCHEMA_URL)); + assert_eq!(parent.instrumentation_scope.name(), "generated-semconv"); + assert_eq!(parent.instrumentation_scope.version(), None); + assert!(parent + .instrumentation_scope + .attributes() + .any(|kv| kv.key.as_str() == "scope.kind")); + let refined = spans + .iter() + .find(|span| span.name == "dispatch worker-2:9090") + .unwrap(); + assert!(refined + .attributes + .iter() + .any(|kv| kv.key.as_str() == "myapp.task.attempt" + && kv.value == opentelemetry::Value::I64(3))); + assert!(spans.iter().any(|span| span.name == "myapp.task.run")); + for name in ["'1", "\\1", "é1", "\u{0008}1"] { + assert!(spans.iter().any(|span| span.name == name)); + } + for value in [f64::MAX, -f64::from_bits(1)] { + assert!(value.to_string().len() <= 328); + assert!(spans.iter().any(|span| span.name == value.to_string())); + } + assert!(spans + .iter() + .any(|span| span.name == "[\"a\",\"b\"]:[-1,2]:[1.5,2.5]:[true,false]")); + for name in ["myapp.name.static", "explicit static", "owned dynamic"] { + assert!(spans.iter().any(|span| span.name == name)); + } + let mut noop = myappspan::TaskRunSpan::default(); + assert!(!noop.span().is_recording()); + noop.end(); +} diff --git a/templates/code/rust/tests/spans/tests/allocations.rs b/templates/code/rust/tests/spans/tests/allocations.rs new file mode 100644 index 0000000..6c3b775 --- /dev/null +++ b/templates/code/rust/tests/spans/tests/allocations.rs @@ -0,0 +1,122 @@ +use generated_semconv::{myappattr, myappspan, tracer, workerattr}; +use opentelemetry::trace::noop::NoopTracerProvider; +use std::alloc::{GlobalAlloc, Layout, System}; +use std::cell::Cell; + +struct CountingAllocator; +thread_local! { + static ALLOCATIONS: Cell> = const { Cell::new(None) }; +} +fn record_allocation() { + let _ = ALLOCATIONS.try_with(|count| { + if let Some(value) = count.get() { + count.set(Some(value + 1)); + } + }); +} +unsafe impl GlobalAlloc for CountingAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + record_allocation(); + unsafe { System.alloc(layout) } + } + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + unsafe { System.dealloc(ptr, layout) } + } + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, size: usize) -> *mut u8 { + record_allocation(); + unsafe { System.realloc(ptr, layout, size) } + } +} +#[global_allocator] +static ALLOCATOR: CountingAllocator = CountingAllocator; +fn allocations(operation: impl FnOnce() -> T) -> (T, usize) { + ALLOCATIONS.with(|count| count.set(Some(0))); + let value = operation(); + let count = ALLOCATIONS.with(|count| count.replace(None).unwrap()); + (value, count) +} +#[test] +fn fixed_scope_identity_has_no_generated_heap_allocations() { + let (_, count) = allocations(|| { + tracer::Tracer::new( + &NoopTracerProvider::new(), + generated_semconv::options::InstrumentationOptions::default(), + ) + }); + assert_eq!(count, 0); +} +#[test] +fn derived_name_only_allocates_output_and_attribute_vector() { + let tracer = tracer::Tracer::default(); + let context = opentelemetry::Context::new(); + let host = workerattr::HostNameAttr::from("worker-1"); + let port = workerattr::HostPortAttr::from(8080); + let (_, count) = + allocations(|| myappspan::start_task_dispatch(&context, &tracer, host, port, [])); + assert_eq!(count, 2); +} + +#[test] +fn extreme_float_names_do_not_reallocate_the_output() { + let tracer = tracer::Tracer::default(); + let context = opentelemetry::Context::new(); + for value in [f64::MAX, -f64::from_bits(1)] { + let attribute = myappattr::NumberValueAttr::from(value); + let (_, count) = allocations(|| myappspan::start_name_float(&context, &tracer, attribute)); + assert_eq!(count, 2); + } +} +#[test] +fn long_owned_string_names_do_not_clone_or_reallocate() { + let tracer = tracer::Tracer::default(); + let context = opentelemetry::Context::new(); + let host = workerattr::HostNameAttr::from("worker".repeat(1000)); + let port = workerattr::HostPortAttr::from(8080); + let (_, count) = + allocations(|| myappspan::start_task_dispatch(&context, &tracer, host, port, [])); + assert_eq!(count, 2); +} +#[test] +fn array_names_only_allocate_output_and_builder_attributes() { + let tracer = tracer::Tracer::default(); + let context = opentelemetry::Context::new(); + let flags = myappattr::ArrayFlagsAttr::from(vec![true, false]); + let indices = myappattr::ArrayIndicesAttr::from(vec![-1, 2]); + let samples = myappattr::ArraySamplesAttr::from(vec![f64::MAX, -f64::from_bits(1)]); + let tags = myappattr::ArrayTagsAttr::from(vec![ + opentelemetry::StringValue::from("a"), + opentelemetry::StringValue::from("b"), + ]); + let (_, count) = allocations(|| { + myappspan::start_name_arrays(&context, &tracer, flags, indices, samples, tags) + }); + assert_eq!(count, 2); +} + +#[test] +fn untemplated_names_move_or_borrow_without_heap_allocations() { + let tracer = tracer::Tracer::default(); + let context = opentelemetry::Context::new(); + for name in [ + myappspan::NameStaticName::default(), + myappspan::NameStaticName::from("explicit static"), + myappspan::NameStaticName::from(String::from("owned dynamic")), + ] { + let (_, count) = allocations(|| myappspan::start_name_static(&context, &tracer, name)); + assert_eq!(count, 0); + } +} + +#[test] +fn scope_attributes_move_without_cloning() { + let scope = generated_semconv::scope::Scope { + attributes: vec![opentelemetry::KeyValue::new("scope.kind", "worker")], + }; + let (_, count) = allocations(|| { + tracer::Tracer::new( + &NoopTracerProvider::new(), + generated_semconv::options::InstrumentationOptions { scope }, + ) + }); + assert_eq!(count, 0); +} diff --git a/templates/code/rust/tests/spans/tests/tracer_traits.rs b/templates/code/rust/tests/spans/tests/tracer_traits.rs new file mode 100644 index 0000000..9d688b1 --- /dev/null +++ b/templates/code/rust/tests/spans/tests/tracer_traits.rs @@ -0,0 +1,71 @@ +use generated_semconv::{options::InstrumentationOptions, scope::Scope, tracer, SCHEMA_URL}; +use opentelemetry::trace::{Span as _, Tracer as _}; +use opentelemetry::{Context, InstrumentationScope, KeyValue}; +use opentelemetry_sdk::trace::{InMemorySpanExporter, SdkTracerProvider}; +use std::fmt::Debug; + +fn require_clone_and_debug(_: &T) {} + +#[test] +fn default_tracer_supports_clone_and_debug() { + let tracer = tracer::Tracer::default(); + require_clone_and_debug(&tracer); + require_clone_and_debug(&tracer.clone()); +} + +#[test] +fn cloned_sdk_tracer_preserves_scope_and_exports_spans() { + let exporter = InMemorySpanExporter::default(); + let provider = SdkTracerProvider::builder() + .with_simple_exporter(exporter.clone()) + .build(); + let original = tracer::Tracer::new( + &provider, + InstrumentationOptions { + scope: Scope { + attributes: vec![KeyValue::new("scope.kind", "clone-test")], + }, + }, + ); + require_clone_and_debug(&original); + let cloned = original.clone(); + drop(original); + let mut span = cloned.inner().start("cloned-handle"); + span.end(); + provider.force_flush().unwrap(); + let spans = exporter.get_finished_spans().unwrap(); + assert_eq!(spans.len(), 1); + assert_eq!(spans[0].name, "cloned-handle"); + assert_eq!( + spans[0].instrumentation_scope.schema_url(), + Some(SCHEMA_URL) + ); + assert!(spans[0] + .instrumentation_scope + .attributes() + .any(|attribute| attribute == &KeyValue::new("scope.kind", "clone-test"))); + provider.shutdown().unwrap(); +} + +struct BareTracer; +impl opentelemetry::trace::Tracer for BareTracer { + type Span = opentelemetry::trace::noop::NoopSpan; + fn build_with_context(&self, _: opentelemetry::trace::SpanBuilder, _: &Context) -> Self::Span { + opentelemetry::trace::noop::NoopSpan::DEFAULT + } +} +struct BareProvider; +impl opentelemetry::trace::TracerProvider for BareProvider { + type Tracer = BareTracer; + fn tracer_with_scope(&self, _: InstrumentationScope) -> Self::Tracer { + BareTracer + } +} + +#[test] +fn custom_tracer_without_clone_or_debug_still_constructs_and_starts_spans() { + let tracer = tracer::Tracer::new(&BareProvider, InstrumentationOptions::default()); + let mut span = tracer.inner().start("bare-handle"); + assert!(!span.is_recording()); + span.end(); +} diff --git a/templates/code/rust/tracer.rs.j2 b/templates/code/rust/tracer.rs.j2 new file mode 100644 index 0000000..e1cd4be --- /dev/null +++ b/templates/code/rust/tracer.rs.j2 @@ -0,0 +1,19 @@ +{%- filter trim %} +use super::options::InstrumentationOptions; + +#[derive(Clone, Debug)] +pub struct Tracer(T); + +impl Tracer { + pub fn new>(provider: &P, options: InstrumentationOptions) -> Self { + let tracer = provider.tracer_with_scope(options.scope.into()); + Self(tracer) + } + pub fn inner(&self) -> &T { &self.0 } +} + +impl Default for Tracer { + fn default() -> Self { Self::new(&opentelemetry::trace::noop::NoopTracerProvider::new(), InstrumentationOptions::default()) } +} + +{% endfilter -%}{{ "\n" -}} diff --git a/templates/code/rust/weaver.yaml b/templates/code/rust/weaver.yaml new file mode 100644 index 0000000..8ca0cd8 --- /dev/null +++ b/templates/code/rust/weaver.yaml @@ -0,0 +1,659 @@ +whitespace_control: + trim_blocks: true + lstrip_blocks: true + +params: + instrumentation_name: generated-semconv + instrumentation_version: "" + module_description: "" + histogram_boundaries_by_unit: + s: [0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10] + vendor_prefixes: [] + exclude_deprecated: false + stable_only: false + imported_crate_paths: {} + header_source: "" + regenerate_command: "" + +templates: + - template: mod.rs.j2 + application_mode: single + file_name: mod.rs + filter: |- + def ns: split(".") as $s + | if ($s | length) > 1 and ($vendor_prefixes | any(. == $s[0])) + then {namespace: $s[1], short_name: ($s[2:] | join("."))} + else {namespace: $s[0], short_name: ($s[1:] | join("."))} end | if (.namespace | test("^[a-z][a-z0-9_]*$") | not) + then error("namespace \(.namespace) is not a supported Rust module name") else . end; + def rust_literal: + if type == "string" then explode | map([.] | implode | tojson | gsub("\\\\b"; "\\u{0008}") | gsub("\\\\f"; "\\u{000c}") | gsub("\\\\u(?[0-9a-fA-F]{4})"; "\\u{\(.code)}") | .[1:-1]) | join("") | "\"" + . + "\"" + else tostring end; + if ($instrumentation_name | type) != "string" then error("instrumentation_name must be a nonempty string") + elif ($instrumentation_name | length) == 0 then error("instrumentation_name must be a nonempty string") + elif ($instrumentation_version | type) != "string" then error("instrumentation_version must be a string") + else . end + | .schema_url as $schema + | if $schema == null or $schema == "https://unknown/unknown" or ($schema | test("^https?://[^/]+(/[^/]+)*/[0-9]+\\.[0-9]+\\.[0-9]+(-[0-9A-Za-z.-]+)?$") | not) + then error("the registry schema_url must name a schema version") else . end + | {v2: true, exclude_deprecated: $exclude_deprecated, stable_only: $stable_only} as $opts + | (semconv_grouped_metrics($opts) | map(.metrics) | add // [] | map(.name)) as $metrics + | (.registry.spans // [] | stability_filter($opts) | code_generation_exclude_filter($opts) | deprecated_filter($opts) | map(.type)) as $spans + | (.registry.entities // [] | stability_filter($opts) | code_generation_exclude_filter($opts) | deprecated_filter($opts) | map(.type)) as $entities + | ([($metrics + (.refinements.metrics // [] | stability_filter($opts) | code_generation_exclude_filter($opts) | deprecated_filter($opts) | map(select(.id != .name and (.name as $base | $metrics | any(. == $base))) | .id)))[] | ns | .namespace + "metric"] + + [($spans + (.refinements.spans // [] | stability_filter($opts) | code_generation_exclude_filter($opts) | deprecated_filter($opts) | map(select(.id != .type and (.type as $base | $spans | any(. == $base))) | .id)))[] | ns | .namespace + "span"] + + [((.registry.events // [] | stability_filter($opts) | code_generation_exclude_filter($opts) | deprecated_filter($opts) | map(.name)) + (.refinements.events // [] | stability_filter($opts) | code_generation_exclude_filter($opts) | deprecated_filter($opts) | map(.id)))[] | ns | .namespace + "event"] + + [($entities + (.refinements.entities // [] | stability_filter($opts) | code_generation_exclude_filter($opts) | deprecated_filter($opts) | map(select(.id != .type and (.type as $base | $entities | any(. == $base))) | .id)))[] | ns | .namespace + "entity"] | unique) as $signal_modules + | semconv_grouped_attributes($opts) + | map(.attributes) | add // [] | map(.key | ns | .namespace + "attr") | unique + | {signal_modules: $signal_modules, has_metrics: ($signal_modules | any(endswith("metric"))), has_spans: ($signal_modules | any(endswith("span"))), has_events: ($signal_modules | any(endswith("event"))), schema_literal: ($schema | rust_literal), namespaces: .} + + - template: attributes.rs.j2 + application_mode: each + file_name: "{{ ctx.name }}.rs" + filter: |- + def title: split(".") | map(split("_")) | flatten | map(.[0:1] as $first | ($first | ascii_upcase) + .[1:]) | join(""); + def keyword: . as $name | ["as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref", "return", "self", "Self", "static", "struct", "super", "trait", "true", "type", "unsafe", "use", "where", "while", "async", "await", "dyn", "abstract", "become", "box", "do", "final", "gen", "macro", "override", "priv", "try", "typeof", "unsized", "virtual", "yield"] | any(. == $name); + def valid_name: test("^[A-Za-z][A-Za-z0-9_]*(\\.[A-Za-z][A-Za-z0-9_]*)*$"); + def ns: split(".") as $s + | if ($s | length) > 1 and ($vendor_prefixes | any(. == $s[0])) + then {namespace: $s[1], short_name: ($s[2:] | join("."))} + else {namespace: $s[0], short_name: ($s[1:] | join("."))} end | if (.namespace | test("^[a-z][a-z0-9_]*$") | not) + then error("namespace \(.namespace) is not a supported Rust module name") else . end; + def value_type($key): + if type != "object" then . + elif (.members | length) == 0 then error("attribute \($key) has no enum members left after filtering") + elif (.members | all(.value | type == "string")) then "string" + elif (.members | all(.value | type == "boolean")) then "boolean" + elif (.members | all(.value | type == "number" and . == floor)) then "int" + elif (.members | all(.value | type == "number")) then "double" + else error("attribute \($key) has enum members of more than one value type") end; + def rust_literal: + if type == "string" then explode | map([.] | implode | tojson | gsub("\\\\b"; "\\u{0008}") | gsub("\\\\f"; "\\u{000c}") | gsub("\\\\u(?[0-9a-fA-F]{4})"; "\\u{\(.code)}") | .[1:-1]) | join("") | "\"" + . + "\"" + else tostring end; + {v2: true, exclude_deprecated: $exclude_deprecated, stable_only: $stable_only} as $opts + | semconv_grouped_attributes($opts) + | map(.attributes) | add // [] + | map(if (.key | valid_name | not) then error("attribute \(.key) is not a supported Rust attribute name") else . end) + | map(. + (.key | ns)) + | map(if .short_name == "" then error("attribute \(.key) has no name after its namespace") else . end) + | map(. + {name: ((.short_name | title) + "Attr"), value_type: (.key as $key | .type | value_type($key)), key_literal: (.key | rust_literal), deprecated_literal: (if .deprecated then (.deprecated.note // .deprecated.renamed_to // .deprecated.reason | rust_literal) else null end)}) + | map(if (.value_type as $t | ["string", "int", "double", "boolean", "string[]", "int[]", "double[]", "boolean[]"] | any(. == $t) | not) + then error("attribute \(.key) has an unsupported Rust value type") else . end) + | map(if .value_type == "int" and (.type | type) == "object" + and (.type.members | any(.value >= 9223372036854775808 or .value < -9223372036854775808 or (.value | tostring | test("^-?[0-9]+$") | not))) + then error("attribute \(.key) has an enum value that cannot be represented by Rust i64") else . end) + | map(if (.type | type) == "object" then + .key as $key | .deprecated_literal as $attribute_deprecated | .type.members |= map( + if (.id | valid_name | not) then error("enum member \(.id) of \($key) is not a supported Rust identifier") else . end + | . + {name: (.id | title), literal: (.value | rust_literal), deprecated_literal: (if .deprecated then (.deprecated.note // .deprecated.renamed_to // .deprecated.reason | rust_literal) else $attribute_deprecated end)} + | if (.name | keyword) then error("enum member \(.id) of \($key) becomes a Rust keyword") else . end) + | (.type.members | group_by(.name) | map(select(length > 1))) as $collisions + | if ($collisions | length) > 0 then error("attribute \($key) has enum members that render to the same Rust identifier") else . end + else . end) + | group_by(.namespace) + | map(if (group_by(.name) | any(length > 1)) then error("namespace \(.[0].namespace) has attributes that render to the same Rust identifier") else . end) + + | map({name: (.[0].namespace + "attr"), attributes: sort_by(.key)}) + + - template: metrics.rs.j2 + filter: |- + def ns: if (test("^[A-Za-z][A-Za-z0-9_]*(\\.[A-Za-z][A-Za-z0-9_]*)*$") | not) + then error("name \(.) is not a supported Rust signal identifier") else . end + | split(".") as $s + | if ($s | length) > 1 and ($vendor_prefixes | any(. == $s[0])) + then {namespace: $s[1], prefix: ($s[0:2] | join(".")), short_name: ($s[2:] | join(".")), param: ($s[1:] | join("."))} + else {namespace: $s[0], prefix: $s[0], short_name: ($s[1:] | join(".")), param: ($s | join("."))} + end | if (.namespace | test("^[a-z][a-z0-9_]*$") | not) + then error("namespace \(.namespace) is not a supported Rust module name") else . end; + def enum_value_type($key): + .members as $members + | if ($members | length) == 0 + then error("attribute \($key) has no enum members left after exclude_deprecated or stable_only, so no value of it could be built") + elif ($members | all(.value | type == "string")) then "string" + elif ($members | all(.value | type == "boolean")) then "boolean" + elif ($members | all(.value | type == "number" and . == floor)) then "int" + elif ($members | all(.value | type == "number")) then "double" + else error("attribute \($key) has enum members of more than one value type, which no single Rust type can hold") + end; + def kept($o): + stability_filter($o) | code_generation_exclude_filter($o) | deprecated_filter($o) + | map(.attributes = (if .attributes == null then .attributes else .attributes | stability_filter($o) | code_generation_exclude_filter($o) | deprecated_filter($o) end)); + def instruments($o): + (semconv_grouped_metrics($o) | map(.metrics) | add // []) as $metrics + | ($metrics | map(.name)) as $kept_bases + | (.registry.metrics // [] | map({key: .name, value: .}) | from_entries) as $bases + | $metrics + (.refinements.metrics // [] | map(select(.id != .name)) | kept($o) + | map(select(.name as $base | $kept_bases | any(. == $base))) + | map(. as $r | $bases[$r.name] as $b + | if ($r.annotations.code_generation.metric_value_type // null) != ($b.annotations.code_generation.metric_value_type // null) + then error("metric refinement \($r.id) sets a different code_generation.metric_value_type than \($r.name), but both record into one instrument") + elif ($r.annotations.aggregation // null) != ($b.annotations.aggregation // null) + then error("metric refinement \($r.id) sets a different annotations.aggregation than \($r.name), but both record into one instrument") + else . + {name: $r.id, refines: $r.name, instrument_name: $r.name, description: $b.brief, unit: $b.unit} end)); + {v2: true, exclude_deprecated: $exclude_deprecated, stable_only: $stable_only} as $opts + | (semconv_grouped_attributes($opts) | map(.attributes) | add // [] | map(.key)) as $rendered + | (instruments({v2: true}) + | map({key: .name, value: [(.attributes // [])[] | select(.requirement_level == "required") | .key]}) + | from_entries) as $declared_required + | instruments($opts) + | map({instrument_name: .name, description: .brief} + .) + | map(if (.annotations.code_generation.metric_value_type // "") as $t | ["int", "double"] | any(. == $t) | not + then error("metric \(.name) must set code_generation.metric_value_type to int or double") + elif (.instrument as $kind | ["counter", "updowncounter", "histogram", "gauge"] | any(. == $kind) | not) + then error("metric \(.name) has an unsupported Rust instrument") else . end) + | map(.name as $metric + | (($declared_required[$metric] // []) - [(.attributes // [])[] | .key]) as $dropped + | if ($dropped | length) > 0 + then error("metric \($metric) requires \($dropped | join(", ")), which exclude_deprecated or stable_only leaves out, so its generated method could not set them") + else . end) + | map(.name as $metric + | .unit as $unit + | (.annotations.aggregation // null) as $aggregation + | ($aggregation | if type == "object" and (.parameters | type) == "object" then .parameters.boundaries else null end) as $bounds + | if $aggregation != null then + if .instrument != "histogram" + then error("metric \($metric) sets annotations.aggregation, which the Rust template supports only on a histogram") + elif ($aggregation | type) != "object" or ($aggregation.parameters // {} | type) != "object" + then error("metric \($metric) must set annotations.aggregation to an object with a method and parameters") + elif $aggregation.method != "explicithistogram" + then error("metric \($metric) sets annotations.aggregation.method to \($aggregation.method | tostring), but the Rust template supports only explicithistogram") + elif (($aggregation | keys) - ["method", "parameters"] | length) > 0 or (($aggregation.parameters // {} | keys) - ["boundaries"] | length) > 0 + then error("metric \($metric) sets annotations.aggregation fields other than method and parameters.boundaries, which the Rust API cannot express") + elif ($bounds | type) != "array" or ($bounds | length) == 0 or ($bounds | any(type != "number")) + then error("metric \($metric) must set annotations.aggregation.parameters.boundaries to a non-empty list of numbers") + elif ([range(1; $bounds | length) | select($bounds[.] <= $bounds[. - 1])] | length) > 0 + then error("metric \($metric) must list annotations.aggregation.parameters.boundaries in strictly increasing order") + else . + {bucket_boundaries: $bounds} end + elif .instrument == "histogram" and ($unit | type) == "string" and ($histogram_boundaries_by_unit | has($unit)) then + ($histogram_boundaries_by_unit[$unit]) as $default_bounds + | if ($default_bounds | type) != "array" or ($default_bounds | length) == 0 or ($default_bounds | any(type != "number")) + then error("metric \($metric) resolves histogram_boundaries_by_unit for unit \($unit) to boundaries that are not a non-empty list of numbers") + elif ([range(1; $default_bounds | length) | select($default_bounds[.] <= $default_bounds[. - 1])] | length) > 0 + then error("metric \($metric) resolves histogram_boundaries_by_unit for unit \($unit) to boundaries that are not in strictly increasing order") + else . + {bucket_boundaries: $default_bounds} end + else . end) + | map(. + (.name | ns | {namespace, prefix, short_name})) + | map(if .short_name == "" + then error("metric \(.name) has no name left after its namespace, so it cannot become a Rust identifier") + else . end) + | map(.name as $metric + | .attributes |= ((. // []) | sort_by(.key) | map( + (.key | ns) as $attr_ns + | if (.key as $k | $rendered | any(. == $k)) + then . + {attr_namespace: $attr_ns.namespace, attr_short_name: $attr_ns.short_name, attr_param: $attr_ns.param} + elif ($imported_crate_paths | has($attr_ns.namespace)) + then (if (.type | type) == "object" and (((.type.members // []) | length) == 0) + then error("metric \($metric) references imported attribute \(.key) whose enum has no members, so no value of it could be built") + elif (.type | type) != "object" and ([.type] | inside(["string", "string[]", "int", "int[]", "double", "double[]", "boolean", "boolean[]"]) | not) + then error("metric \($metric) references imported attribute \(.key) of type \(.type | tostring), which the Rust template does not render") + else . end + | . + {attr_namespace: $attr_ns.namespace, attr_short_name: $attr_ns.short_name, attr_param: $attr_ns.param, + attr_import_path: ($imported_crate_paths[$attr_ns.namespace] + "::" + $attr_ns.namespace + "attr")}) + else . + {imported: true, attr_short_name: .key, attr_param: .key, + attr_type: (if (.type | type) == "object" + then (.key as $key | .type | enum_value_type($key)) + elif ([.type] | inside(["string", "string[]", "int", "int[]", "double", "double[]", "boolean", "boolean[]"])) + then .type + else error("metric \($metric) references imported attribute \(.key) of type \(.type | tostring), which the Rust template does not render") end)} + end))) + | map(.attributes |= map(. + {requirement_doc: (.requirement_level + | if type == "object" and has("conditionally_required") then "Conditionally required: \(.conditionally_required)" + elif type == "object" and has("recommended") then "Recommended: \(.recommended)" + elif . == "opt_in" then "Opt-in: the convention records it only when a user asks for it." + else null end)})) + | map(.required = (.attributes | map(select(.requirement_level == "required")))) + | map(if (.required | any(.attr_param == "self" or .attr_param == "Self" or .attr_param == "crate" or .attr_param == "super")) + then error("signal \(.id // .name) has a required attribute that becomes a reserved Rust parameter") else . end) + | map(.optional = (.attributes | map(select(.requirement_level != "required")))) + | map(if (.optional | any(.attr_short_name == "self" or .attr_short_name == "Self")) + then error("signal \(.id // .name) has an optional attribute that becomes the reserved Rust Self identifier") else . end) + | map( + (.required + | group_by(.attr_param | gsub("[._]"; "") | ascii_downcase) + | map(select(length > 1) | map(.key) | join(" and "))) as $required_collisions + | (.optional + | group_by(.attr_short_name | gsub("[._]"; "") | ascii_downcase) + | map(select(length > 1) | map(.key) | join(" and "))) as $optional_collisions + | if ($required_collisions | length) > 0 + then error("metric \(.name) has required attributes that render to the same Rust parameter: \($required_collisions | join("; "))") + elif ($optional_collisions | length) > 0 + then error("metric \(.name) has optional attributes that render to the same Rust identifier: \($optional_collisions | join("; "))") + else . end + ) + | map(if (.required | any(.attr_param == "value" or .attr_param == "options" or .attr_param == "attributes")) + then error("metric \(.name) has a required attribute whose parameter shadows its generated method") else . end) + | group_by(.namespace) + | map( + ([.[] | .name as $metric | (.short_name | gsub("[._]"; "") | ascii_downcase) as $short | .instrument as $kind + | ([$short + $kind, $short + $kind + "attr", $short + $kind + "observer", $short + $kind + "callback"] + + (if $kind == "histogram" then [] else [$short + "observable" + $kind] end))[] + | {identifier: ., name: $metric}] + | group_by(.identifier) + | map(select(length > 1) | map(.name) | unique | join(" and ")) + | unique) as $collisions + | if ($collisions | length) > 0 + then error("package \(.[0].namespace)metric has metrics that render to the same Rust identifier: \($collisions | join("; "))") + else . end + ) + | map({namespace: .[0].namespace, prefix: .[0].prefix, metrics: sort_by(.name), + uses_attributes: ([.[] | .attributes[]] | length > 0)}) + application_mode: each + file_name: "{{ ctx.namespace }}metric.rs" + + - template: spans.rs.j2 + filter: |- + def ns: if (test("^[A-Za-z][A-Za-z0-9_]*(\\.[A-Za-z][A-Za-z0-9_]*)*$") | not) + then error("name \(.) is not a supported Rust signal identifier") else . end + | split(".") as $s + | if ($s | length) > 1 and ($vendor_prefixes | any(. == $s[0])) + then {namespace: $s[1], prefix: ($s[0:2] | join(".")), short_name: ($s[2:] | join(".")), param: ($s[1:] | join("."))} + else {namespace: $s[0], prefix: $s[0], short_name: ($s[1:] | join(".")), param: ($s | join("."))} + end | if (.namespace | test("^[a-z][a-z0-9_]*$") | not) + then error("namespace \(.namespace) is not a supported Rust module name") else . end; + def enum_value_type($key): + .members as $members + | if ($members | length) == 0 + then error("attribute \($key) has no enum members left after exclude_deprecated or stable_only, so no value of it could be built") + elif ($members | all(.value | type == "string")) then "string" + elif ($members | all(.value | type == "boolean")) then "boolean" + elif ($members | all(.value | type == "number" and . == floor)) then "int" + elif ($members | all(.value | type == "number")) then "double" + else error("attribute \($key) has enum members of more than one value type, which no single Rust type can hold") + end; + def norm: gsub("[._]"; "") | ascii_downcase; + def kept($o): + stability_filter($o) | code_generation_exclude_filter($o) | deprecated_filter($o) + | map(.attributes = (if .attributes == null then .attributes else .attributes | stability_filter($o) | code_generation_exclude_filter($o) | deprecated_filter($o) end)); + def spans($o): + (.registry.spans // [] | kept($o) | map(. + {id: .type})) as $spans + | ($spans | map(.type)) as $kept_bases + | $spans + (.refinements.spans // [] | map(select(.id != .type)) | kept($o) + | map(select(.type as $base | $kept_bases | any(. == $base)) | . + {refines: .type})); + def name_parts: + .required as $required + | [(.name.note // "") | scan("`[^`]*`"; "g")] as $codes + | if ($codes | length) != 1 then null + else ($codes[0] | .[1:-1]) as $code + | [$code | scan("\\{[^{}]*\\}"; "g") | .[1:-1]] as $keys + | [$code | splits("\\{[^{}]*\\}")] as $texts + | if ($keys | length) == 0 + or ($texts | any(test("[{}]"))) + or ($keys | any(. as $k | $required | any(.key == $k) | not)) + then null + else [range(0; $keys | length) as $i | {text: $texts[$i]}, ($keys[$i] as $k | {attr: ($required | map(select(.key == $k)) | .[0])})] + + [{text: $texts[-1]}] + | map(select(.text != "")) + end + end; + {v2: true, exclude_deprecated: $exclude_deprecated, stable_only: $stable_only} as $opts + | (semconv_grouped_attributes($opts) | map(.attributes) | add // [] | map(.key)) as $rendered + | (spans({v2: true}) + | map({key: .id, value: [(.attributes // [])[] | select(.requirement_level == "required") | .key]}) + | from_entries) as $declared_required + | spans($opts) + | map(.id as $span + | (($declared_required[$span] // []) - [(.attributes // [])[] | .key]) as $dropped + | if ($dropped | length) > 0 + then error("span \($span) requires \($dropped | join(", ")), which exclude_deprecated or stable_only leaves out, so its generated starter could not set them") + else . end) + | map(. + (.id | ns | {namespace, prefix, short_name})) + | map(.id as $span + | .attributes |= ((. // []) | sort_by(.key) | map( + (.key | ns) as $attr_ns + | if (.key as $k | $rendered | any(. == $k)) + then . + {attr_namespace: $attr_ns.namespace, attr_short_name: $attr_ns.short_name, attr_param: $attr_ns.param} + elif ($imported_crate_paths | has($attr_ns.namespace)) + then (if (.type | type) == "object" and (((.type.members // []) | length) == 0) + then error("span \($span) references imported attribute \(.key) whose enum has no members, so no value of it could be built") + elif (.type | type) != "object" and ([.type] | inside(["string", "string[]", "int", "int[]", "double", "double[]", "boolean", "boolean[]"]) | not) + then error("span \($span) references imported attribute \(.key) of type \(.type | tostring), which the Rust template does not render") + else . end + | . + {attr_namespace: $attr_ns.namespace, attr_short_name: $attr_ns.short_name, attr_param: $attr_ns.param, + attr_import_path: ($imported_crate_paths[$attr_ns.namespace] + "::" + $attr_ns.namespace + "attr")}) + else . + {imported: true, attr_short_name: .key, attr_param: .key, + attr_type: (if (.type | type) == "object" + then (.key as $key | .type | enum_value_type($key)) + elif ([.type] | inside(["string", "string[]", "int", "int[]", "double", "double[]", "boolean", "boolean[]"])) + then .type + else error("span \($span) references imported attribute \(.key) of type \(.type | tostring), which the Rust template does not render") end)} + end))) + | map(.attributes |= map(. + {requirement_doc: (.requirement_level + | if type == "object" and has("conditionally_required") then "Conditionally required: \(.conditionally_required)" + elif type == "object" and has("recommended") then "Recommended: \(.recommended)" + elif . == "opt_in" then "Opt-in: the convention records it only when a user asks for it." + else null end)})) + | map(.required = (.attributes | map(select(.requirement_level == "required")))) + | map(if (.required | any(.attr_param == "self" or .attr_param == "Self" or .attr_param == "crate" or .attr_param == "super")) + then error("signal \(.id // .name) has a required attribute that becomes a reserved Rust parameter") else . end) + | map(.optional = (.attributes | map(select(.requirement_level != "required")))) + | map(if (.optional | any(.attr_short_name == "self" or .attr_short_name == "Self")) + then error("signal \(.id // .name) has an optional attribute that becomes the reserved Rust Self identifier") else . end) + | map(.has_late_attrs = (.optional | any(.sampling_relevant != true))) + | map(.name_parts = name_parts) + | map(.name_template = (if .name_parts then [.name.note | scan("`[^`]*`"; "g")][0][1:-1] else null end)) + | map( + (.required + | group_by(.attr_param | norm) + | map(select(length > 1) | map(.key) | join(" and "))) as $required_collisions + | (.optional + | group_by(.attr_short_name | norm) + | map(select(length > 1) | map(.key) | join(" and "))) as $optional_collisions + | (.required | map(select(.attr_param | norm | . as $p | ["context", "tracer", "name", "options", "option", "attributes", "span"] | any(. == $p))) | map(.key)) as $shadowing + | if ($required_collisions | length) > 0 + then error("span \(.id) has required attributes that render to the same Rust parameter: \($required_collisions | join("; "))") + elif ($optional_collisions | length) > 0 + then error("span \(.id) has optional attributes that render to the same Rust identifier: \($optional_collisions | join("; "))") + elif ($shadowing | length) > 0 + then error("span \(.id) has required attributes whose Rust parameter would shadow a name its starter declares: \($shadowing | join(", "))") + else . end + ) + | group_by(.namespace) + | map( + ([.[] | . as $def | .id as $span | (.short_name | norm) as $short + | (["P:start" + $short, "P:" + $short + "span"] + + (if $def.name_parts then [] else ["P:" + $short + "name", "P:new" + $short + "name"] end) + + (if ($def.optional | length) > 0 then ["P:" + $short + "startattr"] else [] end) + + (if $def.has_late_attrs then ["P:" + $short + "attr"] else [] end) + + ($def.optional | map(.attr_short_name | norm) | map("P:with" + $short + ., "p:" + $short + "span" + .)))[] + | {identifier: ., name: $span}] + | group_by(.identifier) + | map(select(length > 1) | map(.name) | unique | join(" and ")) + | unique) as $collisions + | if ($collisions | length) > 0 + then error("package \(.[0].namespace)span has spans that render to the same Rust identifier: \($collisions | join("; "))") + else . end + ) + | map({namespace: .[0].namespace, prefix: .[0].prefix, spans: sort_by(.id), + uses_attributes: ([.[] | .attributes[]] | length > 0)}) + application_mode: each + file_name: "{{ ctx.namespace }}span.rs" + + - template: events.rs.j2 + filter: |- + def ns: if (test("^[A-Za-z][A-Za-z0-9_]*(\\.[A-Za-z][A-Za-z0-9_]*)*$") | not) + then error("name \(.) is not a supported Rust signal identifier") else . end + | split(".") as $s + | if ($s | length) > 1 and ($vendor_prefixes | any(. == $s[0])) + then {namespace: $s[1], prefix: ($s[0:2] | join(".")), short_name: ($s[2:] | join(".")), param: ($s[1:] | join("."))} + else {namespace: $s[0], prefix: $s[0], short_name: ($s[1:] | join(".")), param: ($s | join("."))} + end | if (.namespace | test("^[a-z][a-z0-9_]*$") | not) + then error("namespace \(.namespace) is not a supported Rust module name") else . end; + def enum_value_type($key): + .members as $members + | if ($members | length) == 0 + then error("attribute \($key) has no enum members left after exclude_deprecated or stable_only, so no value of it could be built") + elif ($members | all(.value | type == "string")) then "string" + elif ($members | all(.value | type == "boolean")) then "boolean" + elif ($members | all(.value | type == "number" and . == floor)) then "int" + elif ($members | all(.value | type == "number")) then "double" + else error("attribute \($key) has enum members of more than one value type, which no single Rust type can hold") + end; + def norm: gsub("[._]"; "") | ascii_downcase; + def kept($o): + stability_filter($o) | code_generation_exclude_filter($o) | deprecated_filter($o) + | map(.attributes = (if .attributes == null then .attributes else .attributes | stability_filter($o) | code_generation_exclude_filter($o) | deprecated_filter($o) end)); + def events($o): + [.registry.events // [] | .[].name] as $bases + | (.registry.events // [] | kept($o) | map(. + {id: .name})) + + (.refinements.events // [] | map(select(.id as $id | $bases | any(. == $id) | not)) | kept($o)); + {v2: true, exclude_deprecated: $exclude_deprecated, stable_only: $stable_only} as $opts + | (semconv_grouped_attributes($opts) | map(.attributes) | add // [] | map(.key)) as $rendered + | (events({v2: true}) + | map({key: .id, value: [(.attributes // [])[] | select(.requirement_level == "required") | .key]}) + | from_entries) as $declared_required + | events($opts) + | map(.id as $event + | (($declared_required[$event] // []) - [(.attributes // [])[] | .key]) as $dropped + | if ($dropped | length) > 0 + then error("event \($event) requires \($dropped | join(", ")), which exclude_deprecated or stable_only leaves out, so its generated Emit function could not set them") + else . end) + | map(. + (.id | ns | {namespace, prefix, short_name})) + | map(.id as $event + | .attributes |= ((. // []) | sort_by(.key) | map( + (.key | ns) as $attr_ns + | if (.key as $k | $rendered | any(. == $k)) + then . + {attr_namespace: $attr_ns.namespace, attr_short_name: $attr_ns.short_name, attr_param: $attr_ns.param} + elif ($imported_crate_paths | has($attr_ns.namespace)) + then (if (.type | type) == "object" and (((.type.members // []) | length) == 0) + then error("event \($event) references imported attribute \(.key) whose enum has no members, so no value of it could be built") + elif (.type | type) != "object" and ([.type] | inside(["string", "string[]", "int", "int[]", "double", "double[]", "boolean", "boolean[]"]) | not) + then error("event \($event) references imported attribute \(.key) of type \(.type | tostring), which the Rust template does not render") + else . end + | . + {attr_namespace: $attr_ns.namespace, attr_short_name: $attr_ns.short_name, attr_param: $attr_ns.param, + attr_import_path: ($imported_crate_paths[$attr_ns.namespace] + "::" + $attr_ns.namespace + "attr")}) + else . + {imported: true, attr_short_name: .key, attr_param: .key, + attr_type: (if (.type | type) == "object" + then (.key as $key | .type | enum_value_type($key)) + elif ([.type] | inside(["string", "string[]", "int", "int[]", "double", "double[]", "boolean", "boolean[]"])) + then .type + else error("event \($event) references imported attribute \(.key) of type \(.type | tostring), which the Rust template does not render") end)} + end))) + | map(.attributes |= map(. + {requirement_doc: (.requirement_level + | if type == "object" and has("conditionally_required") then "Conditionally required: \(.conditionally_required)" + elif type == "object" and has("recommended") then "Recommended: \(.recommended)" + elif . == "opt_in" then "Opt-in: the convention records it only when a user asks for it." + else null end)})) + | map(.required = (.attributes | map(select(.requirement_level == "required")))) + | map(if (.required | any(.attr_param == "self" or .attr_param == "Self" or .attr_param == "crate" or .attr_param == "super")) + then error("signal \(.id // .name) has a required attribute that becomes a reserved Rust parameter") else . end) + | map(.optional = (.attributes | map(select(.requirement_level != "required")))) + | map(if (.optional | any(.attr_short_name == "self" or .attr_short_name == "Self")) + then error("signal \(.id // .name) has an optional attribute that becomes the reserved Rust Self identifier") else . end) + | map( + (.required + | group_by(.attr_param | norm) + | map(select(length > 1) | map(.key) | join(" and "))) as $required_collisions + | (.optional + | group_by(.attr_short_name | norm) + | map(select(length > 1) | map(.key) | join(" and "))) as $optional_collisions + | (.optional | map(select(.attr_short_name | norm | . as $p | ["severity", "timestamp"] | any(. == $p))) | map(.key)) as $reserved + | (.required | map(select(.attr_param | norm | . as $p | ["context", "logger", "options", "option", "record", "attributes", "severity", "timestamp", "span", "spancontext"] | any(. == $p))) | map(.key)) as $shadowing + | if ($required_collisions | length) > 0 + then error("event \(.id) has required attributes that render to the same Rust parameter: \($required_collisions | join("; "))") + elif ($optional_collisions | length) > 0 + then error("event \(.id) has optional attributes that render to the same Rust identifier: \($optional_collisions | join("; "))") + elif ($reserved | length) > 0 + then error("event \(.id) has optional attributes whose option would share a name with its severity or timestamp option: \($reserved | join(", "))") + elif ($shadowing | length) > 0 + then error("event \(.id) has required attributes whose Rust parameter would shadow a name its Emit function declares: \($shadowing | join(", "))") + else . end + ) + | group_by(.namespace) + | map( + ([.[] | . as $def | .id as $event | (.short_name | norm) as $short + | (["P:emit" + $short, "P:" + (if $short == "" then "event" else $short end) + "option", + "P:with" + $short + "severity", "P:with" + $short + "timestamp", + "p:" + $short + "option", "p:" + $short + "eventseverity", "p:" + $short + "eventtimestamp"] + + ($def.optional | map(.attr_short_name | norm) | map("P:with" + $short + ., "p:" + $short + "event" + .)))[] + | {identifier: ., name: $event}] + | group_by(.identifier) + | map(select(length > 1) | map(.name) | unique | join(" and ")) + | unique) as $collisions + | if ($collisions | length) > 0 + then error("package \(.[0].namespace)event has events that render to the same Rust identifier: \($collisions | join("; "))") + else . end + ) + | map({namespace: .[0].namespace, prefix: .[0].prefix, events: sort_by(.id), + uses_attributes: ([.[] | .attributes[] | select(.imported)] | length > 0)}) + application_mode: each + file_name: "{{ ctx.namespace }}event.rs" + + - template: entities.rs.j2 + filter: |- + def ns: if (test("^[A-Za-z][A-Za-z0-9_]*(\\.[A-Za-z][A-Za-z0-9_]*)*$") | not) + then error("name \(.) is not a supported Rust signal identifier") else . end + | split(".") as $s + | if ($s | length) > 1 and ($vendor_prefixes | any(. == $s[0])) + then {namespace: $s[1], prefix: ($s[0:2] | join(".")), short_name: ($s[2:] | join(".")), param: ($s[1:] | join("."))} + else {namespace: $s[0], prefix: $s[0], short_name: ($s[1:] | join(".")), param: ($s | join("."))} + end | if (.namespace | test("^[a-z][a-z0-9_]*$") | not) + then error("namespace \(.namespace) is not a supported Rust module name") else . end; + def enum_value_type($key): + .members as $members + | if ($members | length) == 0 + then error("attribute \($key) has no enum members left after exclude_deprecated or stable_only, so no value of it could be built") + elif ($members | all(.value | type == "string")) then "string" + elif ($members | all(.value | type == "boolean")) then "boolean" + elif ($members | all(.value | type == "number" and . == floor)) then "int" + elif ($members | all(.value | type == "number")) then "double" + else error("attribute \($key) has enum members of more than one value type, which no single Rust type can hold") + end; + def norm: gsub("[._]"; "") | ascii_downcase; + def kept($o): + stability_filter($o) | code_generation_exclude_filter($o) | deprecated_filter($o) + | map(.identity = (if .identity == null then .identity else .identity | stability_filter($o) | code_generation_exclude_filter($o) | deprecated_filter($o) end)) + | map(.description = (if .description == null then .description else .description | stability_filter($o) | code_generation_exclude_filter($o) | deprecated_filter($o) end)); + def entities($o): + (.registry.entities // [] | kept($o) | map(. + {id: .type})) as $entities + | ($entities | map(.type)) as $kept_bases + | $entities + (.refinements.entities // [] | map(select(.id != .type)) | kept($o) + | map(select(.type as $base | $kept_bases | any(. == $base)) | . + {refines: .type})); + def resolved($entity; $rendered): + (.key | ns) as $attr_ns + | if (.key as $k | $rendered | any(. == $k)) + then . + {attr_namespace: $attr_ns.namespace, attr_short_name: $attr_ns.short_name, attr_param: $attr_ns.param} + elif ($imported_crate_paths | has($attr_ns.namespace)) + then (if (.type | type) == "object" and (((.type.members // []) | length) == 0) + then error("entity \($entity) references imported attribute \(.key) whose enum has no members, so no value of it could be built") + elif (.type | type) != "object" and ([.type] | inside(["string", "string[]", "int", "int[]", "double", "double[]", "boolean", "boolean[]"]) | not) + then error("entity \($entity) references imported attribute \(.key) of type \(.type | tostring), which the Rust template does not render") + else . end + | . + {attr_namespace: $attr_ns.namespace, attr_short_name: $attr_ns.short_name, attr_param: $attr_ns.param, + attr_import_path: ($imported_crate_paths[$attr_ns.namespace] + "::" + $attr_ns.namespace + "attr")}) + else . + {imported: true, attr_short_name: .key, attr_param: .key, + attr_type: (if (.type | type) == "object" + then (.key as $key | .type | enum_value_type($key)) + elif ([.type] | inside(["string", "string[]", "int", "int[]", "double", "double[]", "boolean", "boolean[]"])) + then .type + else error("entity \($entity) references imported attribute \(.key) of type \(.type | tostring), which the Rust template does not render") end)} + end; + {v2: true, exclude_deprecated: $exclude_deprecated, stable_only: $stable_only} as $opts + | (semconv_grouped_attributes($opts) | map(.attributes) | add // [] | map(.key)) as $rendered + | (entities({v2: true}) + | map({key: .id, value: [((.identity // []) + (.description // []))[] | select(.requirement_level == "required") | .key]}) + | from_entries) as $declared_required + | entities($opts) + | map(.id as $entity + | (($declared_required[$entity] // []) - [((.identity // []) + (.description // []))[] | .key]) as $dropped + | if ($dropped | length) > 0 + then error("entity \($entity) requires \($dropped | join(", ")), which exclude_deprecated or stable_only leaves out, so its generated constructor could not set them") + else . end) + | map(. + (.id | ns | {namespace, prefix, short_name})) + | map(.id as $entity + | .identity = ((.identity // []) | sort_by(.key) | map(resolved($entity; $rendered))) + | .description = ((.description // []) | sort_by(.key) | map(resolved($entity; $rendered))) + | .attributes = (.identity + .description)) + | map(. + {identity_doc: (if (.identity | length) > 0 then "Identifies the entity: " + (.identity | map("`" + .key + "`") | join(", ")) + "." else null end), + description_doc: (if (.description | length) > 0 then "Describes the entity: " + (.description | map("`" + .key + "`") | join(", ")) + "." else null end)}) + | map(.attributes |= map(. + {requirement_doc: (.requirement_level + | if type == "object" and has("conditionally_required") then "Conditionally required: \(.conditionally_required)" + elif type == "object" and has("recommended") then "Recommended: \(.recommended)" + elif . == "opt_in" then "Opt-in: the convention records it only when a user asks for it." + else null end)})) + | map(.required = (.attributes | map(select(.requirement_level == "required")))) + | map(if (.required | any(.attr_param == "self" or .attr_param == "Self" or .attr_param == "crate" or .attr_param == "super")) + then error("signal \(.id // .name) has a required attribute that becomes a reserved Rust parameter") else . end) + | map(.optional = (.attributes | map(select(.requirement_level != "required")))) + | map(if (.optional | any(.attr_short_name == "self" or .attr_short_name == "Self")) + then error("signal \(.id // .name) has an optional attribute that becomes the reserved Rust Self identifier") else . end) + | map( + (.required + | group_by(.attr_param | norm) + | map(select(length > 1) | map(.key) | join(" and "))) as $required_collisions + | (.optional + | group_by(.attr_short_name | norm) + | map(select(length > 1) | map(.key) | join(" and "))) as $optional_collisions + | (.required | map(select(.attr_param | norm | . as $p | ["attributes", "options", "option"] | any(. == $p))) | map(.key)) as $shadowing + | if ($required_collisions | length) > 0 + then error("entity \(.id) has required attributes that render to the same Rust parameter: \($required_collisions | join("; "))") + elif ($optional_collisions | length) > 0 + then error("entity \(.id) has optional attributes that render to the same Rust identifier: \($optional_collisions | join("; "))") + elif ($shadowing | length) > 0 + then error("entity \(.id) has required attributes whose Rust parameter would shadow a name its constructor declares: \($shadowing | join(", "))") + else . end + ) + | group_by(.namespace) + | map( + ([.[] | . as $def | .id as $entity | (.short_name | norm) as $short + | (["P:new" + $short + "entity", "P:" + $short + "entity"] + + (if ($def.optional | length) > 0 then ["P:" + $short + "entityattr"] else [] end) + + ($def.optional | map(.attr_short_name | norm) | map("P:with" + $short + "entity" + ., "p:" + $short + "entity" + .)))[] + | {identifier: ., name: $entity}] + | group_by(.identifier) + | map(select(length > 1) | map(.name) | unique | join(" and ")) + | unique) as $collisions + | if ($collisions | length) > 0 + then error("package \(.[0].namespace)entity has entities that render to the same Rust identifier: \($collisions | join("; "))") + else . end + ) + | map({namespace: .[0].namespace, prefix: .[0].prefix, entities: sort_by(.id), + uses_attributes: ([.[] | .attributes[]] | length > 0)}) + application_mode: each + file_name: "{{ ctx.namespace }}entity.rs" + + - template: scope.rs.j2 + filter: &instrumentation_filter |- + {v2: true, exclude_deprecated: $exclude_deprecated, stable_only: $stable_only} as $o + | if ((semconv_grouped_metrics($o) | map(.metrics) | add // [] | length) > 0 + or (.registry.spans // [] | stability_filter($o) | code_generation_exclude_filter($o) | deprecated_filter($o) | length) > 0 + or ((.registry.events // []) + (.refinements.events // []) | stability_filter($o) | code_generation_exclude_filter($o) | deprecated_filter($o) | length) > 0) + then [{}] else [] end + application_mode: each + file_name: scope.rs + + - template: options.rs.j2 + filter: *instrumentation_filter + application_mode: each + file_name: options.rs + + - template: meter.rs.j2 + filter: |- + {v2: true, exclude_deprecated: $exclude_deprecated, stable_only: $stable_only} as $o + | if (semconv_grouped_metrics($o) | map(.metrics) | add // [] | length) > 0 then [{}] else [] end + application_mode: each + file_name: meter.rs + - template: tracer.rs.j2 + filter: |- + {v2: true, exclude_deprecated: $exclude_deprecated, stable_only: $stable_only} as $o + | if (.registry.spans // [] | stability_filter($o) | code_generation_exclude_filter($o) | deprecated_filter($o) | length) > 0 then [{}] else [] end + application_mode: each + file_name: tracer.rs + - template: logger.rs.j2 + filter: |- + def kept($o): + stability_filter($o) | code_generation_exclude_filter($o) | deprecated_filter($o); + {v2: true, exclude_deprecated: $exclude_deprecated, stable_only: $stable_only} as $o + | [.registry.events // [] | .[].name] as $bases + | ((.registry.events // [] | kept($o)) + + (.refinements.events // [] | map(select(.id as $id | $bases | any(. == $id) | not)) | kept($o))) + | if length > 0 then [{}] else [] end + application_mode: each + file_name: logger.rs + +text_maps: + metric_instrument: + counter: Counter + updowncounter: UpDownCounter + histogram: Histogram + gauge: Gauge + rust_type: + string: String + int: i64 + double: f64 + boolean: bool + string[]: Vec + int[]: Vec + double[]: Vec + boolean[]: Vec + rust_array: + string[]: String + int[]: I64 + double[]: F64 + boolean[]: Bool + +comment_formats: + rust: + format: markdown + prefix: "/// " + trim: true +default_comment_format: rust