Telemetry

Crate hopf-otel: OpenTelemetry-style exporters and HTTP Stream instrumentation. Hot-path methods only enqueue; encoding and I/O run on a dedicated export worker (never on accept or reactor threads). Own VERSION. Connection-level hooks implement hopf_core::TelemetryHook; per-request traces/metrics wrap HTTP ServerHandlerFactory.

Standards

Topic Detail
OTLP/HTTP Protobuf to /v1/logs, /v1/traces, /v1/metrics
W3C Trace Context traceparent = 00-<32hex trace>-<16hex span>-<flags>
JSONL Optional file sinks
Semconv http.method, http.target, http.host, http.scheme, http.status_code

Architecture

Accept / dial / close     HTTP ServerHandler
        │                         │
        ▼                         ▼
TelemetryHook enqueue    InstrumentedServerFactory
        │                  (SERVER span + RED metrics)
        └──────────► TelemetryPipeline export worker
                          │
                          ├── OTLP HTTP endpoints
                          └── JSONL files

Connection TelemetryHook

Core trait (implemented / adapted by the pipeline):

pub trait TelemetryHook: Send + Sync {
    fn on_accept(&self, peer: SocketAddr) {}
    fn on_dial(&self, peer: SocketAddr) {}
    fn on_close(&self, peer: SocketAddr) {}
    fn on_error(&self, peer: Option<SocketAddr>, msg: &str) {}
}

Attach via Runtime::start_with_telemetry or Composition::new_with_telemetry / from_xml*_with_telemetry. ACL / rate-limit denials report through on_error. NopTelemetry discards everything.

Implementations must not perform I/O on accept or reactor threads.

Events: TelemetryEvent / EventKind::{Accept,Dial,Close,Error}.

HTTP instrumentation

Instrument at the Stream handler layer — not at TCP accept:

Type Role
InstrumentedServerFactory Wraps ServerHandlerFactory
HttpServerMetrics RED-style counters/gauges
MetricPoint Counter / UpDown / …
RequestTimer Latency
Trace / Span / FinishedSpan / SpanContext Tracing
SpanKind / SpanStatusCode Span metadata
ExportHandle Enqueue handle

Flags traces_enabled / metrics_enabled mirror Gumdrop-style toggles.

use std::sync::Arc;
use hopf_otel::{InstrumentedServerFactory, OtelConfig, TelemetryPipeline};

let pipeline = TelemetryPipeline::start(
    OtelConfig::new("echo").with_otlp_endpoint("http://127.0.0.1:4318"),
)?;
let factory = InstrumentedServerFactory::new(inner_factory, &pipeline);
// or: InstrumentedServerFactory::with_parts(inner, export, metrics, traces_enabled)

Configuration

OtelConfig

Property Type Default Notes
service_name String "hopf" (new(name) sets it) Resource
service_version Option<String> None
service_namespace Option<String> None
batch_size usize 64 Export batch
flush_interval duration 5s Background flush
queue_capacity usize 4096 Overflow drops
traces_enabled bool true
metrics_enabled bool true
logs_enabled bool true
otlp_logs_endpoint Option<…> None
otlp_traces_endpoint Option<…> None
otlp_metrics_endpoint Option<…> None
jsonl_logs_path Option<…> None
jsonl_traces_path Option<…> None
jsonl_metrics_path Option<…> None

Builders: with_otlp_endpoint(base) (derives the three /v1/* URLs), with_otlp_logs, with_jsonl_logs / with_jsonl_traces / with_jsonl_metrics, with_traces(bool), with_metrics(bool), has_sink().

TelemetryPipeline::start validates OTLP URLs and errors if no sink is configured or a URL is bad.

No Cargo features.

Propagation

API Role
inject_trace / inject_traceparent Write into Headers
with_trace / with_traceparent Wrap ClientWriter
PropagatingClientWriter / OwnedPropagatingClientWriter Outbound
Trace::from_traceparent Continue inbound context
trace.traceparent() Serialise current

HTTP writers may also expose traceparent() for response continuation (HTTP overview).

DNS

DNS is deliberately not part of Hopf’s W3C Trace Context chaining. Unlike HTTP (and SMTP/FTP/POP3/IMAP/MQTT control paths), there is no IETF standard for carrying traceparent on DNS queries — PowerDNS’s experimental TRACEPARENT EDNS option is out of scope.

What people usually do instead:

See DNS: Telemetry.

Wiring

use hopf_core::{Runtime, RuntimeConfig};
use hopf_otel::{OtelConfig, TelemetryPipeline};

let pipeline = TelemetryPipeline::start(
    OtelConfig::new("my-service")
        .with_otlp_endpoint("http://127.0.0.1:4318"),
)?;

let rt = Runtime::start_with_telemetry(
    RuntimeConfig::default(),
    Some(pipeline.hook()),
)?;

// HTTP:
let instrumented = InstrumentedServerFactory::new(app_factory, &pipeline);

// Later:
pipeline.flush();
pipeline.shutdown();

Accessors: pipeline.hook(), pipeline.http_metrics(), pipeline.smtp_metrics(), pipeline.ftp_metrics(), pipeline.pop3_metrics(), pipeline.imap_metrics(), pipeline.mqtt_metrics(), pipeline.export_handle().

Examples

No standalone example crate. README-style snippet:

OtelConfig::new("echo").with_otlp_endpoint("http://127.0.0.1:4318")

Use with any HTTP cookbook page (HTTP hello / get) by wrapping the server factory. For SMTP, FTP, POP3, IMAP, or MQTT, call *Service::with_telemetry(&pipeline) so handlers see traceparent on connection metadata and OTLP metrics are emitted.

Limitations