diff options
| author | Nikolay Govorov <me@govorov.online> | 2026-01-30 04:09:30 +0000 |
|---|---|---|
| committer | Nikolay Govorov <me@govorov.online> | 2026-01-30 04:09:30 +0000 |
| commit | ac40af04288b870641fe1a2ac23157b228ee13f6 (patch) | |
| tree | 6b909089a77fee3992b07e4e1487d60873e411e9 | |
| parent | a5b92c6faeaef6f7e07da124115a8032c439497d (diff) | |
| download | tar tar.gz tar.bz2 tar.lz tar.xz tar.zst zip | |
Fixes adding timestamps to logs
Diffstat
| -rw-r--r-- | src/telemetry.rs | 43 | +36 −7 |
1 files changed, 36 insertions, 7 deletions
diff --git a/src/telemetry.rs b/src/telemetry.rs index e108a59..116a7d0 100644 --- a/src/telemetry.rs +++ b/src/telemetry.rs @@ -1,13 +1,19 @@ // SPDX-FileCopyrightText: 2026 Nikolay Govorov <me@govorov.online> // SPDX-License-Identifier: AGPL-3.0-or-later +use std::fmt::Debug; +use std::time::SystemTime; + +use opentelemetry::InstrumentationScope; use opentelemetry::KeyValue; +use opentelemetry::logs::LogRecord as _; use opentelemetry::trace::TracerProvider as _; use opentelemetry_otlp::{ LogExporter, Protocol, SpanExporter, WithExportConfig, WithHttpConfig, WithTonicConfig, }; use opentelemetry_sdk::Resource; -use opentelemetry_sdk::logs::SdkLoggerProvider; +use opentelemetry_sdk::error::OTelSdkResult; +use opentelemetry_sdk::logs::{BatchLogProcessor, LogProcessor, SdkLogRecord, SdkLoggerProvider}; use opentelemetry_sdk::trace::SdkTracerProvider; use tonic::metadata::{MetadataKey, MetadataMap, MetadataValue}; use tonic::transport::{Certificate, ClientTlsConfig, Identity}; @@ -17,6 +23,31 @@ use tracing_subscriber::util::SubscriberInitExt; use crate::config::{LogLevel, OtelcolConfig, StdoutFormat, TelemetryConfig}; +/// LogProcessor wrapper that sets timestamp from observed_timestamp. +/// +/// opentelemetry-appender-tracing intentionally doesn't set timestamp because +/// tracing doesn't provide it. This causes some backends to show epoch (1970-01-01). +/// See: https://github.com/open-telemetry/opentelemetry-rust/issues/1479 +#[derive(Debug)] +struct TimestampLogProcessor<P>(P); +impl<P: LogProcessor> LogProcessor for TimestampLogProcessor<P> { + fn emit(&self, record: &mut SdkLogRecord, scope: &InstrumentationScope) { + if record.timestamp().is_none() { + let ts = record.observed_timestamp().unwrap_or_else(SystemTime::now); + record.set_timestamp(ts); + } + self.0.emit(record, scope); + } + + fn force_flush(&self) -> OTelSdkResult { + self.0.force_flush() + } + + fn shutdown(&self) -> OTelSdkResult { + self.0.shutdown() + } +} + macro_rules! build_exporter { ($Exporter:ident, $cfg:expr, $url:expr, $tls:expr, $path:expr) => { if $cfg.endpoint.starts_with("http://") { @@ -103,14 +134,12 @@ impl TelemetryService { .build(); let logs = cfg.logs.then(|| { + let exporter = + build_exporter!(LogExporter, cfg, url.clone(), tls.clone(), "/v1/logs"); let provider = SdkLoggerProvider::builder() .with_resource(resource.clone()) - .with_batch_exporter(build_exporter!( - LogExporter, - cfg, - url.clone(), - tls.clone(), - "/v1/logs" + .with_log_processor(TimestampLogProcessor( + BatchLogProcessor::builder(exporter).build(), )) .build(); let layer = |
