Skip to content

feat: stabilization of opentelemetry-etw-logs #247

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 70 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
488fc72
feat: Use "Log" as default event name
psandana Apr 11, 2025
fe0926c
make clippy happy
psandana Apr 11, 2025
f84eba6
fix tests
psandana Apr 11, 2025
b196973
Merge branch 'main' into log-by-default
psandana Apr 11, 2025
f9d3d13
Introducing ExporterOptions to ETW log exporter
psandana Apr 16, 2025
0854965
trying frozen_collections and String instead of &str
psandana Apr 17, 2025
50b61c0
build fix
psandana Apr 21, 2025
b47781a
fmt
psandana Apr 21, 2025
cdeb6ba
improving mapping
psandana Apr 22, 2025
d31264b
refactor get_event_name
psandana Apr 22, 2025
b826bd9
rename to get_etw_event_name
psandana Apr 22, 2025
e219cfb
fmt
psandana Apr 22, 2025
a4cc027
implement missing logic
psandana Apr 22, 2025
bbaa7b4
using generics
psandana Apr 22, 2025
0ced617
remove frozen_collections
psandana Apr 22, 2025
e8c10f3
remove duplication
psandana Apr 22, 2025
b1e4175
fmt
psandana Apr 22, 2025
86e829f
fix typo
psandana Apr 22, 2025
efb3424
small perf optimization: return &str
psandana Apr 22, 2025
3b26295
use � for key
psandana Apr 22, 2025
968c1f8
More Cow in provider and default event names
psandana Apr 22, 2025
59f7660
small reword
psandana Apr 23, 2025
e4b85a4
Cow ➡ Into<Cow>
psandana Apr 23, 2025
6db9260
fmt
psandana Apr 23, 2025
f888fca
hidding non-public API
psandana Apr 25, 2025
3d0510f
adding prefix target mapping
psandana Apr 25, 2025
b976992
removing event mapping
psandana Apr 28, 2025
42d1bd2
fmt
psandana Apr 28, 2025
adb0ce5
tests fix
psandana Apr 28, 2025
23804cc
adding exporter_options example
psandana May 6, 2025
48de956
reword crate-level docs
psandana May 6, 2025
4a7c83f
more docs
psandana May 6, 2025
b7d218a
adding log test
psandana May 6, 2025
13e7b38
more docs
psandana May 6, 2025
2e867bf
adding batch test
psandana May 6, 2025
59bd835
adding exporter_options tests
psandana May 6, 2025
4302016
format
psandana May 6, 2025
7d0e664
Merge branch 'main' into log-by-default
psandana May 6, 2025
b340be7
restrict visibility
psandana May 6, 2025
5427076
improve readme
psandana May 6, 2025
7f8a35f
fix tracing subscriber double init
psandana May 6, 2025
b88e289
update changelog
psandana May 7, 2025
daa7ec1
allow - in provider name
psandana May 7, 2025
6acdbd9
cargo fmt
psandana May 7, 2025
cbf8b5f
rename new_log_processor to etw_log_processor
psandana May 7, 2025
81f694c
ability to select between default, target or name as etw event name
psandana May 7, 2025
405b331
Merge branch 'main' into log-by-default
psandana May 7, 2025
52c2218
Merge branch 'main' into log-by-default
psandana May 12, 2025
deaa26a
fix comment
psandana May 12, 2025
b10be4a
fix comments
psandana May 12, 2025
63a44e0
more docs improvements
psandana May 12, 2025
54cf98d
undo user-events options
psandana May 12, 2025
1c4fb2e
more meaningful example
psandana May 12, 2025
413de5e
improve error handling
psandana May 12, 2025
4cdd2c1
trim test
psandana May 12, 2025
a53f7aa
remove example
psandana May 12, 2025
e309ed7
fix fmt
psandana May 12, 2025
8751cf0
Merge branch 'main' into log-by-default
psandana May 12, 2025
3ec8f10
refactor test
psandana May 13, 2025
9b8ed22
remove ext trait
psandana May 13, 2025
d19051e
remove default and target/name set
psandana May 13, 2025
c42da19
rename ExportOptions to Processor
psandana May 13, 2025
fcab098
big refactor
psandana May 14, 2025
506420a
rename file
psandana May 14, 2025
220717f
fix errors
psandana May 14, 2025
b224c46
move test
psandana May 14, 2025
3d013e6
first callback
psandana May 14, 2025
f8991c0
callback API
psandana May 14, 2025
436d779
increase coverage
psandana May 14, 2025
49844ef
Merge branch 'main' into log-by-default
psandana May 14, 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
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ debug = 1

