Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b40bd10
First draft of logs enrichment example
stefanobaghino Nov 24, 2025
565d640
Add required feature flag https://github.com/open-telemetry/opentelem…
stefanobaghino Nov 25, 2025
0f3545a
Remove unnecessary debug print
stefanobaghino Nov 25, 2025
542786d
Remove unnecessary comment
stefanobaghino Nov 25, 2025
a446807
Run formatter https://github.com/open-telemetry/opentelemetry-rust/pu…
stefanobaghino Nov 26, 2025
dfc718a
`logs-enrichment` -> `logs-advanced` https://github.com/open-telemetr…
stefanobaghino Nov 26, 2025
e23c21b
Highlight performance reason to implement `event_enabled` https://git…
stefanobaghino Nov 26, 2025
757a802
`SlowEnrichmentLogProcessor` -> `EnrichmentLogProcessor` https://gith…
stefanobaghino Nov 26, 2025
efc20c6
Address linting errors
stefanobaghino Nov 26, 2025
50dba6d
Remove unnecessary artificial slowness https://github.com/open-teleme…
stefanobaghino Nov 27, 2025
3c7623f
Remove filtering log processor https://github.com/open-telemetry/open…
stefanobaghino Dec 2, 2025
90b9752
Addresses https://github.com/open-telemetry/opentelemetry-rust/pull/3…
stefanobaghino Dec 3, 2025
28f3cec
Addresses https://github.com/open-telemetry/opentelemetry-rust/pull/3…
stefanobaghino Dec 3, 2025
e95656e
Addresses https://github.com/open-telemetry/opentelemetry-rust/pull/3…
stefanobaghino Dec 3, 2025
00c0897
Delegate all trait methods https://github.com/open-telemetry/opentele…
stefanobaghino Dec 4, 2025
8da1410
Document `EnrichmentLogProcessor` more comprehensively https://github…
stefanobaghino Dec 4, 2025
013c15f
Merge remote-tracking branch 'upstream/main' into stefanobaghino/2759…
stefanobaghino Dec 4, 2025
4ee58a3
Remove unnecessary feature flag https://github.com/open-telemetry/ope…
stefanobaghino Dec 4, 2025
42d0340
Remove spurious dash in `README.md`
stefanobaghino Dec 4, 2025
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
13 changes: 12 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This directory contains some examples that should help you get start crates from `opentelemetry-rust`.

## log-basic
## logs-basic

This example uses following crates from this repo:

Expand All @@ -12,6 +12,17 @@ This example uses following crates from this repo:

Check this example if you want to understand *how to instrument logs using opentelemetry*.

## logs-advanced

This example uses following crates from this repo:

- opentelemetry(log)
- opentelemetry-appender-tracing
- opentelemetry-stdout

This builds on top of the logs-basic,
and shows how to implement and compose `LogProcessor`s..

## metrics-basic

This example uses following crates from this repo:
Expand Down
21 changes: 21 additions & 0 deletions examples/logs-advanced/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "logs-advanced"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
rust-version = "1.75.0"
publish = false
autobenches = false

[[bin]]
name = "logs-advanced"
path = "src/main.rs"
bench = false

[dependencies]
opentelemetry_sdk = { workspace = true }
opentelemetry-stdout = { workspace = true, features = ["logs"] }
opentelemetry-appender-tracing = { workspace = true }
tracing = { workspace = true, features = ["std"]}
tracing-subscriber = { workspace = true, features = ["env-filter","registry", "std", "fmt"] }
opentelemetry = { workspace = true }
13 changes: 13 additions & 0 deletions examples/logs-advanced/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# OpenTelemetry Log Processor Implementation and Composition - Example

This example builds on top of the `logs-basic`, showing how to implement `LogProcessor`s correctly.

The `EnrichmentProcessor` simulates a processor adding information
to the log captured by the OpenTelemetry SDK, which correctly ensures that the
downstream processor's filtering is captured, avoiding unnecessary work.

## Usage

```shell
cargo run
```
114 changes: 114 additions & 0 deletions examples/logs-advanced/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use opentelemetry::logs::{LogRecord, Severity};
use opentelemetry::InstrumentationScope;
use opentelemetry_appender_tracing::layer;
use opentelemetry_sdk::error::OTelSdkResult;
use opentelemetry_sdk::logs::{LogProcessor, SdkLogRecord, SdkLoggerProvider, SimpleLogProcessor};
use opentelemetry_sdk::Resource;
use tracing::{error, info};
use tracing_subscriber::{prelude::*, EnvFilter};

