Thanks to visit codestin.com
Credit goes to lib.rs

54 releases (23 stable)

new 1.16.0 Jan 11, 2026
1.14.0 Dec 5, 2025
1.13.1 Oct 16, 2025
1.11.0 Jun 21, 2025
0.11.0-alpha.7 Jul 31, 2024

#1183 in Debugging

Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App Codestin Search App

440 downloads per month

MIT/Apache

3.5MB
18K SLoC

Codestin Search App Codestin Search App

Emit diagnostic events via the OpenTelemetry Protocol (OTLP).

This library provides Otlp, an emit::Emitter that sends export requests directly to some remote OTLP receiver. If you need to integrate emit with the OpenTelemetry SDK, see emit-opentelemetry.

How it works

┌────────────────────────────────────────┐  ┌─────────────┐    ┌─────────────────────────────┐
│                caller                  │  │   channel   │    │     background worker       │
│                                        │  │             │    │                             │
│ emit::Event─┬─*─►is trace?──►Span──────┼──┼──►Trace─────┼─┐  │ ExportTraceServiceRequest   │
│             │                          │  │             │ │  │                             │
│             ├─*─►is metric?─►Metric────┼──┼──►Metrics───┼─┼──► ExportMetricsServiceRequest │
│             │                          │  │             │ │  │                             │
│             └─*─────────────►LogRecord─┼──┼──►Logs──────┼─┘  │ ExportLogsServiceRequest    │
└────────────────────────────────────────┘  └─────────────┘    └─────────────────────────────┘
* Only if the logs/trace/metrics signal is configured

The emitter is based on an asynchronous, batching channel. A diagnostic event makes its way from emit::emit! through to the remote OTLP receiver in the following key steps:

  1. Determine what kind of signal the event belongs to:
  • If the event carries emit::Kind::Span, and the trace signal is configured, then treat it as a span.
  • If the event carries emit::Kind::Metric, and the metrics signal is configured, then treat it as a metric.
  • In any other case, if the logs signal is configured, then treat it as a log record.
  1. Serialize the event into the OTLP datastructure in the target format (JSON/protobuf).
  2. Put the serialized event into a channel. Each signal has its own internal queue in the channel.
  3. On a background worker, process the events in the channel by forming them up into OTLP export requests and sending them using the target protocol (HTTP/gRPC).

This library is based on hyper with tokio for HTTP, and rustls with ring for TLS. Some of these dependencies can be configured using Cargo features:

Getting started

Add emit and emit_otlp to your Cargo.toml:

[dependencies.emit]
version = "1.16.0"

[dependencies.emit_otlp]
version = "1.16.0"

Initialize emit at the start of your main.rs using an OTLP emitter:

fn main() {
let rt = emit::setup()
.emit_to(emit_otlp::new()
// Add required resource properties for OTLP
.resource(emit::props! {
#[emit::key("service.name")]
service_name: emit::pkg!(),
})
// Configure endpoints for logs/traces/metrics using gRPC + protobuf
.logs(emit_otlp::logs_grpc_proto("http://localhost:4319"))
.traces(emit_otlp::traces_grpc_proto("http://localhost:4319"))
.metrics(emit_otlp::metrics_grpc_proto("http://localhost:4319"))
.spawn())
.init();

// Your app code goes here

rt.blocking_flush(std::time::Duration::from_secs(30));
}

The [new] function returns an OtlpBuilder, which can be configured with endpoints for the desired signals through its OtlpBuilder::logs, OtlpBuilder::traces, and OtlpBuilder::metrics methods.

You don't need to configure all signals, but you should at least configure OtlpBuilder::logs.

Once the builder is configured, call OtlpBuilder::spawn and pass the resulting Otlp to emit::Setup::emit_to.

Where the background worker is spawned

The Otlp emitter doesn't do any work directly. That's all handled by a background worker created through OtlpBuilder::spawn. The worker will spawn on a background thread with a single-threaded tokio executor on it.

Configuring for gRPC+protobuf

The logs_grpc_proto, traces_grpc_proto, and metrics_grpc_proto functions produce builders for gRPC+protobuf:

emit_otlp::new()
.resource(emit::props! {
#[emit::key("service.name")]
service_name: emit::pkg!(),
})
.logs(emit_otlp::logs_grpc_proto("http://localhost:4319"))
.traces(emit_otlp::traces_grpc_proto("http://localhost:4319"))
.metrics(emit_otlp::metrics_grpc_proto("http://localhost:4319"))
.spawn()

