below_common/
logutil.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::io;
16use std::sync::Arc;
17use std::sync::Mutex;
18use std::sync::RwLock;
19
20use once_cell::sync::Lazy;
21use slog::Drain;
22use slog::Level;
23
24#[derive(PartialEq, Copy, Clone)]
25pub enum TargetLog {
26    All,
27    File,
28    Term,
29}
30
31pub static LOG_TARGET: Lazy<Arc<RwLock<TargetLog>>> =
32    Lazy::new(|| Arc::new(RwLock::new(TargetLog::All)));
33
34pub fn get_current_log_target() -> TargetLog {
35    *LOG_TARGET
36        .read()
37        .expect("Failed to acquire read lock on the LOG_TARGET")
38}
39
40pub fn set_current_log_target(target: TargetLog) {
41    let mut log_target = LOG_TARGET
42        .write()
43        .expect("Failed to acquire write lock on the LOG_TARGET");
44    *log_target = target;
45}
46
47/// CPMsgRecord stands for command palette record which used to pass the
48/// logging message from slog object to view objet by LAST_LOG_TO_DISPLAY
49pub struct CPMsgRecord {
50    level: Level,
51    msg: String,
52    consumed: bool,
53}
54
55impl CPMsgRecord {
56    pub fn get_msg(&mut self) -> Option<String> {
57        if self.consumed {
58            None
59        } else {
60            self.consumed = true;
61            Some(Self::construct_msg(self.level, &self.msg))
62        }
63    }
64
65    /// Convenience function of construct message.
66    // Since we have method in StatsView that raise warning message directly to
67    // CommandPalette instead of going through the RwLock process, we need to have
68    // such function to align the message format
69    pub fn construct_msg(level: Level, msg: &str) -> String {
70        format!("{}: {}", level.as_str(), msg)
71    }
72
73    fn set_msg(&mut self, msg: String, level: Level) {
74        self.msg = msg;
75        self.level = level;
76        self.consumed = false;
77    }
78
79    fn new() -> Self {
80        Self {
81            level: Level::Trace,
82            msg: String::new(),
83            consumed: true,
84        }
85    }
86}
87
88/// LAST_LOG_TO_DISPLAY here is used to pass msg to CommandPalette.
89// This is necessary because:
90// a. we cannot reference view inside log drain for:
91//     1. Once log constructed, we are no longer able to access the drain.
92//     2. In order to construct a view, we need a constructed log.
93// b. We are also not able to pass the view struct as a key value pair since
94//    slog's key val pair is a trait that does not implement `Any`, we are not
95//    able to downcast it.
96// c. Only reference the CommandPalette inside the log is not acceptable since
97//    there no implementation of IntoBoxedView<RefCell<View>>
98pub static LAST_LOG_TO_DISPLAY: Lazy<Arc<Mutex<CPMsgRecord>>> =
99    Lazy::new(|| Arc::new(Mutex::new(CPMsgRecord::new())));
100
101pub fn get_last_log_to_display() -> Option<String> {
102    LAST_LOG_TO_DISPLAY
103        .lock()
104        .expect("Fail to acquire lock for LAST_LOG_TO_DISPLAY")
105        .get_msg()
106}
107
108pub struct CompoundDecorator<W: io::Write, T: io::Write> {
109    file: Arc<Mutex<W>>,
110    term: Arc<Mutex<T>>,
111}
112
113impl<W, T> CompoundDecorator<W, T>
114where
115    W: io::Write,
116    T: io::Write,
117{
118    pub fn new(file_io: W, term_io: T) -> Self {
119        Self {
120            file: Arc::new(Mutex::new(file_io)),
121            term: Arc::new(Mutex::new(term_io)),
122        }
123    }
124}
125
126impl<W, T> slog_term::Decorator for CompoundDecorator<W, T>
127where
128    W: io::Write,
129    T: io::Write,
130{
131    fn with_record<F>(
132        &self,
133        _record: &slog::Record,
134        _logger_values: &slog::OwnedKVList,
135        f: F,
136    ) -> io::Result<()>
137    where
138        F: FnOnce(&mut dyn slog_term::RecordDecorator) -> io::Result<()>,
139    {
140        f(&mut CompoundRecordDecorator(
141            &*self.file,
142            &*self.term,
143            *LOG_TARGET
144                .read()
145                .expect("Failed to acquire write lock on the LOG_TARGET"),
146        ))
147    }
148}
149
150pub struct CompoundRecordDecorator<'a, W: 'a, T: 'a>(&'a Mutex<W>, &'a Mutex<T>, TargetLog)
151where
152    W: io::Write,
153    T: io::Write;
154
155impl<'a, W, T> io::Write for CompoundRecordDecorator<'a, W, T>
156where
157    W: io::Write,
158    T: io::Write,
159{
160    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
161        match self.2 {
162            TargetLog::All => {
163                let term_res = self.1.lock().unwrap().write(buf);
164                let file_res = self.0.lock().unwrap().write(buf);
165                term_res?;
166                file_res
167            }
168            TargetLog::File => self.0.lock().unwrap().write(buf),
169            TargetLog::Term => self.1.lock().unwrap().write(buf),
170        }
171    }
172
173    fn flush(&mut self) -> io::Result<()> {
174        let term_res = self.1.lock().unwrap().flush();
175        let file_res = self.0.lock().unwrap().flush();
176        term_res?;
177        file_res
178    }
179}
180
181impl<'a, W, T> Drop for CompoundRecordDecorator<'a, W, T>
182where
183    W: io::Write,
184    T: io::Write,
185{
186    fn drop(&mut self) {
187        let _ = self.1.lock().unwrap().flush();
188        let _ = self.0.lock().unwrap().flush();
189    }
190}
191
192impl<'a, W, T> slog_term::RecordDecorator for CompoundRecordDecorator<'a, W, T>
193where
194    W: io::Write,
195    T: io::Write,
196{
197    fn reset(&mut self) -> io::Result<()> {
198        Ok(())
199    }
200}
201
202pub struct CommandPaletteDrain<D> {
203    drain: D,
204}
205
206impl<D> CommandPaletteDrain<D> {
207    pub fn new(drain: D) -> Self {
208        Self { drain }
209    }
210}
211
212impl<D> Drain for CommandPaletteDrain<D>
213where
214    D: Drain,
215{
216    type Ok = Option<D::Ok>;
217    type Err = Option<D::Err>;
218
219    fn log(
220        &self,
221        record: &slog::Record,
222        values: &slog::OwnedKVList,
223    ) -> std::result::Result<Self::Ok, Self::Err> {
224        // We will use tag V as indicator of whether or not log to CommandPalette.
225        if record.tag() == "V" {
226            LAST_LOG_TO_DISPLAY
227                .lock()
228                .expect("Fail to acquire write lock for LAST_LOG_TO_DISPLAY")
229                .set_msg(format!("{}", record.msg()), record.level());
230        }
231        self.drain.log(record, values).map(Some).map_err(Some)
232    }
233}
234
235pub fn get_logger() -> slog::Logger {
236    let plain = slog_term::PlainSyncDecorator::new(std::io::stderr());
237    slog::Logger::root(slog_term::FullFormat::new(plain).build().fuse(), slog::o!())
238}