opentelemetry

The MoonBit implementation of OpenTelemetry.

moon add moonbit-community/opentelemetry@0.1.4
Download zip
Version
0.1.4
License
Apache-2.0
Last updated
2 months ago
Downloads
139
README

#OpenTelemetry for MoonBit

MoonBit implementation of OpenTelemetry: APIs for instrumenting libraries and applications, SDK providers for processing telemetry, and exporters for getting telemetry out of the process.

OpenTelemetry is the standard instrumentation and transport layer that lets your code produce portable telemetry data, then send it to tools such as the OpenTelemetry Collector, Jaeger, Prometheus, or a vendor backend.

This repository contains:

  • public API packages for traces, metrics, logs, context, baggage, and propagation
  • SDK packages for providers, processors, readers, samplers, resources, and in-memory test exporters
  • print exporters for local learning and debugging
  • OTLP HTTP exporters for production-oriented export through the OpenTelemetry Protocol
  • semantic-convention constants and generated OTLP protocol model types

#Mental Model

OpenTelemetry has two layers:

  • API layer: what libraries should depend on. It provides stable instrumentation types such as Tracer, Meter, Logger, Context, and KeyValue. If an application does not install an SDK provider, the API layer is no-op by default.
  • SDK layer: what applications configure. It owns resources, samplers, processors, readers, exporters, batching, flushing, and shutdown.

