Skip to content

Commit 0eae60f

Browse files
authored
fix: fixed errors on unknown level in configuration files (#787)
1 parent 27b3570 commit 0eae60f

11 files changed

+192
-26
lines changed

Cargo.lock

+11-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ members = [".", "crate/encstr", "crate/heapopt"]
44
[workspace.package]
55
repository = "https://github.com/pamburus/hl"
66
authors = ["Pavel Ivanov <[email protected]>"]
7-
version = "0.30.4-alpha.2"
7+
version = "0.30.4-alpha.3"
88
edition = "2021"
99
license = "MIT"
1010

@@ -72,6 +72,7 @@ regex = "1"
7272
rust-embed = "8"
7373
serde = { version = "1", features = ["derive"] }
7474
serde_json = { version = "1", features = ["raw_value"] }
75+
serde_plain = "1"
7576
serde_yml = "0"
7677
serde-logfmt = { path = "./crate/serde-logfmt" }
7778
sha2 = "0"

build/ci/coverage.sh

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ function test() {
4949
${MAIN_EXECUTABLE:?} --config - --man-page > /dev/null
5050
${MAIN_EXECUTABLE:?} --config - --list-themes > /dev/null
5151
${MAIN_EXECUTABLE:?} --config - sample/prometheus.log -P > /dev/null
52+
HL_DEBUG_LOG=info ${MAIN_EXECUTABLE:?} --config - sample/prometheus.log -P > /dev/null
5253
echo "" | ${MAIN_EXECUTABLE:?} --config - --concurrency 4 > /dev/null
5354
}
5455

src/app.rs

+11-6
Original file line numberDiff line numberDiff line change
@@ -1078,7 +1078,12 @@ mod tests {
10781078

10791079
// local imports
10801080
use crate::{
1081-
filtering::MatchOptions, level::Level, model::FieldFilterSet, settings, themecfg::testing, LinuxDateFormat,
1081+
filtering::MatchOptions,
1082+
level::{InfallibleLevel, Level},
1083+
model::FieldFilterSet,
1084+
settings,
1085+
themecfg::testing,
1086+
LinuxDateFormat,
10821087
};
10831088

10841089
#[test]
@@ -1397,13 +1402,13 @@ mod tests {
13971402
settings: Fields {
13981403
predefined: settings::PredefinedFields {
13991404
level: settings::LevelField {
1400-
variants: vec![settings::LevelFieldVariant {
1405+
variants: vec![settings::RawLevelFieldVariant {
14011406
names: vec!["level".to_string()],
14021407
values: hashmap! {
1403-
Level::Debug => vec!["dbg".to_string()],
1404-
Level::Info => vec!["INF".to_string()],
1405-
Level::Warning => vec!["wrn".to_string()],
1406-
Level::Error => vec!["ERR".to_string()],
1408+
InfallibleLevel::new(Level::Debug) => vec!["dbg".to_string()],
1409+
InfallibleLevel::new(Level::Info) => vec!["INF".to_string()],
1410+
InfallibleLevel::new(Level::Warning) => vec!["wrn".to_string()],
1411+
InfallibleLevel::new(Level::Error) => vec!["ERR".to_string()],
14071412
},
14081413
level: None,
14091414
}],

src/config.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ mod tests {
128128

129129
use maplit::hashmap;
130130

131-
use crate::level::Level;
131+
use crate::level::{InfallibleLevel, Level};
132132

133133
#[test]
134134
fn test_default() {
@@ -152,11 +152,11 @@ mod tests {
152152
assert_eq!(
153153
variant.values,
154154
hashmap! {
155-
Level::Debug => vec!["dbg".to_owned()],
155+
InfallibleLevel::new(Level::Debug) => vec!["dbg".to_owned()],
156156
// TODO: replace `"inf"` with `"INF"` when https://github.com/mehcode/config-rs/issues/568 is fixed
157-
Level::Info => vec!["inf".to_owned()],
158-
Level::Warning => vec!["wrn".to_owned()],
159-
Level::Error => vec!["ERR".to_owned()],
157+
InfallibleLevel::new(Level::Info) => vec!["inf".to_owned()],
158+
InfallibleLevel::new(Level::Warning) => vec!["wrn".to_owned()],
159+
InfallibleLevel::new(Level::Error) => vec!["ERR".to_owned()],
160160
}
161161
);
162162
}

src/level.rs

+21
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,27 @@ pub enum Level {
4141

4242
// ---
4343

44+
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
45+
#[serde(untagged)]
46+
pub enum InfallibleLevel {
47+
Valid(Level),
48+
Invalid(String),
49+
}
50+
51+
impl InfallibleLevel {
52+
pub const fn new(level: Level) -> Self {
53+
Self::Valid(level)
54+
}
55+
}
56+
57+
impl From<Level> for InfallibleLevel {
58+
fn from(value: Level) -> Self {
59+
InfallibleLevel::Valid(value)
60+
}
61+
}
62+
63+
// ---
64+
4465
#[derive(Debug, Clone, PartialEq, Eq)]
4566
pub struct ParseError;
4667

src/main.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use std::{
1111
// third-party imports
1212
use chrono::Utc;
1313
use clap::{CommandFactory, Parser};
14-
use env_logger::{self as logger, fmt::TimestampPrecision};
14+
use env_logger::{self as logger};
1515
use itertools::Itertools;
1616

1717
// local imports
@@ -36,10 +36,13 @@ const HL_DEBUG_LOG: &str = "HL_DEBUG_LOG";
3636

3737
fn bootstrap() -> Result<Settings> {
3838
if std::env::var(HL_DEBUG_LOG).is_ok() {
39-
logger::Builder::from_env(HL_DEBUG_LOG)
40-
.format_timestamp(Some(TimestampPrecision::Micros))
41-
.init();
39+
logger::Builder::from_env(HL_DEBUG_LOG).format_timestamp_micros().init();
4240
log::debug!("logging initialized");
41+
} else {
42+
logger::Builder::new()
43+
.filter_level(log::LevelFilter::Warn)
44+
.format_timestamp_millis()
45+
.init()
4346
}
4447

4548
let opt = cli::BootstrapOpt::parse().args;

src/model.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use serde_logfmt::logfmt;
2626
use crate::{
2727
app::{InputFormat, UnixTimestampUnit},
2828
error::{Error, Result},
29-
level,
29+
level::{self},
3030
serdex::StreamDeserializerWithOffsets,
3131
settings::PredefinedFields,
3232
timestamp::Timestamp,
@@ -529,6 +529,10 @@ impl ParserSettings {
529529

530530
let mut j = 0;
531531
for variant in &pf.level.variants {
532+
let Some(variant) = variant.resolve() else {
533+
continue;
534+
};
535+
532536
let mut mapping = HashMap::new();
533537
for (level, values) in &variant.values {
534538
for value in values {

src/settings.rs

+104-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// std imports
22
use std::{
3-
collections::{BTreeMap, HashMap},
3+
collections::{BTreeMap, HashMap, HashSet},
44
include_str,
55
path::{Path, PathBuf},
66
};
@@ -15,7 +15,7 @@ use strum::IntoEnumIterator;
1515

1616
// local imports
1717
use crate::error::Error;
18-
use crate::level::Level;
18+
use crate::level::{InfallibleLevel, Level};
1919

2020
// ---
2121

@@ -163,17 +163,17 @@ impl Default for TimeField {
163163
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
164164
pub struct LevelField {
165165
pub show: FieldShowOption,
166-
pub variants: Vec<LevelFieldVariant>,
166+
pub variants: Vec<RawLevelFieldVariant>,
167167
}
168168

169169
impl Default for LevelField {
170170
fn default() -> Self {
171171
Self {
172172
show: FieldShowOption::default(),
173-
variants: vec![LevelFieldVariant {
173+
variants: vec![RawLevelFieldVariant {
174174
names: vec!["level".into()],
175175
values: Level::iter()
176-
.map(|level| (level, vec![level.as_ref().to_lowercase().into()]))
176+
.map(|level| (level.into(), vec![level.as_ref().to_lowercase().into()]))
177177
.collect(),
178178
level: None,
179179
}],
@@ -183,6 +183,56 @@ impl Default for LevelField {
183183

184184
// ---
185185

186+
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
187+
pub struct RawLevelFieldVariant {
188+
pub names: Vec<String>,
189+
#[serde(default, serialize_with = "ordered_map_serialize")]
190+
pub values: HashMap<InfallibleLevel, Vec<String>>,
191+
pub level: Option<InfallibleLevel>,
192+
}
193+
194+
impl RawLevelFieldVariant {
195+
pub fn resolve(&self) -> Option<LevelFieldVariant> {
196+
let mut unknowns = HashSet::new();
197+
let mut values = HashMap::new();
198+
let mut valid = true;
199+
200+
for (level, names) in &self.values {
201+
match level {
202+
InfallibleLevel::Valid(level) => {
203+
values.insert(level.clone(), names.clone());
204+
}
205+
InfallibleLevel::Invalid(name) => {
206+
unknowns.insert(name.clone());
207+
}
208+
}
209+
}
210+
211+
let level = self.level.clone().and_then(|level| match level {
212+
InfallibleLevel::Valid(level) => Some(level),
213+
InfallibleLevel::Invalid(name) => {
214+
unknowns.insert(name);
215+
valid = false;
216+
None
217+
}
218+
});
219+
220+
for name in unknowns {
221+
log::warn!("unknown level: {:?}", name);
222+
}
223+
224+
if valid && !values.is_empty() {
225+
Some(LevelFieldVariant {
226+
names: self.names.clone(),
227+
values,
228+
level,
229+
})
230+
} else {
231+
None
232+
}
233+
}
234+
}
235+
186236
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
187237
pub struct LevelFieldVariant {
188238
pub names: Vec<String>,
@@ -410,4 +460,53 @@ mod tests {
410460
assert_eq!(settings.time_zone, chrono_tz::UTC);
411461
assert_eq!(settings.theme, "universal");
412462
}
463+
464+
#[test]
465+
fn test_unknown_level_values() {
466+
let variant = RawLevelFieldVariant {
467+
names: vec!["level".into()],
468+
values: vec![
469+
(InfallibleLevel::Valid(Level::Info), vec!["info".into()]),
470+
(InfallibleLevel::Invalid("unknown".into()), vec!["unknown".into()]),
471+
]
472+
.into_iter()
473+
.collect(),
474+
level: None,
475+
};
476+
477+
assert_eq!(
478+
variant.resolve(),
479+
Some(LevelFieldVariant {
480+
names: vec!["level".into()],
481+
values: vec![(Level::Info, vec!["info".into()])].into_iter().collect(),
482+
level: None,
483+
})
484+
);
485+
}
486+
487+
#[test]
488+
fn test_unknown_level_main() {
489+
let variant = RawLevelFieldVariant {
490+
names: vec!["level".into()],
491+
values: vec![(InfallibleLevel::Valid(Level::Info), vec!["info".into()])]
492+
.into_iter()
493+
.collect(),
494+
level: Some(InfallibleLevel::Invalid("unknown".into())),
495+
};
496+
497+
assert_eq!(variant.resolve(), None);
498+
}
499+
500+
#[test]
501+
fn test_unknown_level_all_unknown() {
502+
let variant = RawLevelFieldVariant {
503+
names: vec!["level".into()],
504+
values: vec![(InfallibleLevel::Invalid("unknown".into()), vec!["unknown".into()])]
505+
.into_iter()
506+
.collect(),
507+
level: Some(InfallibleLevel::Valid(Level::Info)),
508+
};
509+
510+
assert_eq!(variant.resolve(), None);
511+
}
413512
}

src/theme.rs

+23-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ use crate::{
1010
error::*,
1111
eseq::{Brightness, Color, ColorCode, Mode, Sequence, StyleCode},
1212
fmtx::Push,
13-
level, themecfg,
13+
level::{self, InfallibleLevel},
14+
themecfg,
1415
};
1516

1617
// ---
@@ -79,6 +80,13 @@ impl<S: Borrow<themecfg::Theme>> From<S> for Theme {
7980
let default = StylePack::load(&s.elements);
8081
let mut packs = EnumMap::default();
8182
for (level, pack) in &s.levels {
83+
let level = match level {
84+
InfallibleLevel::Valid(level) => level,
85+
InfallibleLevel::Invalid(s) => {
86+
log::warn!("unknown level: {:?}", s);
87+
continue;
88+
}
89+
};
8290
packs[*level] = StylePack::load(&s.elements.clone().merged(pack.clone()));
8391
}
8492
Self {
@@ -357,5 +365,19 @@ mod tests {
357365
theme.apply(&mut buf, &Some(Level::Debug), |s| {
358366
s.element(Element::Message, |s| s.batch(|buf| buf.extend_from_slice(b"hello!")));
359367
});
368+
assert_eq!(buf, b"hello!");
369+
}
370+
371+
#[test]
372+
fn test_unknown_level() {
373+
let mut cfg = themecfg::Theme::default();
374+
cfg.levels
375+
.insert(InfallibleLevel::Invalid("unknown".to_string()), Default::default());
376+
let theme = Theme::from(&cfg);
377+
let mut buf = Vec::new();
378+
theme.apply(&mut buf, &Some(Level::Debug), |s| {
379+
s.element(Element::Message, |s| s.batch(|buf| buf.extend_from_slice(b"hello!")));
380+
});
381+
assert_eq!(buf, b"hello!");
360382
}
361383
}

0 commit comments

Comments
 (0)