blob: 0ead11739b3470f2a8363a1187bfbda817c9cb5f [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
chromium::import! {
"//base:logging_log_severity_bindgen" as log_severity;
}
use log::Level::{Debug, Error, Info, Trace, Warn};
use log::{LevelFilter, Metadata, Record};
use log_severity::logging::{LOGGING_ERROR, LOGGING_INFO, LOGGING_WARNING};
use std::ffi::CString;
use std::pin::Pin;
struct RustLogger;
impl log::Log for RustLogger {
fn enabled(&self, _metadata: &Metadata) -> bool {
// Always enabled, as it's controlled by command line flags managed by the C++
// implementation.
true
}
fn log(&self, record: &Record) {
// TODO([email protected]): Rather than using heap allocation to pass |msg|
// and |file|, we should return a pointer and size object to leverage the
// string_view object in C++. https://crbug.com/371112531
let file = CString::new(record.file().unwrap())
.expect("CString::new failed to create the log file name!");
let wrapped_args = RustFmtArguments(*record.args());
unsafe {
ffi::print_rust_log(
&wrapped_args,
file.as_ptr(),
record.line().unwrap() as i32,
match record.metadata().level() {
Error => LOGGING_ERROR,
Warn => LOGGING_WARNING,
Info => LOGGING_INFO,
// Note that Debug and Trace level logs are dropped at
// compile time at the macro call-site when DCHECK_IS_ON()
// is false. This is done through a Cargo feature.
Debug | Trace => LOGGING_INFO,
},
record.metadata().level() == Trace,
)
}
}
fn flush(&self) {}
}
static RUST_LOGGER: RustLogger = RustLogger;
/// Wrap a `std::fmt::Arguments` to pass to C++ code.
struct RustFmtArguments<'a>(std::fmt::Arguments<'a>);
impl<'a> RustFmtArguments<'a> {
/// Format `msg` to the C++-provided stream in `wrapper`.
fn format(&self, mut wrapper: Pin<&mut ffi::LogMessageRustWrapper>) {
// No error expected because our `Write` impl below is infallible.
std::fmt::write(&mut wrapper, self.0).unwrap();
}
}
// Glue impl to use std::fmt tools with `ffi::LogMessageRustWrapper`.
impl std::fmt::Write for Pin<&mut ffi::LogMessageRustWrapper> {
fn write_str(&mut self, s: &str) -> Result<(), std::fmt::Error> {
self.as_mut().write_to_stream(s);
Ok(())
}
}
#[cxx::bridge(namespace = "logging::internal")]
mod ffi {
extern "Rust" {
type RustFmtArguments<'a>;
fn format(&self, wrapper: Pin<&mut LogMessageRustWrapper>);
fn init_rust_log_crate();
}
unsafe extern "C++" {
include!("base/logging/rust_log_integration.h");
/// Wraps a C++ LogMessage object so we can write to its ostream.
type LogMessageRustWrapper;
/// Write a block of characters to the stream.
fn write_to_stream(self: Pin<&mut LogMessageRustWrapper>, s: &str);
/// Emit a log message to the C++-managed logger. `msg` is passed back
/// to `format_to_wrapped_message` to be stringified.
unsafe fn print_rust_log(
msg: &RustFmtArguments,
file: *const c_char,
line: i32,
severity: i32,
verbose: bool,
);
}
}
pub fn init_rust_log_crate() {
// An error may occur if set_logger has already been called, which can happen
// during unit tests. In that case, return from the method without executing the
// subsequent code.
if let Err(_) = log::set_logger(&RUST_LOGGER) {
return;
};
log::set_max_level(LevelFilter::Trace);
}