opentelemetry_sdk/logs/
log_processor.rs1use crate::error::OTelSdkResult;
30use crate::{logs::SdkLogRecord, Resource};
31
32use opentelemetry::logs::Severity;
33use opentelemetry::{otel_warn, InstrumentationScope};
34
35use std::fmt::Debug;
36use std::time::Duration;
37
38pub trait LogProcessor: Send + Sync + Debug {
42 fn emit(&self, data: &mut SdkLogRecord, instrumentation: &InstrumentationScope);
54 fn force_flush(&self) -> OTelSdkResult;
56 fn shutdown_with_timeout(&self, _timeout: Duration) -> OTelSdkResult {
66 otel_warn!(
69 name: "LogProcessor.DefaultShutdownWithTimeout",
70 message = "LogProcessor is using default shutdown implementation. If this processor manages background threads, network connections, file handles, or other resources that need cleanup, implement `shutdown_with_timeout()` to properly release them. Simple processors that only transform log data can safely use this default."
71 );
72 Ok(())
73 }
74 fn shutdown(&self) -> OTelSdkResult {
79 self.shutdown_with_timeout(Duration::from_secs(5))
80 }
81
82 fn event_enabled(&self, _level: Severity, _target: &str, _name: Option<&str>) -> bool {
84 true
86 }
87
88 fn set_resource(&mut self, _resource: &Resource) {}
90}
91
92#[cfg(all(test, feature = "testing", feature = "logs"))]
93pub(crate) mod tests {
94 use crate::logs::{LogBatch, LogExporter, SdkLogRecord};
95 use crate::Resource;
96 use crate::{
97 error::OTelSdkResult,
98 logs::{LogProcessor, SdkLoggerProvider},
99 };
100 use opentelemetry::logs::AnyValue;
101 use opentelemetry::logs::LogRecord as _;
102 use opentelemetry::logs::{Logger, LoggerProvider};
103 use opentelemetry::{InstrumentationScope, Key};
104 use std::sync::{Arc, Mutex};
105
106 #[derive(Debug, Clone)]
107 pub(crate) struct MockLogExporter {
108 pub resource: Arc<Mutex<Option<Resource>>>,
109 }
110
111 impl LogExporter for MockLogExporter {
112 async fn export(&self, _batch: LogBatch<'_>) -> OTelSdkResult {
113 Ok(())
114 }
115
116 fn set_resource(&mut self, resource: &Resource) {
117 self.resource
118 .lock()
119 .map(|mut res_opt| {
120 res_opt.replace(resource.clone());
121 })
122 .expect("mock log exporter shouldn't error when setting resource");
123 }
124 }
125
126 impl MockLogExporter {
128 pub(crate) fn get_resource(&self) -> Option<Resource> {
129 (*self.resource).lock().unwrap().clone()
130 }
131 }
132
133 #[derive(Debug)]
134 struct FirstProcessor {
135 pub(crate) logs: Arc<Mutex<Vec<(SdkLogRecord, InstrumentationScope)>>>,
136 }
137
138 impl LogProcessor for FirstProcessor {
139 fn emit(&self, record: &mut SdkLogRecord, instrumentation: &InstrumentationScope) {
140 record.add_attribute(
142 Key::from_static_str("processed_by"),
143 AnyValue::String("FirstProcessor".into()),
144 );
145 record.body = Some("Updated by FirstProcessor".into());
147
148 self.logs
149 .lock()
150 .unwrap()
151 .push((record.clone(), instrumentation.clone())); }
153
154 fn force_flush(&self) -> OTelSdkResult {
155 Ok(())
156 }
157
158 fn shutdown_with_timeout(&self, _timeout: std::time::Duration) -> OTelSdkResult {
159 Ok(())
160 }
161 }
162
163 #[derive(Debug)]
164 struct SecondProcessor {
165 pub(crate) logs: Arc<Mutex<Vec<(SdkLogRecord, InstrumentationScope)>>>,
166 }
167
168 impl LogProcessor for SecondProcessor {
169 fn emit(&self, record: &mut SdkLogRecord, instrumentation: &InstrumentationScope) {
170 assert!(record.attributes_contains(
171 &Key::from_static_str("processed_by"),
172 &AnyValue::String("FirstProcessor".into())
173 ));
174 assert!(
175 record.body.clone().unwrap()
176 == AnyValue::String("Updated by FirstProcessor".into())
177 );
178 self.logs
179 .lock()
180 .unwrap()
181 .push((record.clone(), instrumentation.clone()));
182 }
183
184 fn force_flush(&self) -> OTelSdkResult {
185 Ok(())
186 }
187
188 fn shutdown_with_timeout(&self, _timeout: std::time::Duration) -> OTelSdkResult {
189 Ok(())
190 }
191 }
192
193 #[test]
194 fn test_log_data_modification_by_multiple_processors() {
195 let first_processor_logs = Arc::new(Mutex::new(Vec::new()));
196 let second_processor_logs = Arc::new(Mutex::new(Vec::new()));
197
198 let first_processor = FirstProcessor {
199 logs: Arc::clone(&first_processor_logs),
200 };
201 let second_processor = SecondProcessor {
202 logs: Arc::clone(&second_processor_logs),
203 };
204
205 let logger_provider = SdkLoggerProvider::builder()
206 .with_log_processor(first_processor)
207 .with_log_processor(second_processor)
208 .build();
209
210 let logger = logger_provider.logger("test-logger");
211 let mut log_record = logger.create_log_record();
212 log_record.body = Some(AnyValue::String("Test log".into()));
213
214 logger.emit(log_record);
215
216 assert_eq!(first_processor_logs.lock().unwrap().len(), 1);
217 assert_eq!(second_processor_logs.lock().unwrap().len(), 1);
218
219 let first_log = &first_processor_logs.lock().unwrap()[0];
220 let second_log = &second_processor_logs.lock().unwrap()[0];
221
222 assert!(first_log.0.attributes_contains(
223 &Key::from_static_str("processed_by"),
224 &AnyValue::String("FirstProcessor".into())
225 ));
226 assert!(second_log.0.attributes_contains(
227 &Key::from_static_str("processed_by"),
228 &AnyValue::String("FirstProcessor".into())
229 ));
230
231 assert!(
232 first_log.0.body.clone().unwrap()
233 == AnyValue::String("Updated by FirstProcessor".into())
234 );
235 assert!(
236 second_log.0.body.clone().unwrap()
237 == AnyValue::String("Updated by FirstProcessor".into())
238 );
239 }
240}