Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ opentelemetry-zipkin = { version = "0.31", default-features = false }
rstest = "0.26"
tokio = { version = "1", default-features = false }
tokio-stream = { version = "0.1", default-features = false }
tonic = { version = "0.14", default-features = false } # should be sync with opentelemetry-proto
tonic = { version = "0.14", default-features = false } # should be sync with opentelemetry-proto
tower = { version = "0.5", default-features = false }
tracing = "0.1"
tracing-opentelemetry = "0.32"
Expand Down
20 changes: 20 additions & 0 deletions examples/logging/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "examples-logging"
publish = false
edition.workspace = true
version.workspace = true
repository.workspace = true
license.workspace = true

[dependencies]
init-tracing-opentelemetry = { path = "../../init-tracing-opentelemetry", features = [
"otlp",
"tracing_subscriber_ext",
"logs"
] }
memory-stats = "1"
opentelemetry = { workspace = true }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
tracing = { workspace = true }
tracing-opentelemetry-instrumentation-sdk = { path = "../../tracing-opentelemetry-instrumentation-sdk" }
25 changes: 25 additions & 0 deletions examples/logging/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#[tracing::instrument]
async fn log() {
tracing::error!("This is ground control to Major Tom");
tracing::warn!("Houston, we have a problem");
tracing::info!("We have contact");
tracing::debug!("Roger, copy that");
tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
}

#[tracing::instrument]
async fn calc(a: i32, b: i32) {
let result = a + b;
tracing::info!(result, "calculated result");
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// setting up tracing
let _guard = init_tracing_opentelemetry::TracingConfig::production().init_subscriber()?;

log().await;
calc(1, 2).await;

Ok(())
}
11 changes: 8 additions & 3 deletions init-tracing-opentelemetry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ rustdoc-args = ["--cfg", "docs_rs"]

