For weeks I’ve been trying to figure out how to get more organized logging out of the Rust code, and I have never worked with Rust before. The default behavior of lemmy_server is to put all Rust logging into the Linux syslog and you typically filter by the Linux user account ‘lemmy’.
Where to write Files ==========
I did “Lemmy from Scratch” and run without Docker. So my lemmy_server lemmy I throw logs into /home/lemmy/logs folder. I don’t think Docker even creates a /home/lemmy directory, not sure. I’m inclined to make this an environment variable to set the output folder - suggestions welcome.
Rust Library =========
crate add tracing-appender
Code surgery to /src/lib.rs ==========
use tracing::Level;
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::{
fmt::{self, writer::MakeWriterExt}
};
pub fn init_logging(opentelemetry_url: &Option<Url>) -> Result<Vec<Option<WorkerGuard>>, LemmyError> {
LogTracer::init()?;
let log_dir = Some("/home/lemmy/logs");
// Code may have multiple log files (target tags) in the future, so return an array.
let mut guards = Vec::new();
let file_filter = Targets::new()
.with_target("federation", Level::TRACE);
let file_log = log_dir
.map(|p| tracing_appender::non_blocking(tracing_appender::rolling::daily(p, "federation")))
.map(|(appender_type, g)| {
let guard = Some(g);
guards.push(guard);
fmt::Layer::new()
.with_writer(appender_type.with_max_level(Level::TRACE))
.with_ansi(false)
.with_filter(file_filter)
});
let log_description = std::env::var("RUST_LOG").unwrap_or_else(|_| "info".into());
let targets = log_description
.trim()
.trim_matches('"')
.parse::<Targets>()?;
let format_layer = {
#[cfg(feature = "json-log")]
let layer = tracing_subscriber::fmt::layer().json();
#[cfg(not(feature = "json-log"))]
let layer = tracing_subscriber::fmt::layer();
layer.with_filter(targets.clone())
};
let subscriber = Registry::default()
.with(format_layer)
.with(ErrorLayer::default())
.with(file_log)
;
if let Some(_url) = opentelemetry_url {
#[cfg(feature = "console")]
telemetry::init_tracing(_url.as_ref(), subscriber, targets)?;
#[cfg(not(feature = "console"))]
tracing::error!("Feature `console` must be enabled for opentelemetry tracing");
} else {
set_global_default(subscriber)?;
}
tracing::warn!("zebracat logging checkpoint A");
tracing::info!(target:"federation", "zebracat file logging startup");
Ok(guards)
}
Code surgery to /src/main.rs ==========
let _guards = init_logging(&SETTINGS.opentelemetry_url)?;
How to use ================
Anywhere you want in Lemmy code, you can now log to your file:
tracing::info!(target:"federation", "this will go to the file");
2 log files, “federation” and “misc” targets
pub fn init_logging(opentelemetry_url: &Option<Url>) -> Result<Vec<Option<WorkerGuard>>, LemmyError> { LogTracer::init()?; let log_dir = Some("/home/lemmy/logs"); // Code may have multiple log files (target tags) in the future, so return an array. let mut guards = Vec::new(); let file_filter = Targets::new() .with_target("federation", Level::TRACE); let file_log = log_dir .map(|p| tracing_appender::non_blocking(tracing_appender::rolling::daily(p, "federation"))) .map(|(appender_type, g)| { let guard = Some(g); guards.push(guard); fmt::Layer::new() .with_writer(appender_type.with_max_level(Level::TRACE)) .with_ansi(false) .with_filter(file_filter) }); let file_filter1 = Targets::new() .with_target("misc", Level::TRACE); let file_log1 = log_dir .map(|p| tracing_appender::non_blocking(tracing_appender::rolling::daily(p, "misc"))) .map(|(appender_type, g)| { let guard = Some(g); guards.push(guard); fmt::Layer::new() .with_writer(appender_type.with_max_level(Level::TRACE)) .with_ansi(false) .with_filter(file_filter1) }); let log_description = std::env::var("RUST_LOG").unwrap_or_else(|_| "info".into()); let targets = log_description .trim() .trim_matches('"') .parse::<Targets>()?; let format_layer = { #[cfg(feature = "json-log")] let layer = tracing_subscriber::fmt::layer().json(); #[cfg(not(feature = "json-log"))] let layer = tracing_subscriber::fmt::layer(); layer.with_filter(targets.clone()) }; let subscriber = Registry::default() .with(format_layer) .with(ErrorLayer::default()) .with(file_log) .with(file_log1) ; if let Some(_url) = opentelemetry_url { #[cfg(feature = "console")] telemetry::init_tracing(_url.as_ref(), subscriber, targets)?; #[cfg(not(feature = "console"))] tracing::error!("Feature `console` must be enabled for opentelemetry tracing"); } else { set_global_default(subscriber)?; } tracing::warn!("zebracat logging checkpoint A"); tracing::info!(target:"federation", "zebracat file logging startup"); tracing::info!(target:"misc", "zebracat file logging startup misc"); Ok(guards) }