Skip to main content

captains_log/
recipe.rs

1//! The recipe module contains some prelude functions that construct a [Builder] for
2//! convenience use.
3//!
4//! All Builder object returned by recipe can be modified additionally by user.
5//!
6//! Please click to the description and source for reference.
7
8use crate::*;
9use log::Level;
10use std::path;
11use std::path::{Path, PathBuf};
12use std::str::FromStr;
13
14pub const DEFAULT_TIME: &'static str = "%Y-%m-%d %H:%M:%S%.6f";
15
16/// [{time}][{level}][{file}:{line}] {msg}
17pub const LOG_FORMAT_DEBUG: LogFormat = LogFormat::new(DEFAULT_TIME, debug_format_f);
18
19/// [{time}][{level}][thread_id][{file}:{line}] {msg}
20pub const LOG_FORMAT_THREADED_DEBUG: LogFormat =
21    LogFormat::new(DEFAULT_TIME, threaded_debug_format_f);
22
23/// [{time}][{level}] {msg}
24pub const LOG_FORMAT_PROD: LogFormat = LogFormat::new(DEFAULT_TIME, prod_format_f);
25
26/// formatter function: [{time}][{level}][{file}:{line}] {msg}
27pub fn debug_format_f(r: FormatRecord) -> String {
28    let time = r.time();
29    let level = r.level();
30    let file = r.file();
31    let line = r.line();
32    let msg = r.msg();
33    format!("[{time}][{level}][{file}:{line}] {msg}\n").to_string()
34}
35
36/// formatter function: [{time}][{level}][thread_id][{file}:{line}] {msg}
37pub fn threaded_debug_format_f(r: FormatRecord) -> String {
38    let time = r.time();
39    let level = r.level();
40    let file = r.file();
41    let line = r.line();
42    let msg = r.msg();
43    let thread_id = r.thread_id();
44    format!("[{time}][{level}][{:?}][{file}:{line}] {msg}\n", thread_id).to_string()
45}
46
47/// formatter function: [{time}][{level}] {msg}
48pub fn prod_format_f(r: FormatRecord) -> String {
49    let time = r.time();
50    let level = r.level();
51    let msg = r.msg();
52    format!("[{time}][{level}] {msg}\n").to_string()
53}
54
55pub fn console_logger(target: ConsoleTarget, max_level: Level) -> Builder {
56    let console_config = LogConsole::new(target, max_level, LOG_FORMAT_DEBUG);
57    return Builder::default().add_sink(console_config);
58}
59
60/// Output to stdout with LOG_FORMAT_DEBUG, with dynamic=true.
61///
62/// You don't care the speed when output to console.
63#[inline]
64pub fn stdout_logger(max_level: Level) -> Builder {
65    console_logger(ConsoleTarget::Stdout, max_level).test()
66}
67
68/// Output to stderr with LOG_FORMAT_DEBUG, with dynamic=true.
69///
70/// You don't care the speed when output to console.
71#[inline]
72pub fn stderr_logger(max_level: Level) -> Builder {
73    console_logger(ConsoleTarget::Stderr, max_level).test()
74}
75
76/// Configure dynamic file/console logger from environment.
77///
78/// # Arguments:
79///
80///   - file_env_name:
81///
82///     If valid as stdout/stderr/1/2, output to console target;
83///
84///     When a file path is configured, create a raw_file_logger();
85///
86///     For empty string, default output to Stderr.
87///
88///   - level_env_name: configure the log level, default to Info.
89///
90/// # Example:
91///
92/// ``` rust
93/// use captains_log::recipe;
94/// let _ = recipe::env_logger("LOG_FILE", "LOG_LEVEL").build();
95/// ```
96pub fn env_logger(file_env_name: &str, level_env_name: &str) -> Builder {
97    let level: Level = crate::env::env_or(level_env_name, Level::Info).into();
98    let mut console: Option<ConsoleTarget> = None;
99    if let Ok(file_path) = std::env::var(file_env_name) {
100        if let Ok(target) = ConsoleTarget::from_str(file_path.as_str()) {
101            console = Some(target);
102        } else if file_path.len() > 0 {
103            return raw_file_logger(file_path, level).test();
104        }
105    }
106    return console_logger(console.unwrap_or(ConsoleTarget::Stderr), level).test();
107}
108
109/// Setup one log file, with custom time_fmt & format_func.
110///
111/// See the source for details.
112///
113/// # Arguments:
114///
115/// - `file_path`: can be &str / String / &OsStr / OsString / Path / PathBuf
116///
117/// - `time_fmt`: The timestamp format inside the logs.
118///
119/// - `format_func`: function that format the line inside the logs
120pub fn raw_file_logger_custom<P: Into<PathBuf>>(
121    file_path: P, max_level: Level, time_fmt: &'static str, format_func: FormatFunc,
122) -> Builder {
123    let format = LogFormat::new(time_fmt, format_func);
124    let _file_path = file_path.into();
125    let p = path::absolute(&_file_path).expect("path convert to absolute");
126    let dir = p.parent().unwrap();
127    let file_name = Path::new(p.file_name().unwrap());
128    let file = LogRawFile::new(dir, file_name, max_level, format);
129    return Builder::default().signal(signal_hook::consts::SIGUSR1).add_sink(file);
130}
131
132/// Setup one log file.
133///
134/// See the source for details.
135///
136/// # Arguments:
137///
138/// - `file_path`: can be &str / String / &OsStr / OsString / Path / PathBuf
139pub fn raw_file_logger<P: Into<PathBuf>>(file_path: P, max_level: Level) -> Builder {
140    raw_file_logger_custom(file_path, max_level, DEFAULT_TIME, debug_format_f)
141}
142
143/// Setup two log files.
144/// One as "{{name}}.log" for debug purpose, with file line to track problem.
145/// One as "{{name}}.log.wf" for error level log.
146///
147/// See the source for details.
148///
149/// # Arguments:
150///
151/// - `dir`: directory of the log files, can be &str / String / &OsStr / OsString / Path / PathBuf.
152///
153/// - `name`: log name, not including the suffix, can be &str / String.
154pub fn split_error_file_logger<P1, P2>(dir: P1, name: P2, max_level: Level) -> Builder
155where
156    P1: Into<PathBuf>,
157    P2: Into<String>,
158{
159    let _name: String = name.into();
160    let debug_file_name = format!("{}.log", _name);
161    let _dir: PathBuf = dir.into();
162    let debug_file = LogRawFile::new(_dir.clone(), debug_file_name, max_level, LOG_FORMAT_DEBUG);
163    let err_file_name = format!("{}.log.wf", _name);
164    let error_file = LogRawFile::new(_dir.clone(), err_file_name, Level::Error, LOG_FORMAT_PROD);
165
166    return Builder::default()
167        .signal(signal_hook::consts::SIGUSR1)
168        .add_sink(debug_file)
169        .add_sink(error_file);
170}
171
172/// Setup one buffered log file, with custom time_fmt & format_func.
173///
174/// See the source for details.
175///
176/// - `file_path`: The type of file_path can be &str / String / &OsStr / OsString / Path / PathBuf
177///
178/// - `flush_millis`:
179///
180///     - default to 0, means always flush when no more message to write.
181///
182///     - when larger than zero, will wait for new message when timeout occur.
183///
184///     - the max value is 1000 (1 sec).
185pub fn buffered_file_logger_custom<P: Into<PathBuf>>(
186    file_path: P, max_level: Level, time_fmt: &'static str, format_func: FormatFunc,
187    flush_millis: usize, rotate: Option<crate::rotation::Rotation>,
188) -> Builder {
189    let format = LogFormat::new(time_fmt, format_func);
190    let _file_path = file_path.into();
191    let p = path::absolute(&_file_path).expect("path convert to absolute");
192    let dir = p.parent().unwrap();
193    let file_name = Path::new(p.file_name().unwrap());
194    let mut file = LogBufFile::new(dir, file_name, max_level, format, flush_millis);
195    if let Some(ro) = rotate {
196        file = file.rotation(ro);
197    }
198    return Builder::default().signal(signal_hook::consts::SIGUSR1).add_sink(file);
199}
200
201/// Setup one buffered log file, with flush_millis set to 0
202///
203/// See the source for details.
204///
205/// # Arguments:
206///
207/// - `file_path`: The type of file_path can be &str / String / &OsStr / OsString / Path / PathBuf
208pub fn buffered_file_logger<P: Into<PathBuf>>(file_path: P, max_level: Level) -> Builder {
209    buffered_file_logger_custom(file_path, max_level, DEFAULT_TIME, debug_format_f, 0, None)
210}
211
212/// Setup one buffered log file, capable of self rotation, with flush_millis set to 0,
213///
214/// See the source for details.
215///
216/// # Arguments:
217///
218/// - `file_path`: The type of file_path can be &str / String / &OsStr / OsString / Path / PathBuf
219///
220/// - `rotation`: rotation and archive strategy
221pub fn buffered_rotated_file_logger<P: Into<PathBuf>>(
222    file_path: P, max_level: Level, rotation: crate::rotation::Rotation,
223) -> Builder {
224    buffered_file_logger_custom(
225        file_path,
226        max_level,
227        DEFAULT_TIME,
228        debug_format_f,
229        0,
230        Some(rotation),
231    )
232}
233
234/// Output to local syslog
235#[cfg(feature = "syslog")]
236#[cfg_attr(docsrs, doc(cfg(feature = "syslog")))]
237pub fn syslog_local(facility: syslog::Facility, max_level: Level) -> Builder {
238    let syslog = syslog::Syslog::new(facility, max_level);
239    return Builder::default().add_sink(syslog);
240}
241
242/// Initialize a ring buffer to hold the log. For complete usage, refer to the doc in [ringfile].
243///
244/// # NOTE:
245/// the recipe already register signal and dynamic=true, **do not use test()** here,
246/// because [test()](crate::Builder::test()) will clear the signal.
247///
248/// # Arguments:
249///
250/// - `file_path`: path on disk to which the log content will be dumped
251///
252/// - `buf_size`: the size of memory within each thread. limit: 0 < buf_size < i32::MAX
253///
254/// - `max_level`: filter the log by level.
255///
256/// - `dump_signal`: Dump the content from memory buffer to disk when the signal received.
257#[cfg(feature = "ringfile")]
258#[cfg_attr(docsrs, doc(cfg(feature = "ringfile")))]
259pub fn ring_file<P: Into<PathBuf>>(
260    file_path: P, buf_size: i32, max_level: Level, dump_signal: i32,
261) -> Builder {
262    let ring =
263        ringfile::LogRingFile::new(file_path, buf_size, max_level, LOG_FORMAT_THREADED_DEBUG);
264    let mut config = Builder::default().signal(dump_signal).add_sink(ring);
265    config.dynamic = true;
266    config
267}