@@ -59,39 +59,17 @@ pub(crate) mod log_handler {
5959 //! we need to figure out a way to process C-style formatting strings with
6060 //! a [`va_list`] from inside of Rust, which seems to be very messy.
6161
62- use std:: {
63- borrow:: Cow ,
64- ffi:: CStr ,
65- sync:: {
66- atomic:: { AtomicBool , Ordering } ,
67- OnceLock ,
68- } ,
69- } ;
70-
71- use crate :: { rcl_bindings:: * , LogSeverity , ENTITY_LIFECYCLE_MUTEX } ;
72-
73- /// Global variable that allows a custom log handler to be set. This log
74- /// handler will be applied throughout the entire application and cannot be
75- /// replaced with a different custom log handler. If you want to be able to
76- /// change the log handler over the lifetime of your application, you should
77- /// design your own custom handler with an Arc<Mutex<T>> inside that allows
78- /// its own behavior to be modified.
79- static LOGGING_OUTPUT_HANDLER : OnceLock < RawLogHandler > = OnceLock :: new ( ) ;
80-
81- /// Alias for an arbitrary log handler that is compatible with raw rcl types
82- pub ( crate ) type RawLogHandler = Box <
83- dyn Fn (
84- * const rcutils_log_location_t , // location
85- std:: os:: raw:: c_int , // severity
86- * const std:: os:: raw:: c_char , // logger name
87- rcutils_time_point_value_t , // timestamp
88- * const std:: os:: raw:: c_char , // format
89- * mut va_list , // formatting arguments
90- )
91- + ' static
92- + Send
93- + Sync ,
94- > ;
62+ use std:: { borrow:: Cow , cell:: RefCell , ffi:: CStr , sync:: Arc } ;
63+
64+ use crate :: { rcl_bindings:: * , LogSeverity } ;
65+
66+ thread_local ! {
67+ /// Thread-local variable that allows tests to observe log entries.
68+ static LOG_OBSERVER : RefCell <Option <LogObserver >> = RefCell :: new( None ) ;
69+ }
70+
71+ /// Alias for a test observer of log entries emitted through rclrs logging macros.
72+ pub ( crate ) type LogObserver = Arc < dyn Fn ( LogEntry ) + ' static + Send + Sync > ;
9573
9674 /// This is an idiomatic representation of all the information for a log entry
9775 #[ derive( Clone ) ]
@@ -137,115 +115,84 @@ pub(crate) mod log_handler {
137115 #[ derive( Debug ) ]
138116 pub ( crate ) struct OutputHandlerAlreadySet ;
139117
140- static USING_CUSTOM_HANDLER : OnceLock < AtomicBool > = OnceLock :: new ( ) ;
141-
142118 /// Set an idiomatic log hander
143119 pub ( crate ) fn set_logging_output_handler (
144120 handler : impl Fn ( LogEntry ) + ' static + Send + Sync ,
145121 ) -> Result < ( ) , OutputHandlerAlreadySet > {
146- let raw_handler = Box :: new (
147- move |raw_location : * const rcutils_log_location_t ,
148- raw_severity : std:: os:: raw:: c_int ,
149- raw_logger_name : * const std:: os:: raw:: c_char ,
150- raw_timestamp : rcutils_time_point_value_t ,
151- raw_format : * const std:: os:: raw:: c_char ,
152- // NOTE: In the rclrs logging test we are choosing to format
153- // the full message in advance when using the custom handler,
154- // so the format field always contains the finished formatted
155- // message. Therefore we can just ignore the raw formatting
156- // arguments.
157- _raw_formatting_arguments : * mut va_list | {
158- unsafe {
159- // NOTE: We use .unwrap() extensively inside this function because
160- // it only gets used during tests. We should reconsider this if
161- // we ever make this public.
162- let location = LogLocation {
163- function_name : Cow :: Borrowed (
164- CStr :: from_ptr ( ( * raw_location) . function_name )
165- . to_str ( )
166- . unwrap ( ) ,
167- ) ,
168- file_name : Cow :: Borrowed (
169- CStr :: from_ptr ( ( * raw_location) . file_name ) . to_str ( ) . unwrap ( ) ,
170- ) ,
171- line_number : ( * raw_location) . line_number ,
172- } ;
173- let severity = LogSeverity :: from_native ( raw_severity) ;
174- let logger_name =
175- Cow :: Borrowed ( CStr :: from_ptr ( raw_logger_name) . to_str ( ) . unwrap ( ) ) ;
176- let timestamp: i64 = raw_timestamp;
177- let message = Cow :: Borrowed ( CStr :: from_ptr ( raw_format) . to_str ( ) . unwrap ( ) ) ;
178- handler ( LogEntry {
179- location,
180- severity,
181- logger_name,
182- timestamp,
183- message,
184- } ) ;
185- }
186- } ,
187- ) ;
188-
189- set_raw_logging_output_handler ( raw_handler)
190- }
191-
192- /// Set the logging output handler directly
193- pub ( crate ) fn set_raw_logging_output_handler (
194- handler : RawLogHandler ,
195- ) -> Result < ( ) , OutputHandlerAlreadySet > {
196- LOGGING_OUTPUT_HANDLER
197- . set ( handler)
198- . map_err ( |_| OutputHandlerAlreadySet ) ?;
199- let _lifecycle = ENTITY_LIFECYCLE_MUTEX . lock ( ) . unwrap ( ) ;
200- unsafe {
201- // SAFETY:
202- // - We have locked the global mutex
203- rcutils_logging_set_output_handler ( Some ( rclrs_logging_output_handler) ) ;
204- }
205-
206- USING_CUSTOM_HANDLER
207- . get_or_init ( || AtomicBool :: new ( false ) )
208- . store ( true , Ordering :: Release ) ;
209- Ok ( ( ) )
122+ let observer: LogObserver = Arc :: new ( handler) ;
123+ LOG_OBSERVER . with ( |handler_slot| {
124+ let mut handler_slot = handler_slot. borrow_mut ( ) ;
125+ if handler_slot. is_some ( ) {
126+ return Err ( OutputHandlerAlreadySet ) ;
127+ }
128+ * handler_slot = Some ( observer) ;
129+ Ok ( ( ) )
130+ } )
210131 }
211132
212133 pub ( crate ) fn is_using_custom_handler ( ) -> bool {
213- USING_CUSTOM_HANDLER
214- . get_or_init ( || AtomicBool :: new ( false ) )
215- . load ( Ordering :: Acquire )
134+ LOG_OBSERVER . with ( |handler_slot| handler_slot. borrow ( ) . is_some ( ) )
216135 }
217136
218- /// This function exists so that we can give a raw function pointer to
219- /// rcutils_logging_set_output_handler, which is needed by its API.
220- unsafe extern "C" fn rclrs_logging_output_handler (
137+ /// Dispatch a log entry to the active test handler, if one is set.
138+ ///
139+ /// SAFETY: The raw pointers must be valid for the duration of the call.
140+ pub ( crate ) unsafe fn dispatch_logging_output_handler (
221141 location : * const rcutils_log_location_t ,
222142 severity : std:: os:: raw:: c_int ,
223143 logger_name : * const std:: os:: raw:: c_char ,
224144 timestamp : rcutils_time_point_value_t ,
225145 message : * const std:: os:: raw:: c_char ,
226- logging_output : * mut va_list ,
227146 ) {
228- let handler = LOGGING_OUTPUT_HANDLER . get ( ) . unwrap ( ) ;
229- ( * handler) (
147+ let handler = LOG_OBSERVER . with ( |handler_slot| handler_slot. borrow ( ) . as_ref ( ) . cloned ( ) ) ;
148+ let Some ( handler) = handler else {
149+ return ;
150+ } ;
151+
152+ let location = if location. is_null ( ) {
153+ LogLocation {
154+ function_name : Cow :: Borrowed ( "" ) ,
155+ file_name : Cow :: Borrowed ( "" ) ,
156+ line_number : 0 ,
157+ }
158+ } else {
159+ LogLocation {
160+ function_name : Cow :: Borrowed (
161+ CStr :: from_ptr ( ( * location) . function_name ) . to_str ( ) . unwrap ( ) ,
162+ ) ,
163+ file_name : Cow :: Borrowed ( CStr :: from_ptr ( ( * location) . file_name ) . to_str ( ) . unwrap ( ) ) ,
164+ line_number : ( * location) . line_number ,
165+ }
166+ } ;
167+ let severity = LogSeverity :: from_native ( severity) ;
168+ let logger_name = if logger_name. is_null ( ) {
169+ Cow :: Borrowed ( "" )
170+ } else {
171+ Cow :: Borrowed ( CStr :: from_ptr ( logger_name) . to_str ( ) . unwrap ( ) )
172+ } ;
173+ let message = if message. is_null ( ) {
174+ Cow :: Borrowed ( "" )
175+ } else {
176+ Cow :: Borrowed ( CStr :: from_ptr ( message) . to_str ( ) . unwrap ( ) )
177+ } ;
178+
179+ handler ( LogEntry {
230180 location,
231181 severity,
232182 logger_name,
233183 timestamp,
234184 message,
235- logging_output,
236- ) ;
185+ } ) ;
237186 }
238187
239188 /// Reset the logging output handler to the default one
240189 pub ( crate ) fn reset_logging_output_handler ( ) {
241- let _lifecycle = ENTITY_LIFECYCLE_MUTEX . lock ( ) . unwrap ( ) ;
242- unsafe {
243- // SAFETY: The global mutex is locked. No other precondition is
244- // required.
245- rcutils_logging_set_output_handler ( Some ( rcl_logging_multiple_output_handler) ) ;
246- }
247- USING_CUSTOM_HANDLER
248- . get_or_init ( || AtomicBool :: new ( false ) )
249- . store ( false , Ordering :: Release ) ;
190+ LOG_OBSERVER . with ( |handler_slot| * handler_slot. borrow_mut ( ) = None ) ;
191+ }
192+
193+ pub ( crate ) fn null_timestamp ( ) -> rcutils_time_point_value_t {
194+ // This value is only used by the Rust-side test hook. The real rcutils
195+ // output handler still receives the timestamp produced by rcutils_log.
196+ 0
250197 }
251198}
0 commit comments