Skip to content

Commit 40e4e5c

Browse files
lpraneisTheJokr
authored andcommitted
feat: Add tracing-rs-compat feature with tracing compat logging drain
1 parent 2311453 commit 40e4e5c

File tree

7 files changed

+157
-3
lines changed

7 files changed

+157
-3
lines changed

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ tokio = "1.41.0"
6969
thread_local = "1.1"
7070
tikv-jemallocator = "0.5"
7171
tikv-jemalloc-ctl = "0.5"
72+
tracing-slog = "0.3.0"
73+
tracing-subscriber = "0.3"
7274
yaml-merge-keys = "0.5"
7375

7476
# needed for minver

foundations/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ testing = ["dep:foundations-macros"]
164164
# Enables panicking when too much nesting is reached on the logger
165165
panic_on_too_much_logger_nesting = []
166166

167+
# Enables compatibility with `tracing-rs` by adding the ability to configure a
168+
# logging drain that forwards logs
169+
tracing-rs-compat = ["dep:tracing-slog"]
170+
167171
[package.metadata.docs.rs]
168172
all-features = true
169173
rustdoc-args = ["--cfg", "docsrs", "--cfg", "tokio_unstable", "--cfg", "foundations_unstable"]
@@ -205,6 +209,7 @@ slog-async = { workspace = true, optional = true }
205209
slog-json = { workspace = true, optional = true }
206210
slog-term = { workspace = true, optional = true }
207211
socket2 = { workspace = true, optional = true }
212+
tracing-slog = { workspace = true, optional = true }
208213
thread_local = { workspace = true, optional = true }
209214
tokio = { workspace = true, optional = true, features = ["sync", "rt"] }
210215
tonic = { workspace = true, optional = true, features = ["channel", "transport"] }
@@ -243,6 +248,7 @@ tempfile = { workspace = true }
243248
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
244249
ipnetwork = { workspace = true }
245250
nix = { workspace = true , features = ["fs"] }
251+
tracing-subscriber = { workspace = true }
246252

247253
[build-dependencies]
248254
bindgen = { workspace = true, features = ["runtime"], optional = true }

foundations/src/telemetry/log/init.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ pub(crate) fn init(service_info: &ServiceInfo, settings: &LoggingSettings) -> Bo
107107
let drain = build_json_log_drain(buf);
108108
AsyncDrain::new(drain).chan_size(CHANNEL_SIZE).build()
109109
}
110+
#[cfg(feature = "tracing-rs-compat")]
111+
(LogOutput::TracingRsCompat, _) => AsyncDrain::new(tracing_slog::TracingSlogDrain {})
112+
.chan_size(CHANNEL_SIZE)
113+
.build(),
110114
};
111115

112116
let root_drain = get_root_drain(settings, Arc::new(async_drain.fuse()));

foundations/src/telemetry/log/testing.rs

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::telemetry::log::init::{apply_filters_to_drain, LogHarness};
22
use crate::telemetry::log::internal::LoggerWithKvNestingTracking;
33
use crate::telemetry::settings::LoggingSettings;
44
use parking_lot::RwLock as ParkingRwLock;
5-
use slog::{Drain, Key, Level, Logger, Never, OwnedKVList, Record, Serializer, KV};
5+
use slog::{Discard, Drain, Key, Level, Logger, Never, OwnedKVList, Record, Serializer, KV};
66
use std::fmt::Arguments;
77
use std::sync::{Arc, RwLock};
88

@@ -36,11 +36,16 @@ impl Serializer for TestFieldSerializer {
3636
}
3737
}
3838

