Skip to content

Commit 1d62fd5

Browse files
authored
access-log: Add JSON support (#1350)
This change adds the option to configure the access logging layer to output JSON rather than the Apache Common access log format. This is configured by the `LINKERD2_PROXY_ACCESS_LOG` environment variable. When set to `json`, JSON-formatted logs are emitted; and it must be set to `apache` for apache-formatted logging.
1 parent d4dfe91 commit 1d62fd5

File tree

2 files changed

+45
-16
lines changed

2 files changed

+45
-16
lines changed

linkerd/tracing/src/access_log.rs

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ use tracing_subscriber::{
1010

1111
pub const TRACE_TARGET: &str = "_access_log";
1212

13-
pub(super) type AccessLogLayer<S> = Filtered<Writer, FilterFn, S>;
13+
pub(super) type AccessLogLayer<S> =
14+
Filtered<Box<dyn Layer<S> + Send + Sync + 'static>, FilterFn, S>;
1415

16+
#[derive(Default)]
1517
pub(super) struct Writer<F = ApacheCommon> {
1618
formatter: F,
1719
}
@@ -21,16 +23,27 @@ pub(super) struct ApacheCommon {
2123
_p: (),
2224
}
2325

26+
#[derive(Copy, Clone, Debug)]
27+
pub(super) enum Format {
28+
Apache,
29+
Json,
30+
}
31+
2432
struct ApacheCommonVisitor<'writer> {
2533
res: fmt::Result,
2634
writer: format::Writer<'writer>,
2735
}
2836

29-
pub(super) fn build<S>() -> (AccessLogLayer<S>, Directive)
37+
pub(super) fn build<S>(format: Format) -> (AccessLogLayer<S>, Directive)
3038
where
3139
S: Subscriber + for<'span> LookupSpan<'span>,
3240
{
33-
let writer = Writer::new().with_filter(
41+
let writer: Box<dyn Layer<S> + Send + Sync + 'static> = match format {
42+
Format::Apache => Box::new(Writer::<ApacheCommon>::default()),
43+
Format::Json => Box::new(Writer::<format::JsonFields>::default()),
44+
};
45+
46+
let writer = writer.with_filter(
3447
FilterFn::new(
3548
(|meta| meta.level() == &Level::INFO && meta.target().starts_with(TRACE_TARGET))
3649
as fn(&Metadata<'_>) -> bool,
@@ -49,14 +62,6 @@ where
4962

5063
// === impl Writer ===
5164

52-
impl Writer {
53-
pub fn new() -> Self {
54-
Self {
55-
formatter: Default::default(),
56-
}
57-
}
58-
}
59-
6065
impl<S, F> Layer<S> for Writer<F>
6166
where
6267
S: Subscriber + for<'span> LookupSpan<'span>,
@@ -154,3 +159,16 @@ impl field::Visit for ApacheCommonVisitor<'_> {
154159
}
155160
}
156161
}
162+
163+
// === impl Format ===
164+
165+
impl std::str::FromStr for Format {
166+
type Err = &'static str;
167+
fn from_str(s: &str) -> Result<Self, Self::Err> {
168+
match s {
169+
s if s.eq_ignore_ascii_case("json") => Ok(Self::Json),
170+
s if s.eq_ignore_ascii_case("apache") => Ok(Self::Apache),
171+
_ => Err("expected either 'apache' or 'json'"),
172+
}
173+
}
174+
}

linkerd/tracing/src/lib.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const DEFAULT_LOG_FORMAT: &str = "PLAIN";
2929
pub struct Settings {
3030
filter: Option<String>,
3131
format: Option<String>,
32-
access_log: bool,
32+
access_log: Option<access_log::Format>,
3333
is_test: bool,
3434
}
3535

@@ -81,7 +81,7 @@ impl Settings {
8181
Some(Self {
8282
filter,
8383
format: std::env::var(ENV_LOG_FORMAT).ok(),
84-
access_log: std::env::var(ENV_ACCESS_LOG).is_ok(),
84+
access_log: Self::access_log_format(),
8585
is_test: false,
8686
})
8787
}
@@ -90,7 +90,7 @@ impl Settings {
9090
Self {
9191
filter: Some(filter),
9292
format: Some(format),
93-
access_log: false,
93+
access_log: Self::access_log_format(),
9494
is_test: true,
9595
}
9696
}
@@ -102,6 +102,17 @@ impl Settings {
102102
.to_uppercase()
103103
}
104104

105+
fn access_log_format() -> Option<access_log::Format> {
106+
let env = std::env::var(ENV_ACCESS_LOG).ok()?;
107+
match env.parse() {
108+
Ok(format) => Some(format),
109+
Err(err) => {
110+
eprintln!("Invalid {}={:?}: {}", ENV_ACCESS_LOG, env, err);
111+
None
112+
}
113+
}
114+
}
115+
105116
fn mk_json<S>(&self) -> Box<dyn Layer<S> + Send + Sync + 'static>
106117
where
107118
S: Subscriber + for<'span> LookupSpan<'span>,
@@ -154,8 +165,8 @@ impl Settings {
154165
let mut filter = EnvFilter::new(log_level);
155166

156167
// If access logging is enabled, build the access log layer.
157-
let access_log = if self.access_log {
158-
let (access_log, directive) = access_log::build();
168+
let access_log = if let Some(format) = self.access_log {
169+
let (access_log, directive) = access_log::build(format);
159170
filter = filter.add_directive(directive);
160171
Some(access_log)
161172
} else {

0 commit comments

Comments
 (0)