@@ -2,6 +2,8 @@ use opentelemetry::InstrumentationScope;
22use opentelemetry_sdk:: error:: OTelSdkResult ;
33use opentelemetry_sdk:: logs:: { LogBatch , LogExporter , SdkLogRecord } ;
44use opentelemetry_sdk:: Resource ;
5+ use std:: borrow:: Cow ;
6+ use std:: collections:: HashSet ;
57use std:: error:: Error ;
68use std:: fmt:: Debug ;
79
@@ -59,8 +61,11 @@ impl Processor {
5961 }
6062
6163 /// 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) ;
6469 Processor {
6570 event_exporter : exporter,
6671 }
@@ -113,6 +118,7 @@ impl opentelemetry_sdk::logs::LogProcessor for Processor {
113118pub struct ProcessorBuilder {
114119 options : Options ,
115120 provider_name_compat_mode : ProviderNameCompatMode ,
121+ resource_attribute_keys : HashSet < Cow < ' static , str > > ,
116122}
117123
118124impl ProcessorBuilder {
@@ -125,6 +131,7 @@ impl ProcessorBuilder {
125131 ProcessorBuilder {
126132 options : Options :: new ( provider_name. to_string ( ) ) ,
127133 provider_name_compat_mode : ProviderNameCompatMode :: CrossCompat ,
134+ resource_attribute_keys : HashSet :: new ( ) ,
128135 }
129136 }
130137
@@ -137,6 +144,7 @@ impl ProcessorBuilder {
137144 ProcessorBuilder {
138145 options : Options :: new ( provider_name. to_string ( ) ) ,
139146 provider_name_compat_mode : ProviderNameCompatMode :: EtwCompatOnly ,
147+ resource_attribute_keys : HashSet :: new ( ) ,
140148 }
141149 }
142150
@@ -153,11 +161,48 @@ impl ProcessorBuilder {
153161 self
154162 }
155163
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+
156201 /// Builds the processor with given options, returning `Error` if it fails.
157202 pub fn build ( self ) -> Result < Processor , Box < dyn Error > > {
158203 self . validate ( ) ?;
159204
160- Ok ( Processor :: new ( self . options ) )
205+ Ok ( Processor :: new ( self . options , self . resource_attribute_keys ) )
161206 }
162207
163208 fn validate ( & self ) -> Result < ( ) , Box < dyn Error > > {
@@ -214,21 +259,21 @@ mod tests {
214259
215260 #[ test]
216261 fn test_shutdown ( ) {
217- let processor = Processor :: new ( test_options ( ) ) ;
262+ let processor = Processor :: new ( test_options ( ) , HashSet :: new ( ) ) ;
218263
219264 assert ! ( processor. shutdown( ) . is_ok( ) ) ;
220265 }
221266
222267 #[ test]
223268 fn test_force_flush ( ) {
224- let processor = Processor :: new ( test_options ( ) ) ;
269+ let processor = Processor :: new ( test_options ( ) , HashSet :: new ( ) ) ;
225270
226271 assert ! ( processor. force_flush( ) . is_ok( ) ) ;
227272 }
228273
229274 #[ test]
230275 fn test_emit ( ) {
231- let processor: Processor = Processor :: new ( test_options ( ) ) ;
276+ let processor: Processor = Processor :: new ( test_options ( ) , HashSet :: new ( ) ) ;
232277
233278 let mut record = SdkLoggerProvider :: builder ( )
234279 . build ( )
@@ -241,7 +286,7 @@ mod tests {
241286 #[ test]
242287 #[ cfg( feature = "spec_unstable_logs_enabled" ) ]
243288 fn test_event_enabled ( ) {
244- let processor = Processor :: new ( test_options ( ) ) ;
289+ let processor = Processor :: new ( test_options ( ) , HashSet :: new ( ) ) ;
245290
246291 // Unit test are forced to return true as there is no ETW session listening for the event
247292 assert ! ( processor. event_enabled( opentelemetry:: logs:: Severity :: Info , "test" , Some ( "test" ) ) ) ;
@@ -427,4 +472,36 @@ mod tests {
427472 ) ;
428473 assert ! ( result. is_ok( ) ) ;
429474 }
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+ }
430507}
0 commit comments