[dependencies]
opentelemetry = { workspace = true }
opentelemetry-appender-tracing = { version = "0.31", default-features = false, optional = true }
opentelemetry-aws = { workspace = true, optional = true, features = ["trace"] }
opentelemetry-jaeger-propagator = { workspace = true, optional = true }
opentelemetry-otlp = { workspace = true, optional = true, features = [
Expand All @@ -28,9 +29,7 @@ opentelemetry-stdout = { workspace = true, features = [
], optional = true }
opentelemetry-semantic-conventions = { workspace = true, optional = true }
opentelemetry-zipkin = { workspace = true, features = [], optional = true }
opentelemetry_sdk = { workspace = true, features = [
"trace",
] }
opentelemetry_sdk = { workspace = true, features = ["trace"] }
thiserror = "2"
tonic = { workspace = true, optional = true }
tracing = { workspace = true }
Expand Down Expand Up @@ -81,6 +80,12 @@ tls = ["opentelemetry-otlp/tls", "tonic"]
tls-roots = ["opentelemetry-otlp/tls-roots"]
tls-webpki-roots = ["opentelemetry-otlp/tls-webpki-roots"]
logfmt = ["dep:tracing-logfmt"]
logs = [
"dep:opentelemetry-appender-tracing",
"opentelemetry-otlp/logs",
"opentelemetry_sdk/logs",
"opentelemetry/logs",
]
metrics = [
"opentelemetry-otlp/metrics",
"tracing-opentelemetry/metrics",
Expand Down
50 changes: 50 additions & 0 deletions init-tracing-opentelemetry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,56 @@ Configure the following set of environment variables to configure the metrics ex
- `OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE` to set the temporality preference for the exporter
- `OTEL_METRIC_EXPORT_INTERVAL` to set frequence of metrics export in **_milliseconds_**, defaults to 60s

## Logs

To configure OpenTelemetry log export, enable the `logs` feature. This initializes a `SdkLoggerProvider` and adds a log bridge layer so that `tracing` events are forwarded to the OpenTelemetry log pipeline and exported via OTLP.

```toml
[dependencies]
init-tracing-opentelemetry = { version = "*", features = ["otlp", "logs"] }
```

Standard `tracing` macros emit logs that are automatically bridged:

```rust
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let _guard = init_tracing_opentelemetry::TracingConfig::production().init_subscriber()?;

tracing::error!("This is ground control to Major Tom");
tracing::warn!("Houston, we have a problem");
tracing::info!("We have contact");
tracing::debug!("Roger, copy that");

Ok(())
}
```

Log export can be toggled at runtime via `.with_logs(bool)`:

```rust,no_run
TracingConfig::default()
.with_logs(false) // disable log export (default: enabled when feature is active)
.init_subscriber()?;
```

Configure the following environment variables to control the logs exporter (in addition to the shared variables above):

- `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` overrides `OTEL_EXPORTER_OTLP_ENDPOINT` for the log pipeline; for HTTP the path `/v1/logs` is appended automatically
- `OTEL_EXPORTER_OTLP_LOGS_PROTOCOL` overrides `OTEL_EXPORTER_OTLP_PROTOCOL`, falls back to port-based auto-detection

```sh
# For GRPC:
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT="http://localhost:4317"
export OTEL_EXPORTER_OTLP_LOGS_PROTOCOL="grpc"

# For HTTP:
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT="http://127.0.0.1:4318/v1/logs"
export OTEL_EXPORTER_OTLP_LOGS_PROTOCOL="http/protobuf"
```

> **Note:** A protocol must be set (via env var or inferable from the endpoint port). If neither is found, no log exporter is created and a warning is emitted on target `otel::setup`.

## Changelog - History

[CHANGELOG.md](https://github.com/davidB/tracing-opentelemetry-instrumentation-sdk/blob/main/CHANGELOG.md)
18 changes: 18 additions & 0 deletions init-tracing-opentelemetry/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ use crate::formats::{
CompactLayerBuilder, FullLayerBuilder, JsonLayerBuilder, LayerBuilder, PrettyLayerBuilder,
};

#[cfg(feature = "logs")]
use crate::tracing_subscriber_ext::build_logger_layer_with_resource;
#[cfg(feature = "metrics")]
use crate::tracing_subscriber_ext::build_metrics_layer_with_resource;
use crate::tracing_subscriber_ext::build_tracer_layer_with_resource_and_name;
Expand Down Expand Up @@ -244,6 +246,8 @@ pub struct OtelConfig {
pub enabled: bool,
/// Resource configuration for OTEL
pub resource_config: Option<DetectResource>,
/// Enable log export via OTLP
pub logs_enabled: bool,
/// Enable metrics collection
pub metrics_enabled: bool,
}
Expand All @@ -253,6 +257,7 @@ impl Default for OtelConfig {
Self {
enabled: true,
resource_config: None,
logs_enabled: cfg!(feature = "logs"),
metrics_enabled: cfg!(feature = "metrics"),
}
}
Expand Down Expand Up @@ -477,6 +482,13 @@ impl TracingConfig {
self
}

/// Enable or disable log export via OTLP
#[must_use]
pub fn with_logs(mut self, enabled: bool) -> Self {
self.otel_config.logs_enabled = enabled;
self
}

/// Enable or disable metrics collection
#[must_use]
pub fn with_metrics(mut self, enabled: bool) -> Self {
Expand Down Expand Up @@ -626,16 +638,22 @@ impl TracingConfig {
.clone()
.unwrap_or_default()
.build();
#[cfg(feature = "logs")]
let (logs_layer, logger_provider) = build_logger_layer_with_resource(otel_rsrc.clone())?;
#[cfg(feature = "metrics")]
let (metrics_layer, meter_provider) = build_metrics_layer_with_resource(otel_rsrc.clone())?;
let (trace_layer, tracer_provider) =
build_tracer_layer_with_resource_and_name(otel_rsrc, self.tracer_name.clone())?;
let subscriber = subscriber.with(trace_layer);
#[cfg(feature = "logs")]
let subscriber = subscriber.with(self.otel_config.logs_enabled.then_some(logs_layer));
#[cfg(feature = "metrics")]
let subscriber = subscriber.with(metrics_layer);
Ok((
subscriber,
OtelGuard {
#[cfg(feature = "logs")]
logger_provider,
#[cfg(feature = "metrics")]
meter_provider,
tracer_provider,
Expand Down
69 changes: 69 additions & 0 deletions init-tracing-opentelemetry/src/otlp/logs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use super::infer_protocol;
use opentelemetry_otlp::{ExporterBuildError, LogExporter};
use opentelemetry_sdk::{Resource, logs::LoggerProviderBuilder, logs::SdkLoggerProvider};
#[cfg(feature = "tls")]
use {opentelemetry_otlp::WithTonicConfig, tonic::transport::ClientTlsConfig};

#[must_use]
pub fn identity(v: LoggerProviderBuilder) -> LoggerProviderBuilder {
v
}

pub fn init_loggerprovider<F>(
resource: Resource,
transform: F,
) -> Result<SdkLoggerProvider, ExporterBuildError>
where
F: FnOnce(LoggerProviderBuilder) -> LoggerProviderBuilder,
{
let (maybe_protocol, maybe_endpoint) = read_protocol_and_endpoint_from_env();
let protocol = infer_protocol(maybe_protocol.as_deref(), maybe_endpoint.as_deref());

let exporter: Option<LogExporter> = match protocol.as_deref() {
Some("http/protobuf") => Some(LogExporter::builder().with_http().build()?),
#[cfg(feature = "tls")]
Some("grpc/tls") => Some(
LogExporter::builder()
.with_tonic()
.with_tls_config(ClientTlsConfig::new().with_enabled_roots())
.build()?,
),
Some("grpc") => Some(LogExporter::builder().with_tonic().build()?),
Some(x) => {
tracing::warn!(
"unknown '{x}' env var set or infered for OTEL_EXPORTER_OTLP_LOGS_PROTOCOL or OTEL_EXPORTER_OTLP_PROTOCOL; no log exporter will be created"
);
None
}
None => {
tracing::warn!(
"no env var set or infered for OTEL_EXPORTER_OTLP_LOGS_PROTOCOL or OTEL_EXPORTER_OTLP_PROTOCOL; no log exporter will be created"
);
None
}
};
Comment on lines +22 to +44

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The maybe_endpoint retrieved from environment variables is never used to configure the LogExporter. This means the exporter will always use default endpoints (e.g., http://localhost:4318/v1/logs for HTTP or http://localhost:4317 for gRPC), ignoring the OTEL_EXPORTER_OTLP_LOGS_ENDPOINT or OTEL_EXPORTER_OTLP_ENDPOINT settings.

    let exporter: Option<LogExporter> = match protocol.as_deref() {
        Some("http/protobuf") => {
            let mut builder = LogExporter::builder().with_http();
            if let Some(endpoint) = maybe_endpoint {
                builder = builder.with_endpoint(endpoint);
            }
            Some(builder.build()?)
        }
        #[cfg(feature = "tls")]
        Some("grpc/tls") => {
            let mut builder = LogExporter::builder()
                .with_tonic()
                .with_tls_config(ClientTlsConfig::new().with_enabled_roots());
            if let Some(endpoint) = maybe_endpoint {
                builder = builder.with_endpoint(endpoint);
            }
            Some(builder.build()?)
        }
        Some("grpc") => {
            let mut builder = LogExporter::builder().with_tonic();
            if let Some(endpoint) = maybe_endpoint {
                builder = builder.with_endpoint(endpoint);
            }
            Some(builder.build()?)
        }
        Some(x) => {
            tracing::warn!(
                "unknown '{x}' env var set or infered for OTEL_EXPORTER_OTLP_LOGS_PROTOCOL or OTEL_EXPORTER_OTLP_PROTOCOL; no log exporter will be created"
            );
            None
        }
        None => {
            tracing::warn!(
                "no env var set or infered for OTEL_EXPORTER_OTLP_LOGS_PROTOCOL or OTEL_EXPORTER_OTLP_PROTOCOL; no log exporter will be created"
            );
            None
        }
    };

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar logic is used for tracing code, I pretty much copied the implementation from traces. Can fix that though.

let mut logger_provider = SdkLoggerProvider::builder().with_resource(resource);
if let Some(exporter) = exporter {
logger_provider = logger_provider.with_batch_exporter(exporter);
}

logger_provider = transform(logger_provider);
Ok(logger_provider.build())
}

fn read_protocol_and_endpoint_from_env() -> (Option<String>, Option<String>) {
let maybe_protocol = std::env::var("OTEL_EXPORTER_OTLP_LOGS_PROTOCOL")
.or_else(|_| std::env::var("OTEL_EXPORTER_OTLP_PROTOCOL"))
.ok();
let maybe_endpoint = std::env::var("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT")
.or_else(|_| {
std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").map(|endpoint| match &maybe_protocol {
Some(protocol) if protocol.contains("http") => {
format!("{endpoint}/v1/logs")
}
_ => endpoint,
})
})
.ok();
(maybe_protocol, maybe_endpoint)
}
24 changes: 22 additions & 2 deletions init-tracing-opentelemetry/src/otlp/mod.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
#[cfg(feature = "logs")]
pub mod logs;
#[cfg(feature = "metrics")]
pub mod metrics;
pub mod traces;

#[cfg(feature = "logs")]
use opentelemetry::logs::LoggerProvider;
#[cfg(feature = "metrics")]
use opentelemetry::metrics::MeterProvider;
#[cfg(feature = "logs")]
use opentelemetry_sdk::logs::SdkLoggerProvider;
#[cfg(feature = "metrics")]
use opentelemetry_sdk::metrics::SdkMeterProvider;

use opentelemetry::trace::TracerProvider;
use opentelemetry_sdk::trace::SdkTracerProvider;

#[must_use = "Recommend holding with 'let _guard = ' pattern to ensure final traces/metrics are sent to the server"]
#[must_use = "Recommend holding with 'let _guard = ' pattern to ensure final traces/logs/metrics are sent to the server"]
/// On Drop of the `OtelGuard` instance,
/// the wrapped Tracer/Meter Provider is force to flush and to shutdown (ignoring error).
/// the wrapped Tracer/Logger/Meter Provider is force to flush and to shutdown (ignoring error).
#[allow(clippy::struct_field_names)]
pub struct OtelGuard {
#[cfg(feature = "logs")]
pub(crate) logger_provider: SdkLoggerProvider,
#[cfg(feature = "metrics")]
pub(crate) meter_provider: SdkMeterProvider,
pub(crate) tracer_provider: SdkTracerProvider,
}

impl OtelGuard {
#[cfg(feature = "logs")]
#[must_use]
pub fn logger_provider(&self) -> &impl LoggerProvider {
&self.logger_provider
}

#[must_use]
pub fn tracer_provider(&self) -> &impl TracerProvider {
&self.tracer_provider
Expand All @@ -37,6 +52,11 @@ impl Drop for OtelGuard {
fn drop(&mut self) {
let _ = self.tracer_provider.force_flush();
let _ = self.tracer_provider.shutdown();
#[cfg(feature = "logs")]
{
let _ = self.logger_provider.force_flush();
let _ = self.logger_provider.shutdown();
}
#[cfg(feature = "metrics")]
{
let _ = self.meter_provider.force_flush();
Expand Down
25 changes: 25 additions & 0 deletions init-tracing-opentelemetry/src/tracing_subscriber_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
use std::borrow::Cow;

use opentelemetry::trace::TracerProvider;
#[cfg(feature = "logs")]
use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
#[cfg(feature = "logs")]
use opentelemetry_sdk::logs::{SdkLogger, SdkLoggerProvider};
#[cfg(feature = "metrics")]
use opentelemetry_sdk::metrics::SdkMeterProvider;
use opentelemetry_sdk::{
Expand Down Expand Up @@ -95,15 +99,21 @@ pub fn register_otel_layers_with_resource<S>(
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
#[cfg(feature = "logs")]
let (logs_layer, logger_provider) = build_logger_layer_with_resource(otel_rsrc.clone())?;
#[cfg(feature = "metrics")]
let (metrics_layer, meter_provider) = build_metrics_layer_with_resource(otel_rsrc.clone())?;
let (trace_layer, tracer_provider) = build_tracer_layer_with_resource(otel_rsrc)?;
let subscriber = subscriber.with(trace_layer);
#[cfg(feature = "logs")]
let subscriber = subscriber.with(logs_layer);
#[cfg(feature = "metrics")]
let subscriber = subscriber.with(metrics_layer);
Ok((
subscriber,
OtelGuard {
#[cfg(feature = "logs")]
logger_provider,
#[cfg(feature = "metrics")]
meter_provider,
tracer_provider,
Expand Down Expand Up @@ -135,6 +145,21 @@ where
build_tracer_layer_with_resource_and_name(otel_rsrc, "")
}

#[cfg(feature = "logs")]
pub(crate) fn build_logger_layer_with_resource(
otel_rsrc: Resource,
) -> Result<
(
OpenTelemetryTracingBridge<SdkLoggerProvider, SdkLogger>,
SdkLoggerProvider,
),
crate::Error,
> {
let logger_provider = otlp::logs::init_loggerprovider(otel_rsrc, otlp::logs::identity)?;
let layer = OpenTelemetryTracingBridge::new(&logger_provider);
Ok((layer, logger_provider))
}

pub(crate) fn build_tracer_layer_with_resource_and_name<S>(
otel_rsrc: Resource,
tracer_name: impl Into<Cow<'static, str>>,
Expand Down
Loading