gRPC is based on HTTP and internally uses well-known URI paths to route RPC requests. These paths are appended automatically to the endpoint, so you don't need to specify them during configuration.

Configuring for HTTP+JSON

The logs_http_json, traces_http_json, and metrics_http_json functions produce builders for HTTP+JSON:

emit_otlp::new()
.resource(emit::props! {
#[emit::key("service.name")]
service_name: emit::pkg!(),
})
.logs(emit_otlp::logs_http_json("http://localhost:4318/v1/logs"))
.traces(emit_otlp::traces_http_json("http://localhost:4318/v1/traces"))
.metrics(emit_otlp::metrics_http_json("http://localhost:4318/v1/metrics"))
.spawn()

Configuring for HTTP+protobuf

The logs_http_proto, traces_http_proto, and metrics_http_proto functions produce builders for HTTP+protobuf:

emit_otlp::new()
.resource(emit::props! {
#[emit::key("service.name")]
service_name: emit::pkg!(),
})
.logs(emit_otlp::logs_http_proto("http://localhost:4318/v1/logs"))
.traces(emit_otlp::traces_http_proto("http://localhost:4318/v1/traces"))
.metrics(emit_otlp::metrics_http_proto("http://localhost:4318/v1/metrics"))
.spawn()

Configuring TLS

If the tls Cargo feature is enabled, and the scheme of your endpoint uses the https:// scheme then it will use TLS from rustls and rustls-native-certs.

You can specify the tls-native Cargo feature to use native-tls instead of rustls.

Configuring compression

If the gzip Cargo feature is enabled then gzip compression will be applied automatically to all export requests.

You can disable any compression through an OtlpTransportBuilder:

emit_otlp::new()
.logs(emit_otlp::logs_proto(emit_otlp::http("http://localhost:4318/v1/logs")
.allow_compression(false))
)

Customizing HTTP headers

You can specify custom headers to be used for HTTP or gRPC requests through an OtlpTransportBuilder:

emit_otlp::new()
.logs(emit_otlp::logs_proto(emit_otlp::http("http://localhost:4318/v1/logs")
.headers([
("X-ApiKey", "abcd"),
]))
)

Configuring a resource

The OtlpBuilder::resource method configures the OTLP resource to send with each export request. Some OTLP receivers accept data without a resource but the OpenTelemetry specification itself mandates it.

At a minimum, you should add the service.name property:

emit_otlp::new()
.resource(emit::props! {
#[emit::key("service.name")]
service_name: emit::pkg!(),
})

You should also consider setting other well-known resource properties:

emit_otlp::new()
.resource(emit::props! {
#[emit::key("service.name")]
service_name: emit::pkg!(),
#[emit::key("telemetry.sdk.language")]
language: emit_otlp::telemetry_sdk_language(),
#[emit::key("telemetry.sdk.name")]
sdk: emit_otlp::telemetry_sdk_name(),
#[emit::key("telemetry.sdk.version")]
version: emit_otlp::telemetry_sdk_version(),
})

Configuring from environment variables

You can configure emit_otlp from OpenTelemetry's environment variables using the from_env function:

emit_otlp::from_env().spawn()

The from_env function will create a builder with configuration for all signals and a resource. You can also configure individual signals from the environment if you want to further tweak them, or only configure a subset:

emit_otlp::new()
.logs(emit_otlp::logs_from_env())
.traces(emit_otlp::traces_from_env())
.metrics(emit_otlp::metrics_from_env())
.resource(emit_otlp::resource_from_env())
.spawn()

The following table lists currently supported environment variables:

