Skip to content

Commit 8db4f03

Browse files
authored
Remove env_logger and use telemetry logging in tests (#1247)
* Remove env_logger and reuse telemetry logging in tests * Use test-captured writer for telemetry logs in tests * Isolate test logging init from OTEL and keep tests quiet
1 parent 4e00acd commit 8db4f03

File tree

7 files changed

+79
-110
lines changed

7 files changed

+79
-110
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ actix-web = { version = "4", features = ["rustls-0_23", "cookies"] }
3737
percent-encoding = "2.2.0"
3838
handlebars = "6.2.0"
3939
log = "0.4.17"
40-
env_logger = "0.11.1"
4140
mime_guess = "2.0.4"
4241
futures-util = "0.3.21"
4342
tokio = { version = "1.24.1", features = ["macros", "rt", "process", "sync"] }

src/telemetry.rs

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,45 @@
77
//! fields for human readability and machine parseability.
88
99
use std::env;
10-
use std::sync::OnceLock;
10+
use std::sync::{Once, OnceLock};
1111

1212
use opentelemetry_sdk::metrics::SdkMeterProvider;
1313
use opentelemetry_sdk::trace::SdkTracerProvider;
1414

1515
static TRACER_PROVIDER: OnceLock<SdkTracerProvider> = OnceLock::new();
1616
static METER_PROVIDER: OnceLock<SdkMeterProvider> = OnceLock::new();
17+
static TEST_LOGGING_INIT: Once = Once::new();
18+
const DEFAULT_ENV_FILTER_DIRECTIVES: &str = "sqlpage=info,actix_web=info,tracing_actix_web=info";
1719

1820
/// Initializes logging / tracing. Returns `true` if `OTel` was activated.
1921
#[must_use]
2022
pub fn init_telemetry() -> bool {
23+
init_telemetry_with_log_layer(logfmt::LogfmtLayer::new())
24+
}
25+
26+
fn init_telemetry_with_log_layer(logfmt_layer: logfmt::LogfmtLayer) -> bool {
2127
let otel_endpoint = env::var("OTEL_EXPORTER_OTLP_ENDPOINT").ok();
2228
let otel_active = otel_endpoint.as_deref().is_some_and(|v| !v.is_empty());
2329

2430
if otel_active {
25-
init_otel_tracing();
31+
init_otel_tracing(logfmt_layer);
2632
} else {
27-
init_tracing();
33+
init_tracing(logfmt_layer);
2834
}
2935

3036
otel_active
3137
}
3238

39+
/// Initializes logging once for tests using the same formatter as production.
40+
///
41+
/// Unlike `init_telemetry`, this does not initialize OTEL exporters and does
42+
/// not panic on invalid `LOG_LEVEL` / `RUST_LOG` values.
43+
pub fn init_test_logging() {
44+
TEST_LOGGING_INIT.call_once(|| {
45+
init_test_tracing();
46+
});
47+
}
48+
3349
/// Shuts down the `OTel` tracer provider, flushing pending spans.
3450
pub fn shutdown_telemetry() {
3551
if let Some(provider) = TRACER_PROVIDER.get() {
@@ -45,17 +61,27 @@ pub fn shutdown_telemetry() {
4561
}
4662

4763
/// Tracing subscriber without `OTel` export — logfmt output only.
48-
fn init_tracing() {
64+
fn init_tracing(logfmt_layer: logfmt::LogfmtLayer) {
4965
use tracing_subscriber::layer::SubscriberExt;
5066

5167
let subscriber = tracing_subscriber::registry()
5268
.with(default_env_filter())
53-
.with(logfmt::LogfmtLayer::new());
69+
.with(logfmt_layer);
70+
71+
set_global_subscriber(subscriber);
72+
}
73+
74+
fn init_test_tracing() {
75+
use tracing_subscriber::layer::SubscriberExt;
76+
77+
let subscriber = tracing_subscriber::registry()
78+
.with(test_env_filter())
79+
.with(logfmt::LogfmtLayer::test_writer());
5480

5581
set_global_subscriber(subscriber);
5682
}
5783

58-
fn init_otel_tracing() {
84+
fn init_otel_tracing(logfmt_layer: logfmt::LogfmtLayer) {
5985
use opentelemetry::global;
6086
use opentelemetry::trace::TracerProvider as _;
6187
use opentelemetry_sdk::propagation::TraceContextPropagator;
@@ -117,7 +143,7 @@ fn init_otel_tracing() {
117143

118144
let subscriber = tracing_subscriber::registry()
119145
.with(default_env_filter())
120-
.with(logfmt::LogfmtLayer::new())
146+
.with(logfmt_layer)
121147
.with(otel_layer)
122148
.with(tracing_opentelemetry::MetricsLayer::new(meter_provider));
123149

@@ -133,13 +159,26 @@ fn default_env_filter() -> tracing_subscriber::EnvFilter {
133159
.expect("Invalid log filter value in LOG_LEVEL or RUST_LOG")
134160
}
135161

162+
fn test_env_filter() -> tracing_subscriber::EnvFilter {
163+
env_filter_directives(
164+
env::var("LOG_LEVEL").ok().as_deref(),
165+
env::var("RUST_LOG").ok().as_deref(),
166+
)
167+
.parse()
168+
.unwrap_or_else(|_| {
169+
DEFAULT_ENV_FILTER_DIRECTIVES
170+
.parse()
171+
.expect("Default filter directives should always be valid")
172+
})
173+
}
174+
136175
fn env_filter_directives(log_level: Option<&str>, rust_log: Option<&str>) -> String {
137176
match (
138177
log_level.filter(|value| !value.is_empty()),
139178
rust_log.filter(|value| !value.is_empty()),
140179
) {
141180
(Some(value), _) | (None, Some(value)) => value.to_owned(),
142-
(None, None) => "sqlpage=info,actix_web=info,tracing_actix_web=info".to_owned(),
181+
(None, None) => DEFAULT_ENV_FILTER_DIRECTIVES.to_owned(),
143182
}
144183
}
145184

@@ -217,14 +256,29 @@ mod logfmt {
217256
const BOLD: &str = "\x1b[1m";
218257
const RESET: &str = "\x1b[0m";
219258

259+
#[derive(Copy, Clone)]
260+
enum OutputMode {
261+
Stderr,
262+
TestWriter,
263+
}
264+
220265
pub(super) struct LogfmtLayer {
221266
use_colors: bool,
267+
output_mode: OutputMode,
222268
}
223269

224270
impl LogfmtLayer {
225271
pub fn new() -> Self {
226272
Self {
227273
use_colors: io::stderr().is_terminal(),
274+
output_mode: OutputMode::Stderr,
275+
}
276+
}
277+
278+
pub fn test_writer() -> Self {
279+
Self {
280+
use_colors: false,
281+
output_mode: OutputMode::TestWriter,
228282
}
229283
}
230284
}
@@ -280,7 +334,14 @@ mod logfmt {
280334

281335
buf.push('\n');
282336
write_multiline_message(&mut buf, msg, multiline_msg);
283-
let _ = io::Write::write_all(&mut io::stderr().lock(), buf.as_bytes());
337+
match self.output_mode {
338+
OutputMode::Stderr => {
339+
let _ = io::Write::write_all(&mut io::stderr().lock(), buf.as_bytes());
340+
}
341+
OutputMode::TestWriter => {
342+
eprint!("{buf}");
343+
}
344+
}
284345
}
285346
}
286347

src/webserver/database/sql_to_json.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,7 @@ mod tests {
174174
use sqlx::Connection;
175175

176176
fn setup_logging() {
177-
let _ = env_logger::builder()
178-
.parse_default_env()
179-
.is_test(true)
180-
.try_init();
177+
crate::telemetry::init_test_logging();
181178
}
182179

183180
fn db_specific_test(db_type: &str) -> Option<String> {

src/webserver/http_request_info.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ mod test {
371371

372372
#[actix_web::test]
373373
async fn test_extract_multipart_form_data() {
374-
let _ = env_logger::try_init();
374+
crate::telemetry::init_test_logging();
375375
let config =
376376
serde_json::from_str::<AppConfig>(r#"{"listen_on": "localhost:1234"}"#).unwrap();
377377
let mut service_request = TestRequest::get()

tests/common/mod.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use actix_web::{
1111
};
1212
use sqlpage::{
1313
app_config::{test_database_url, AppConfig},
14+
telemetry,
1415
webserver::http::{form_config, main_handler, payload_config},
1516
AppState,
1617
};
@@ -111,10 +112,7 @@ pub fn test_config() -> AppConfig {
111112
}
112113

113114
pub fn init_log() {
114-
let _ = env_logger::builder()
115-
.parse_default_env()
116-
.is_test(true)
117-
.try_init();
115+
telemetry::init_test_logging();
118116
}
119117

120118
fn format_request_line_and_headers(req: &ServiceRequest) -> String {

tests/uploads/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,10 @@ async fn test_file_upload_too_large() -> actix_web::Result<()> {
141141
.await
142142
.expect_err("Expected an error response")
143143
.to_string();
144+
let msg = "max file size";
144145
assert!(
145-
err_str.to_ascii_lowercase().contains("max file size"),
146-
"{err_str}\nexpected to contain: File too large"
146+
err_str.to_ascii_lowercase().contains(msg),
147+
"{err_str}\nexpected to contain: {msg}"
147148
);
148149
Ok(())
149150
}

0 commit comments

Comments
 (0)