mountpoint_s3_crt/common/
logging.rs1use 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
17pub struct Logger {
19 inner: Pin<Box<aws_logger>>,
20 _vtable: Pin<Box<aws_logger_vtable>>,
21 _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 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 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 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#[derive(Debug, thiserror::Error)]
87pub enum LoggerInitError {
88 #[error("logger was already initialized")]
91 AlreadyInitialized,
92}
93
94pub trait LoggerImpl {
96 fn log(&self, _log_level: Level, _subject: Subject, _message: &str) {}
98 fn get_log_level(&self, _subject: Subject) -> Level {
101 Level::None
102 }
103 fn set_log_level(&self, _level: Level) -> Result<(), Box<dyn std::error::Error>> {
105 Ok(())
106 }
107 fn clean_up(&self) {}
110}
111
112unsafe fn logger_impl<'a>(logger: *mut aws_logger) -> &'a dyn LoggerImpl {
117 let logger = unsafe { logger.as_ref().expect("logger is not null") };
119 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 let impl_ = unsafe { logger_impl(logger) };
138 let message = {
139 let body = unsafe { body.as_ref().expect("body cannot be null") };
141 let bytes = unsafe { std::slice::from_raw_parts(&body.bytes as *const u8, body_length) };
143 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 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 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 let impl_ = unsafe { logger_impl(logger) };
174 impl_.clean_up();
175}
176
177#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
179pub enum Level {
180 None,
182 Trace,
184 Debug,
186 Info,
188 Warn,
190 Error,
192 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
228pub struct Subject(u32);
229
230impl Subject {
231 #[allow(clippy::unnecessary_cast)]
233 pub fn name(&self) -> &str {
234 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 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}