Variable Name Default Value Valid Values Notes
OTEL_EXPORTER_OTLP_PROTOCOL grpc grpc, http/proto, http/json -
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL OTEL_EXPORTER_OTLP_PROTOCOL grpc, http/proto, http/json -
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL OTEL_EXPORTER_OTLP_PROTOCOL grpc, http/proto, http/json -
OTEL_EXPORTER_OTLP_METRICS_PROTOCOL OTEL_EXPORTER_OTLP_PROTOCOL grpc, http/proto, http/json -
OTEL_EXPORTER_OTLP_ENDPOINT http://localhost:4317 when OTEL_EXPORTER_OTLP_PROTOCOL is grpc, http://localhost:4318 when OTEL_EXPORTER_OTLP_PROTOCOL is http Any valid HTTP/S URI -
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT http://localhost:4317 when OTEL_EXPORTER_OTLP_LOGS_PROTOCOL is grpc, http://localhost:4318 when OTEL_EXPORTER_OTLP_LOGS_PROTOCOL is http* Any valid HTTP/S URI -
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT http://localhost:4317 when OTEL_EXPORTER_OTLP_TRACES_PROTOCOL is grpc, http://localhost:4318 when OTEL_EXPORTER_OTLP_TRACES_PROTOCOL is http* Any valid HTTP/S URI -
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT http://localhost:4317 when OTEL_EXPORTER_OTLP_METRICS_PROTOCOL is grpc, http://localhost:4318 when OTEL_EXPORTER_OTLP_METRICS_PROTOCOL is http* Any valid HTTP/S URI -
OTEL_EXPORTER_OTLP_HEADERS Empty W3C Baggage without ;-separated properties -
OTEL_EXPORTER_OTLP_LOGS_HEADERS OTEL_EXPORTER_OTLP_HEADERS W3C Baggage without ;-separated properties If defined, headers are merged with OTEL_EXPORTER_OTLP_HEADERS, preferring those in OTEL_EXPORTER_OTLP_LOGS_HEADERS
OTEL_EXPORTER_OTLP_TRACES_HEADERS OTEL_EXPORTER_OTLP_HEADERS W3C Baggage without ;-separated properties If defined, headers are merged with OTEL_EXPORTER_OTLP_HEADERS, preferring those in OTEL_EXPORTER_OTLP_TRACES_HEADERS
OTEL_EXPORTER_OTLP_METRICS_HEADERS OTEL_EXPORTER_OTLP_HEADERS W3C Baggage without ;-separated properties If defined, headers are merged with OTEL_EXPORTER_OTLP_HEADERS, preferring those in OTEL_EXPORTER_OTLP_METRICS_HEADERS
OTEL_SERVICE_NAME unknown_service Any string When set, the service name sets the service.name property in OTEL_RESOURCE_ATTRIBUTES, overriding any that's already there.
OTEL_RESOURCE_ATTRIBUTES Empty W3C Baggage without ;-separated properties The resource will also include values for telemetry.sdk.name, telemetry.sdk.version, and telemetry.sdk.language.

New environment variables that affect configuration may be added in the future.

WebAssembly

emit_otlp can be used in Node and browser applications by compiling to WebAssembly using the wasm32-unknown-unknown target.

When running in WebAssembly, requests are made using the fetch API. This supports the following transports:

  • HTTP+protobuf
  • HTTP+JSON

Compression via gzip is supported in WebAssembly.

CORS

If you're running in a browser, you'll likely need to configure CORS on your upstream OpenTelemetry-compatible service, otherwise attempts to export telemetry will fail. CORS configuration requires allow-listing a set of origins, and request headers.

emit_otlp issues requests using the following HTTP methods:

  • POST

emit_otlp may add the following request headers:

  • content-type
  • content-encoding
  • traceparent
  • tracestate
  • user-agent
  • Any custom headers you've configured

Flushing

Calling blocking_flush in WebAssembly will immediately return false without flushing. To flush emit_otlp in WebAssembly, you can call Otlp::flush on your spawned emitter directly. emit makes this available to you in the return value of emit::setup:

let rt = emit::setup()
.emit_to(emit_otlp::new()
.resource(emit::props! {
#[emit::key("service.name")]
service_name: emit::pkg!(),
})
.logs(emit_otlp::logs_grpc_proto("http://localhost:4318"))
.spawn())
.init();

// This will not flush in WebAssembly
let flushed = rt.blocking_flush(std::time::Duration::from_secs(30));
assert!(!flushed);

// This will flush in WebAssembly
let flushed = rt.emitter().flush(std::time::Duration::from_secs(30)).await;
assert!(flushed);

Logs

All emit::Events can be represented as OTLP log records. You should at least configure the logs signal to make sure all diagnostics are captured in some way. A minimal logging configuration for gRPC+protobuf is:

