feat(gateway): add option for outputting logs as JSON (#10620)

To enable customers to ingest flow logs (#8353) into various SIEMS,
outputting structured logs is crucial.
This commit is contained in:
Thomas Eizinger
2025-10-22 14:09:33 +11:00
committed by GitHub
parent 08857d602b
commit 80331b4e93
5 changed files with 49 additions and 11 deletions

View File

@@ -22,7 +22,7 @@ use phoenix_channel::get_user_agent;
use phoenix_channel::PhoenixChannel;
use secrecy::{Secret, SecretString};
use std::{collections::BTreeSet, path::Path};
use std::{collections::BTreeSet, fmt, path::Path};
use std::{path::PathBuf, process::ExitCode};
use std::{sync::Arc, time::Duration};
use tracing_subscriber::layer;
@@ -91,8 +91,14 @@ fn has_necessary_permissions() -> bool {
}
async fn try_main(cli: Cli, telemetry: &mut Telemetry) -> Result<()> {
firezone_logging::setup_global_subscriber(layer::Identity::default())
.context("Failed to set up logging")?;
firezone_logging::setup_global_subscriber(
layer::Identity::default(),
match cli.log_format {
LogFormat::Json => true,
LogFormat::Human => false,
},
)
.context("Failed to set up logging")?;
tracing::info!(
arch = std::env::consts::ARCH,
@@ -306,6 +312,9 @@ struct Cli {
#[arg(short = 'i', long, env = "FIREZONE_ID")]
firezone_id: Option<String>,
#[arg(long, env = "FIREZONE_LOG_FORMAT", default_value_t = LogFormat::Human)]
log_format: LogFormat,
/// Where to export metrics to.
///
/// This configuration option is private API and has no stability guarantees.
@@ -336,6 +345,21 @@ struct Cli {
no_inc_buf: bool,
}
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
enum LogFormat {
Json,
Human,
}
impl fmt::Display for LogFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
LogFormat::Json => write!(f, "json"),
LogFormat::Human => write!(f, "human"),
}
}
}
#[derive(Debug, Clone, Copy, clap::ValueEnum)]
enum MetricsExporter {
Stdout,

View File

@@ -215,7 +215,7 @@ fn try_main() -> Result<()> {
.as_deref()
.map(|dir| firezone_logging::file::layer(dir, "firezone-headless-client"))
.unzip();
firezone_logging::setup_global_subscriber(layer).context("Failed to set up logging")?;
firezone_logging::setup_global_subscriber(layer, false).context("Failed to set up logging")?;
// Deactivate DNS control before starting telemetry or connecting to the portal,
// in case a previous run of Firezone left DNS control on and messed anything up.

View File

@@ -19,7 +19,7 @@ time = { workspace = true, features = ["formatting"] }
tracing = { workspace = true }
tracing-appender = { workspace = true }
tracing-log = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter"] }
tracing-subscriber = { workspace = true, features = ["env-filter", "json"] }
[dev-dependencies]
tempfile = { workspace = true }

View File

@@ -33,7 +33,10 @@ pub use err_with_sources::{ErrorWithSources, err_with_src};
pub use format::Format;
/// Registers a global subscriber with stdout logging and `additional_layer`
pub fn setup_global_subscriber<L>(additional_layer: L) -> Result<FilterReloadHandle>
pub fn setup_global_subscriber<L>(
additional_layer: L,
stdout_json: bool,
) -> Result<FilterReloadHandle>
where
L: Layer<Registry> + Send + Sync,
{
@@ -51,12 +54,19 @@ where
let subscriber = Registry::default()
.with(additional_layer.with_filter(filter1))
.with(sentry_layer())
.with(
fmt::layer()
.with(match stdout_json {
true => fmt::layer()
.json()
.flatten_event(true)
.with_ansi(stdout_supports_ansi())
.with_filter(filter2)
.boxed(),
false => fmt::layer()
.with_ansi(stdout_supports_ansi())
.event_format(Format::new())
.with_filter(filter2),
);
.with_filter(filter2)
.boxed(),
});
init(subscriber)?;
Ok(reload_handle1.merge(reload_handle2))

View File

@@ -22,7 +22,11 @@ export default function Gateway() {
return (
<Entries downloadLinks={downloadLinks} title="Gateway">
<Unreleased></Unreleased>
<Unreleased>
<ChangeItem pull="10620">
Adds a `--log-format` CLI option to output logs as JSON.
</ChangeItem>
</Unreleased>
<Entry version="1.4.17" date={new Date("2025-10-16")}>
<ChangeItem pull="10367">
Fixes a rare CPU-spike issue in case a Client connected with many