[workspace.dependencies]
opentelemetry = "0.29"
opentelemetry-appender-log = "0.29"
opentelemetry-appender-tracing = "0.29"
opentelemetry-http = "0.29"
opentelemetry-proto = { version = "0.29", default-features = false }
Expand Down
12 changes: 11 additions & 1 deletion opentelemetry-etw-logs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@

## vNext

- Added validation to provider name
- Added validation to provider name.
- Added optional feature `serde_json` to serialize List and Maps.
- Added `etw_log_processor()` method that returns an `impl LogProcessor`.
- Added `ExporterOptions` which uses a builder pattern to configure the exporter.
- The `with_etw_exporter` trait method now accepts an `ExporterOptions` instance:

```rust
let options = ExporterOptions::builder("provider-name").build().unwrap();
let logger_provider = SdkLoggerProvider::builder()
.with_etw_exporter(options)
.build();
```

## v0.8.0

Expand Down
14 changes: 10 additions & 4 deletions opentelemetry-etw-logs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,21 @@ keywords = ["opentelemetry", "log", "trace", "etw"]
license = "Apache-2.0"

[dependencies]
tracelogging_dynamic = "1.2.1"
chrono = { version = "0.4.40", default-features = false, features = [
"alloc",
"std",
] }
futures-executor = "0.3.31"
opentelemetry = { workspace = true, features = ["logs"] }
opentelemetry_sdk = { workspace = true, features = ["logs"] }
tracing = { version = "0.1", optional = true }
serde_json = { version = "1.0.113", optional = true }
futures-executor = "0.3.31"
chrono = { version = "0.4.40", default-features = false, features = ["alloc", "std"] }
thiserror = { version = "2", default-features = false }
tracelogging_dynamic = "1.2.1"
tracing = { version = "0.1", optional = true }

[dev-dependencies]
log = { version = "0.4.21", features = ["kv_serde"] }
opentelemetry-appender-log = { workspace = true }
opentelemetry-appender-tracing = { workspace = true }
opentelemetry_sdk = { workspace = true, features = ["logs", "trace"] }
tokio = { version = "1.0", features = ["full"] }
Expand Down
49 changes: 46 additions & 3 deletions opentelemetry-etw-logs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ ETW events can be consumed in real-time or from a log file.

ETW events created with this crate can be generated and collected on Windows Vista or later.

This ETW exporter enables applications to use OpenTelemetry APIs to capture telemetry events and write to the ETW subsystem. From ETWs, the events can be
This ETW exporter enables applications to use OpenTelemetry APIs to capture telemetry events and write to the ETW subsystem. From ETW, the events can be
captured by agents running locally and listening for specific ETW events.