emit_otlp::new()
.resource(emit::props! {
#[emit::key("service.name")]
service_name: emit::pkg!(),
})
.logs(emit_otlp::logs_grpc_proto("http://localhost:4318"))
.spawn()

The following diagnostic:

emit::info!("Hello, OTLP!");

will produce the following HTTP+JSON export request:

http://localhost:4318/v1/logs
{
"resourceLogs": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": {
"stringValue": "my_app"
}
}
]
},
"scopeLogs": [
{
"scope": {
"name": "my_app"
},
"logRecords": [
{
"timeUnixNano": 1716804019165847000,
"observedTimeUnixNano": 1716804019165847000,
"body": {
"stringValue": "Hello, OTLP!"
},
"attributes": [],
"severityNumber": 9,
"severityText": "info"
}
]
}
]
}
]
}

Configuring the log record body

By default, emit::Event::msg is used as the body of the OTLP log record.

The OtlpLogsBuilder::body method can be used to customize the body. This method accepts an emit::Event and a Formatter to write the body into.

In this example, the body is customized to use the emit::Event::tpl instead:

emit_otlp::new()
.logs(emit_otlp::logs_grpc_proto("http://localhost:4318")
.body(|evt, f| write!(f, "{}", evt.tpl())))
.spawn()

Fallback for traces

When the traces signal is not configured, diagnostic events for spans are represented as regular OTLP log records. The following diagnostic:

#[emit::span("Compute {a} + {b}")]
fn add(a: i32, b: i32) -> i32 {
let r = a + b;

emit::info!("Produced {r}", r);

r
}

add(1, 3);

will produce the following HTTP+JSON export request:

http://localhost:4318/v1/logs
{
"resourceLogs": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": {
"stringValue": "my_app"
}
}
]
},
"scopeLogs": [
{
"scope": {
"name": "my_app"
},
"logRecords": [
{
"timeUnixNano": 1716804240222377000,
"observedTimeUnixNano": 1716804240222377000,
"body": {
"stringValue": "Produced 4"
},
"attributes": [
{
"key": "a",
"value": {
"intValue": 1
}
},
{
"key": "b",
"value": {
"intValue": 3
}
},
{
"key": "r",
"value": {
"intValue": 4
}
}
],
"severityNumber": 9,
"severityText": "info",
"traceId": "489571cc6b94414ceb4a32ccc2c7df09",
"spanId": "a93239061c12aa4c"
},
{
"timeUnixNano": 1716804240222675000,
"observedTimeUnixNano": 1716804240222675000,
"body": {
"stringValue": "Compute 1 + 3"
},
"attributes": [
{
"key": "a",
"value": {
"intValue": 1
}
},
{
"key": "b",
"value": {
"intValue": 3
}
},
{
"key": "evt_kind",
"value": {
"stringValue": "span"
}
},
{
"key": "span_name",
"value": {
"stringValue": "Compute {a} + {b}"
}
}
],
"severityNumber": 9,
"severityText": "info",
"traceId": "489571cc6b94414ceb4a32ccc2c7df09",
"spanId": "a93239061c12aa4c"
}
]
}
]
}
]
}

Fallback for metrics

When the metrics signal is not configured, diagnostic events for metric samples are represented as regular OTLP log records. The following diagnostic:

emit::emit!(
evt: emit::Metric::new(
emit::mdl!(),
"my_metric",
"count",
emit::Empty,
42,
emit::Empty,
)
);

will produce the following HTTP+JSON export request:

http://localhost:4318/v1/logs
{
"resourceLogs": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": {
"stringValue": "my_app"
}
}
]
},
"scopeLogs": [
{
"scope": {
"name": "my_app"
},
"logRecords": [
{
"timeUnixNano": 1716876516012074000,
"observedTimeUnixNano": 1716876516012074000,
"body": {
"stringValue": "count of my_metric is 42"
},
"attributes": [
{
"key": "evt_kind",
"value": {
"stringValue": "metric"
}
},
{
"key": "metric_agg",
"value": {
"stringValue": "count"
}
},
{
"key": "metric_name",
"value": {
"stringValue": "my_metric"
}
},
{
"key": "metric_value",
"value": {
"intValue": 42
}
}
],
"severityNumber": 9,
"severityText": "info"
}
]
}
]
}
]
}

Traces

When the traces signal is configured, emit::Events can be represented as OTLP spans so long as they satisfy the following conditions:

