Skip to content

Commit 6412dcd

Browse files
KaczDevTommyCppshaun-coxlalitbcijothomas
authored
feat: add InMemoryLogsExporter (#1231)
* feat: addInMemoryLogExporter * linting * Update CHANGELOG.md * changed finished to emitted logs, moved example * replace clone_log with cloned() * remove proj example in favor of single file * corrected dependency, removed repeated code * changed finished to emitted logs, moved example * remove proj example in favor of single file * corrected dependency, removed repeated code * corrected dependency, removed repeated code * fix: endpoint urls for otlp http exporter. (#1210) * Move opentelemetry_api code back into opentelemetry and remove the former (#1226) * [user_events log exporter] Upgrade eventheader-dynamic dependency (#1230) * feat: addInMemoryLogExporter * linting * Update CHANGELOG.md * remove the example from examples * fixes after rebase * missing doc, added no_run in examples * fix ci test(stable) and docs * more examples ci fixes * added dev-dependencies to resolve ci issue * corrected required features * remove comments about returning error * Update example description Co-authored-by: Cijo Thomas <[email protected]> --------- Co-authored-by: Zhongyang Wu <[email protected]> Co-authored-by: Shaun Cox <[email protected]> Co-authored-by: Lalit Kumar Bhasin <[email protected]> Co-authored-by: Cijo Thomas <[email protected]>
1 parent 2a2e4b1 commit 6412dcd

File tree

9 files changed

+229
-2
lines changed

9 files changed

+229
-2
lines changed

opentelemetry-appender-log/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,7 @@ log = {version = "0.4.17", features = ["kv_unstable", "std"]}
1717
[features]
1818
logs_level_enabled = ["opentelemetry/logs_level_enabled"]
1919
default = ["logs_level_enabled"]
20+
21+
[dev-dependencies]
22+
opentelemetry_sdk = { path = "../opentelemetry-sdk", features = [ "testing", "logs_level_enabled" ] }
23+
tokio = "1.32.0"
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//! run with `$ cargo run --example logs-basic-in-memory
2+
3+
/// This example shows how to use in_memory_exporter for logs. This uses opentelemetry-appender-log crate, which is a
4+
/// [logging appender](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/glossary.md#log-appender--bridge) that bridges logs from the [log crate](https://docs.rs/log/latest/log/) to OpenTelemetry.
5+
/// The example setups a LoggerProvider with a in-memory exporter, so emitted logs are stored in memory.
6+
///
7+
use log::{error, info, warn, Level};
8+
use opentelemetry_appender_log::OpenTelemetryLogBridge;
9+
use opentelemetry_sdk::logs::{BatchLogProcessor, LoggerProvider};
10+
use opentelemetry_sdk::runtime;
11+
use opentelemetry_sdk::testing::logs::InMemoryLogsExporter;
12+
13+
#[tokio::main]
14+
async fn main() {
15+
//Create an InMemoryLogsExporter
16+
let exporter: InMemoryLogsExporter = InMemoryLogsExporter::default();
17+
//Create a LoggerProvider and register the exporter
18+
let logger_provider = LoggerProvider::builder()
19+
.with_log_processor(BatchLogProcessor::builder(exporter.clone(), runtime::Tokio).build())
20+
.build();
21+
22+
// Setup Log Appender for the log crate.
23+
let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider);
24+
log::set_boxed_logger(Box::new(otel_log_appender)).unwrap();
25+
log::set_max_level(Level::Info.to_level_filter());
26+
27+
// Emit logs using macros from the log crate.
28+
error!("hello from {}. My price is {}", "apple", 2.99);
29+
warn!("warn!");
30+
info!("test log!");
31+
32+
logger_provider.force_flush();
33+
34+
let emitted_logs = exporter.get_emitted_logs().unwrap();
35+
for log in emitted_logs {
36+
println!("{:?}", log);
37+
}
38+
}

opentelemetry-sdk/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
- Changed dependency from `opentelemetry_api` to `opentelemetry` as the latter
1313
is now the API crate. [#1226](https://github.com/open-telemetry/opentelemetry-rust/pull/1226)
1414
- Add in memory span exporter [#1216](https://github.com/open-telemetry/opentelemetry-rust/pull/1216)
15+
- Add in memory log exporter [#1231](https://github.com/open-telemetry/opentelemetry-rust/pull/1231)
1516

1617
### Removed
1718

1819
- Remove context from Metric force_flush [#1245](https://github.com/open-telemetry/opentelemetry-rust/pull/1245)
1920

21+
2022
## v0.20.0
2123

2224
### Added

opentelemetry-sdk/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ rustdoc-args = ["--cfg", "docsrs"]
3838
[dev-dependencies]
3939
indexmap = "2.0"
4040
criterion = { version = "0.5", features = ["html_reports"] }
41+
log = { version = "0.4.17" }
42+
opentelemetry-appender-log = { path = "../opentelemetry-appender-log" }
4143
[target.'cfg(not(target_os = "windows"))'.dev-dependencies]
4244
pprof = { version = "0.12", features = ["flamegraph", "criterion"] }
4345

opentelemetry-sdk/src/export/logs/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub trait LogExporter: Send + Debug {
2525

2626
/// `LogData` associates a [`LogRecord`] with a [`Resource`] and
2727
/// [`InstrumentationLibrary`].
28-
#[derive(Debug)]
28+
#[derive(Clone, Debug)]
2929
pub struct LogData {
3030
/// Log record
3131
pub record: LogRecord,
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
use crate::export::logs::{LogData, LogExporter};
2+
use async_trait::async_trait;
3+
use opentelemetry::logs::{LogError, LogResult};
4+
use std::sync::{Arc, Mutex};
5+
6+
/// An in-memory logs exporter that stores logs data in memory..
7+
///
8+
/// This exporter is useful for testing and debugging purposes.
9+
/// It stores logs in a `Vec<LogData>`. Logs can be retrieved using
10+
/// `get_emitted_logs` method.
11+
///
12+
/// # Example
13+
/// ```no_run
14+
///# use log::{error, info, Level, warn};
15+
///# use opentelemetry_appender_log::OpenTelemetryLogBridge;
16+
///# use opentelemetry_sdk::logs::{BatchLogProcessor, LoggerProvider};
17+
///# use opentelemetry_sdk::runtime;
18+
///# use opentelemetry_sdk::testing::logs::InMemoryLogsExporter;
19+
///
20+
///# #[tokio::main]
21+
///# async fn main() {
22+
/// //Create an InMemoryLogsExporter
23+
/// let exporter: InMemoryLogsExporter = InMemoryLogsExporter::default();
24+
/// //Create a LoggerProvider and register the exporter
25+
/// let logger_provider = LoggerProvider::builder()
26+
/// .with_log_processor(BatchLogProcessor::builder(exporter.clone(), runtime::Tokio).build())
27+
/// .build();
28+
29+
/// // Setup Log Appender for the log crate.
30+
/// let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider);
31+
/// log::set_boxed_logger(Box::new(otel_log_appender)).unwrap();
32+
/// log::set_max_level(Level::Info.to_level_filter());
33+
34+
/// // Emit logs using macros from the log crate.
35+
/// error!("hello from {}. My price is {}", "apple", 2.99);
36+
/// warn!("warn!");
37+
/// info!("test log!");
38+
39+
/// logger_provider.force_flush();
40+
41+
/// let emitted_logs = exporter.get_emitted_logs().unwrap();
42+
/// for log in emitted_logs {
43+
/// println!("{:?}", log);
44+
/// }
45+
///# }
46+
/// ```
47+
///
48+
#[derive(Clone, Debug)]
49+
pub struct InMemoryLogsExporter {
50+
logs: Arc<Mutex<Vec<LogData>>>,
51+
}
52+
53+
impl Default for InMemoryLogsExporter {
54+
fn default() -> Self {
55+
InMemoryLogsExporterBuilder::new().build()
56+
}
57+
}
58+
59+
///Builder for ['InMemoryLogsExporter'].
60+
/// # Example
61+
///
62+
/// ```no_run
63+
///# use opentelemetry_sdk::testing::logs::{InMemoryLogsExporter, InMemoryLogsExporterBuilder};
64+
///# use log::{error, info, Level, warn};
65+
///# use opentelemetry_appender_log::OpenTelemetryLogBridge;
66+
///# use opentelemetry_sdk::logs::{BatchLogProcessor, LoggerProvider};
67+
///# use opentelemetry_sdk::runtime;
68+
///
69+
///# #[tokio::main]
70+
///# async fn main() {
71+
/// //Create an InMemoryLogsExporter
72+
/// let exporter: InMemoryLogsExporter = InMemoryLogsExporterBuilder::default().build();
73+
/// //Create a LoggerProvider and register the exporter
74+
/// let logger_provider = LoggerProvider::builder()
75+
/// .with_log_processor(BatchLogProcessor::builder(exporter.clone(), runtime::Tokio).build())
76+
/// .build();
77+
/// // Setup Log Appender for the log crate.
78+
/// let otel_log_appender = OpenTelemetryLogBridge::new(&logger_provider);
79+
/// log::set_boxed_logger(Box::new(otel_log_appender)).unwrap();
80+
/// log::set_max_level(Level::Info.to_level_filter());
81+
/// // Emit logs using macros from the log crate.
82+
/// error!("hello from {}. My price is {}", "apple", 2.99);
83+
/// warn!("warn!");
84+
/// info!("test log!");
85+
/// logger_provider.force_flush();
86+
/// let emitted_logs = exporter.get_emitted_logs().unwrap();
87+
/// for log in emitted_logs {
88+
/// println!("{:?}", log);
89+
/// }
90+
///# }
91+
///
92+
/// ```
93+
///
94+
#[derive(Debug, Clone)]
95+
pub struct InMemoryLogsExporterBuilder {}
96+
97+
impl Default for InMemoryLogsExporterBuilder {
98+
fn default() -> Self {
99+
Self::new()
100+
}
101+
}
102+
103+
impl InMemoryLogsExporterBuilder {
104+
/// Creates a new instance of `InMemoryLogsExporter`.
105+
///
106+
pub fn new() -> Self {
107+
Self {}
108+
}
109+
110+
/// Creates a new instance of `InMemoryLogsExporter`.
111+
///
112+
pub fn build(&self) -> InMemoryLogsExporter {
113+
InMemoryLogsExporter {
114+
logs: Arc::new(Mutex::new(Vec::new())),
115+
}
116+
}
117+
}
118+
119+
impl InMemoryLogsExporter {
120+
/// Returns the logs emitted via Logger as a vector of `LogData`.
121+
///
122+
/// # Example
123+
///
124+
/// ```
125+
/// use opentelemetry_sdk::testing::logs::{InMemoryLogsExporter, InMemoryLogsExporterBuilder};
126+
///
127+
/// let exporter = InMemoryLogsExporterBuilder::default().build();
128+
/// let emitted_logs = exporter.get_emitted_logs().unwrap();
129+
/// ```
130+
///
131+
pub fn get_emitted_logs(&self) -> LogResult<Vec<LogData>> {
132+
self.logs
133+
.lock()
134+
.map(|logs_guard| logs_guard.iter().cloned().collect())
135+
.map_err(LogError::from)
136+
}
137+
138+
/// Clears the internal (in-memory) storage of logs.
139+
///
140+
/// # Example
141+
///
142+
/// ```
143+
/// use opentelemetry_sdk::testing::logs::{InMemoryLogsExporter, InMemoryLogsExporterBuilder};
144+
///
145+
/// let exporter = InMemoryLogsExporterBuilder::default().build();
146+
/// exporter.reset();
147+
/// ```
148+
///
149+
pub fn reset(&self) {
150+
let _ = self
151+
.logs
152+
.lock()
153+
.map(|mut logs_guard| logs_guard.clear())
154+
.map_err(LogError::from);
155+
}
156+
}
157+
158+
#[async_trait]
159+
impl LogExporter for InMemoryLogsExporter {
160+
async fn export(&mut self, batch: Vec<LogData>) -> LogResult<()> {
161+
self.logs
162+
.lock()
163+
.map(|mut logs_guard| logs_guard.append(&mut batch.clone()))
164+
.map_err(LogError::from)
165+
}
166+
fn shutdown(&mut self) {
167+
self.reset();
168+
}
169+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub use in_memory_exporter::{InMemoryLogsExporter, InMemoryLogsExporterBuilder};
2+
3+
mod in_memory_exporter;

opentelemetry-sdk/src/testing/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ pub mod trace;
33

44
#[cfg(all(feature = "testing", feature = "metrics"))]
55
pub mod metrics;
6+
7+
#[cfg(all(feature = "testing", feature = "logs"))]
8+
pub mod logs;

opentelemetry/src/logs/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
//! # OpenTelemetry Logs API
22
33
use crate::ExportError;
4-
use std::time::Duration;
4+
5+
use std::{sync::PoisonError, time::Duration};
56
use thiserror::Error;
67

78
mod logger;
@@ -53,6 +54,11 @@ impl From<&'static str> for LogError {
5354
}
5455
}
5556

57+
impl<T> From<PoisonError<T>> for LogError {
58+
fn from(err: PoisonError<T>) -> Self {
59+
LogError::Other(err.to_string().into())
60+
}
61+
}
5662
/// Wrap type for string
5763
#[derive(Error, Debug)]
5864
#[error("{0}")]

0 commit comments

Comments
 (0)