use std::{ffi::CString, io, ptr::null, result::Result as StdResult};
use libc::EPERM;
use crate::{
formatter::{Formatter, FormatterContext, FullFormatter},
prelude::*,
sink::{GetSinkProp, Sink, SinkProp},
sync::*,
Error, ErrorHandler, Record, Result, StringBuf,
};
#[cfg(not(doc))]
mod ffi {
use android_log_sys::{__android_log_write, c_int, LogPriority};
use super::*;
pub(super) struct AndroidLevelsMapping([LogPriority; Level::count()]);
impl AndroidLevelsMapping {
#[must_use]
pub(super) const fn new() -> Self {
Self([
LogPriority::FATAL, LogPriority::ERROR, LogPriority::WARN, LogPriority::INFO, LogPriority::DEBUG, LogPriority::VERBOSE, ])
}
#[must_use]
pub(super) fn level(&self, level: Level) -> LogPriority {
self.0[level as usize]
}
}
pub(super) fn android_log_write(
priority: LogPriority,
tag: Option<&str>,
text: &str,
) -> StdResult<(), io::Error> {
let tag = tag
.map(CString::new)
.transpose()
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
let text =
CString::new(text).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
let tag_ptr = tag.as_deref().map(|tag| tag.as_ptr()).unwrap_or_else(null);
let text_ptr = text.as_ptr();
let result = unsafe { __android_log_write(priority as c_int, tag_ptr, text_ptr) };
drop((tag, text));
if result >= 0 || result == -EPERM {
Ok(())
} else {
Err(io::Error::from_raw_os_error(-result))
}
}
}
pub enum AndroidLogTag {
Default,
LoggerName,
Custom(String),
}
#[allow(missing_docs)]
pub struct AndroidSinkBuilder {
prop: SinkProp,
tag: AndroidLogTag,
}
impl AndroidSinkBuilder {
#[must_use]
pub fn tag(mut self, tag: AndroidLogTag) -> Self {
self.tag = tag;
self
}
#[must_use]
pub fn level_filter(self, level_filter: LevelFilter) -> Self {
self.prop.set_level_filter(level_filter);
self
}
#[must_use]
pub fn formatter<F>(self, formatter: F) -> Self
where
F: Formatter + 'static,
{
self.prop.set_formatter(formatter);
self
}
#[must_use]
pub fn error_handler<F: Into<ErrorHandler>>(self, handler: F) -> Self {
self.prop.set_error_handler(handler);
self
}
pub fn build(self) -> Result<AndroidSink> {
Ok(AndroidSink {
prop: self.prop,
tag: self.tag,
})
}
pub fn build_arc(self) -> Result<Arc<AndroidSink>> {
self.build().map(Arc::new)
}
}
pub struct AndroidSink {
prop: SinkProp,
tag: AndroidLogTag,
}
impl AndroidSink {
#[cfg(not(doc))]
const LEVELS_MAPPING: ffi::AndroidLevelsMapping = ffi::AndroidLevelsMapping::new();
#[must_use]
pub fn builder() -> AndroidSinkBuilder {
let prop = SinkProp::default();
prop.set_formatter(
FullFormatter::builder()
.time(false)
.level(false)
.eol(false)
.build(),
);
AndroidSinkBuilder {
prop,
tag: AndroidLogTag::Default,
}
}
#[must_use]
pub fn tag(&self) -> &AndroidLogTag {
&self.tag
}
pub fn set_tag(&mut self, tag: AndroidLogTag) {
self.tag = tag;
}
}
impl GetSinkProp for AndroidSink {
fn prop(&self) -> &SinkProp {
&self.prop
}
}
impl Sink for AndroidSink {
fn log(&self, record: &Record) -> Result<()> {
let mut string_buf = StringBuf::new();
let mut ctx = FormatterContext::new();
self.prop
.formatter()
.format(record, &mut string_buf, &mut ctx)?;
let priority = Self::LEVELS_MAPPING.level(record.level());
let tag = match &self.tag {
AndroidLogTag::Default => None,
AndroidLogTag::LoggerName => record.logger_name(),
AndroidLogTag::Custom(tag) => Some(tag.as_str()),
};
ffi::android_log_write(priority, tag, &string_buf).map_err(Error::WriteRecord)
}
fn flush(&self) -> Result<()> {
Ok(())
}
}