Skip to main content

mountpoint_s3_crt/common/
logging.rs

1//! Logging infrastructure
2
3use std::fmt::Debug;
4use std::pin::Pin;
5use std::sync::atomic::{AtomicBool, Ordering};
6
7use mountpoint_s3_crt_sys::{
8    AWS_OP_ERR, AWS_OP_SUCCESS, aws_log_level, aws_log_subject_name, aws_log_subject_t, aws_logger, aws_logger_set,
9    aws_logger_vtable, aws_string, logging_shim,
10};
11
12use crate::common::allocator::Allocator;
13use crate::common::common_library_init;
14
15static LOGGER_INIT: AtomicBool = AtomicBool::new(false);
16
17/// A logger that supports log levels and subjects
18pub struct Logger {
19    inner: Pin<Box<aws_logger>>,
20    _vtable: Pin<Box<aws_logger_vtable>>,
21    // Double indirection allows us to pass `dyn LoggerImpl` across the FFI boundary.
22    _impl: Pin<Box<Box<dyn LoggerImpl>>>,
23}
24
25impl Debug for Logger {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        f.debug_struct("Logger")
28            .field("inner", &self.inner)
29            .finish_non_exhaustive()
30    }
31}
32
33impl Logger {
34    /// Create a new Logger that will dispatch log messages to the given implementation
35    pub fn new(allocator: &Allocator, impl_: impl LoggerImpl + 'static) -> Self {
36        common_library_init(allocator);
37
38        let mut impl_: Pin<Box<Box<dyn LoggerImpl>>> = Box::pin(Box::new(impl_));
39        let mut vtable = Box::pin(aws_logger_vtable {
40            log: Some(logging_shim::aws_crt_s3_rs_logging_shim_log_fn_trampoline),
41            get_log_level: Some(logger_vtable_get_log_level_fn),
42            clean_up: Some(logger_vtable_clean_up_fn),
43            set_log_level: Some(logger_vtable_set_log_level_fn),
44        });
45        let logger = Box::pin(aws_logger {
46            vtable: &mut *vtable.as_mut() as *mut _,
47            allocator: allocator.inner.as_ptr(),
48            p_impl: &mut *impl_.as_mut() as *mut Box<dyn LoggerImpl> as *mut _,
49        });
50
51        Self {
52            inner: logger,
53            _vtable: vtable,
54            _impl: impl_,
55        }
56    }
57
58    /// Try to install this Logger as the global logger for the CRT. Only one logger can ever be
59    /// installed for the lifetime of the program, so returns Err if a logger has already been
60    /// installed.
61    pub fn try_init(mut self) -> Result<(), LoggerInitError> {
62        if LOGGER_INIT
63            .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
64            .is_ok()
65        {
66            logging_shim::try_init(logger_vtable_log_fn)
67                .expect("logging shim should not be initialized if logger isn't");
68
69            let ptr = &mut *self.inner.as_mut() as *mut _;
70            // SAFETY: The global logger lives for the lifetime of the program (the CRT requires
71            // that `aws_logger_set` is only ever called once), so we can leak it rather than
72            // holding onto it in some global static. `ptr` is guaranteed to be a valid
73            // `aws_logger`.
74            unsafe {
75                std::mem::forget(self);
76                aws_logger_set(ptr)
77            };
78            Ok(())
79        } else {
80            Err(LoggerInitError::AlreadyInitialized)
81        }
82    }
83}
84
85/// Errors returned by methods that install [`Logger`]s
86#[derive(Debug, thiserror::Error)]
87pub enum LoggerInitError {
88    /// A logger has already been initialized. Only one logger can be initialized for the lifetime
89    /// of the program.
90    #[error("logger was already initialized")]
91    AlreadyInitialized,
92}
93
94/// Methods that a [`Logger`] can implement to filter and receive log messages
95pub trait LoggerImpl {
96    /// Log a new message at the given level for a subject
97    fn log(&self, _log_level: Level, _subject: Subject, _message: &str) {}
98    /// Get the maximum log level that this logger is currently interested in. This method allows
99    /// the CRT to avoid the cost of formatting log messages that won't be emitted.
100    fn get_log_level(&self, _subject: Subject) -> Level {
101        Level::None
102    }
103    /// Set the maximum log level that this logger should be interested in.
104    fn set_log_level(&self, _level: Level) -> Result<(), Box<dyn std::error::Error>> {
105        Ok(())
106    }
107    /// Clean up this logger. Called when the logger is being uninstalled. It will not receive any
108    /// further calls after this one.
109    fn clean_up(&self) {}
110}
111
112/// Get a reference to the internal `LoggerImpl`.
113///
114/// # Safety
115/// `aws_logger` points to the instance initialized in `Logger::new`.
116unsafe fn logger_impl<'a>(logger: *mut aws_logger) -> &'a dyn LoggerImpl {
117    // SAFETY: `logger` is a non-null pointer to a valid `aws_logger`.
118    let logger = unsafe { logger.as_ref().expect("logger is not null") };
119    // SAFETY: `logger.p_impl` points to a valid `Box<dyn LoggerImpl>`.
120    let box_impl = unsafe {
121        (logger.p_impl as *mut Box<dyn LoggerImpl>)
122            .as_ref()
123            .expect("p_impl is not null")
124    };
125    box_impl.as_ref()
126}
127
128#[unsafe(no_mangle)]
129unsafe extern "C" fn logger_vtable_log_fn(
130    logger: *mut aws_logger,
131    log_level: aws_log_level::Type,
132    subject: aws_log_subject_t,
133    body: *mut aws_string,
134    body_length: usize,
135) -> libc::c_int {
136    // SAFETY: `logger` was initialized in `Logger::new`.
137    let impl_ = unsafe { logger_impl(logger) };
138    let message = {
139        // SAFETY: logger API guarantees that `body` is a non-null pointer to a valid `aws_string`.
140        let body = unsafe { body.as_ref().expect("body cannot be null") };
141        // SAFETY: logger API guarantees that `body.bytes` points to a slice of at least `body_length`.
142        let bytes = unsafe { std::slice::from_raw_parts(&body.bytes as *const u8, body_length) };
143        // We assume log messages are valid ASCII
144        std::str::from_utf8(bytes).expect("log messages should be valid UTF-8")
145    };
146    impl_.log(log_level.into(), subject.into(), message);
147    AWS_OP_SUCCESS
148}
149
150unsafe extern "C" fn logger_vtable_get_log_level_fn(
151    logger: *mut aws_logger,
152    subject: aws_log_subject_t,
153) -> aws_log_level::Type {
154    // SAFETY: `logger` was initialized in `Logger::new`.
155    let impl_ = unsafe { logger_impl(logger) };
156    impl_.get_log_level(subject.into()).into()
157}
158
159unsafe extern "C" fn logger_vtable_set_log_level_fn(
160    logger: *mut aws_logger,
161    level: aws_log_level::Type,
162) -> libc::c_int {
163    // SAFETY: `logger` was initialized in `Logger::new`.
164    let impl_ = unsafe { logger_impl(logger) };
165    impl_
166        .set_log_level(level.into())
167        .map(|_| AWS_OP_SUCCESS)
168        .unwrap_or(AWS_OP_ERR)
169}
170
171unsafe extern "C" fn logger_vtable_clean_up_fn(logger: *mut aws_logger) {
172    // SAFETY: `logger` was initialized in `Logger::new`.
173    let impl_ = unsafe { logger_impl(logger) };
174    impl_.clean_up();
175}
176
177/// The log level associated with a message
178#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
179pub enum Level {
180    /// No log level set
181    None,
182    /// The "trace" level for very verbose logging
183    Trace,
184    /// The "debug" level for lower priority information
185    Debug,
186    /// The "info" level for useful information
187    Info,
188    /// The "warn" level for hazardous situations
189    Warn,
190    /// The "error" level for serious errors
191    Error,
192    /// The "fatal" level for errors that cannot be recovered from
193    Fatal,
194}
195
196impl From<aws_log_level::Type> for Level {
197    fn from(level: aws_log_level::Type) -> Self {
198        match level {
199            aws_log_level::AWS_LL_NONE => Level::None,
200            aws_log_level::AWS_LL_TRACE => Level::Trace,
201            aws_log_level::AWS_LL_DEBUG => Level::Debug,
202            aws_log_level::AWS_LL_INFO => Level::Info,
203            aws_log_level::AWS_LL_WARN => Level::Warn,
204            aws_log_level::AWS_LL_ERROR => Level::Error,
205            aws_log_level::AWS_LL_FATAL => Level::Fatal,
206            _ => unreachable!("unknown aws_log_level"),
207        }
208    }
209}
210
211impl From<Level> for aws_log_level::Type {
212    fn from(level: Level) -> Self {
213        match level {
214            Level::None => aws_log_level::AWS_LL_NONE,
215            Level::Trace => aws_log_level::AWS_LL_TRACE,
216            Level::Debug => aws_log_level::AWS_LL_DEBUG,
217            Level::Info => aws_log_level::AWS_LL_INFO,
218            Level::Warn => aws_log_level::AWS_LL_WARN,
219            Level::Error => aws_log_level::AWS_LL_ERROR,
220            Level::Fatal => aws_log_level::AWS_LL_FATAL,
221        }
222    }
223}
224
225/// The subject of a log message. Subjects are the component of the CRT that a message is generated
226/// by.
227#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228pub struct Subject(u32);
229
230impl Subject {
231    /// Generate a string representation of the subject
232    #[allow(clippy::unnecessary_cast)]
233    pub fn name(&self) -> &str {
234        // Safety: `aws_log_subject_name` always returns a valid C string. Technically it's possible
235        // for someone to call `aws_unregister_log_subject_info_list` to unregister a subject name
236        // and then free the pointer, but the CRT only does that at tear down time, and we don't
237        // otherwise expose that function.
238        unsafe {
239            let s = aws_log_subject_name(self.0);
240            let len = libc::strnlen(s, 4096);
241            let bytes = std::slice::from_raw_parts(s as *const u8, len);
242            // Valid ASCII is valid UTF-8
243            std::str::from_utf8_unchecked(bytes)
244        }
245    }
246}
247
248impl From<aws_log_subject_t> for Subject {
249    fn from(subject: aws_log_subject_t) -> Self {
250        Self(subject)
251    }
252}