spdlog/sink/android_sink.rs
1use std::{ffi::CString, io, ptr::null, result::Result as StdResult};
2
3use libc::EPERM;
4
5use crate::{
6 formatter::{Formatter, FormatterContext, FullFormatter},
7 prelude::*,
8 sink::{GetSinkProp, Sink, SinkProp},
9 sync::*,
10 Error, ErrorHandler, Record, Result, StringBuf,
11};
12
13#[cfg(not(doc))]
14mod ffi {
15 use android_log_sys::{LogPriority, __android_log_write, c_int};
16
17 use super::*;
18
19 pub(super) struct AndroidLevelsMapping([LogPriority; Level::count()]);
20
21 impl AndroidLevelsMapping {
22 #[must_use]
23 pub(super) const fn new() -> Self {
24 Self([
25 LogPriority::FATAL, // spdlog::Critical
26 LogPriority::ERROR, // spdlog::Error
27 LogPriority::WARN, // spdlog::Warn
28 LogPriority::INFO, // spdlog::Info
29 LogPriority::DEBUG, // spdlog::Debug
30 LogPriority::VERBOSE, // spdlog::Trace
31 ])
32 }
33
34 #[must_use]
35 pub(super) fn level(&self, level: Level) -> LogPriority {
36 self.0[level as usize]
37 }
38 }
39
40 pub(super) fn android_log_write(
41 priority: LogPriority,
42 tag: Option<&str>,
43 text: &str,
44 ) -> StdResult<(), io::Error> {
45 let tag = tag
46 .map(CString::new)
47 .transpose()
48 .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
49 let text =
50 CString::new(text).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
51
52 let tag_ptr = tag.as_deref().map(|tag| tag.as_ptr()).unwrap_or_else(null);
53 let text_ptr = text.as_ptr();
54
55 let result = unsafe { __android_log_write(priority as c_int, tag_ptr, text_ptr) };
56
57 // Explicitly drop to ensure that they have not been moved to cause dangling
58 // pointers.
59 drop((tag, text));
60
61 // Although the documentation [^1] says that:
62 // 1 if the message was written to the log, or -EPERM if it was not;
63 //
64 // It doesn't point out that the behavior differs between versions. The above
65 // behavior is available since Android 11 (API 30). Before that, the behavior of
66 // the return value was not clarified in the documentation, but referring to the
67 // implementation, for a successful log write, the number of bytes written is
68 // actually returned. This behavior is changed in this commit [^2].
69 //
70 // For compatible with more versions, we do not use `result == 1` as the success
71 // condition, but `result >= 0` instead.
72 //
73 // [^1]: https://developer.android.com/ndk/reference/group/logging#group___logging_1ga32a7173b092ec978b50490bd12ee523b
74 // [^2]: https://android.googlesource.com/platform/system/logging/+/c17613c4582d4f6eecb3965bb96584f25762b827%5E%21/
75 //
76 // ---
77 //
78 // For the condition `result == -EPERM`, see
79 // https://github.com/gabime/spdlog/commit/01b3724c484eebb42d83fa21aa8d71a57b2b8fb6
80 if result >= 0 || /* !__android_log_is_loggable */ result == -EPERM {
81 Ok(())
82 } else {
83 Err(io::Error::from_raw_os_error(-result))
84 }
85 }
86}
87
88/// Represents how to choose a tag for Android logs.
89///
90/// # Log Level Mapping
91///
92/// | spdlog-rs | Android NDK |
93/// |------------|-------------|
94/// | `Critical` | `FATAL` |
95/// | `Error` | `ERROR` |
96/// | `Warn` | `WARN` |
97/// | `Info` | `INFO` |
98/// | `Debug` | `DEBUG` |
99/// | `Trace` | `VERBOSE` |
100///
101/// # Note
102///
103/// It requires linking to Android NDK `liblog`.
104pub enum AndroidLogTag {
105 /// The default tag determined by Android NDK.
106 Default,
107 /// The name of the `spdlog-rs` logger that generated the log.
108 LoggerName,
109 /// A custom string.
110 Custom(String),
111}
112
113#[allow(missing_docs)]
114pub struct AndroidSinkBuilder {
115 prop: SinkProp,
116 tag: AndroidLogTag,
117}
118
119impl AndroidSinkBuilder {
120 /// Specifies how to choose a tag for Android logs.
121 ///
122 /// This parameter is **optional**, and defaults to
123 /// [`AndroidLogTag::Default`].
124 #[must_use]
125 pub fn tag(mut self, tag: AndroidLogTag) -> Self {
126 self.tag = tag;
127 self
128 }
129
130 // Prop
131 //
132
133 /// Specifies a log level filter.
134 ///
135 /// This parameter is **optional**, and defaults to [`LevelFilter::All`].
136 #[must_use]
137 pub fn level_filter(self, level_filter: LevelFilter) -> Self {
138 self.prop.set_level_filter(level_filter);
139 self
140 }
141
142 /// Specifies a formatter.
143 ///
144 /// This parameter is **optional**, and defaults to [`FullFormatter`]
145 /// `(!time !level !eol)`.
146 #[must_use]
147 pub fn formatter<F>(self, formatter: F) -> Self
148 where
149 F: Formatter + 'static,
150 {
151 self.prop.set_formatter(formatter);
152 self
153 }
154
155 /// Specifies an error handler.
156 ///
157 /// This parameter is **optional**, and defaults to
158 /// [`ErrorHandler::default()`].
159 #[must_use]
160 pub fn error_handler<F: Into<ErrorHandler>>(self, handler: F) -> Self {
161 self.prop.set_error_handler(handler);
162 self
163 }
164
165 //
166
167 /// Constructs a `AndroidSink`.
168 pub fn build(self) -> Result<AndroidSink> {
169 Ok(AndroidSink {
170 prop: self.prop,
171 tag: self.tag,
172 })
173 }
174
175 /// Builds a `Arc<AndroidSink>`.
176 ///
177 /// This is a shorthand method for `.build().map(Arc::new)`.
178 pub fn build_arc(self) -> Result<Arc<AndroidSink>> {
179 self.build().map(Arc::new)
180 }
181}
182
183/// A sink with Android NDK API `__android_log_write` as the target.
184pub struct AndroidSink {
185 prop: SinkProp,
186 tag: AndroidLogTag,
187}
188
189impl AndroidSink {
190 #[cfg(not(doc))]
191 const LEVELS_MAPPING: ffi::AndroidLevelsMapping = ffi::AndroidLevelsMapping::new();
192
193 /// Gets a builder of `AndroidSink` with default parameters:
194 ///
195 /// | Parameter | Default Value |
196 /// |-----------------|-----------------------------------------|
197 /// | [level_filter] | [`LevelFilter::All`] |
198 /// | [formatter] | [`FullFormatter`] `(!time !level !eol)` |
199 /// | [error_handler] | [`ErrorHandler::default()`] |
200 /// | | |
201 /// | [tag] | [`AndroidLogTag::Default`] |
202 ///
203 /// [level_filter]: AndroidSinkBuilder::level_filter
204 /// [formatter]: AndroidSinkBuilder::formatter
205 /// [error_handler]: AndroidSinkBuilder::error_handler
206 /// [tag]: AndroidSinkBuilder::tag
207 #[must_use]
208 pub fn builder() -> AndroidSinkBuilder {
209 let prop = SinkProp::default();
210 prop.set_formatter(
211 FullFormatter::builder()
212 .time(false)
213 .level(false)
214 .eol(false)
215 .build(),
216 );
217
218 AndroidSinkBuilder {
219 prop,
220 tag: AndroidLogTag::Default,
221 }
222 }
223
224 /// Gets how to choose a tag for Android logs.
225 #[must_use]
226 pub fn tag(&self) -> &AndroidLogTag {
227 &self.tag
228 }
229
230 /// Sets how to choose a tag for Android logs.
231 pub fn set_tag(&mut self, tag: AndroidLogTag) {
232 self.tag = tag;
233 }
234}
235
236impl GetSinkProp for AndroidSink {
237 fn prop(&self) -> &SinkProp {
238 &self.prop
239 }
240}
241
242impl Sink for AndroidSink {
243 fn log(&self, record: &Record) -> Result<()> {
244 let mut string_buf = StringBuf::new();
245 let mut ctx = FormatterContext::new();
246 self.prop
247 .formatter()
248 .format(record, &mut string_buf, &mut ctx)?;
249
250 let priority = Self::LEVELS_MAPPING.level(record.level());
251 let tag = match &self.tag {
252 AndroidLogTag::Default => None,
253 AndroidLogTag::LoggerName => record.logger_name(),
254 AndroidLogTag::Custom(tag) => Some(tag.as_str()),
255 };
256 ffi::android_log_write(priority, tag, &string_buf).map_err(Error::WriteRecord)
257 }
258
259 fn flush(&self) -> Result<()> {
260 Ok(())
261 }
262}