Skip to content

Commit fe1f27b

Browse files
committed
feat(logs): support json formatter
1 parent 46e1186 commit fe1f27b

File tree

9 files changed

+123
-12
lines changed

9 files changed

+123
-12
lines changed

Cargo.lock

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ regex = "1.11.1"
2020
mockall = "0.13.1"
2121
clap = "4.5.31"
2222
nix = "0.30.0"
23-
tracing-subscriber = "0.3.19"
23+
tracing-subscriber = { version = "0.3.19", features = [ "json" ] }
2424
tracing-test = "0.2.5"
2525
assert_cmd = "2.0.16"
2626
assert_matches = "1.5.0"

THIRD_PARTY_NOTICES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2338,6 +2338,12 @@ Distributed under the following license(s):
23382338

23392339
* MIT
23402340

2341+
## tracing-serde <https://crates.io/crates/tracing-serde>
2342+
2343+
Distributed under the following license(s):
2344+
2345+
* MIT
2346+
23412347
## tracing-subscriber <https://crates.io/crates/tracing-subscriber>
23422348

23432349
Distributed under the following license(s):

agent-control/src/agent_control/config.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,7 @@ agents: {}
508508
target: true,
509509
timestamp: TimestampFormat("%Y".to_string()),
510510
ansi_colors: false,
511+
formatter: Default::default(),
511512
},
512513
..Default::default()
513514
}

agent-control/src/instrumentation/config/logs/format.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,22 @@ impl Default for TimestampFormat {
1515
}
1616
}
1717

18+
/// Represents the supported logging formatters
19+
#[derive(Debug, Deserialize, PartialEq, Clone)]
20+
#[serde(rename_all = "snake_case")]
21+
pub enum Formatter {
22+
/// Human-readable single-line logs
23+
Default,
24+
/// Newline delimited JSON logs
25+
Json,
26+
}
27+
28+
impl Default for Formatter {
29+
fn default() -> Self {
30+
Self::Default
31+
}
32+
}
33+
1834
/// Defines the format to be used for logging, including target and timestamp.
1935
///
2036
/// # Fields:
@@ -29,4 +45,60 @@ pub struct LoggingFormat {
2945
pub(crate) timestamp: TimestampFormat,
3046
#[serde(default)]
3147
pub(crate) ansi_colors: bool,
48+
#[serde(default)]
49+
pub(crate) formatter: Formatter,
50+
}
51+
52+
#[cfg(test)]
53+
mod tests {
54+
use super::*;
55+
56+
use rstest::rstest;
57+
use serde_yaml;
58+
59+
#[rstest]
60+
#[case::with_defaults(
61+
r#"
62+
target: false
63+
"#,
64+
LoggingFormat {
65+
target: false,
66+
timestamp: TimestampFormat::default(),
67+
ansi_colors: false,
68+
formatter: Formatter::Default,
69+
}
70+
)]
71+
#[case::with_custom_values(
72+
r#"
73+
target: true
74+
timestamp: "custom_format"
75+
ansi_colors: true
76+
formatter: json
77+
"#,
78+
LoggingFormat {
79+
target: true,
80+
timestamp: TimestampFormat("custom_format".to_string()),
81+
ansi_colors: true,
82+
formatter: Formatter::Json,
83+
}
84+
)]
85+
#[case::with_partial_values(
86+
r#"
87+
target: true
88+
"#,
89+
LoggingFormat {
90+
target: true,
91+
timestamp: TimestampFormat::default(),
92+
ansi_colors: false,
93+
formatter: Formatter::Default,
94+
}
95+
)]
96+
fn test_logging_format_deserialization(
97+
#[case] yaml_data: &str,
98+
#[case] expected_logging_format: LoggingFormat,
99+
) {
100+
let logging_format: LoggingFormat = serde_yaml::from_str(yaml_data).unwrap();
101+
102+
assert_eq!(logging_format, expected_logging_format);
103+
}
32104
}

