Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add json log format as an option #500

Merged
merged 11 commits into from
Jun 16, 2020
2 changes: 1 addition & 1 deletion linkerd/app/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ tracing-log = "0.1"
version = "0.2.1"
# we don't need `chrono` time formatting
default-features = false
features = ["env-filter", "fmt", "smallvec", "tracing-log", "ansi"]
features = ["env-filter", "fmt", "smallvec", "tracing-log", "ansi", "json"]

[target.'cfg(target_os = "linux")'.dependencies]
libc = "0.2"
Expand Down
106 changes: 73 additions & 33 deletions linkerd/app/core/src/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,28 @@ use tracing_subscriber::{
reload, EnvFilter, FmtSubscriber,
};

const ENV_LOG: &str = "LINKERD2_PROXY_LOG";
const ENV_LOG_LEVEL: &str = "LINKERD2_PROXY_LOG";
const ENV_LOG_FORMAT: &str = "LINKERD2_PROXY_LOG_FORMAT";
hawkw marked this conversation as resolved.
Show resolved Hide resolved

type Subscriber = Formatter<format::DefaultFields, format::Format<format::Full, Uptime>>;
const DEFAULT_LOG_LEVEL: &str = "warn,linkerd2_proxy=info";
const DEFAULT_LOG_FORMAT: &str = "PLAIN";

type JsonFormatter = Formatter<format::JsonFields, format::Format<format::Json, Uptime>>;
type PlainFormatter = Formatter<format::DefaultFields, format::Format<format::Full, Uptime>>;

#[derive(Clone)]
pub struct LevelHandle {
inner: reload::Handle<EnvFilter, Subscriber>,
pub enum LevelHandle {
Json(reload::Handle<EnvFilter, JsonFormatter>),
Plain(reload::Handle<EnvFilter, PlainFormatter>),
}

/// Initialize tracing and logging with the value of the `ENV_LOG`
/// environment variable as the verbosity-level filter.
pub fn init() -> Result<LevelHandle, Error> {
let env = env::var(ENV_LOG).unwrap_or_default();
let (dispatch, handle) = with_filter(env);
let log_level = env::var(ENV_LOG_LEVEL).unwrap_or(DEFAULT_LOG_LEVEL.to_string());
let log_format = env::var(ENV_LOG_FORMAT).unwrap_or(DEFAULT_LOG_FORMAT.to_string());

let (dispatch, handle) = with_filter_and_format(log_level, log_format);

// Set up log compatibility.
init_log_compat()?;
Expand All @@ -33,25 +41,36 @@ pub fn init_log_compat() -> Result<(), Error> {
tracing_log::LogTracer::init().map_err(Error::from)
}

pub fn with_filter(filter: impl AsRef<str>) -> (Dispatch, LevelHandle) {
pub fn with_filter_and_format(
filter: impl AsRef<str>,
format: impl AsRef<str>,
) -> (Dispatch, LevelHandle) {
let filter = filter.as_ref();

// Set up the subscriber
let start_time = clock::now();

let builder = FmtSubscriber::builder()
.with_timer(Uptime { start_time })
.with_env_filter(filter)
.with_filter_reloading()
.with_ansi(cfg!(test));
let handle = LevelHandle {
inner: builder.reload_handle(),
};
let dispatch = Dispatch::new(builder.finish());

(dispatch, handle)
.with_env_filter(filter);

match format.as_ref().to_uppercase().as_ref() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also do this using the str::eq_ignore_ascii_case method in an if-statement.

"JSON" => {
let builder = builder.json().with_filter_reloading();
let handle = LevelHandle::Json(builder.reload_handle());
let dispatch = Dispatch::new(builder.finish());
(dispatch, handle)
}
"PLAIN" | _ => {
let builder = builder.with_ansi(cfg!(test)).with_filter_reloading();
let handle = LevelHandle::Plain(builder.reload_handle());
let dispatch = Dispatch::new(builder.finish());
(dispatch, handle)
}
}
}

struct Uptime {
pub struct Uptime {
start_time: Instant,
}

Expand All @@ -68,37 +87,58 @@ impl LevelHandle {
/// This will do nothing, but is required for admin endpoint tests which
/// do not exercise the `proxy-log-level` endpoint.
pub fn dangling() -> Self {
let (_, handle) = with_filter("");
let (_, handle) = with_filter_and_format("", "");
handle
}

pub fn set_level(&self, level: impl AsRef<str>) -> Result<(), Error> {
let level = level.as_ref();
let filter = level.parse::<EnvFilter>()?;
self.inner.reload(filter)?;
match self {
Self::Json(level) => level.reload(filter)?,
Self::Plain(level) => level.reload(filter)?,
}
tracing::info!(%level, "set new log level");
Ok(())
}

pub fn current(&self) -> Result<String, Error> {
self.inner
.with_current(|f| format!("{}", f))
.map_err(Into::into)
match self {
Self::Json(handle) => handle
.with_current(|f| format!("{}", f))
.map_err(Into::into),
Self::Plain(handle) => handle
.with_current(|f| format!("{}", f))
.map_err(Into::into),
}
}
}

impl fmt::Debug for LevelHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner
.with_current(|c| {
f.debug_struct("LevelHandle")
.field("current", &format_args!("{}", c))
.finish()
})
.unwrap_or_else(|e| {
f.debug_struct("LevelHandle")
.field("current", &format_args!("{}", e))
.finish()
})
match self {
Self::Json(handle) => handle
.with_current(|c| {
f.debug_struct("LevelHandle")
.field("current", &format_args!("{}", c))
.finish()
})
.unwrap_or_else(|e| {
f.debug_struct("LevelHandle")
.field("current", &format_args!("{}", e))
.finish()
}),
Self::Plain(handle) => handle
.with_current(|c| {
f.debug_struct("LevelHandle")
.field("current", &format_args!("{}", c))
.finish()
})
.unwrap_or_else(|e| {
f.debug_struct("LevelHandle")
.field("current", &format_args!("{}", e))
.finish()
}),
}
}
}
10 changes: 6 additions & 4 deletions linkerd/app/integration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,17 @@ const DEFAULT_LOG: &'static str = "error,\

pub fn trace_init() -> (Dispatch, app::core::trace::LevelHandle) {
use std::env;
let log = env::var("LINKERD2_PROXY_LOG")
let log_level = env::var("LINKERD2_PROXY_LOG")
.or_else(|_| env::var("RUST_LOG"))
.unwrap_or_else(|_| DEFAULT_LOG.to_owned());
env::set_var("RUST_LOG", &log);
env::set_var("LINKERD2_PROXY_LOG", &log);
env::set_var("RUST_LOG", &log_level);
env::set_var("LINKERD2_PROXY_LOG", &log_level);
let log_format = env::var("LINKERD2_PROXY_LOG_FORMAT").unwrap_or_else(|_| "PLAIN".to_string());
env::set_var("LINKERD2_PROXY_LOG_FORMAT", &log_format);
// This may fail, since the global log compat layer may have been
// initialized by another test.
let _ = app::core::trace::init_log_compat();
app::core::trace::with_filter(&log)
app::core::trace::with_filter_and_format(&log_level, &log_format)
}

/// Retry an assertion up to a specified number of times, waiting
Expand Down