If any condition is not met, the event will be represented as an OTLP log record. If the logs signal is not configured then it will be discarded.

A minimal logging configuration for gRPC+protobuf is:

emit_otlp::new()
.resource(emit::props! {
#[emit::key("service.name")]
service_name: emit::pkg!(),
})
.traces(emit_otlp::traces_grpc_proto("http://localhost:4318"))
.logs(emit_otlp::logs_grpc_proto("http://localhost:4318"))
.spawn()

The following diagnostic:

#[emit::span("Compute {a} + {b}")]
fn add(a: i32, b: i32) -> i32 {
let r = a + b;

emit::info!("Produced {r}", r);

r
}

add(1, 3);

will produce the following HTTP+JSON export requests:

http://localhost:4318/v1/traces
{
"resourceSpans": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": {
"stringValue": "my_app"
}
}
]
},
"scopeSpans": [
{
"scope": {
"name": "my_app"
},
"spans": [
{
"name": "Compute {a} + {b}",
"kind": 0,
"startTimeUnixNano": 1716888416629816000,
"endTimeUnixNano": 1716888416630814000,
"attributes": [
{
"key": "a",
"value": {
"intValue": 1
}
},
{
"key": "b",
"value": {
"intValue": 3
}
}
],
"traceId": "0a85ccaf666e11aaca6bd5d469e2850d",
"spanId": "2b9caa35eaefed3a"
}
]
}
]
}
]
}
http://localhost:4318/v1/logs
{
"resourceLogs": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": {
"stringValue": "my_app"
}
}
]
},
"scopeLogs": [
{
"scope": {
"name": "my_app"
},
"logRecords": [
{
"timeUnixNano": 1716888416630507000,
"observedTimeUnixNano": 1716888416630507000,
"body": {
"stringValue": "Produced 4"
},
"attributes": [
{
"key": "a",
"value": {
"intValue": 1
}
},
{
"key": "b",
"value": {
"intValue": 3
}
},
{
"key": "r",
"value": {
"intValue": 4
}
}
],
"severityNumber": 9,
"severityText": "info",
"traceId": "0a85ccaf666e11aaca6bd5d469e2850d",
"spanId": "2b9caa35eaefed3a"
}
]
}
]
}
]
}

Errors

If the event contains an err property, then the resulting OTLP span will carry the semantic exception event:

#[emit::span(guard: span, "Compute {a} + {b}")]
fn add(a: i32, b: i32) -> i32 {
let r = a + b;

if r == 4 {
span.complete_with(emit::span::completion::from_fn(|evt| {
emit::error!(
evt,
"Compute {a} + {b} failed",
a,
b,
r,
err: "Invalid result",
);
}));
}

r
}

add(1, 3);
http://localhost:4318/v1/traces
{
"resourceSpans": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": {
"stringValue": "my_app"
}
}
]
},
"scopeSpans": [
{
"scope": {
"name": "my_app"
},
"spans": [
{
"name": "Compute {a} + {b}",
"kind": 0,
"startTimeUnixNano": 1716936430882852000,
"endTimeUnixNano": 1716936430883250000,
"attributes": [
{
"key": "a",
"value": {
"intValue": 1
}
},
{
"key": "b",
"value": {
"intValue": 3
}
},
{
"key": "r",
"value": {
"intValue": 4
}
}
],
"traceId": "6499bc190add060dad8822600ba65226",
"spanId": "b72c5152c32cc432",
"events": [
{
"name": "exception",
"timeUnixNano": 1716936430883250000,
"attributes": [
{
"key": "exception.message",
"value": {
"stringValue": "Invalid result"
}
}
]
}
],
"status": {
"message": "Invalid result",
"code": 2
}
}
]
}
]
}
]
}

If the event contains a span_links property, then the resulting OTLP span will carry that set of links:

#[emit::span(
guard: span,
"Compute {a} + {b}",
)]
fn add(a: i32, b: i32) -> i32 {
let span_links = [
"0a85ccaf666e11aaca6bd5d469e2850d-2b9caa35eaefed3a",
];

let _span = span.push_prop("span_links", emit::Value::capture_sval(&span_links));

a + b
}

add(1, 3);

will produce the following HTTP+JSON export requests:

