feat(gui-client): send Linux GUI logs to journald (#8236)

This configures the GUI client to log to journald in addition to files
as well. For better or worse, this logs all events such that structured
information is preserved, e.g. all additional fields next to the message
are also saved as fields in the journal. By default, when viewing the
logs via `journalctl`, those fields are not displayed. This makes the
default output of `journalctl` for the FIrezone GUI not as useful as it
could be. Fixing that is left to a later stage.

Related: #8173
This commit is contained in:
Thomas Eizinger
2025-02-24 15:28:56 +11:00
committed by GitHub
parent b9c0ba9c3a
commit a0f079f1cd
5 changed files with 49 additions and 9 deletions

12
rust/Cargo.lock generated
View File

@@ -2120,6 +2120,7 @@ dependencies = [
"tokio",
"tokio-stream",
"tracing",
"tracing-journald",
"tracing-log",
"tracing-subscriber",
"url",
@@ -7321,6 +7322,17 @@ dependencies = [
"valuable",
]
[[package]]
name = "tracing-journald"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0b4143302cf1022dac868d521e36e8b27691f72c84b3311750d5188ebba657"
dependencies = [
"libc",
"tracing-core",
"tracing-subscriber",
]
[[package]]
name = "tracing-log"
version = "0.2.0"

View File

@@ -140,6 +140,7 @@ tracing-log = "0.2.0"
tracing-macros = { git = "https://github.com/tokio-rs/tracing", branch = "v0.1.x" } # Contains `dbg!` but for `tracing`.
tracing-opentelemetry = "0.27.0"
tracing-stackdriver = "0.11.0"
tracing-journald = "0.3.1"
tracing-subscriber = { version = "0.3.19", features = ["parking_lot"] }
trackable = "1.3.0"
url = "2.5.2"

View File

@@ -42,6 +42,7 @@ zip = { workspace = true, features = ["deflate", "time"] }
[target.'cfg(target_os = "linux")'.dependencies]
dirs = { workspace = true }
tracing-journald = { workspace = true }
[target.'cfg(target_os = "windows")'.dependencies]
winreg = { workspace = true }

View File

@@ -53,34 +53,53 @@ pub fn setup(directives: &str) -> Result<Handles> {
}
let log_path = known_dirs::logs().context("Can't compute app log dir")?;
std::fs::create_dir_all(&log_path).map_err(Error::CreateDirAll)?;
// Logfilter for stdout cannot be reloaded. This is okay because we are using it only for local dev and debugging anyway.
// Having multiple reload handles makes their type-signature quite complex so we don't bother with that.
let (stdout_filter, stdout_reloader) = firezone_logging::try_filter(directives)?;
let stdout = tracing_subscriber::fmt::layer()
let stdout_layer = tracing_subscriber::fmt::layer()
.with_ansi(firezone_logging::stdout_supports_ansi())
.event_format(firezone_logging::Format::new())
.with_filter(stdout_filter);
.event_format(firezone_logging::Format::new());
let (system_filter, system_reloader) = firezone_logging::try_filter(directives)?;
let system_layer = system_layer().context("Failed to init system logger")?;
#[cfg(target_os = "linux")]
let syslog_identifier = Some(system_layer.syslog_identifier().to_owned());
#[cfg(not(target_os = "linux"))]
let syslog_identifier = Option::<String>::None;
std::fs::create_dir_all(&log_path).map_err(Error::CreateDirAll)?;
let (file_layer, logger) = firezone_logging::file::layer(&log_path, "gui-client");
let (file_filter, file_reloader) = firezone_logging::try_filter(directives)?;
let subscriber = Registry::default()
.with(file_layer.with_filter(file_filter))
.with(stdout)
.with(stdout_layer.with_filter(stdout_filter))
.with(system_layer.with_filter(system_filter))
.with(firezone_logging::sentry_layer());
firezone_logging::init(subscriber)?;
tracing::debug!(log_path = %log_path.display(), "Log path");
tracing::debug!(log_path = %log_path.display(), syslog_identifier = syslog_identifier.map(tracing::field::display));
Ok(Handles {
logger,
reloader: stdout_reloader.merge(file_reloader),
reloader: stdout_reloader.merge(file_reloader).merge(system_reloader),
})
}
#[cfg(target_os = "linux")]
fn system_layer() -> Result<tracing_journald::Layer> {
let layer = tracing_journald::layer()?;
Ok(layer)
}
#[cfg(not(target_os = "linux"))]
#[expect(clippy::unnecessary_wraps, reason = "Linux signature needs `Result`")]
fn system_layer() -> Result<tracing_subscriber::layer::Identity> {
Ok(tracing_subscriber::layer::Identity::new())
}
#[derive(Clone, Default, Serialize)]
pub struct FileCount {
bytes: u64,

View File

@@ -158,7 +158,14 @@ captured by systemd and sent to journald. To view the logs of the IPC service,
use:
```bash
journalctl -efu firezone-client-ipc.service
journalctl --pager-end --follow --unit firezone-client-ipc.service
```
The GUI client logs to journald directly as well with the syslog identifier
`firezone-client-gui`. To view the logs via `journalctl`, use:
```bash
journalctl --pager-end --follow --identifier firezone-client-gui
```
## Uninstalling