agent-control/src/instrumentation/tracing_layers/file.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::instrumentation::config::logs::config::{LoggingConfig, LoggingConfigError};
2+
use crate::instrumentation::config::logs::format::Formatter;
23
use crate::instrumentation::tracing::{LayerBox, TracingGuard};
34
use std::path::PathBuf;
45
use tracing_appender::non_blocking::WorkerGuard;
@@ -28,13 +29,21 @@ pub fn file(
2829
.map(|(file_writer, guard)| {
2930
let layer = tracing_subscriber::fmt::layer()
3031
.with_writer(file_writer)
31-
.with_ansi(false) // Disable colors for file
3232
.with_span_events(config.fmt_span_events())
3333
.with_target(target)
34-
.with_timer(ChronoLocal::new(timestamp_fmt.clone()))
35-
.fmt_fields(PrettyFields::new())
36-
.with_filter(config.filter()?)
37-
.boxed();
34+
.with_timer(ChronoLocal::new(timestamp_fmt.clone()));
35+
let layer = match config.format.formatter {
36+
Formatter::Default => layer
37+
.with_ansi(false) // Disable colors for file
38+
.fmt_fields(PrettyFields::new())
39+
.with_filter(config.filter()?)
40+
.boxed(),
41+
Formatter::Json => layer
42+
.json()
43+
.flatten_event(true)
44+
.with_filter(config.filter()?)
45+
.boxed(),
46+
};
3847
Ok((layer, guard))
3948
})
4049
.transpose()
Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::instrumentation::config::logs::config::{LoggingConfig, LoggingConfigError};
2+
use crate::instrumentation::config::logs::format::Formatter;
23
use crate::instrumentation::tracing::LayerBox;
34
use tracing_subscriber::Layer;
45
use tracing_subscriber::fmt::format::PrettyFields;
@@ -11,12 +12,21 @@ pub fn stdout(config: &LoggingConfig) -> Result<LayerBox, LoggingConfigError> {
1112

1213
let layer = tracing_subscriber::fmt::layer()
1314
.with_writer(std::io::stdout)
14-
.with_ansi(config.format.ansi_colors)
1515
.with_target(target)
1616
.with_span_events(config.fmt_span_events())
17-
.with_timer(ChronoLocal::new(timestamp_fmt))
18-
.fmt_fields(PrettyFields::new())
19-
.with_filter(config.filter()?)
20-
.boxed();
17+
.with_timer(ChronoLocal::new(timestamp_fmt));
18+
19+
let layer = match config.format.formatter {
20+
Formatter::Default => layer
21+
.with_ansi(config.format.ansi_colors)
22+
.fmt_fields(PrettyFields::new())
23+
.with_filter(config.filter()?)
24+
.boxed(),
25+
Formatter::Json => layer
26+
.json()
27+
.flatten_event(true)
28+
.with_filter(config.filter()?)
29+
.boxed(),
30+
};
2131
Ok(layer)
2232
}

agent-control/tests/on_host/scenarios/opamp.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ use tempfile::tempdir;
3030
#[test]
3131
fn onhost_opamp_agent_control_local_effective_config() {
3232
// Given a agent-control without agents and opamp configured.
33-
3433
let opamp_server = FakeServer::start_new();
3534

3635
let local_dir = tempdir().expect("failed to create local temp dir");

docs/CONFIG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ log:
3434
target: true # Defaults to false, includes extra information for debugging purposes
3535
timestamp: "%Y-%m-%dT%H:%M%S" # Defaults to "%Y-%m-%dT%H:%M%S", details in <https://docs.rs/chrono/0.4.40/chrono/format/strftime/index.html#fn7>
3636
ansi_colors: true # Defaults to false, set up ansi-colors in the stdout logs output.
37+
formatter: json # Defaults to "default". It accepts "json" (newline delimited JSON logs) or "default" (human-readable, single-line logs)
3738
level: debug # Defines the log level, defaults to "info".
3839
insecure_fine_grained_level: debug # Enables logs for external dependencies and sets its level. This cannot be considered secure since external dependencies may leek secrets. If this is set the 'level' field does not apply.
3940
file:

0 commit comments

Comments
 (0)