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.
Contents
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:
- Time a client lookup as a local child span under the application request (duration/error only — nothing propagates on the DNS wire).
- Export resolver/server ops metrics (qps, NXDOMAIN, cache hits). Hopf’s
DnsService::metrics()is process-local; it is not an OTLP instrument set. - Still get Accept/Dial/Close via
TelemetryHookwhen the runtime uses a pipeline.
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
- Connection Accept/Dial/Close logs apply to every protocol via
TelemetryHook. - HTTP Stream RED metrics and SERVER spans:
InstrumentedServerFactory. - SMTP transaction duration histograms and handler
traceparent:SmtpService::with_telemetry(see SMTP metrics). - FTP transfer duration histograms and handler
traceparent:FtpService::with_telemetry(see FTP metrics). - POP3 retrieve duration histograms and handler
traceparent:Pop3Service::with_telemetry(see POP3 metrics). - IMAP command duration histograms and handler
traceparent:ImapService::with_telemetry(see IMAP metrics). - MQTT publish duration histograms and handler
traceparent:MqttService::with_telemetry/MqttWsFactory::with_telemetry(see MQTT metrics). - DNS is not instrumented for Trace Context propagation (no IETF standard; experimental EDNS TRACEPARENT out of scope). Client lookups may still be timed as local child spans;
DnsService::metrics()stays process-local — see DNS and DNS docs. - Bounded queue drops on overflow.
flush()sleeps ~50 ms to let the worker drain.- OTLP is HTTP/protobuf only (no gRPC OTLP exporter).