constellation/
logging.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! The constellation uses logging to perform crash reporting.
6//! The constellation receives all `warn!`, `error!` and `panic!` messages,
7//! and generates a crash report when it receives a panic.
8
9use std::borrow::ToOwned;
10use std::sync::Arc;
11use std::thread;
12
13use backtrace::Backtrace;
14use base::id::{ScriptEventLoopId, TEST_PIPELINE_ID, TEST_WEBVIEW_ID};
15use constellation_traits::{
16    EmbedderToConstellationMessage, LogEntry, ScriptToConstellationMessage,
17    ScriptToConstellationSender,
18};
19use crossbeam_channel::Sender;
20use log::{Level, LevelFilter, Log, Metadata, Record};
21use parking_lot::ReentrantMutex;
22
23/// A logger directed at the constellation from content processes
24/// #[derive(Clone)]
25pub struct FromScriptLogger {
26    /// A channel to the constellation
27    pub script_to_constellation_sender: Arc<ReentrantMutex<ScriptToConstellationSender>>,
28}
29
30/// A logger directed at the constellation from content processes
31impl FromScriptLogger {
32    /// Create a new constellation logger.
33    pub fn new(script_to_constellation_chan: ScriptToConstellationSender) -> FromScriptLogger {
34        FromScriptLogger {
35            script_to_constellation_sender: Arc::new(ReentrantMutex::new(
36                script_to_constellation_chan,
37            )),
38        }
39    }
40
41    /// The maximum log level the constellation logger is interested in.
42    pub fn filter(&self) -> LevelFilter {
43        LevelFilter::Warn
44    }
45}
46
47impl Log for FromScriptLogger {
48    fn enabled(&self, metadata: &Metadata) -> bool {
49        metadata.level() <= Level::Warn
50    }
51
52    fn log(&self, record: &Record) {
53        if let Some(entry) = log_entry(record) {
54            let thread_name = thread::current().name().map(ToOwned::to_owned);
55            let _ = self.script_to_constellation_sender.lock().send(
56                // TODO: The WebViewId and PipelineId are unused for the `LogEntry` method, but it
57                // would probably be better to have an entirely new message variant or sender for
58                // messages that definitely do not need these fields to avoid having to use the
59                // TEST_* values here.
60                (
61                    TEST_WEBVIEW_ID,
62                    TEST_PIPELINE_ID,
63                    ScriptToConstellationMessage::LogEntry(
64                        ScriptEventLoopId::installed(),
65                        thread_name,
66                        entry,
67                    ),
68                ),
69            );
70        }
71    }
72
73    fn flush(&self) {}
74}
75
76/// A logger directed at the constellation from the compositor
77#[derive(Clone)]
78pub struct FromEmbedderLogger {
79    /// A channel to the constellation
80    pub constellation_chan: Arc<ReentrantMutex<Sender<EmbedderToConstellationMessage>>>,
81}
82
83impl FromEmbedderLogger {
84    /// Create a new constellation logger.
85    pub fn new(constellation_chan: Sender<EmbedderToConstellationMessage>) -> FromEmbedderLogger {
86        FromEmbedderLogger {
87            constellation_chan: Arc::new(ReentrantMutex::new(constellation_chan)),
88        }
89    }
90
91    /// The maximum log level the constellation logger is interested in.
92    pub fn filter(&self) -> LevelFilter {
93        LevelFilter::Warn
94    }
95}
96
97impl Log for FromEmbedderLogger {
98    fn enabled(&self, metadata: &Metadata) -> bool {
99        metadata.level() <= Level::Warn
100    }
101
102    fn log(&self, record: &Record) {
103        if let Some(entry) = log_entry(record) {
104            let event_loop_id = ScriptEventLoopId::installed();
105            let thread_name = thread::current().name().map(ToOwned::to_owned);
106            let msg = EmbedderToConstellationMessage::LogEntry(event_loop_id, thread_name, entry);
107            let chan = self.constellation_chan.lock();
108            let _ = chan.send(msg);
109        }
110    }
111
112    fn flush(&self) {}
113}
114
115/// Rust uses `Record` for storing logging, but servo converts that to
116/// a `LogEntry`. We do this so that we can record panics as well as log
117/// messages, and because `Record` does not implement serde (de)serialization,
118/// so cannot be used over an IPC channel.
119fn log_entry(record: &Record) -> Option<LogEntry> {
120    match record.level() {
121        Level::Error if thread::panicking() => Some(LogEntry::Panic(
122            format!("{}", record.args()),
123            format!("{:?}", Backtrace::new()),
124        )),
125        Level::Error => Some(LogEntry::Error(format!("{}", record.args()))),
126        Level::Warn => Some(LogEntry::Warn(format!("{}", record.args()))),
127        _ => None,
128    }
129}