apache_avro_test_helper/logger.rs
1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18#![cfg(not(target_arch = "wasm32"))]
19
20use crate::LOG_MESSAGES;
21use log::{LevelFilter, Log, Metadata};
22use std::sync::OnceLock;
23
24struct TestLogger {
25 delegate: env_logger::Logger,
26}
27
28impl Log for TestLogger {
29 fn enabled(&self, _metadata: &Metadata) -> bool {
30 true
31 }
32
33 fn log(&self, record: &log::Record) {
34 if self.enabled(record.metadata()) {
35 LOG_MESSAGES.with(|msgs| msgs.borrow_mut().push(format!("{}", record.args())));
36
37 self.delegate.log(record);
38 }
39 }
40
41 fn flush(&self) {}
42}
43
44fn test_logger() -> &'static TestLogger {
45 // Lazy static because the Logger has to be 'static
46 static TEST_LOGGER_ONCE: OnceLock<TestLogger> = OnceLock::new();
47 TEST_LOGGER_ONCE.get_or_init(|| TestLogger {
48 delegate: env_logger::Builder::from_default_env()
49 .filter_level(LevelFilter::Off)
50 .parse_default_env()
51 .build(),
52 })
53}
54
55/// Clears all log messages of this thread.
56pub fn clear_log_messages() {
57 LOG_MESSAGES.with_borrow_mut(Vec::clear);
58}
59
60/// Asserts that the message is not in the log for this thread.
61///
62/// # Panics
63/// Will panic if the provided message is an exact match for any message in the log.
64///
65/// # Example
66/// ```should_panic
67/// # use apache_avro_test_helper::logger::assert_not_logged;
68/// #
69/// log::error!("Unexpected Error");
70///
71/// // This will not panic as it is not an exact match
72/// assert_not_logged("No Unexpected Error");
73///
74/// // This will panic
75/// assert_not_logged("Unexpected Error");
76/// ```
77#[track_caller]
78pub fn assert_not_logged(unexpected_message: &str) {
79 LOG_MESSAGES.with_borrow(|msgs| {
80 let is_logged = msgs.iter().any(|msg| msg == unexpected_message);
81 assert!(
82 !is_logged,
83 "The following log message should not have been logged: '{unexpected_message}'"
84 );
85 });
86}
87
88/// Asserts that the message has been logged and removes it from the log of this thread.
89///
90/// # Panics
91/// Will panic if the message does not appear in the log.
92///
93/// # Example
94/// ```should_panic
95/// # use apache_avro_test_helper::logger::assert_logged;
96/// #
97/// log::error!("Something went wrong");
98/// log::info!("Something happened");
99/// log::error!("Something went wrong");
100///
101/// // This will not panic as the message was logged
102/// assert_logged("Something went wrong");
103///
104/// // This will not panic as the message was logged
105/// assert_logged("Something happened");
106///
107/// // This will not panic as the first call removed only the first matching log message
108/// assert_logged("Something went wrong");
109///
110/// // This will panic as all matching log messages have been removed
111/// assert_logged("Something went wrong");
112/// ```
113#[track_caller]
114pub fn assert_logged(expected_message: &str) {
115 let mut deleted = false;
116 LOG_MESSAGES.with_borrow_mut(|msgs| {
117 if let Some(pos) = msgs.iter().position(|msg| msg == expected_message) {
118 msgs.remove(pos);
119 deleted = true;
120 }
121 });
122
123 assert!(
124 deleted,
125 "Expected log message has not been logged: '{expected_message}'"
126 );
127}
128
129pub(crate) fn install() {
130 log::set_logger(test_logger())
131 .map(|()| log::set_max_level(LevelFilter::Trace))
132 .map_err(|err| {
133 eprintln!("Failed to set the custom logger: {err:?}");
134 })
135 .expect("Failed to set the custom TestLogger");
136}