[![Crates.io: opentelemetry-etw-logs](https://img.shields.io/crates/v/opentelemetry-etw-logs.svg)](https://crates.io/crates/opentelemetry-etw-logs)
Expand All @@ -28,7 +28,51 @@ captured by agents running locally and listening for specific ETW events.
## Viewing ETW Logs

Logs exported to ETW can be viewed using tools like `logman`, `perfview` etc.
// TODO - add instructions.

### Using `logman`

To view the telemetry emitted to ETW you can use [`logman`](https://learn.microsoft.com/windows-server/administration/windows-commands/logman) along with `perfview`.
`logman` will listen to ETW events from the given provider (on this example, `provider-name`) and store them in a `.etl` file.

[`perfview`](https://github.com/microsoft/perfview) will allow you to visualize the events.

Instructions using Powershell:

1. Get the ETW Session Guid for the given provider (on this example `provider-name`):

```ps
$EtwSessionGuid = (new-object System.Diagnostics.Tracing.EventSource("provider-name")).Guid.ToString()`
```

1. Start Logman session:

```ps
logman create trace OtelETWExampleBasic -o OtelETWExampleBasic.log -p "{$EtwSessionGuid}" -f bincirc -max 1000
logman start OtelETWExampleBasic
```

1. Execute this example:

```ps
cd opentelemetry-etw-logs
cargo run --example basic
```

1. Stop and Remove `logman` session:

```ps
logman stop OtelETWExampleBasic
logman delete OtelETWExampleBasic
```

1. View the events with `perfview`:

- Download PerfView: [Instructions](https://github.com/microsoft/perfview/blob/main/documentation/Downloading.md), [Releases](https://github.com/Microsoft/perfview/releases).
- Open PerfView.
- Go the location of the `.etl` file: `OtelETWExampleBasic.log_000001.etl` and open it.
- Double-click `Events` in the left-panel.
- Double-click the `provider-name/event-name` in the left-panel.
- You should see the events in the right-panel.

## OpenTelemetry Overview

Expand All @@ -47,4 +91,3 @@ of telemetry is intentionally left to other tools.

[Prometheus]: https://prometheus.io
[Jaeger]: https://www.jaegertracing.io

10 changes: 8 additions & 2 deletions opentelemetry-etw-logs/examples/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,20 @@
//!

use opentelemetry_appender_tracing::layer;
use opentelemetry_etw_logs::ETWLoggerProviderBuilderExt;
use opentelemetry_etw_logs::Processor;
use opentelemetry_sdk::logs::SdkLoggerProvider;
use tracing::error;
use tracing_subscriber::prelude::*;

fn init_logger() -> SdkLoggerProvider {
let processor = Processor::builder("provider-name")
// Use a constant custom name for all events instead of "Log"
.etw_event_name_from_callback(|_| "CustomEvent")
.build()
.unwrap();

SdkLoggerProvider::builder()
.with_etw_exporter("provider-name")
.with_log_processor(processor)
.build()
}

Expand Down
2 changes: 1 addition & 1 deletion opentelemetry-etw-logs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! The ETW exporter will enable applications to use OpenTelemetry API
//! to capture the telemetry events, and write to ETW subsystem.
//! to capture the telemetry events, and write them to the ETW subsystem.

#![warn(missing_debug_implementations, missing_docs)]

Expand Down
39 changes: 13 additions & 26 deletions opentelemetry-etw-logs/src/logs/exporter/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use opentelemetry::{
};
use tracelogging_dynamic as tld;

pub fn add_attribute_to_event(event: &mut tld::EventBuilder, key: &Key, value: &AnyValue) {
pub(crate) fn add_attribute_to_event(event: &mut tld::EventBuilder, key: &Key, value: &AnyValue) {
match value {
AnyValue::Boolean(b) => {
event.add_bool32(key.as_str(), *b as i32, tld::OutType::Default, 0);
Expand Down Expand Up @@ -48,7 +48,7 @@ pub fn add_attribute_to_event(event: &mut tld::EventBuilder, key: &Key, value: &
}
}

pub const fn convert_severity_to_level(severity: Severity) -> tld::Level {
pub(crate) const fn convert_severity_to_level(severity: Severity) -> tld::Level {
match severity {
Severity::Debug
| Severity::Debug2
Expand All @@ -75,32 +75,33 @@ pub const fn convert_severity_to_level(severity: Severity) -> tld::Level {
}
}

pub fn get_event_name(log_record: &opentelemetry_sdk::logs::SdkLogRecord) -> &str {
log_record.event_name().unwrap_or("Log")
}

#[cfg(test)]
pub mod test_utils {
pub(crate) mod test_utils {
use opentelemetry::logs::Logger;
use opentelemetry::logs::LoggerProvider;
use opentelemetry_sdk::logs::SdkLoggerProvider;

use super::super::ETWExporter;
use crate::logs::exporter::options::Options;
use crate::logs::exporter::ETWExporter;

pub fn new_etw_exporter() -> ETWExporter {
ETWExporter::new("test-provider-name")
pub(crate) fn new_etw_exporter() -> ETWExporter {
ETWExporter::new(test_options())
}

pub fn new_instrumentation_scope() -> opentelemetry::InstrumentationScope {
pub(crate) fn new_instrumentation_scope() -> opentelemetry::InstrumentationScope {
opentelemetry::InstrumentationScope::default()
}

pub fn new_sdk_log_record() -> opentelemetry_sdk::logs::SdkLogRecord {
pub(crate) fn new_sdk_log_record() -> opentelemetry_sdk::logs::SdkLogRecord {
SdkLoggerProvider::builder()
.build()
.logger("test")
.create_log_record()
}

pub(crate) fn test_options() -> Options {
Options::builder("ContosoProvider").build().unwrap()
}
}

#[test]
Expand All @@ -120,17 +121,3 @@ fn test_get_severity_level() {
let result = convert_severity_to_level(Severity::Warn);
assert_eq!(result, tld::Level::Warning);
}

#[test]
fn test_get_event_name() {
use opentelemetry::logs::LogRecord;

let mut log_record = test_utils::new_sdk_log_record();

let result = get_event_name(&log_record);
assert_eq!(result, "Log");

log_record.set_event_name("event-name");
let result = get_event_name(&log_record);
assert_eq!(result, "event-name");
}
36 changes: 29 additions & 7 deletions opentelemetry-etw-logs/src/logs/exporter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ use tracelogging_dynamic as tld;
use opentelemetry::logs::Severity;
use opentelemetry::Key;
use opentelemetry_sdk::error::{OTelSdkError, OTelSdkResult};
use std::str;

mod common;
pub(crate) mod common;
pub(crate) mod options;
mod part_a;
mod part_b;
mod part_c;

pub(crate) use options::{Options, OptionsBuilder};

#[derive(Default)]
struct Resource {
pub cloud_role: Option<String>,
Expand All @@ -23,6 +25,7 @@ struct Resource {
pub(crate) struct ETWExporter {
provider: Pin<Arc<tld::Provider>>,
resource: Resource,
options: Options,
}

fn enabled_callback_noop(
Expand All @@ -40,11 +43,14 @@ fn enabled_callback_noop(
impl ETWExporter {
const KEYWORD: u64 = 1;

pub(crate) fn new(provider_name: &str) -> Self {
let mut options = tld::Provider::options();
pub(crate) fn new(options: Options) -> Self {
let mut provider_options = tld::Provider::options();

options.callback(enabled_callback_noop, 0x0);
let provider = Arc::pin(tld::Provider::new(provider_name, &options));
provider_options.callback(enabled_callback_noop, 0x0);
let provider = Arc::pin(tld::Provider::new(
options.provider_name(),
&provider_options,
));
// SAFETY: tracelogging (ETW) enables an ETW callback into the provider when `register()` is called.
// This might crash if the provider is dropped without calling unregister before.
// This only affects static providers.
Expand All @@ -56,6 +62,7 @@ impl ETWExporter {
ETWExporter {
provider,
resource: Default::default(),
options,
}
}

Expand Down Expand Up @@ -86,7 +93,7 @@ impl ETWExporter {

// reset
event.reset(
common::get_event_name(log_record),
self.options.get_etw_event_name(log_record),
level,
Self::KEYWORD,
event_tags,
Expand Down Expand Up @@ -233,6 +240,21 @@ mod tests {
assert!(result.await.is_ok());
}

#[tokio::test]
#[should_panic]
async fn test_export_empty_batch_produces_failure() {
use opentelemetry_sdk::logs::LogBatch;
use opentelemetry_sdk::logs::LogExporter;

let records = [];
let batch = LogBatch::new(&records);

let exporter = common::test_utils::new_etw_exporter();
let result = exporter.export(batch);

assert!(result.await.is_ok());
}

#[test]
fn test_callback_noop() {
enabled_callback_noop(
Expand Down
Loading