fn main() {
let exporter = opentelemetry_stdout::LogExporter::default();
let enriching_processor = EnrichmentLogProcessor::new(SimpleLogProcessor::new(exporter));
let provider: SdkLoggerProvider = SdkLoggerProvider::builder()
.with_resource(
Resource::builder()
.with_service_name("log-appender-tracing-example")
.build(),
)
.with_log_processor(enriching_processor)
.build();

// To prevent a telemetry-induced-telemetry loop, OpenTelemetry's own internal
// logging is properly suppressed. However, logs emitted by external components
// (such as reqwest, tonic, etc.) are not suppressed as they do not propagate
// OpenTelemetry context. Until this issue is addressed
// (https://github.com/open-telemetry/opentelemetry-rust/issues/2877),
// filtering like this is the best way to suppress such logs.
//
// The filter levels are set as follows:
// - Allow `info` level and above by default.
// - Completely restrict logs from `hyper`, `tonic`, `h2`, and `reqwest`.
//
// Note: This filtering will also drop logs from these components even when
// they are used outside of the OTLP Exporter.
let filter_otel = EnvFilter::new("info")
.add_directive("hyper=off".parse().unwrap())
.add_directive("tonic=off".parse().unwrap())
.add_directive("h2=off".parse().unwrap())
.add_directive("reqwest=off".parse().unwrap());
let otel_layer = layer::OpenTelemetryTracingBridge::new(&provider).with_filter(filter_otel);

// Create a new tracing::Fmt layer to print the logs to stdout. It has a
// default filter of `info` level and above, and `debug` and above for logs
// from OpenTelemetry crates. The filter levels can be customized as needed.
let filter_fmt = EnvFilter::new("error").add_directive("opentelemetry=debug".parse().unwrap());
let fmt_layer = tracing_subscriber::fmt::layer()
.with_thread_names(true)
.with_filter(filter_fmt);

tracing_subscriber::registry()
.with(otel_layer)
.with(fmt_layer)
.init();

info!(name: "my-event-name", target: "my-system", event_id = 20, user_name = "otel", user_email = "[email protected]", message = "This is an example message");
error!(name: "my-event-name", target: "my-system", event_id = 50, user_name = "otel", user_email = "[email protected]", message = "This is an example message");
let _ = provider.shutdown();
}

/// A log processor that enriches log records with additional attributes before
/// delegating to an underlying processor.
///
/// If this were implemented as a standalone processor in a chain (e.g.,
/// EnrichmentProcessor -> SimpleLogProcessor), the performance benefits of the
/// `event_enabled` check would be nullified. Here's why:
///
/// - The `event_enabled` method is crucial for performance - it allows processors
/// to skip expensive operations for logs that will ultimately be filtered out
/// - A standalone EnrichmentProcessor would need to implement `event_enabled`,
/// but it has no knowledge of downstream filtering logic
/// - It would have to return `true` by default, causing unnecessary enrichment
/// work even for logs that the downstream processor will discard
///
/// Because this processor wraps another, it must delegate all trait methods
/// to the underlying processor. This ensures the underlying processor receives
/// all necessary lifecycle events.
#[derive(Debug)]
pub struct EnrichmentLogProcessor<P: LogProcessor> {
/// The wrapped processor that will receive enriched log records
delegate: P,
}

impl<P: LogProcessor> EnrichmentLogProcessor<P> {
pub fn new(delegate: P) -> EnrichmentLogProcessor<P> {
EnrichmentLogProcessor { delegate }
}
}

impl<P: LogProcessor> LogProcessor for EnrichmentLogProcessor<P> {
fn emit(&self, data: &mut SdkLogRecord, instrumentation: &InstrumentationScope) {
data.add_attribute("enriched", true);
self.delegate.emit(data, instrumentation);
}

fn force_flush(&self) -> OTelSdkResult {
self.delegate.force_flush()
}

fn shutdown_with_timeout(&self, timeout: std::time::Duration) -> OTelSdkResult {
self.delegate.shutdown_with_timeout(timeout)
}

fn shutdown(&self) -> OTelSdkResult {
self.delegate.shutdown()
}

fn set_resource(&mut self, resource: &Resource) {
self.delegate.set_resource(resource);
}

fn event_enabled(&self, level: Severity, target: &str, name: Option<&str>) -> bool {
self.delegate.event_enabled(level, target, name)
}
}