glean_ffi/fd_logger.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
5use std::fs::File;
6use std::io::Write;
7use std::sync::RwLock;
8
9#[cfg(target_os = "windows")]
10use std::os::windows::io::FromRawHandle;
11
12#[cfg(target_os = "windows")]
13use std::ffi::c_void;
14
15#[cfg(not(target_os = "windows"))]
16use std::os::unix::io::FromRawFd;
17
18use serde::Serialize;
19
20/// An implementation of log::Log that writes log messages in JSON format to a
21/// file descriptor/handle. The logging level is ignored in this implementation:
22/// it is up to the receiver of these log messages (on the language binding
23/// side) to filter the log messages based on their level.
24/// The JSON payload of each message in an object with the following keys:
25/// - `level` (string): One of the logging levels defined here:
26/// https://docs.rs/log/0.4.11/log/enum.Level.html
27/// - `message` (string): The logging message.
28pub struct FdLogger {
29 pub file: RwLock<File>,
30}
31
32#[derive(Serialize)]
33struct FdLoggingRecord {
34 level: String,
35 message: String,
36}
37
38#[cfg(target_os = "windows")]
39unsafe fn get_file_from_fd(fd: u64) -> File {
40 File::from_raw_handle(fd as *mut c_void)
41}
42
43#[cfg(not(target_os = "windows"))]
44unsafe fn get_file_from_fd(fd: u64) -> File {
45 File::from_raw_fd(fd as i32)
46}
47
48impl FdLogger {
49 pub unsafe fn new(fd: u64) -> Self {
50 FdLogger {
51 file: RwLock::new(get_file_from_fd(fd)),
52 }
53 }
54}
55
56impl log::Log for FdLogger {
57 fn enabled(&self, _metadata: &log::Metadata) -> bool {
58 // This logger always emits logging messages of any level, and the
59 // language binding consuming these messages is responsible for
60 // filtering and routing them.
61 true
62 }
63
64 fn log(&self, record: &log::Record) {
65 // Normally, classes implementing the Log trait would filter based on
66 // the log level here. But in this case, we want to emit all log
67 // messages and let the logging system in the language binding filter
68 // and route them.
69 let payload = FdLoggingRecord {
70 level: record.level().to_string(),
71 message: record.args().to_string(),
72 };
73 let _ = writeln!(
74 self.file.write().unwrap(),
75 "{}",
76 serde_json::to_string(&payload).unwrap()
77 );
78 }
79
80 fn flush(&self) {
81 let _ = self.file.write().unwrap().flush();
82 }
83}