39-
struct TestLogDrain {
39+
struct TestLogDrain<D> {
4040
records: TestLogRecords,
41+
/// Optional drain to forward logs to after recording in the test log
42+
forward: Option<D>,
4143
}
4244

43-
impl Drain for TestLogDrain {
45+
impl<D> Drain for TestLogDrain<D>
46+
where
47+
D: Drain<Ok = (), Err = Never>,
48+
{
4449
type Ok = ();
4550
type Err = Never;
4651

@@ -56,6 +61,10 @@ impl Drain for TestLogDrain {
5661
fields: serializer.fields,
5762
});
5863

64+
if let Some(forward_drain) = &self.forward {
65+
let _ = forward_drain.log(record, kv);
66+
}
67+
5968
Ok(())
6069
}
6170
}
@@ -65,8 +74,43 @@ pub(crate) fn create_test_log(
6574
) -> (LoggerWithKvNestingTracking, TestLogRecords) {
6675
let log_records = Arc::new(RwLock::new(vec![]));
6776

77+
let drain: TestLogDrain<Discard> = TestLogDrain {
78+
records: Arc::clone(&log_records),
79+
forward: None,
80+
};
81+
82+
let drain = Arc::new(apply_filters_to_drain(drain, settings));
83+
let log = LoggerWithKvNestingTracking::new(Logger::root(Arc::clone(&drain), slog::o!()));
84+
85+
let _ = LogHarness::override_for_testing(LogHarness {
86+
root_log: Arc::new(ParkingRwLock::new(log.clone())),
87+
root_drain: drain,
88+
settings: settings.clone(),
89+
log_scope_stack: Default::default(),
90+
});
91+
92+
(log, log_records)
93+
}
94+
95+
/// Create a test log with a tracing-rs compat drain installed in addition to the test drain
96+
#[cfg(feature = "tracing-rs-compat")]
97+
pub(crate) fn create_test_log_for_tracing_compat(
98+
settings: &LoggingSettings,
99+
) -> (LoggerWithKvNestingTracking, TestLogRecords) {
100+
use tracing_slog::TracingSlogDrain;
101+
102+
assert!(matches!(
103+
settings.output,
104+
crate::telemetry::settings::LogOutput::TracingRsCompat
105+
));
106+
107+
let base_drain = TracingSlogDrain {};
108+
109+
let log_records = Arc::new(RwLock::new(vec![]));
110+
68111
let drain = TestLogDrain {
69112
records: Arc::clone(&log_records),
113+
forward: Some(base_drain.fuse()),
70114
};
71115

72116
let drain = Arc::new(apply_filters_to_drain(drain, settings));

foundations/src/telemetry/settings/logging.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ pub enum LogOutput {
5151
///
5252
/// File will be created if it doesn't exist and overwritten otherwise.
5353
File(PathBuf),
54+
55+
///Install a logging drain that forwards to `tracing-rs`
56+
///
57+
///WARN: If this output format is used, the settings in [`LoggingSettings`] other than the
58+
///verbosity will not be respected
59+
#[cfg(feature = "tracing-rs-compat")]
60+
TracingRsCompat,
5461
}
5562

5663
/// Format of the log output.

foundations/src/telemetry/testing.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,18 @@ impl TestTelemetryContext {
8888
self.log_records = log_records;
8989
}
9090

91+
/// Overrides the logging settings on the test telemetry context with the tracing-rs compat
92+
/// layer. The `logging_settings` provided must use the [tracing compat] output format
93+
///
94+
/// [tracing compat]: super::settings::LogOutput::TracingRsCompat
95+
#[cfg(all(feature = "logging", feature = "tracing-rs-compat"))]
96+
pub fn set_tracing_rs_log_drain(&mut self, logging_settings: LoggingSettings) {
97+
use crate::telemetry::log::testing::create_test_log_for_tracing_compat;
98+
let (log, log_records) = { create_test_log_for_tracing_compat(&logging_settings) };
99+
*self.inner.log.write() = log;
100+
self.log_records = log_records;
101+
}
102+
91103
/// Overrides the tracing settings on the test telemetry context, creating a new test tracer
92104
/// with the settings
93105
#[cfg(feature = "tracing")]

foundations/tests/logging.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,82 @@ fn test_not_exceed_limit_kv_nesting(_ctx: TestTelemetryContext) {
7272
set_verbosity(LogVerbosity::Info).expect("set_verbosity");
7373
}
7474
}
75+
76+
#[cfg(feature = "tracing-rs-compat")]
77+
mod tracing_rs_compat {
78+
use std::io;
79+
use std::sync::{Arc, Mutex};
80+
81+
use foundations::telemetry::log::{warn, TestLogRecord};
82+
use foundations::telemetry::settings::LoggingSettings;
83+
use foundations::telemetry::TelemetryContext;
84+
use tracing_subscriber::filter::LevelFilter;
85+
use tracing_subscriber::util::SubscriberInitExt as _;
86+
87+
struct TestWriter {
88+
log_entries: Arc<Mutex<Vec<String>>>,
89+
}
90+
91+
impl TestWriter {
92+
fn with_entries(entries: Arc<Mutex<Vec<String>>>) -> Self {
93+
Self {
94+
log_entries: entries,
95+
}
96+
}
97+
}
98+
99+
impl io::Write for TestWriter {
100+
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
101+
let s = String::from_utf8(buf.to_vec()).unwrap();
102+
self.log_entries.lock().unwrap().push(s.trim().to_string());
103+
Ok(buf.len())
104+
}
105+
106+
fn flush(&mut self) -> io::Result<()> {
107+
unimplemented!()
108+
}
109+
}
110+
111+
#[test]
112+
fn test_tracing_rs_compat() {
113+
let entries = Arc::new(Mutex::new(Vec::new()));
114+
let tracing_log_entries = entries.clone();
115+
let _subscriber = tracing_subscriber::fmt()
116+
.with_max_level(LevelFilter::TRACE)
117+
.with_writer(move || TestWriter::with_entries(entries.clone()))
118+
.without_time()
119+
.with_level(true)
120+
.with_ansi(false)
121+
.set_default();
122+
123+
let settings = LoggingSettings {
124+
output: foundations::telemetry::settings::LogOutput::TracingRsCompat,
125+
..Default::default()
126+
};
127+
128+
let mut ctx = TelemetryContext::test();
129+
ctx.set_tracing_rs_log_drain(settings);
130+
131+
let _scope = ctx.scope();
132+
133+
warn!("compat-layer-works");
134+
135+
// Validate slog is seeing all of the records
136+
let slog_records = ctx.log_records();
137+
let expected_slog_records = TestLogRecord {
138+
level: slog::Level::Warning,
139+
message: "compat-layer-works".to_string(),
140+
fields: vec![],
141+
};
142+
143+
assert_eq!(*slog_records, vec![expected_slog_records]);
144+
145+
let tracing_record = tracing_log_entries
146+
.lock()
147+
.unwrap()
148+
.first()
149+
.cloned()
150+
.unwrap();
151+
assert!(tracing_record.contains("WARN slog: compat-layer-works"));
152+
}
153+
}

0 commit comments

Comments
 (0)