///|
/// OpenTelemetry span kinds.
///
/// Span kind describes the relationship between this operation and remote
/// systems. It affects how backends render service maps and client/server
/// relationships.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#spankind
pub(all) enum SpanKind {
Internal
Client
Server
Producer
Consumer
} derive(Eq, Compare, Hash, ToJson, Debug)
///|
/// OpenTelemetry status code attached to a completed span.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#set-status
pub(all) enum StatusCode {
Unset
Ok
Error
} derive(Eq, Compare, Hash, ToJson, Debug)
///|
/// Span status consisting of a code and optional description.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#set-status
pub struct Status {
code : StatusCode
description : String?
} derive(Eq, ToJson, Debug)
///|
/// Event recorded on a span timeline.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#add-events
pub struct SpanEvent {
name : String
timestamp_unix_nano : Int64
attributes : Array[@common.KeyValue]
dropped_attributes_count : Int
} derive(Eq, ToJson, Debug)
///|
/// Link from one span to another span context.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#link
pub struct SpanLink {
span_context : @common.SpanContext
attributes : Array[@common.KeyValue]
dropped_attributes_count : Int
} derive(Eq, ToJson, Debug)
///|
/// Per-span limits for attributes, events, and links.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-limits
pub struct SpanLimits {
max_attributes_per_span : Int
max_events_per_span : Int
max_links_per_span : Int
max_attributes_per_event : Int
max_attributes_per_link : Int
} derive(Eq, Compare, Hash, ToJson, Debug)
///|
/// Result of a sampler decision.
///
/// `Drop` creates a non-recording span context, `RecordOnly` records locally
/// without setting the sampled flag, and `RecordAndSample` records and marks the
/// context as sampled for downstream services.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#sampling
pub(all) enum SamplingDecision {
Drop
RecordOnly
RecordAndSample
} derive(Eq, Compare, Hash, ToJson, Debug)
///|
/// Full sampler output, including attributes and trace state to apply to the
/// started span.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#sampling
pub struct SamplingResult {
decision : SamplingDecision
attributes : Array[@common.KeyValue]
trace_state : @common.TraceState
} derive(Eq, ToJson, Debug)
///|
/// Inputs provided to a sampler when a span is started.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#sampling
pub struct SamplingParameters {
parent_context : @context.Context
trace_id : @common.TraceId
name : String
kind : SpanKind
attributes : Array[@common.KeyValue]
links : Array[SpanLink]
} derive(Eq, ToJson, Debug)
///|
/// Sampling policy wrapper.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#sampling
pub struct Sampler {
should_sample_fn : (SamplingParameters) -> SamplingResult
description_fn : () -> String
}
///|
/// Strategy object for generating trace and span identifiers.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#id-generators
pub struct IdGenerator {
new_trace_id_fn : () -> @common.TraceId
new_span_id_fn : () -> @common.SpanId
}
///|
/// Default random ID generator.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#id-generators
pub struct RandomIdGenerator {
inner : IdGenerator
}
///|
/// Configuration for batch span processing.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#batching-processor
pub struct BatchConfig {
max_queue_size : Int
max_export_batch_size : Int
scheduled_delay_millis : Int
export_timeout_millis : Int
} derive(Eq, Compare, Hash, ToJson, Debug)
///|
/// Provider-wide trace configuration.
///
/// Configuration is read when a span starts. Builder methods override defaults
/// derived from environment variables.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#tracer-provider
pub struct Config {
sampler : Sampler
id_generator : IdGenerator
span_limits : SpanLimits
resource : @resource.Resource
}
///|
/// Immutable snapshot of a completed or in-flight span.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-exporter
pub struct SpanData {
name : String
span_context : @common.SpanContext
parent_span_context : @common.SpanContext?
kind : SpanKind
start_time_unix_nano : Int64
end_time_unix_nano : Int64?
attributes : Array[@common.KeyValue]
dropped_attributes_count : Int
events : Array[SpanEvent]
dropped_events_count : Int
links : Array[SpanLink]
dropped_links_count : Int
status : Status
resource : @resource.Resource
instrumentation_scope : @common.InstrumentationScope
} derive(Eq, ToJson, Debug)
///|
/// Exporter callback wrapper for finished span batches.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-exporter
pub struct SpanExporter {
export_fn : async (ArrayView[SpanData]) -> @error.OTelSdkResult
force_flush_fn : async () -> @error.OTelSdkResult
shutdown_fn : async () -> @error.OTelSdkResult
name_fn : () -> String
}
///|
/// Processing hook interface around span start and end.
///
/// Processors should avoid expensive work on `on_start`. Exporters usually run
/// from `on_end` through simple or batch processor implementations.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-processor
pub struct SpanProcessor {
on_start_fn : (@context.Context, SpanData) -> Unit
on_end_fn : async (SpanData) -> Unit
force_flush_fn : async () -> @error.OTelSdkResult
shutdown_fn : async () -> @error.OTelSdkResult
}
///|
/// Processor that exports spans immediately when `Span::end()` is awaited.
///
/// This is useful for tests and local debugging. For network exporters, prefer
/// `BatchSpanProcessor` to reduce request-path latency.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#simple-processor
pub struct SimpleSpanProcessor {
inner : SpanProcessor
}
///|
/// Processor that buffers ended spans and exports them in batches.
///
/// The background loop is not started automatically. Register the provider
/// globally and call `spawn_background_tasks()` from the application runtime.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#batching-processor
pub struct BatchSpanProcessor {
inner : SpanProcessor
state : Ref[BatchProcessorState]
}
///|
struct InMemorySpanExporterState {
finished_spans : Array[SpanData]
is_shutdown : Bool
}
///|
/// Test exporter that keeps finished spans in memory.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-exporter
pub struct InMemorySpanExporter {
state : Ref[InMemorySpanExporterState]
}
///|
struct SpanState {
name : String
span_context : @common.SpanContext
parent_span_context : @common.SpanContext?
kind : SpanKind
start_time_unix_nano : Int64
end_time_unix_nano : Int64?
attributes : Array[@common.KeyValue]
dropped_attributes_count : Int
events : Array[SpanEvent]
dropped_events_count : Int
links : Array[SpanLink]
dropped_links_count : Int
status : Status
resource : @resource.Resource
instrumentation_scope : @common.InstrumentationScope
processors : Array[SpanProcessor]
limits : SpanLimits
is_recording : Bool
has_ended : Bool
}
///|
/// Mutable span handle used while an operation is in flight.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#span
pub struct Span {
state : Ref[SpanState]
}
///|
struct ProviderState {
config : Config
processors : Array[SpanProcessor]
batch_processors : Array[BatchSpanProcessor]
is_shutdown : Bool
}
///|
/// Owns trace configuration, processors, and resource metadata.
///
/// Applications create one provider, configure processors/exporters, and shut it
/// down during application teardown.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#tracer-provider
pub struct SdkTracerProvider {
state : Ref[ProviderState]
}
///|
/// Builder for `SdkTracerProvider`.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#tracer-provider
pub struct SdkTracerProviderBuilder {
mut config : Config
processors : Array[SpanProcessor]
batch_processors : Array[BatchSpanProcessor]
}
///|
/// Tracer scoped to one instrumentation library.
///
/// Tracers are lightweight handles. The instrumentation scope is attached to
/// every span they create.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#tracer
pub struct SdkTracer {
provider : SdkTracerProvider
instrumentation_scope : @common.InstrumentationScope
}
///|
/// Creates a span status.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#set-status
pub fn Status::new(code : StatusCode, description? : String? = None) -> Status {
{ code, description }
}
///|
/// Returns the default unset status.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#set-status
pub fn Status::unset() -> Status {
Status::new(Unset)
}
///|
/// Returns an ok status.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#set-status
pub fn Status::ok() -> Status {
Status::new(Ok)
}
///|
/// Returns an error status with an optional description.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#set-status
pub fn Status::error(description? : String? = None) -> Status {
Status::new(Error, description~)
}
///|
pub impl Default for Status with fn default() -> Status {
Status::unset()
}
///|
/// Creates a span event.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#add-events
pub fn SpanEvent::new(
name : StringView,
attributes? : ArrayView[@common.KeyValue] = [],
timestamp_unix_nano? : Int64 = @utils.now_unix_nano(),
dropped_attributes_count? : Int = 0,
) -> SpanEvent {
{
name: name.to_owned(),
timestamp_unix_nano,
attributes: attributes.to_owned(),
dropped_attributes_count,
}
}
///|
/// Creates a span link.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#link
pub fn SpanLink::new(
span_context : @common.SpanContext,
attributes? : ArrayView[@common.KeyValue] = [],
dropped_attributes_count? : Int = 0,
) -> SpanLink {
{ span_context, attributes: attributes.to_owned(), dropped_attributes_count }
}
///|
/// Creates bounded span limits.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-limits
pub fn SpanLimits::new(
max_attributes_per_span? : Int = 128,
max_events_per_span? : Int = 128,
max_links_per_span? : Int = 128,
max_attributes_per_event? : Int = 128,
max_attributes_per_link? : Int = 128,
) -> SpanLimits {
{
max_attributes_per_span,
max_events_per_span,
max_links_per_span,
max_attributes_per_event,
max_attributes_per_link,
}
}
///|
pub impl Default for SpanLimits with fn default() -> SpanLimits {
SpanLimits::new()
}
///|
/// Creates a sampling result.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#sampling
pub fn SamplingResult::new(
decision : SamplingDecision,
attributes? : ArrayView[@common.KeyValue] = [],
trace_state? : @common.TraceState = Default::default(),
) -> SamplingResult {
{ decision, attributes: attributes.to_owned(), trace_state }
}
///|
/// Converts a sampling decision into trace flags for the created span context.
fn sampled_trace_flags(decision : SamplingDecision) -> @common.TraceFlags {
let trace_flags : @common.TraceFlags = Default::default()
trace_flags.with_sampled(decision == RecordAndSample)
}
///|
fn sampling_description(name : String) -> () -> String {
() => name
}
///|
/// Builds a sampler from callbacks.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#sampling
pub fn Sampler::new(
should_sample_fn : (SamplingParameters) -> SamplingResult,
description_fn : () -> String,
) -> Sampler {
{ should_sample_fn, description_fn }
}
///|
/// Evaluates the sampling policy.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#shouldsample
pub fn Sampler::should_sample(
self : Sampler,
parameters : SamplingParameters,
) -> SamplingResult {
(self.should_sample_fn)(parameters)
}
///|
/// Returns a human-readable description of the sampler.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#sampling
pub fn Sampler::description(self : Sampler) -> String {
(self.description_fn)()
}
///|
/// Sampler that records and samples every span.
///
/// This is useful for development but can be expensive in high-throughput
/// production services.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#sampling
pub fn Sampler::always_on() -> Sampler {
Sampler::new(
_ => SamplingResult::new(RecordAndSample),
sampling_description("always_on"),
)
}
///|
/// Sampler that drops every span.
///
/// Dropped spans still carry a valid context for propagation, but they do not
/// record events, attributes, or processor callbacks.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#sampling
pub fn Sampler::always_off() -> Sampler {
Sampler::new(
_ => SamplingResult::new(Drop),
sampling_description("always_off"),
)
}
///|
fn clamp_ratio(ratio : Double) -> Double {
if ratio <= 0.0 {
0.0
} else if ratio >= 1.0 {
1.0
} else {
ratio
}
}
///|
fn trace_id_prefix(trace_id : @common.TraceId) -> Int {
@string.parse_int(trace_id.to_hex()[:8].to_owned(), base=16) catch {
_ => 0
}
}
///|
fn parse_env_int(key : StringView) -> Int? {
match @utils.getenv(key) {
Some(value) => {
let value = value.trim()
if value == "" {
None
} else {
Some(@string.parse_int(value.to_owned()) catch { _ => return None })
}
}
None => None
}
}
///|
fn parse_decimal(value : StringView) -> Double? {
let value = value.trim()
if value == "" {
return None
}
let mut whole = 0.0
let mut fraction = 0.0
let mut divisor = 1.0
let mut seen_digit = false
let mut seen_dot = false
for _, ch in value {
if ch == '.' {
if seen_dot {
return None
}
seen_dot = true
continue
}
if ch < '0' || ch > '9' {
return None
}
seen_digit = true
let digit = Double::from_int(ch.to_int() - '0'.to_int())
if seen_dot {
divisor = divisor * 10.0
fraction = fraction + digit / divisor
} else {
whole = whole * 10.0 + digit
}
}
if !seen_digit {
None
} else {
Some(whole + fraction)
}
}
///|
fn parse_env_ratio(key : StringView) -> Double? {
match @utils.getenv(key) {
Some(value) => parse_decimal(value)
None => None
}
}
///|
fn env_or(key : StringView, fallback : Int) -> Int {
match parse_env_int(key) {
Some(value) => value
None => fallback
}
}
///|
fn ratio_arg_or_default() -> Double {
match parse_env_ratio("OTEL_TRACES_SAMPLER_ARG") {
Some(ratio) => ratio
None => 1.0
}
}
///|
fn sampler_from_env() -> Sampler {
let default_sampler = Sampler::parent_based(Sampler::always_on())
match @utils.getenv("OTEL_TRACES_SAMPLER") {
Some(value) => {
let value = value.trim()
if value == "always_on" {
Sampler::always_on()
} else if value == "always_off" {
Sampler::always_off()
} else if value == "traceidratio" {
Sampler::trace_id_ratio(ratio_arg_or_default())
} else if value == "parentbased_always_on" {
Sampler::parent_based(Sampler::always_on())
} else if value == "parentbased_always_off" {
Sampler::parent_based(Sampler::always_off())
} else if value == "parentbased_traceidratio" {
Sampler::parent_based(Sampler::trace_id_ratio(ratio_arg_or_default()))
} else {
default_sampler
}
}
None => default_sampler
}
}
///|
fn span_limits_from_env() -> SpanLimits {
SpanLimits::new(
max_attributes_per_span=env_or("OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", 128),
max_events_per_span=env_or("OTEL_SPAN_EVENT_COUNT_LIMIT", 128),
max_links_per_span=env_or("OTEL_SPAN_LINK_COUNT_LIMIT", 128),
)
}
///|
/// Sampler that records and samples according to a trace ID ratio.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#sampling
pub fn Sampler::trace_id_ratio(ratio : Double) -> Sampler {
let ratio = clamp_ratio(ratio)
Sampler::new(
(parameters : SamplingParameters) => {
if ratio <= 0.0 {
SamplingResult::new(Drop)
} else if ratio >= 1.0 {
SamplingResult::new(RecordAndSample)
} else {
// Match the common OpenTelemetry approach of using the high 32 bits of
// the trace ID as a deterministic sampling source.
let threshold = (ratio * 4294967295.0).to_int()
if trace_id_prefix(parameters.trace_id) <= threshold {
SamplingResult::new(RecordAndSample)
} else {
SamplingResult::new(Drop)
}
}
},
() => "trace_id_ratio(\{ratio})",
)
}
///|
/// Sampler that honors a valid parent sampling decision and otherwise delegates
/// to the supplied root sampler.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#sampling
pub fn Sampler::parent_based(root : Sampler) -> Sampler {
let description = root.description()
Sampler::new(
(parameters : SamplingParameters) => {
match parameters.parent_context.span_context() {
Some(parent_span_context) if parent_span_context.is_valid() =>
if parent_span_context.is_sampled() {
SamplingResult::new(
RecordAndSample,
trace_state=parent_span_context.trace_state(),
)
} else {
SamplingResult::new(
Drop,
trace_state=parent_span_context.trace_state(),
)
}
_ => root.should_sample(parameters)
}
},
() => "parent_based(\{description})",
)
}
///|
pub impl Default for Sampler with fn default() -> Sampler {
Sampler::parent_based(Sampler::always_on())
}
///|
/// Builds an ID generator from callbacks.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#id-generators
pub fn IdGenerator::new(
new_trace_id_fn : () -> @common.TraceId,
new_span_id_fn : () -> @common.SpanId,
) -> IdGenerator {
{ new_trace_id_fn, new_span_id_fn }
}
///|
/// Returns a new trace ID.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#id-generators
pub fn IdGenerator::new_trace_id(self : IdGenerator) -> @common.TraceId {
(self.new_trace_id_fn)()
}
///|
/// Returns a new span ID.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#id-generators
pub fn IdGenerator::new_span_id(self : IdGenerator) -> @common.SpanId {
(self.new_span_id_fn)()
}
///|
fn random_hex_16(random : @random.Rand) -> String {
random.uint64().to_string(radix=16).pad_start(16, '0')
}
///|
/// Creates the default random ID generator.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#id-generators
pub fn RandomIdGenerator::new() -> RandomIdGenerator {
let random = Ref(@random.Rand::new())
{
inner: IdGenerator::new(
() => {
let hex = random_hex_16(random.val) + random_hex_16(random.val)
match @common.TraceId::from_hex(hex) {
Some(trace_id) => trace_id
None => @common.TraceId::invalid()
}
},
() => {
let hex = random_hex_16(random.val)
match @common.SpanId::from_hex(hex) {
Some(span_id) => span_id
None => @common.SpanId::invalid()
}
},
),
}
}
///|
/// Erases the concrete random generator type into `IdGenerator`.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#id-generators
pub fn RandomIdGenerator::into_id_generator(
self : RandomIdGenerator,
) -> IdGenerator {
self.inner
}
///|
/// Creates normalized batch processor configuration.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#batching-processor
pub fn BatchConfig::new(
max_queue_size? : Int = 2048,
max_export_batch_size? : Int = 512,
scheduled_delay_millis? : Int = 5000,
export_timeout_millis? : Int = 30000,
) -> BatchConfig {
let max_queue_size = if max_queue_size <= 0 { 1 } else { max_queue_size }
let max_export_batch_size = if max_export_batch_size <= 0 {
1
} else if max_export_batch_size > max_queue_size {
max_queue_size
} else {
max_export_batch_size
}
let scheduled_delay_millis = if scheduled_delay_millis <= 0 {
1
} else {
scheduled_delay_millis
}
let export_timeout_millis = if export_timeout_millis <= 0 {
1
} else {
export_timeout_millis
}
{
max_queue_size,
max_export_batch_size,
scheduled_delay_millis,
export_timeout_millis,
}
}
///|
pub impl Default for BatchConfig with fn default() -> BatchConfig {
BatchConfig::new(
max_queue_size=env_or("OTEL_BSP_MAX_QUEUE_SIZE", 2048),
max_export_batch_size=env_or("OTEL_BSP_MAX_EXPORT_BATCH_SIZE", 512),
scheduled_delay_millis=env_or("OTEL_BSP_SCHEDULE_DELAY", 5000),
export_timeout_millis=env_or("OTEL_BSP_EXPORT_TIMEOUT", 30000),
)
}
///|
/// Creates trace provider configuration.
///
/// Omitted arguments use default sampler, ID generator, span limits, and
/// resource. Environment variables are applied through `Default`.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#tracer-provider
pub fn Config::new(
sampler? : Sampler = Default::default(),
id_generator? : IdGenerator = RandomIdGenerator::new().into_id_generator(),
span_limits? : SpanLimits = Default::default(),
resource? : @resource.Resource = @resource.Resource::builder().build(),
) -> Config {
{ sampler, id_generator, span_limits, resource }
}
///|
pub impl Default for Config with fn default() -> Config {
Config::new(sampler=sampler_from_env(), span_limits=span_limits_from_env())
}
///|
/// Builds a span exporter from callbacks.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-exporter
pub fn SpanExporter::new(
export_fn : async (ArrayView[SpanData]) -> @error.OTelSdkResult,
force_flush_fn? : async () -> @error.OTelSdkResult = () => @error.ok(),
shutdown_fn? : async () -> @error.OTelSdkResult = () => @error.ok(),
name_fn? : () -> String = () => "custom",
) -> SpanExporter {
{ export_fn, force_flush_fn, shutdown_fn, name_fn }
}
///|
/// Exports one batch of finished spans.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#interface-definition
pub async fn SpanExporter::export_batch(
self : SpanExporter,
batch : ArrayView[SpanData],
) -> @error.OTelSdkResult {
(self.export_fn)(batch)
}
///|
/// Requests exporter flush.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#forceflush
pub async fn SpanExporter::force_flush(
self : SpanExporter,
) -> @error.OTelSdkResult {
(self.force_flush_fn)()
}
///|
/// Requests exporter shutdown with an optional timeout budget in milliseconds.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#shutdown
pub async fn SpanExporter::shutdown_with_timeout(
self : SpanExporter,
timeout_millis : Int,
) -> @error.OTelSdkResult {
ignore(timeout_millis)
(self.shutdown_fn)()
}
///|
/// Requests exporter shutdown.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#shutdown
pub async fn SpanExporter::shutdown(
self : SpanExporter,
) -> @error.OTelSdkResult {
self.shutdown_with_timeout(5000)
}
///|
/// Returns a human-readable exporter name.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-exporter
pub fn SpanExporter::name(self : SpanExporter) -> String {
(self.name_fn)()
}
///|
/// Builds a span processor from callbacks.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-processor
pub fn SpanProcessor::new(
on_start_fn? : (@context.Context, SpanData) -> Unit = (_, _) => (),
on_end_fn? : async (SpanData) -> Unit = _ => (),
force_flush_fn? : async () -> @error.OTelSdkResult = () => @error.ok(),
shutdown_fn? : async () -> @error.OTelSdkResult = () => @error.ok(),
) -> SpanProcessor {
{ on_start_fn, on_end_fn, force_flush_fn, shutdown_fn }
}
///|
/// Invokes the processor start hook.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-processor
pub fn SpanProcessor::on_start(
self : SpanProcessor,
parent_context : @context.Context,
span_data : SpanData,
) -> Unit {
(self.on_start_fn)(parent_context, span_data)
}
///|
/// Invokes the processor end hook.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-processor
pub async fn SpanProcessor::on_end(
self : SpanProcessor,
span_data : SpanData,
) -> Unit {
(self.on_end_fn)(span_data)
}
///|
/// Requests processor flush.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-processor
pub async fn SpanProcessor::force_flush(
self : SpanProcessor,
) -> @error.OTelSdkResult {
(self.force_flush_fn)()
}
///|
/// Requests processor shutdown with an optional timeout budget in milliseconds.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-processor
pub async fn SpanProcessor::shutdown_with_timeout(
self : SpanProcessor,
timeout_millis : Int,
) -> @error.OTelSdkResult {
ignore(timeout_millis)
(self.shutdown_fn)()
}
///|
/// Requests processor shutdown.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-processor
pub async fn SpanProcessor::shutdown(
self : SpanProcessor,
) -> @error.OTelSdkResult {
self.shutdown_with_timeout(5000)
}
///|
/// Creates an empty in-memory span exporter.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-exporter
pub fn InMemorySpanExporter::new() -> InMemorySpanExporter {
{ state: Ref({ finished_spans: [], is_shutdown: false }) }
}
///|
/// Returns the finished spans accumulated so far.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-exporter
pub fn InMemorySpanExporter::finished_spans(
self : InMemorySpanExporter,
) -> Array[SpanData] {
self.state.val.finished_spans.copy()
}
///|
/// Clears the in-memory span buffer.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-exporter
pub fn InMemorySpanExporter::reset(self : InMemorySpanExporter) -> Unit {
self.state.val = { ..self.state.val, finished_spans: [] }
}
///|
/// Returns whether the exporter has been shut down.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#shutdown
pub fn InMemorySpanExporter::is_shutdown(self : InMemorySpanExporter) -> Bool {
self.state.val.is_shutdown
}
///|
/// Erases the concrete in-memory exporter type into `SpanExporter`.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#interface-definition
pub fn InMemorySpanExporter::into_span_exporter(
self : InMemorySpanExporter,
) -> SpanExporter {
let state = self.state
SpanExporter::new(
(batch : ArrayView[SpanData]) => {
if state.val.is_shutdown {
return @error.already_shutdown()
}
let finished_spans = state.val.finished_spans.copy()
finished_spans.append(batch)
state.val = { ..state.val, finished_spans, }
@error.ok()
},
shutdown_fn=() => {
if state.val.is_shutdown {
@error.already_shutdown()
} else {
state.val = { ..state.val, is_shutdown: true }
@error.ok()
}
},
name_fn=() => "in_memory",
)
}
///|
/// Creates a simple processor that exports each ended span immediately.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#simple-processor
pub fn SimpleSpanProcessor::new(exporter : SpanExporter) -> SimpleSpanProcessor {
{
inner: SpanProcessor::new(
on_end_fn=(span_data : SpanData) => {
ignore(exporter.export_batch([span_data]))
},
force_flush_fn=() => exporter.force_flush(),
shutdown_fn=() => exporter.shutdown(),
),
}
}
///|
/// Erases the concrete simple processor type into `SpanProcessor`.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#simple-processor
pub fn SimpleSpanProcessor::into_span_processor(
self : SimpleSpanProcessor,
) -> SpanProcessor {
self.inner
}
///|
struct BatchProcessorState {
exporter : SpanExporter
config : BatchConfig
queue : Array[SpanData]
is_shutdown : Bool
}
///|
/// Flushes queued spans to the exporter in batch-sized chunks.
async fn flush_span_queue(
state : Ref[BatchProcessorState],
) -> @error.OTelSdkResult {
if state.val.is_shutdown {
return @error.already_shutdown()
}
while !state.val.queue.is_empty() {
let batch : Array[SpanData] = []
let batch_size = if state.val.queue.length() <
state.val.config.max_export_batch_size {
state.val.queue.length()
} else {
state.val.config.max_export_batch_size
}
for i = 0; i < batch_size; i = i + 1 {
ignore(i)
batch.push(state.val.queue.remove(0))
}
match state.val.exporter.export_batch(batch) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
@error.ok()
}
///|
/// Creates a batch span processor with a bounded in-memory queue.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#batching-processor
pub fn BatchSpanProcessor::new(
exporter : SpanExporter,
config? : BatchConfig = Default::default(),
) -> BatchSpanProcessor {
let state : Ref[BatchProcessorState] = Ref({
exporter,
config,
queue: [],
is_shutdown: false,
})
{
inner: SpanProcessor::new(
on_end_fn=(span_data : SpanData) => {
if state.val.is_shutdown {
return
}
if state.val.queue.length() >= state.val.config.max_queue_size {
// Keep the newest spans under sustained pressure.
ignore(state.val.queue.remove(0))
}
state.val.queue.push(span_data)
if state.val.queue.length() >= state.val.config.max_export_batch_size {
ignore(flush_span_queue(state))
}
},
force_flush_fn=() => {
match flush_span_queue(state) {
Ok(_) => state.val.exporter.force_flush()
Err(err) => Err(err)
}
},
shutdown_fn=() => {
if state.val.is_shutdown {
return @error.already_shutdown()
}
let result = flush_span_queue(state)
state.val = { ..state.val, is_shutdown: true }
match result {
Ok(_) =>
state.val.exporter.shutdown_with_timeout(
state.val.config.export_timeout_millis,
)
Err(err) => Err(err)
}
},
),
state,
}
}
///|
/// Erases the concrete batch processor type into `SpanProcessor`.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#batching-processor
pub fn BatchSpanProcessor::into_span_processor(
self : BatchSpanProcessor,
) -> SpanProcessor {
self.inner
}
///|
/// Background loop for scheduled batch exports.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#batching-processor
pub async fn BatchSpanProcessor::run(self : BatchSpanProcessor) -> Unit {
while !self.state.val.is_shutdown {
@async.sleep(self.state.val.config.scheduled_delay_millis)
if self.state.val.is_shutdown {
return
}
ignore(flush_span_queue(self.state))
ignore(self.state.val.exporter.force_flush())
}
}
///|
fn span_data_from_state(state : SpanState) -> SpanData {
{
name: state.name,
span_context: state.span_context,
parent_span_context: state.parent_span_context,
kind: state.kind,
start_time_unix_nano: state.start_time_unix_nano,
end_time_unix_nano: state.end_time_unix_nano,
attributes: state.attributes.copy(),
dropped_attributes_count: state.dropped_attributes_count,
events: state.events.copy(),
dropped_events_count: state.dropped_events_count,
links: state.links.copy(),
dropped_links_count: state.dropped_links_count,
status: state.status,
resource: state.resource,
instrumentation_scope: state.instrumentation_scope,
}
}
///|
/// Pushes a value into a bounded array, returning the copied array and the
/// number of dropped values.
fn[T] push_bounded(
values : Array[T],
value : T,
limit : Int,
) -> (Array[T], Int) {
if limit <= 0 {
return (values, 1)
}
if values.length() >= limit {
return (values, 1)
}
let copied = values.copy()
copied.push(value)
(copied, 0)
}
///|
fn truncate_attributes(
attributes : ArrayView[@common.KeyValue],
limit : Int,
dropped_attributes_count? : Int = 0,
) -> (Array[@common.KeyValue], Int) {
let copied = attributes.to_owned()
if limit <= 0 {
return ([], copied.length() + dropped_attributes_count)
}
if copied.length() <= limit {
return (copied, dropped_attributes_count)
}
(
copied[:limit].to_owned(),
copied.length() - limit + dropped_attributes_count,
)
}
///|
fn status_priority(code : StatusCode) -> Int {
match code {
Unset => 0
Error => 1
Ok => 2
}
}
///|
fn recording_from_decision(decision : SamplingDecision) -> Bool {
match decision {
Drop => false
_ => true
}
}
///|
/// Returns the immutable span context.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#get-context
pub fn Span::span_context(self : Span) -> @common.SpanContext {
self.state.val.span_context
}
///|
/// Returns a context carrying this span as the active local span.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#get-context
pub fn Span::context(self : Span) -> @context.Context {
@context.Context::empty().with_span_context(self.span_context())
}
///|
/// Returns whether the span is still recording mutations.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#isrecording
pub fn Span::is_recording(self : Span) -> Bool {
self.state.val.is_recording && !self.state.val.has_ended
}
///|
/// Returns whether `end()` has already been called.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#end
pub fn Span::has_ended(self : Span) -> Bool {
self.state.val.has_ended
}
///|
/// Adds one attribute if the span is still recording.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#set-attributes
pub fn Span::set_attribute(
self : Span,
key : StringView,
value : @common.Value,
) -> Unit {
if !self.is_recording() {
return
}
let (attributes, dropped) = push_bounded(
self.state.val.attributes,
@common.KeyValue::new(key, value),
self.state.val.limits.max_attributes_per_span,
)
self.state.val = {
..self.state.val,
attributes,
dropped_attributes_count: self.state.val.dropped_attributes_count + dropped,
}
}
///|
/// Adds one event if the span is still recording.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#add-events
pub fn Span::add_event(
self : Span,
name : StringView,
attributes? : ArrayView[@common.KeyValue] = [],
timestamp_unix_nano? : Int64 = @utils.now_unix_nano(),
dropped_attributes_count? : Int = 0,
) -> Unit {
if !self.is_recording() {
return
}
let (attributes, dropped_attributes_count) = truncate_attributes(
attributes,
self.state.val.limits.max_attributes_per_event,
dropped_attributes_count~,
)
let (events, dropped) = push_bounded(
self.state.val.events,
SpanEvent::new(
name,
attributes~,
timestamp_unix_nano~,
dropped_attributes_count~,
),
self.state.val.limits.max_events_per_span,
)
self.state.val = {
..self.state.val,
events,
dropped_events_count: self.state.val.dropped_events_count + dropped,
}
}
///|
/// Adds one link if the span is still recording.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#add-link
pub fn Span::add_link(
self : Span,
span_context : @common.SpanContext,
attributes? : ArrayView[@common.KeyValue] = [],
dropped_attributes_count? : Int = 0,
) -> Unit {
if !self.is_recording() {
return
}
let (attributes, dropped_attributes_count) = truncate_attributes(
attributes,
self.state.val.limits.max_attributes_per_link,
dropped_attributes_count~,
)
let (links, dropped) = push_bounded(
self.state.val.links,
SpanLink::new(span_context, attributes~, dropped_attributes_count~),
self.state.val.limits.max_links_per_span,
)
self.state.val = {
..self.state.val,
links,
dropped_links_count: self.state.val.dropped_links_count + dropped,
}
}
///|
/// Sets the span status if the span is still recording.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#set-status
pub fn Span::set_status(self : Span, status : Status) -> Unit {
if !self.is_recording() {
return
}
let current = self.state.val.status
if status.code == current.code ||
status_priority(status.code) > status_priority(current.code) {
self.state.val = { ..self.state.val, status, }
}
}
///|
/// Updates the span name.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#updatename
pub fn Span::update_name(self : Span, name : StringView) -> Unit {
self.state.val = { ..self.state.val, name: name.to_owned() }
}
///|
/// Returns an immutable snapshot of the current span state.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#span
pub fn Span::snapshot(self : Span) -> SpanData {
span_data_from_state(self.state.val)
}
///|
/// Ends the span once and forwards the final snapshot to processors when the
/// span is recording.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#end
pub async fn Span::end(
self : Span,
end_time_unix_nano? : Int64 = @utils.now_unix_nano(),
) -> Unit {
if self.state.val.has_ended {
return
}
self.state.val = {
..self.state.val,
end_time_unix_nano: Some(end_time_unix_nano),
has_ended: true,
}
if !self.state.val.is_recording {
return
}
let span_data = self.snapshot()
for processor in self.state.val.processors {
processor.on_end(span_data)
}
}
///|
/// Starts building a tracer provider.
///
/// Add processors or exporters before calling `build()`. A provider with no
/// processors records spans in memory only long enough to return span handles;
/// no telemetry is exported.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#tracer-provider
pub fn SdkTracerProvider::builder() -> SdkTracerProviderBuilder {
{ config: Default::default(), processors: [], batch_processors: [] }
}
///|
/// Replaces the entire trace provider configuration.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#tracer-provider
pub fn SdkTracerProviderBuilder::with_config(
self : SdkTracerProviderBuilder,
config : Config,
) -> SdkTracerProviderBuilder {
self.config = config
self
}
///|
/// Replaces the provider resource.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#tracer-provider
pub fn SdkTracerProviderBuilder::with_resource(
self : SdkTracerProviderBuilder,
resource : @resource.Resource,
) -> SdkTracerProviderBuilder {
self.config = { ..self.config, resource, }
self
}
///|
/// Replaces the sampler.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#tracer-provider
pub fn SdkTracerProviderBuilder::with_sampler(
self : SdkTracerProviderBuilder,
sampler : Sampler,
) -> SdkTracerProviderBuilder {
self.config = { ..self.config, sampler, }
self
}
///|
/// Replaces the ID generator.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#tracer-provider
pub fn SdkTracerProviderBuilder::with_id_generator(
self : SdkTracerProviderBuilder,
id_generator : IdGenerator,
) -> SdkTracerProviderBuilder {
self.config = { ..self.config, id_generator, }
self
}
///|
/// Replaces the span limits.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#tracer-provider
pub fn SdkTracerProviderBuilder::with_span_limits(
self : SdkTracerProviderBuilder,
span_limits : SpanLimits,
) -> SdkTracerProviderBuilder {
self.config = { ..self.config, span_limits, }
self
}
///|
/// Appends a span processor to the provider pipeline.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#tracer-provider
pub fn SdkTracerProviderBuilder::with_span_processor(
self : SdkTracerProviderBuilder,
processor : SpanProcessor,
) -> SdkTracerProviderBuilder {
self.processors.push(processor)
self
}
///|
/// Appends a simple processor around the exporter.
///
/// Finished spans are exported from `Span::end()`.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#tracer-provider
pub fn SdkTracerProviderBuilder::with_simple_exporter(
self : SdkTracerProviderBuilder,
exporter : SpanExporter,
) -> SdkTracerProviderBuilder {
self.with_span_processor(
SimpleSpanProcessor::new(exporter).into_span_processor(),
)
}
///|
/// Appends a batch processor around the exporter and registers it for
/// background execution.
///
/// The processor is registered with the provider, but its export loop only runs
/// after `spawn_batch_processor_tasks()` or the facade/global equivalent is
/// called.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#tracer-provider
pub fn SdkTracerProviderBuilder::with_batch_exporter(
self : SdkTracerProviderBuilder,
exporter : SpanExporter,
config? : BatchConfig = Default::default(),
) -> SdkTracerProviderBuilder {
let batch_processor = BatchSpanProcessor::new(exporter, config~)
self.batch_processors.push(batch_processor)
self.with_span_processor(batch_processor.into_span_processor())
}
///|
/// Builds the tracer provider.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#tracer-provider
pub fn SdkTracerProviderBuilder::build(
self : SdkTracerProviderBuilder,
) -> SdkTracerProvider {
{
state: Ref({
config: self.config,
processors: self.processors.copy(),
batch_processors: self.batch_processors.copy(),
is_shutdown: false,
}),
}
}
///|
/// Returns the provider resource.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#tracer-provider
pub fn SdkTracerProvider::resource(
self : SdkTracerProvider,
) -> @resource.Resource {
self.state.val.config.resource
}
///|
/// Spawns background tasks for all configured batch processors.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#tracer-provider
pub fn SdkTracerProvider::spawn_batch_processor_tasks(
self : SdkTracerProvider,
group : @async.TaskGroup[Unit],
allow_failure? : Bool = false,
) -> Unit {
for batch_processor in self.state.val.batch_processors {
group.spawn_bg(no_wait=true, allow_failure~, () => batch_processor.run())
}
}
///|
/// Creates a tracer scoped to the supplied instrumentation library metadata.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#tracer-creation
pub fn SdkTracerProvider::tracer(
self : SdkTracerProvider,
name : StringView,
version? : String? = None,
schema_url? : String? = None,
attributes? : ArrayView[@common.KeyValue] = [],
) -> SdkTracer {
let mut scope = @common.InstrumentationScope::builder(name)
match version {
Some(version) => scope = scope.with_version(version)
None => ()
}
match schema_url {
Some(schema_url) => scope = scope.with_schema_url(schema_url)
None => ()
}
scope = scope.with_attributes(attributes)
{ provider: self, instrumentation_scope: scope.build() }
}
///|
/// Flushes every configured span processor.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#forceflush
pub async fn SdkTracerProvider::force_flush(
self : SdkTracerProvider,
) -> @error.OTelSdkResult {
if self.state.val.is_shutdown {
return @error.already_shutdown()
}
for processor in self.state.val.processors {
match processor.force_flush() {
Ok(_) => ()
Err(err) => return Err(err)
}
}
@error.ok()
}
///|
/// Shuts down every configured span processor and marks the provider unusable.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#shutdown
pub async fn SdkTracerProvider::shutdown_with_timeout(
self : SdkTracerProvider,
timeout_millis : Int,
) -> @error.OTelSdkResult {
if self.state.val.is_shutdown {
return @error.already_shutdown()
}
let processors = self.state.val.processors.copy()
self.state.val = { ..self.state.val, is_shutdown: true }
for processor in processors {
match processor.shutdown_with_timeout(timeout_millis) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
@error.ok()
}
///|
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#shutdown
pub async fn SdkTracerProvider::shutdown(
self : SdkTracerProvider,
) -> @error.OTelSdkResult {
self.shutdown_with_timeout(5000)
}
///|
fn span_kind_from_api(kind : @api.SpanKind) -> SpanKind {
match kind {
Internal => Internal
Client => Client
Server => Server
Producer => Producer
Consumer => Consumer
}
}
///|
fn status_code_from_api(code : @api.StatusCode) -> StatusCode {
match code {
Unset => Unset
Ok => Ok
Error => Error
}
}
///|
fn status_from_api(status : @api.Status) -> Status {
Status::new(status_code_from_api(status.code), description=status.description)
}
///|
fn span_link_from_api(link : @api.Link) -> SpanLink {
SpanLink::new(
link.span_context,
attributes=link.attributes,
dropped_attributes_count=link.dropped_attributes_count,
)
}
///|
fn Span::into_api_span(
self : Span,
parent_context : @context.Context,
) -> @api.Span {
let span_context = self.span_context()
let context = parent_context.with_span_context(span_context)
@api.Span::from_functions(
() => self.span_context(),
() => context,
() => self.is_recording(),
() => self.has_ended(),
(name, timestamp_unix_nano, attributes) => {
self.add_event(name, attributes~, timestamp_unix_nano~)
},
attribute => self.set_attribute(attribute.key.as_string(), attribute.value),
status => self.set_status(status_from_api(status)),
name => self.update_name(name),
(span_context, attributes) => self.add_link(span_context, attributes~),
timestamp_unix_nano => self.end(end_time_unix_nano=timestamp_unix_nano),
)
}
///|
fn SdkTracer::into_api_tracer(self : SdkTracer) -> @api.Tracer {
@api.Tracer::from_functions((builder, parent_context) => {
let links = []
for link in builder.links {
if link.span_context.is_valid() {
links.push(span_link_from_api(link))
}
}
let span = self.start(
builder.name,
parent_context~,
kind=match builder.span_kind {
Some(kind) => span_kind_from_api(kind)
None => Internal
},
attributes=builder.attributes,
links~,
start_time_unix_nano=match builder.start_time_unix_nano {
Some(start_time) => start_time
None => @utils.now_unix_nano()
},
)
for event in builder.events {
span.add_event(
event.name,
attributes=event.attributes,
timestamp_unix_nano=event.timestamp_unix_nano,
dropped_attributes_count=event.dropped_attributes_count,
)
}
span.into_api_span(parent_context)
})
}
///|
/// Erases this SDK provider into the public trace API provider.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#tracer-provider
pub fn SdkTracerProvider::into_tracer_provider(
self : SdkTracerProvider,
) -> @api.TracerProvider {
@api.TracerProvider::from_functions(scope => {
self
.tracer(
scope.name(),
version=scope.version(),
schema_url=scope.schema_url(),
attributes=scope.attributes(),
)
.into_api_tracer()
})
}
///|
fn span_context_from_parent(
parent_context : @context.Context,
) -> @common.SpanContext? {
match parent_context.span_context() {
Some(span_context) if span_context.is_valid() => Some(span_context)
_ => None
}
}
///|
fn span_state(
name : String,
span_context : @common.SpanContext,
parent_span_context : @common.SpanContext?,
kind : SpanKind,
start_time_unix_nano : Int64,
attributes : Array[@common.KeyValue],
links : Array[SpanLink],
resource : @resource.Resource,
instrumentation_scope : @common.InstrumentationScope,
processors : Array[SpanProcessor],
limits : SpanLimits,
is_recording : Bool,
) -> SpanState {
{
name,
span_context,
parent_span_context,
kind,
start_time_unix_nano,
end_time_unix_nano: None,
attributes,
dropped_attributes_count: 0,
events: [],
dropped_events_count: 0,
links,
dropped_links_count: 0,
status: Default::default(),
resource,
instrumentation_scope,
processors,
limits,
is_recording,
has_ended: false,
}
}
///|
/// Starts a span using the provider configuration, sampler, and resource.
///
/// This evaluates the sampler, generates IDs, applies limits, and notifies
/// processors when the span is recording.
/// Spec: https://opentelemetry.io/docs/specs/otel/trace/sdk/#tracer
pub fn SdkTracer::start(
self : SdkTracer,
name : StringView,
parent_context? : @context.Context = Default::default(),
kind? : SpanKind = Internal,
attributes? : ArrayView[@common.KeyValue] = [],
links? : ArrayView[SpanLink] = [],
start_time_unix_nano? : Int64 = @utils.now_unix_nano(),
) -> Span {
let provider_state = self.provider.state.val
if provider_state.is_shutdown {
return {
state: Ref(
span_state(
name.to_owned(),
Default::default(),
None,
kind,
start_time_unix_nano,
[],
[],
provider_state.config.resource,
self.instrumentation_scope,
[],
provider_state.config.span_limits,
false,
),
),
}
}
let parent_span_context = span_context_from_parent(parent_context)
let trace_id = match parent_span_context {
Some(parent_span_context) => parent_span_context.trace_id()
None => provider_state.config.id_generator.new_trace_id()
}
let sampling_parameters : SamplingParameters = {
parent_context,
trace_id,
name: name.to_owned(),
kind,
attributes: attributes.to_owned(),
links: links.to_owned(),
}
let sampling_result = provider_state.config.sampler.should_sample(
sampling_parameters,
)
let trace_state = sampling_result.trace_state
// Sampling controls both recording and the sampled bit stored in the created
// span context.
let span_context = @common.SpanContext::new(
trace_id,
provider_state.config.id_generator.new_span_id(),
trace_flags=sampled_trace_flags(sampling_result.decision),
trace_state~,
)
let is_recording = recording_from_decision(sampling_result.decision)
let mut span_attributes = sampling_result.attributes.copy()
span_attributes.append(attributes)
let mut dropped_attributes_count = 0
if span_attributes.length() >
provider_state.config.span_limits.max_attributes_per_span {
dropped_attributes_count = span_attributes.length() -
provider_state.config.span_limits.max_attributes_per_span
span_attributes = span_attributes[:provider_state.config.span_limits.max_attributes_per_span].to_owned()
}
let mut dropped_links_count = 0
let span_links = []
for link in links {
if span_links.length() >=
provider_state.config.span_limits.max_links_per_span {
dropped_links_count += 1
continue
}
let (attributes, dropped_attributes_count) = truncate_attributes(
link.attributes,
provider_state.config.span_limits.max_attributes_per_link,
dropped_attributes_count=link.dropped_attributes_count,
)
span_links.push({
span_context: link.span_context,
attributes,
dropped_attributes_count,
})
}
let span : Span = {
state: Ref({
..span_state(
name.to_owned(),
span_context,
parent_span_context,
kind,
start_time_unix_nano,
span_attributes,
span_links,
provider_state.config.resource,
self.instrumentation_scope,
provider_state.processors.copy(),
provider_state.config.span_limits,
is_recording,
),
dropped_attributes_count,
dropped_links_count,
}),
}
if is_recording {
// Processors observe a started span through a context that carries the new
// local span context, matching how downstream log correlation would see it.
let started_context = @context.Context::empty().with_span_context(
span_context,
)
let span_data = span.snapshot()
for processor in provider_state.processors {
processor.on_start(started_context, span_data)
}
}
span
}