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}