psp_logger/
lib.rs

1#![no_std]
2
3//! # psp-logger
4//! A logger capable of outputting to the PSP's stdout and stderr.
5//!
6//! This output can than be viewed using [PSPLink](https://github.com/pspdev/psplinkusb)
7//!
8//! # Usage
9//! ```
10//! use psp_logger::{PspLogger, PspLoggerConfig, OutputStream};
11//! use log::{trace, debug, info, warn, error};
12//!
13//! // Configure logging to only allow messages with debug-level or above.
14//! // Map debug and info to stdout, letting other levels use the default stderr.
15//! let config = PspLoggerConfig::new(log::LevelFilter::Debug)
16//!     .with_debug_stream(OutputStream::StdOut)
17//!     .with_info_stream(OutputStream::StdOut);
18//!
19//! let _ = psp_logger::PspLogger::init(config);
20//!
21//! trace!("This will be filtered out.");
22//! debug!("This will be logged to stdout.");
23//! info!("This will also be logged to stdout");
24//! warn!("This will be logged to stderr.");
25//! error!("This will also be logged to stder.");
26//!
27//! ```
28extern crate alloc;
29
30use core::fmt::Arguments;
31
32use alloc::format;
33use log::{Level, LevelFilter, Metadata, Record};
34use psp::sys::*;
35
36/// Enum holding the possible output streams that the logs can be written to.
37#[derive(Copy, Clone)]
38pub enum OutputStream {
39    StdOut,
40    StdErr,
41}
42
43/// Configuration for the logger.
44///
45/// # Examples
46/// ```
47/// // Create logger for Debug and up.
48/// // All logs will be written to stderr.
49/// let config = psp_logger::PspLoggerConfig::new(log::LevelFilter::Debug);
50/// let _ = psp_logger::PspLogger::init(config);
51///
52/// debug!("I'm a debug log!");
53/// ```
54///
55/// ```
56/// // Create logger for Info and up.
57/// // Info logs will go to stdout, the rest will go to stderr.
58/// let config = psp_logger::PspLoggerConfig::new(log::LevelFilter::Info)
59///     .with_info_stream(psp_logger::OutputStream::StdOut);
60///
61/// let _ = psp_logger::PspLogger::init(config);
62///
63/// info!("I'm an info log!");
64/// ```
65pub struct PspLoggerConfig {
66    error_stream: OutputStream,
67    warn_stream: OutputStream,
68    info_stream: OutputStream,
69    debug_stream: OutputStream,
70    trace_stream: OutputStream,
71    level_filter: LevelFilter,
72}
73
74/// The actual logger instance.
75pub struct PspLogger {}
76
77static LOGGER: PspLogger = PspLogger {};
78static LOGGER_CONF: spin::Once<PspLoggerConfig> = spin::Once::new();
79
80unsafe fn psp_write(stream: OutputStream, args: &Arguments) {
81    let fh = match stream {
82        OutputStream::StdErr => sceKernelStderr(),
83        OutputStream::StdOut => sceKernelStdout(),
84    };
85
86    let msg = alloc::fmt::format(*args);
87    let msg = format!("{}\n\0", msg);
88
89    sceIoWrite(fh, msg.as_ptr() as _, msg.len());
90}
91
92impl log::Log for PspLogger {
93    fn enabled(&self, metadata: &Metadata) -> bool {
94        metadata.level() <= LOGGER_CONF.get().unwrap().level_filter
95    }
96
97    fn log(&self, record: &Record) {
98        let output = LOGGER_CONF
99            .get()
100            .unwrap()
101            .get_stream(record.metadata().level());
102
103        if self.enabled(record.metadata()) {
104            unsafe { psp_write(output, record.args()) }
105        }
106    }
107
108    fn flush(&self) {}
109}
110
111impl PspLogger {
112    /// Initialise the logger.
113    ///
114    /// # Arguments
115    /// - `config`: Logging configuration to be used.
116    pub fn init(config: PspLoggerConfig) -> Result<(), log::SetLoggerError> {
117        let level_filter = config.level_filter;
118
119        LOGGER_CONF.call_once(|| config);
120        log::set_logger(&LOGGER).map(|()| log::set_max_level(level_filter))
121    }
122}
123
124impl PspLoggerConfig {
125    /// Constructs a new PspLoggerConfig.
126    ///
127    /// All log levels will initially be mapped to stderr.
128    /// Use the `with_*_stream` methods on the returned struct to change this.
129    ///
130    /// # Arguments
131    /// - `level_filter`: Filter to control which log levels are actually logged.
132    pub fn new(level_filter: LevelFilter) -> Self {
133        PspLoggerConfig {
134            error_stream: OutputStream::StdErr,
135            warn_stream: OutputStream::StdErr,
136            info_stream: OutputStream::StdErr,
137            debug_stream: OutputStream::StdErr,
138            trace_stream: OutputStream::StdErr,
139            level_filter,
140        }
141    }
142
143    /// Map the error log level to an [OutputStream]
144    ///
145    /// Returns the struct to allow the method to be chained.
146    pub fn with_error_stream(mut self, stream: OutputStream) -> Self {
147        self.error_stream = stream;
148        self
149    }
150
151    /// Map the warn log level to an [OutputStream]
152    ///
153    /// Returns the struct to allow the method to be chained.
154    pub fn with_warn_stream(mut self, stream: OutputStream) -> Self {
155        self.warn_stream = stream;
156        self
157    }
158
159    /// Map the info log level to an [OutputStream]
160    ///
161    /// Returns the struct to allow the method to be chained.
162    pub fn with_info_stream(mut self, stream: OutputStream) -> Self {
163        self.info_stream = stream;
164        self
165    }
166
167    /// Map the debug log level to an [OutputStream]
168    ///
169    /// Returns the struct to allow the method to be chained.
170    pub fn with_debug_stream(mut self, stream: OutputStream) -> Self {
171        self.debug_stream = stream;
172        self
173    }
174
175    /// Map the trace log level to an [OutputStream]
176    ///
177    /// Returns the struct to allow the method to be chained.
178    pub fn with_trace_stream(mut self, stream: OutputStream) -> Self {
179        self.trace_stream = stream;
180        self
181    }
182
183    fn get_stream(&self, level: Level) -> OutputStream {
184        match level {
185            Level::Error => self.error_stream,
186            Level::Warn => self.warn_stream,
187            Level::Info => self.info_stream,
188            Level::Debug => self.debug_stream,
189            Level::Trace => self.trace_stream,
190        }
191    }
192}