http://localhost:4318/v1/traces
{
"resourceSpans": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": {
"stringValue": "my_app"
}
}
]
},
"scopeSpans": [
{
"scope": {
"name": "my_app"
},
"spans": [
{
"name": "Compute {a} + {b}",
"kind": 0,
"startTimeUnixNano": 1767960848098372000,
"endTimeUnixNano": 1767960848100910000,
"attributes": [
{
"key": "a",
"value": {
"intValue": 1
}
},
{
"key": "b",
"value": {
"intValue": 3
}
}
],
"traceId": "23505f707eea9acf98fef2431289389c",
"spanId": "c4e3bd5e6c96d346",
"links": [
{
"traceId": "0a85ccaf666e11aaca6bd5d469e2850d",
"spanId": "2b9caa35eaefed3a"
}
],
"status": {
"message": "info",
"code": 1
}
}
]
}
]
}
]
}

Customizing span names

By default, if an event contains a property called span_name then it will be used as the name field on the resulting OTLP span. If there's no span_name property on the event, then emit::Event::msg is used instead.

The OtlpTracesBuilder::name method can be used to customize the name. This method accepts an emit::Event and a Formatter to write the name into.

In this example, the body is customized to use the emit::Event::tpl instead:

emit_otlp::new()
.traces(emit_otlp::traces_grpc_proto("http://localhost:4318")
.name(|evt, f| write!(f, "{}", evt.tpl())))
.spawn()

Customizing span kinds

By default, if an event contains a property called span_kind with a parsable emit::span::SpanKind then it will be used as the kind field on the resulting OTLP span. If there's no span_kind, or it's not a valid SpanKind, then it's left unspecified.

The OtlpTracesBuilder::kind method can be used to customize the kind. This method accepts an emit::Event and returns an optional emit::span::SpanKind.

In this example, the kind is customized to always be internal:

emit_otlp::new()
.traces(emit_otlp::traces_grpc_proto("http://localhost:4318")
.kind(|_| Some(emit::span::SpanKind::Internal)))
.spawn()

Metrics

When the metrics signal is configured, emit::Events can be represented as OTLP metrics so long as they satisfy the following conditions:

If any condition is not met, the event will be represented as an OTLP log record. If the logs signal is not configured then it will be discarded.

A minimal logging configuration for gRPC+protobuf is:

emit_otlp::new()
.resource(emit::props! {
#[emit::key("service.name")]
service_name: emit::pkg!(),
})
.metrics(emit_otlp::metrics_grpc_proto("http://localhost:4318"))
.logs(emit_otlp::logs_grpc_proto("http://localhost:4318"))
.spawn()

Counts

If the metric aggregation is "count" then the resulting OTLP metric is a monotonic sum:

emit::emit!(
evt: emit::Metric::new(
emit::mdl!(),
"my_metric",
"count",
emit::Empty,
42,
emit::props! {
a: true
},
)
);
http://localhost:4318/v1/metrics
{
"resourceMetrics": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": {
"stringValue": "my_app"
}
}
]
},
"scopeMetrics": [
{
"scope": {
"name": "my_app"
},
"metrics": [
{
"name": "my_metric",
"unit": null,
"sum": {
"dataPoints": [
{
"attributes": [
{
"key": "a",
"value": {
"boolValue": true
}
}
],
"startTimeUnixNano": 1716889540249854000,
"timeUnixNano": 1716889540249854000,
"value": 42
}
],
"aggregationTemporality": 2,
"isMonotonic": true
}
}
]
}
]
}
]
}

Sums

If the metric aggregation is "sum" then the resulting OTLP metric is a non-monotonic sum:

emit::emit!(
evt: emit::Metric::new(
emit::mdl!(),
"my_metric",
"sum",
emit::Empty,
-8,
emit::props! {
a: true
},
)
);
http://localhost:4318/v1/metrics
{
"resourceMetrics": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": {
"stringValue": "my_app"
}
}
]
},
"scopeMetrics": [
{
"scope": {
"name": "my_app"
},
"metrics": [
{
"name": "my_metric",
"unit": null,
"sum": {
"dataPoints": [
{
"attributes": [
{
"key": "a",
"value": {
"boolValue": true
}
}
],
"startTimeUnixNano": 1716889891391075000,
"timeUnixNano": 1716889891391075000,
"value": -8
}
],
"aggregationTemporality": 2,
"isMonotonic": false
}
}
]
}
]
}
]
}

