@@ -2,6 +2,8 @@ use opentelemetry::InstrumentationScope;
2
2
use opentelemetry_sdk:: error:: OTelSdkResult ;
3
3
use opentelemetry_sdk:: logs:: { LogBatch , LogExporter , SdkLogRecord } ;
4
4
use opentelemetry_sdk:: Resource ;
5
+ use std:: borrow:: Cow ;
6
+ use std:: collections:: HashSet ;
5
7
use std:: error:: Error ;
6
8
use std:: fmt:: Debug ;
7
9
@@ -59,8 +61,11 @@ impl Processor {
59
61
}
60
62
61
63
/// Creates a new instance of the [`Processor`] using the given options.
62
- pub ( crate ) fn new ( options : Options ) -> Self {
63
- let exporter: ETWExporter = ETWExporter :: new ( options) ;
64
+ pub ( crate ) fn new (
65
+ options : Options ,
66
+ resource_attribute_keys : HashSet < Cow < ' static , str > > ,
67
+ ) -> Self {
68
+ let exporter: ETWExporter = ETWExporter :: new ( options, resource_attribute_keys) ;
64
69
Processor {
65
70
event_exporter : exporter,
66
71
}
@@ -113,6 +118,7 @@ impl opentelemetry_sdk::logs::LogProcessor for Processor {
113
118
pub struct ProcessorBuilder {
114
119
options : Options ,
115
120
provider_name_compat_mode : ProviderNameCompatMode ,
121
+ resource_attribute_keys : HashSet < Cow < ' static , str > > ,
116
122
}
117
123
118
124
impl ProcessorBuilder {
@@ -125,6 +131,7 @@ impl ProcessorBuilder {
125
131
ProcessorBuilder {
126
132
options : Options :: new ( provider_name. to_string ( ) ) ,
127
133
provider_name_compat_mode : ProviderNameCompatMode :: CrossCompat ,
134
+ resource_attribute_keys : HashSet :: new ( ) ,
128
135
}
129
136
}
130
137
@@ -137,6 +144,7 @@ impl ProcessorBuilder {
137
144
ProcessorBuilder {
138
145
options : Options :: new ( provider_name. to_string ( ) ) ,
139
146
provider_name_compat_mode : ProviderNameCompatMode :: EtwCompatOnly ,
147
+ resource_attribute_keys : HashSet :: new ( ) ,
140
148
}
141
149
}
142
150
@@ -153,11 +161,48 @@ impl ProcessorBuilder {
153
161
self
154
162
}
155
163
164
+ /// Sets the resource attributes for the processor.
165
+ ///
166
+ /// This specifies which resource attributes should be exported with each log record.
167
+ ///
168
+ /// # Performance Considerations
169
+ ///
170
+ /// **Warning**: Each specified resource attribute will be serialized and sent
171
+ /// with EVERY log record. This is different from OTLP exporters where resource
172
+ /// attributes are serialized once per batch. Consider the performance impact
173
+ /// when selecting which attributes to export.
174
+ ///
175
+ /// # Best Practices for ETW
176
+ ///
177
+ /// **Recommendation**: Be selective about which resource attributes to export.
178
+ /// Since ETW requires a local listener/agent, the agent can often deduce many
179
+ /// resource attributes without requiring them to be sent with each log:
180
+ ///
181
+ /// - **Infrastructure attributes** (datacenter, region, availability zone) can
182
+ /// be determined by the local agent.
183
+ /// - **Host attributes** (hostname, IP address, OS version) are available locally.
184
+ /// - **Deployment attributes** (environment, cluster) may be known to the agent.
185
+ ///
186
+ /// Focus on attributes that are truly specific to your application instance
187
+ /// and cannot be easily determined by the local agent.
188
+ ///
189
+ /// Nevertheless, if there are attributes that are fixed and must be emitted
190
+ /// with every log, modeling them as Resource attributes and using this method
191
+ /// is much more efficient than emitting them explicitly with every log.
192
+ pub fn with_resource_attributes < I , S > ( mut self , attributes : I ) -> Self
193
+ where
194
+ I : IntoIterator < Item = S > ,
195
+ S : Into < Cow < ' static , str > > ,
196
+ {
197
+ self . resource_attribute_keys = attributes. into_iter ( ) . map ( |s| s. into ( ) ) . collect ( ) ;
198
+ self
199
+ }
200
+
156
201
/// Builds the processor with given options, returning `Error` if it fails.
157
202
pub fn build ( self ) -> Result < Processor , Box < dyn Error > > {
158
203
self . validate ( ) ?;
159
204
160
- Ok ( Processor :: new ( self . options ) )
205
+ Ok ( Processor :: new ( self . options , self . resource_attribute_keys ) )
161
206
}
162
207
163
208
fn validate ( & self ) -> Result < ( ) , Box < dyn Error > > {
@@ -214,21 +259,21 @@ mod tests {
214
259
215
260
#[ test]
216
261
fn test_shutdown ( ) {
217
- let processor = Processor :: new ( test_options ( ) ) ;
262
+ let processor = Processor :: new ( test_options ( ) , HashSet :: new ( ) ) ;
218
263
219
264
assert ! ( processor. shutdown( ) . is_ok( ) ) ;
220
265
}
221
266
222
267
#[ test]
223
268
fn test_force_flush ( ) {
224
- let processor = Processor :: new ( test_options ( ) ) ;
269
+ let processor = Processor :: new ( test_options ( ) , HashSet :: new ( ) ) ;
225
270
226
271
assert ! ( processor. force_flush( ) . is_ok( ) ) ;
227
272
}
228
273
229
274
#[ test]
230
275
fn test_emit ( ) {
231
- let processor: Processor = Processor :: new ( test_options ( ) ) ;
276
+ let processor: Processor = Processor :: new ( test_options ( ) , HashSet :: new ( ) ) ;
232
277
233
278
let mut record = SdkLoggerProvider :: builder ( )
234
279
. build ( )
@@ -241,7 +286,7 @@ mod tests {
241
286
#[ test]
242
287
#[ cfg( feature = "spec_unstable_logs_enabled" ) ]
243
288
fn test_event_enabled ( ) {
244
- let processor = Processor :: new ( test_options ( ) ) ;
289
+ let processor = Processor :: new ( test_options ( ) , HashSet :: new ( ) ) ;
245
290
246
291
// Unit test are forced to return true as there is no ETW session listening for the event
247
292
assert ! ( processor. event_enabled( opentelemetry:: logs:: Severity :: Info , "test" , Some ( "test" ) ) ) ;
@@ -427,4 +472,36 @@ mod tests {
427
472
) ;
428
473
assert ! ( result. is_ok( ) ) ;
429
474
}
475
+
476
+ #[ test]
477
+ fn test_resource_attributes ( ) {
478
+ use opentelemetry:: logs:: LogRecord ;
479
+ use opentelemetry:: logs:: Logger ;
480
+ use opentelemetry:: logs:: LoggerProvider ;
481
+ use opentelemetry:: KeyValue ;
482
+ use opentelemetry_sdk:: logs:: SdkLoggerProvider ;
483
+ use opentelemetry_sdk:: Resource ;
484
+
485
+ let processor = Processor :: builder ( "test_provider" )
486
+ . with_resource_attributes ( vec ! [ "resource_attribute1" , "resource_attribute2" ] )
487
+ . build ( )
488
+ . unwrap ( ) ;
489
+
490
+ let logger_provider = SdkLoggerProvider :: builder ( )
491
+ . with_resource (
492
+ Resource :: builder ( )
493
+ . with_service_name ( "test_service" )
494
+ . with_attribute ( KeyValue :: new ( "resource_attribute1" , "value1" ) )
495
+ . with_attribute ( KeyValue :: new ( "resource_attribute2" , "value2" ) )
496
+ . with_attribute ( KeyValue :: new ( "resource_attribute3" , "value3" ) ) // This should not be exported
497
+ . build ( ) ,
498
+ )
499
+ . with_log_processor ( processor)
500
+ . build ( ) ;
501
+
502
+ let logger = logger_provider. logger ( "test_logger" ) ;
503
+ let mut log_record = logger. create_log_record ( ) ;
504
+ log_record. add_attribute ( "log_attribute" , "log_value" ) ;
505
+ logger. emit ( log_record) ;
506
+ }
430
507
}
0 commit comments