Skip to content

Commit 647d02e

Browse files
authored
Output logs in JSON with QW_LOG_FORMAT=json (#5672)
1 parent 7bc495a commit 647d02e

File tree

2 files changed

+91
-15
lines changed

2 files changed

+91
-15
lines changed

quickwit/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ tracing = "0.1"
258258
tracing-opentelemetry = "0.28"
259259
tracing-subscriber = { version = "0.3", features = [
260260
"env-filter",
261+
"json",
261262
"std",
262263
"time",
263264
] }

quickwit/quickwit-cli/src/logger.rs

Lines changed: 90 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,28 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
use std::env;
1615
use std::sync::Arc;
16+
use std::{env, fmt};
1717

1818
use anyhow::Context;
1919
use opentelemetry::trace::TracerProvider;
2020
use opentelemetry::{global, KeyValue};
2121
use opentelemetry_sdk::propagation::TraceContextPropagator;
2222
use opentelemetry_sdk::trace::BatchConfigBuilder;
2323
use opentelemetry_sdk::{trace, Resource};
24-
use quickwit_common::get_bool_from_env;
24+
use quickwit_common::{get_bool_from_env, get_from_env_opt};
2525
use quickwit_serve::{BuildInfo, EnvFilterReloadFn};
26-
use tracing::Level;
26+
use time::format_description::BorrowedFormatItem;
27+
use tracing::{Event, Level, Subscriber};
28+
use tracing_subscriber::field::RecordFields;
29+
use tracing_subscriber::fmt::format::{
30+
DefaultFields, Format, FormatEvent, FormatFields, Full, Json, JsonFields, Writer,
31+
};
2732
use tracing_subscriber::fmt::time::UtcTime;
33+
use tracing_subscriber::fmt::FmtContext;
2834
use tracing_subscriber::layer::SubscriberExt;
2935
use tracing_subscriber::prelude::*;
36+
use tracing_subscriber::registry::LookupSpan;
3037
use tracing_subscriber::EnvFilter;
3138

3239
use crate::QW_ENABLE_OPENTELEMETRY_OTLP_EXPORTER_ENV_KEY;
@@ -52,18 +59,6 @@ pub fn setup_logging_and_tracing(
5259
global::set_text_map_propagator(TraceContextPropagator::new());
5360
let (reloadable_env_filter, reload_handle) = tracing_subscriber::reload::Layer::new(env_filter);
5461
let registry = tracing_subscriber::registry().with(reloadable_env_filter);
55-
let event_format = tracing_subscriber::fmt::format()
56-
.with_target(true)
57-
.with_timer(
58-
// We do not rely on the Rfc3339 implementation, because it has a nanosecond precision.
59-
// See discussion here: https://github.com/time-rs/time/discussions/418
60-
UtcTime::new(
61-
time::format_description::parse(
62-
"[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]Z",
63-
)
64-
.expect("Time format invalid."),
65-
),
66-
);
6762
// Note on disabling ANSI characters: setting the ansi boolean on event format is insufficient.
6863
// It is thus set on layers, see https://github.com/tokio-rs/tracing/issues/1817
6964
if get_bool_from_env(QW_ENABLE_OPENTELEMETRY_OTLP_EXPORTER_ENV_KEY, false) {
@@ -91,20 +86,28 @@ pub fn setup_logging_and_tracing(
9186
let tracer = provider.tracer("quickwit");
9287
let telemetry_layer = tracing_opentelemetry::layer().with_tracer(tracer);
9388

89+
let event_format = EventFormat::get_from_env();
90+
let fmt_fields = event_format.format_fields();
91+
9492
registry
9593
.with(telemetry_layer)
9694
.with(
9795
tracing_subscriber::fmt::layer()
9896
.event_format(event_format)
97+
.fmt_fields(fmt_fields)
9998
.with_ansi(ansi_colors),
10099
)
101100
.try_init()
102101
.context("failed to register tracing subscriber")?;
103102
} else {
103+
let event_format = EventFormat::get_from_env();
104+
let fmt_fields = event_format.format_fields();
105+
104106
registry
105107
.with(
106108
tracing_subscriber::fmt::layer()
107109
.event_format(event_format)
110+
.fmt_fields(fmt_fields)
108111
.with_ansi(ansi_colors),
109112
)
110113
.try_init()
@@ -116,3 +119,75 @@ pub fn setup_logging_and_tracing(
116119
Ok(())
117120
}))
118121
}
122+
123+
enum EventFormat<'a> {
124+
Full(Format<Full, UtcTime<Vec<BorrowedFormatItem<'a>>>>),
125+
Json(Format<Json>),
126+
}
127+
128+
impl EventFormat<'_> {
129+
/// Gets the log format from the environment variable `QW_LOG_FORMAT`. Returns a JSON
130+
/// formatter if the variable is set to `json`, otherwise returns a full formatter.
131+
fn get_from_env() -> Self {
132+
if get_from_env_opt::<String>("QW_LOG_FORMAT")
133+
.map(|log_format| log_format.eq_ignore_ascii_case("json"))
134+
.unwrap_or(false)
135+
{
136+
let json_format = tracing_subscriber::fmt::format().json();
137+
EventFormat::Json(json_format)
138+
} else {
139+
// We do not rely on the RFC3339 implementation, because it has a nanosecond precision.
140+
// See discussion here: https://github.com/time-rs/time/discussions/418
141+
let timer_format = time::format_description::parse(
142+
"[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond digits:3]Z",
143+
)
144+
.expect("time format description should be valid");
145+
let timer = UtcTime::new(timer_format);
146+
147+
let full_format = tracing_subscriber::fmt::format()
148+
.with_target(true)
149+
.with_timer(timer);
150+
151+
EventFormat::Full(full_format)
152+
}
153+
}
154+
155+
fn format_fields(&self) -> FieldFormat {
156+
match self {
157+
EventFormat::Full(_) => FieldFormat::Default(DefaultFields::new()),
158+
EventFormat::Json(_) => FieldFormat::Json(JsonFields::new()),
159+
}
160+
}
161+
}
162+
163+
impl<S, N> FormatEvent<S, N> for EventFormat<'_>
164+
where
165+
S: Subscriber + for<'a> LookupSpan<'a>,
166+
N: for<'a> FormatFields<'a> + 'static,
167+
{
168+
fn format_event(
169+
&self,
170+
ctx: &FmtContext<'_, S, N>,
171+
writer: Writer<'_>,
172+
event: &Event<'_>,
173+
) -> fmt::Result {
174+
match self {
175+
EventFormat::Full(format) => format.format_event(ctx, writer, event),
176+
EventFormat::Json(format) => format.format_event(ctx, writer, event),
177+
}
178+
}
179+
}
180+
181+
enum FieldFormat {
182+
Default(DefaultFields),
183+
Json(JsonFields),
184+
}
185+
186+
impl<'a> FormatFields<'a> for FieldFormat {
187+
fn format_fields<R: RecordFields>(&self, writer: Writer<'_>, fields: R) -> fmt::Result {
188+
match self {
189+
FieldFormat::Default(default_fields) => default_fields.format_fields(writer, fields),
190+
FieldFormat::Json(json_fields) => json_fields.format_fields(writer, fields),
191+
}
192+
}
193+
}

0 commit comments

Comments
 (0)