Gauges

Any other aggregation will be represented as an OTLP gauge:

emit::emit!(
evt: emit::Metric::new(
emit::mdl!(),
"my_metric",
"last",
emit::Empty,
615,
emit::props! {
a: true
},
)
);
http://localhost:4318/v1/metrics
{
"resourceMetrics": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": {
"stringValue": "my_app"
}
}
]
},
"scopeMetrics": [
{
"scope": {
"name": "my_app"
},
"metrics": [
{
"name": "my_metric",
"unit": null,
"gauge": {
"dataPoints": [
{
"attributes": [
{
"key": "a",
"value": {
"boolValue": true
}
}
],
"startTimeUnixNano": 1716890230856380000,
"timeUnixNano": 1716890230856380000,
"value": 615
}
]
}
}
]
}
]
}
]
}

Sequences

If the metric aggregation is "count" or "sum", and value is a sequence, then each value will be summed to produce a single data point:

let start = emit::Timestamp::from_unix(std::time::Duration::from_secs(1716890420));
let end = emit::Timestamp::from_unix(std::time::Duration::from_secs(1716890425));

emit::emit!(
evt: emit::Metric::new(
emit::mdl!(),
"my_metric",
"count",
start..end,
&[
1.0,
1.0,
1.0,
1.0,
1.0,
],
emit::props! {
a: true
},
)
);
http://localhost:4318/v1/metrics
{
"resourceMetrics": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": {
"stringValue": "my_app"
}
}
]
},
"scopeMetrics": [
{
"scope": {
"name": "my_app"
},
"metrics": [
{
"name": "my_metric",
"unit": null,
"sum": {
"dataPoints": [
{
"attributes": [
{
"key": "a",
"value": {
"boolValue": true
}
}
],
"startTimeUnixNano": 1716890420000000000,
"timeUnixNano": 1716890425000000000,
"value": 5
}
],
"aggregationTemporality": 1,
"isMonotonic": true
}
}
]
}
]
}
]
}

Limitations

This library is not an alternative to the OpenTelemetry SDK. It's specifically targeted at emitting diagnostic events to OTLP-compatible services. It has some intentional limitations:

  • No propagation. This is the responsibility of the application to manage.
  • No histogram metrics. emit's data model for metrics is simplistic compared to OpenTelemetry's, so it doesn't support histograms or exponential histograms.
  • No span events. Only the conventional exception event is supported. Standalone log events are not converted into span events. They're sent via the logs endpoint instead.
  • No tracestate. emit's data model for spans doesn't include the W3C tracestate.

Troubleshooting

If you're not seeing diagnostics appear in your OTLP receiver, you can rule out configuration issues in emit_otlp by configuring emit's internal logger, and collect metrics from it:

use emit::metric::Source;

fn main() {
// 1. Initialize the internal logger
//    Diagnostics produced by `emit_otlp` itself will go here
let internal = emit::setup()
.emit_to(emit_term::stdout())
.init_internal();

let mut reporter = emit::metric::Reporter::new();

let rt = emit::setup()
.emit_to({
let otlp = emit_otlp::new()
.resource(emit::props! {
#[emit::key("service.name")]
service_name: emit::pkg!(),
})
.logs(emit_otlp::logs_grpc_proto("http://localhost:4319"))
.traces(emit_otlp::traces_grpc_proto("http://localhost:4319"))
.metrics(emit_otlp::metrics_grpc_proto("http://localhost:4319"))
.spawn();

// 2. Add `emit_otlp`'s metrics to a reporter so we can see what it's up to
//    You can do this independently of the internal emitter
reporter.add_source(otlp.metric_source());

otlp
})
.init();

// Your app code goes here

rt.blocking_flush(std::time::Duration::from_secs(30));

// 3. Report metrics after attempting to flush
//    You could also do this periodically as your application runs
reporter.emit_metrics(&internal.emitter());
}

Diagnostics include when batches are emitted, and any failures observed along the way.


emit_otlp

otlp

Current docs

Emit diagnostic events via the OpenTelemetry Protocol (OTLP).

This library sends export requests directly to some remote OTLP receiver. If you need to integrate emit with the OpenTelemetry SDK, see emit-opentelemetry.

Dependencies

~12–32MB
~410K SLoC