Library authors should usually import moonbit-community/opentelemetry or moonbit-community/opentelemetry/interface/*. Application authors add moonbit-community/opentelemetry/sdk, print, or otlp to make the instrumentation do real work.

#Package Guide

PackageUse it for
moonbit-community/opentelemetrySmall public entry point for library instrumentation: tracer(), meter(), logger(), attributes, baggage, and context aliases.
moonbit-community/opentelemetry/interface/traceTrace API: spans, span builders, events, links, status, and no-op trace behavior.
moonbit-community/opentelemetry/interface/metricsMetrics API: meters, counters, up-down counters, histograms, gauges, and observable instruments.
moonbit-community/opentelemetry/interface/logsLog bridge API: structured log records that can be correlated with traces.
moonbit-community/opentelemetry/interface/contextImmutable context container used to carry span context, baggage, and telemetry suppression.
moonbit-community/opentelemetry/interface/propagationW3C trace-context and baggage propagation through text carriers such as HTTP headers.
moonbit-community/opentelemetry/interface/globalProcess-wide API providers. Libraries normally read from here indirectly through the root package.
moonbit-community/opentelemetry/sdkApplication-side SDK facade for providers, processors, readers, resources, samplers, and global SDK helpers.
moonbit-community/opentelemetry/sdk/globalProcess-wide SDK providers used by the SDK facade. Use sdk.set_*_provider() to wire SDK providers into the public root package.
moonbit-community/opentelemetry/printHuman-readable stdout exporters for examples, local debugging, and tests.
moonbit-community/opentelemetry/otlpOTLP HTTP exporters for traces, logs, and metrics.
moonbit-community/opentelemetry/semantics/*Generated semantic-convention constants for standard attribute and metric names.
moonbit-community/opentelemetry/protocol/*Low-level generated OTLP protobuf/JSON models. Most users do not need these directly.

#Quick Start: Trace To Stdout

The shortest useful setup is: build an SDK provider, register it globally, get a tracer, create spans, and shut the provider down.

///|
async fn _readme_trace_to_stdout() -> Unit {
let exporter = @print.SpanExporter::new()
let provider = @sdk.tracer_provider_builder()
.with_simple_exporter(exporter.into_span_exporter())
.build()

@sdk.set_tracer_provider(provider)

let tracer = tracer("checkout-service", version=Some("1.0.0"))
let span = tracer.start("charge-card")
span.set_attribute(KeyValue::new("payment.system", String("test")))
span.set_status(@trace.Status::ok())
span.end()

ignore(provider.shutdown())
}

Span::end() is async because processors and exporters may do I/O. In examples using a simple stdout exporter it returns quickly, but production exporters should still be flushed or shut down before process exit.

#Instrumenting Libraries

Libraries should depend on the API layer and avoid choosing exporters or SDK configuration for their users. The final application decides whether telemetry is enabled.

///|
pub async fn _library_operation() -> Unit {
let tracer = tracer(
"moonbit-community/example-library",
version=Some("0.1.0"),
)
let span = tracer.start("example.operation")
span.set_attribute(KeyValue::new("example.kind", String("demo")))
// Library work goes here.
span.end()
}

If no application registers a provider, the global API providers are no-op. Spans, instruments, and loggers still exist so code can remain unconditional, but they do not record or export telemetry. This is intentionally a low-overhead runtime no-op, not a compile-time removal of all instrumentation code.

#Application Setup

Applications configure one provider per signal:

  1. Build a trace, log, or metric provider in sdk/*.
  2. Register SDK providers with sdk.set_*_provider() if code uses the root moonbit-community/opentelemetry package. These helpers also update interface/global with API-layer providers.
  3. Spawn background tasks when using batch span/log processors or periodic metric readers.
  4. Flush and shut down providers during application teardown.

Simple processors export immediately when spans/logs end. Batch processors and periodic metric readers require background tasks:

///|
async fn _spawn_background_tasks_shape() -> Unit {
@async.with_task_group(group => @sdk.spawn_background_tasks(group))
}

#Metrics

Metrics record measurements and aggregate them in memory until a reader collects them. Reuse instruments instead of creating them on every request.

Choose instruments by meaning:

  • Counter: monotonic value that only increases, such as requests served or bytes sent
  • UpDownCounter: value that can increase or decrease, such as active sessions or queue depth
  • Histogram: distribution of measurements, such as latency or payload size
  • Gauge: latest value for state that can move up or down, such as temperature or memory usage
  • observable instruments: callbacks for values already owned by another system

///|
fn _record_request_metrics() -> Unit {
let meter = meter("checkout-service")
let request_count = meter.u64_counter("http.server.request.count").build()
let request_latency = meter
.f64_histogram("http.server.duration")
.with_unit("ms")
.build()

request_count.add(1UL, attributes=[
KeyValue::new("http.request.method", String("POST")),
])
request_latency.record(32.5, attributes=[
KeyValue::new("http.route", String("/checkout")),
])
}

#Logs

The logging API is a bridge into the OpenTelemetry log data model. Existing application logging can keep its own frontend; bridge code can translate log events into LogRecord values.

///|
async fn _emit_structured_log() -> Unit {
let logger = logger("checkout-service")
if logger.event_enabled(Info, "checkout") {
let record = logger.create_log_record()
record.set_event_name("checkout.completed")
record.set_target("checkout")
record.set_severity_number(Info)
record.set_body(String("checkout completed"))
record.add_attribute("cart.items", Int(3L))
logger.emit(record)
}
}

event_enabled() is the cheap guard to check before building expensive log records. It currently reports whether a real SDK logger exists; future versions may add severity or target filtering.

#Propagation

Propagation carries trace context and baggage across process boundaries. For HTTP-like transports, inject the current context into outgoing headers and extract it from incoming headers.

///|
fn _inject_headers(context : Context) -> Map[String, String] {
let headers = {}
get_text_map_propagator(propagator => {
propagator.inject_context(context, headers)
})
headers
}

The default global propagator is a composite of W3C trace context and W3C baggage.

#OTLP Export

Use print while learning. Use otlp when sending telemetry to the OpenTelemetry Collector or an OTLP-compatible backend.

Typical local Collector command:

docker run --rm -p 4318:4318 otel/opentelemetry-collector:latest

The MoonBit OTLP exporter currently supports HTTP/protobuf and HTTP/JSON. gRPC and compression options are exposed for shape compatibility but rejected during exporter construction when unsupported. See otlp/README.mbt.md for endpoint, protocol, header, timeout, and signal-specific environment variables.

#Environment Variables

Programmatic builder configuration takes precedence over environment defaults where both are available.

AreaVariables
ResourceOTEL_SERVICE_NAME, OTEL_RESOURCE_ATTRIBUTES
Trace samplingOTEL_TRACES_SAMPLER, OTEL_TRACES_SAMPLER_ARG
Trace limitsOTEL_SPAN_ATTRIBUTE_COUNT_LIMIT, OTEL_SPAN_EVENT_COUNT_LIMIT, OTEL_SPAN_LINK_COUNT_LIMIT
Batch spansOTEL_BSP_SCHEDULE_DELAY, OTEL_BSP_MAX_QUEUE_SIZE, OTEL_BSP_MAX_EXPORT_BATCH_SIZE, OTEL_BSP_EXPORT_TIMEOUT
Batch logsOTEL_BLRP_SCHEDULE_DELAY, OTEL_BLRP_MAX_QUEUE_SIZE, OTEL_BLRP_MAX_EXPORT_BATCH_SIZE, OTEL_BLRP_EXPORT_TIMEOUT
Periodic metricsOTEL_METRIC_EXPORT_INTERVAL
OTLP exporterOTEL_EXPORTER_OTLP_* plus signal-specific TRACES, METRICS, and LOGS variants

#Practical Guidance

  • Use instrumentation names that identify the library or component, for example moonbit-community/http-client.
  • Prefer semantic-convention constants from semantics/* instead of hard-coded keys when a standard key exists.
  • Add low-cardinality attributes by default. Avoid user IDs, raw URLs, or unbounded strings as metric attributes.
  • Create metric instruments once and reuse them.
  • Always call shutdown() on providers you own so buffered telemetry is flushed.
  • Library code should not import sdk or exporters unless it is explicitly an integration package.

///|
fn _semantic_convention_attribute() -> Array[KeyValue] {
[KeyValue::new(@semtrace.HTTP_REQUEST_METHOD, String("GET"))]
}

#Integration Tests

Collector-backed OTLP integration tests live in integration/otlp/README.md and run through the helper scripts integration/otlp/scripts/test_with_docker.mjs or integration/otlp/scripts/test_with_binary.mjs.

#
Baggage

Baggage container for small cross-process key/value metadata.

Baggage is propagated to downstream services, so do not store secrets or high-cardinality values in it. Spec: https://opentelemetry.io/docs/specs/otel/baggage/api/#overview

#
BaggageMetadata

Metadata associated with one baggage entry. Spec: https://opentelemetry.io/docs/specs/otel/baggage/api/#overview

#
Context

Immutable context container carrying active span context, baggage, and telemetry suppression. Spec: https://opentelemetry.io/docs/specs/otel/context/#overview

#
InstrumentationScope

Metadata describing the library or component that produced telemetry.

Instrumentation scope is attached to exported telemetry. Use a stable name such as a package or module name, and set version/schema URL when known. Spec: https://opentelemetry.io/docs/specs/otel/common/instrumentation-scope/

#
Key

Attribute key type used by all OpenTelemetry signals.

Keys should be stable, low-cardinality names. Prefer semantic-convention constants from semantics/* when a standard key exists. Spec: https://opentelemetry.io/docs/specs/otel/common/#attribute

#
KeyValue

One OpenTelemetry attribute key/value pair.

Attributes add searchable dimensions to spans, metrics, logs, and resources. Avoid unbounded values such as user IDs or raw URLs on metrics because they can create high-cardinality time series. Spec: https://opentelemetry.io/docs/specs/otel/common/#attribute

#
KeyValueMetadata

One baggage entry plus its metadata. Spec: https://opentelemetry.io/docs/specs/otel/baggage/api/#overview

#
SpanId

8-byte span identifier for one operation inside a trace. Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#spancontext

#
TraceFlags

W3C trace flags, including the sampled bit. Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#spancontext

#
TraceId

16-byte trace identifier propagated across process boundaries. Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#spancontext

#
TraceState

W3C trace state container used by distributed tracing propagators. Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#tracestate

#
Value

Attribute value type accepted by traces, metrics, resources, and baggage conversion helpers. Spec: https://opentelemetry.io/docs/specs/otel/common/#anyvalue

#
get_text_map_propagator

Calls f with the currently registered global text-map propagator.

Use this to inject or extract trace context and baggage for text carriers such as HTTP headers. The default propagator combines W3C trace context and baggage propagation.

The callback shape avoids exposing the mutable global reference directly while still letting callers use the current propagator. Spec: https://opentelemetry.io/docs/specs/otel/context/api-propagators/#get-global-propagator

#
logger

fn logger(name : StringView, version? : String?, schema_url? : String?, attributes? : ArrayView[
KeyValue
]) ->
Logger

Returns a logger from the current global logger provider.

This is the public logs bridge entry point. Use Logger::event_enabled() to avoid constructing expensive log records when no real SDK logger is installed.

Optional version, schema URL, and attributes are folded into the instrumentation scope attached to emitted log records. Spec: https://opentelemetry.io/docs/specs/otel/logs/api/#get-a-logger

#
meter

fn meter(name : StringView, version? : String?, schema_url? : String?, attributes? : ArrayView[
KeyValue
]) ->
Meter

Returns a meter from the current global meter provider.

Use meters to create reusable metric instruments. If the final application has not registered a meter provider, created instruments are no-op and measurements are discarded.

Optional version, schema URL, and attributes are folded into the instrumentation scope attached to instruments created by the returned meter. Spec: https://opentelemetry.io/docs/specs/otel/metrics/api/#get-a-meter

#
tracer

fn tracer(name : StringView, version? : String?, schema_url? : String?, attributes? : ArrayView[
KeyValue
]) ->
Tracer

Returns a tracer from the current global tracer provider.

This is the preferred trace entry point for library instrumentation. The final application can register a real SDK provider through interface/global; if it does not, the returned tracer is no-op and spans do not record telemetry.

name should identify the instrumentation library or component. Optional version, schema URL, and attributes are folded into the instrumentation scope attached to spans created by the returned tracer. Spec: https://opentelemetry.io/docs/specs/otel/trace/api/#get-a-tracer

Source Files