sn_logging/
appender.rs

1// Copyright 2024 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
4// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
5// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
6// KIND, either express or implied. Please review the Licences for the specific language governing
7// permissions and limitations relating to use of the SAFE Network Software.
8
9use file_rotate::{
10    compression::Compression,
11    suffix::{AppendTimestamp, FileLimit},
12    ContentLimit, FileRotate,
13};
14use std::{
15    env,
16    ffi::OsStr,
17    fmt::Debug,
18    io,
19    io::Write,
20    path::{Path, PathBuf},
21};
22use tracing_appender::non_blocking::{NonBlocking, WorkerGuard};
23
24/// max_bytes:
25/// - the maximum size a log can grow to until it is rotated.
26///
27/// uncompressed_files:
28/// - number of files to keep uncompressed.
29/// - should be lesser than `max_files` to enable compression of excess files.
30///
31/// max_files:
32/// - maximum number of files to keep.
33/// - older files are deleted.
34pub(super) fn file_rotater(
35    dir: &PathBuf,
36    max_bytes: usize,
37    uncompressed_files: usize,
38    max_files: usize,
39) -> (NonBlocking, WorkerGuard) {
40    let binary_name = env::current_exe()
41        .map(|path| {
42            path.file_stem()
43                .unwrap_or(OsStr::new("safe"))
44                .to_string_lossy()
45                .into_owned()
46        })
47        .unwrap_or_else(|_| "safe".to_string());
48
49    let file_appender = FileRotateAppender::make_rotate_appender(
50        dir,
51        format!("{binary_name}.log"),
52        AppendTimestamp::default(FileLimit::MaxFiles(max_files)),
53        ContentLimit::BytesSurpassed(max_bytes),
54        Compression::OnRotate(uncompressed_files),
55    );
56
57    // configure how tracing non-blocking works: https://tracing.rs/tracing_appender/non_blocking/struct.nonblockingbuilder#method.default
58    let non_blocking_builder = tracing_appender::non_blocking::NonBlockingBuilder::default();
59
60    non_blocking_builder
61        // lose lines and keep perf, or exert backpressure?
62        .lossy(false)
63        // optionally change buffered lines limit
64        // .buffered_lines_limit(buffered_lines_limit)
65        .finish(file_appender)
66}
67
68/// `FileRotateAppender` is a `tracing_appender` with extra logrotate features:
69///  - most recent logfile name re-used to support following (e.g. 'tail -f=logfile')
70///  - numbered rotation (logfile.1, logfile.2 etc)
71///  - limit logfile by size, lines or time
72///  - limit maximum number of logfiles
73///  - optional compression of rotated logfiles
74//
75// The above functionality is provided using crate file_rotation
76pub(super) struct FileRotateAppender {
77    writer: FileRotate<AppendTimestamp>,
78}
79
80impl FileRotateAppender {
81    /// Create `FileRotateAppender` using parameters
82    pub(super) fn make_rotate_appender(
83        directory: impl AsRef<Path>,
84        file_name_prefix: impl AsRef<Path>,
85        file_limit: AppendTimestamp,
86        max_log_size: ContentLimit,
87        compression: Compression,
88    ) -> Self {
89        let log_directory = directory.as_ref();
90        let log_filename_prefix = file_name_prefix.as_ref();
91        let path = Path::new(&log_directory).join(log_filename_prefix);
92        let writer = FileRotate::new(
93            Path::new(&path),
94            file_limit,
95            max_log_size,
96            compression,
97            #[cfg(unix)]
98            None,
99        );
100
101        Self { writer }
102    }
103}
104
105impl Write for FileRotateAppender {
106    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
107        self.writer.write(buf)
108    }
109
110    fn flush(&mut self) -> io::Result<()> {
111        self.writer.flush()
112    }
113}
114
115use std::fmt;
116
117impl Debug for FileRotateAppender {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        f.debug_struct("FileRotateAppender").finish()
120    }
121}