1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
use std::fs::{File, OpenOptions};
use std::io;
use std::io::Write;
use std::path::{Path, PathBuf};

use tracing_appender::non_blocking::{NonBlocking, WorkerGuard};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{fmt, EnvFilter, Layer};

const ENV_FILTER: &str = "librdkafka=warn,\
    quinn_proto::connection=error,\
    rdkafka::client=warn,\
    summa_core::components=info,\
    summa_core::directories=info,\
    summa_core=warn,\
    summa_server::services=info,\
    summa_server::services::api=warn,\
    summa_server::components=info,\
    summa_server::server[lifecycle]=info,\
    summa_server=warn,\
    tantivy::store::compressors=error,\
    warn";

const REQUEST_ENV_FILTER: &str = "summa_server::server::grpc[request]=info,summa_server::server::metrics[request]=info";

struct WatchedWriter {
    file: File,
    file_name: PathBuf,
}

impl WatchedWriter {
    pub fn new(file_name: impl AsRef<Path>) -> io::Result<WatchedWriter> {
        Ok(WatchedWriter {
            file: OpenOptions::new().append(true).create(true).open(&file_name)?,
            file_name: file_name.as_ref().to_path_buf(),
        })
    }
    fn reopen(&mut self) -> io::Result<()> {
        self.file = OpenOptions::new().append(true).create(true).open(&self.file_name)?;
        Ok(())
    }
}

impl Write for WatchedWriter {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.file.write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.file.flush()?;
        if !self.file_name.exists() {
            self.reopen()?;
        }
        Ok(())
    }
}

fn create_writer(log_path: &Path, name: &str, guards: &mut Vec<WorkerGuard>) -> io::Result<NonBlocking> {
    let file = WatchedWriter::new(log_path.join(name).with_extension("log"))?;
    let (file_writer, guard) = tracing_appender::non_blocking(file);
    guards.push(guard);
    Ok(file_writer)
}

pub fn default() -> Vec<WorkerGuard> {
    let default_layer = fmt::layer()
        .with_level(true)
        .with_target(true)
        .with_thread_names(true)
        .with_filter(EnvFilter::new(format!("{ENV_FILTER},{REQUEST_ENV_FILTER}")));
    tracing_subscriber::registry().with(default_layer).init();
    vec![]
}

pub fn file(log_path: &Path) -> io::Result<Vec<WorkerGuard>> {
    let mut guards = Vec::new();

    std::fs::create_dir_all(log_path)?;
    let file_writer_request = create_writer(log_path, "request", &mut guards)?;
    let file_writer_query = create_writer(log_path, "query", &mut guards)?;
    let file_writer_summa = create_writer(log_path, "summa", &mut guards)?;

    let filter_layer_request = EnvFilter::new(REQUEST_ENV_FILTER);
    let filter_layer_query = EnvFilter::new("query");

    let request_layer = fmt::layer()
        .with_thread_names(false)
        .with_target(false)
        .with_writer(file_writer_request)
        .with_filter(filter_layer_request);
    let query_layer = fmt::layer()
        .with_thread_names(false)
        .with_target(false)
        .with_writer(file_writer_query)
        .with_filter(filter_layer_query);
    let default_layer = fmt::layer()
        .with_thread_names(false)
        .with_target(true)
        .with_level(true)
        .with_writer(file_writer_summa)
        .with_filter(EnvFilter::new(ENV_FILTER));
    tracing_subscriber::registry().with(request_layer).with(query_layer).with(default_layer).init();

    Ok(guards)
}

#[cfg(test)]
pub mod tests {
    use std::sync::Once;

    static INIT: Once = Once::new();
    pub fn initialize_default_once() {
        INIT.call_once(|| {
            super::default();
        });
    }
}