Skip to content

Commit

Permalink
feat: add log level conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
c-git committed Dec 29, 2024
1 parent d4b3817 commit 8e41588
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 8 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
Simple log viewer.
Works with log that have json on each line.
Works in both Web (WASM) and on native.
Defaults are configured for [bunyan formatted logs](https://github.com/trentm/node-bunyan) but these are setup as setting and are able to be changed (UI not implemented to change at present due to no demand).
For rust programs one can use the crate [tracing_bunyan_formatter](https://docs.rs/tracing-bunyan-formatter/latest/tracing_bunyan_formatter/) to generate logs as done in the book [Zero To Production In Rust](https://www.zero2prod.com/index.html).

You can use the deployed version without install at <https://c-git.github.io/log-viewer/>

# Description of expected log file format

It is expected that the file will contain multiple json objects separated by new lines.
If a line is not valid json it can be converted into a Log record with the entire line being one json field.
See [samples](tests/sample_logs/).
A toy example would be:

Expand All @@ -20,7 +23,8 @@ A toy example would be:

# How to run

Make sure you are using the latest version of stable rust by running `rustup update`.
We do not test on older version of rust so it is possible you may get compilation errors if you are not on the latest version of stable rust.
To get the most recent stable version use `rustup update`.

## Native

Expand Down
44 changes: 43 additions & 1 deletion src/app/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use serde_json::Value;

use super::{
calculate_hash,
data_display_options::{DataDisplayOptions, RowParseErrorHandling},
data_display_options::{DataDisplayOptions, LevelConversion, RowParseErrorHandling},
};
mod data_iter;
pub mod filter;
Expand Down Expand Up @@ -393,10 +393,52 @@ impl TryFrom<(&DataDisplayOptions, usize, &str)> for LogRow {
if let Some(key) = data_display_options.row_idx_field_name.as_ref() {
result.or_insert(key.to_string(), row_idx_val.into());
}
if let Some(settings) = data_display_options.level_conversion.as_ref() {
if let Some((key, value)) = level_conversion_to_display(&result, settings) {
result.or_insert(key, value);
}
}
Ok(result)
}
}

fn level_conversion_to_display(
row: &LogRow,
settings: &LevelConversion,
) -> Option<(String, Value)> {
let FieldContent::Present(raw_value) = row.field_value(&settings.source_field_name) else {
return None;
};
let raw_value = match raw_value.as_i64() {
Some(x) => x,
None => {
warn!(
"Failed to convert raw for {:?} to i64: {raw_value:?}",
settings.source_field_name
);
debug_assert!(
false,
"This is not expected to happen. Unable to convert level to string slice"
);
return None;
}
};
match settings.convert_map.get(&raw_value) {
Some(converted_value) => Some((
settings.display_field_name.clone(),
converted_value.clone().into(),
)),
None => {
warn!("Failed to convert raw_value to a displayable log level: {raw_value:?}");
debug_assert!(
false,
"This is not expected to happen. Unable to convert level to a corresponding display value"
);
None
}
}
}

impl TryFrom<(&DataDisplayOptions, &str)> for Data {
type Error = anyhow::Error;

Expand Down
53 changes: 47 additions & 6 deletions src/app/data_display_options.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::BTreeSet;
use std::collections::{BTreeMap, BTreeSet};

#[derive(serde::Deserialize, serde::Serialize, Debug, PartialEq, Eq)]
#[serde(default)] // if we add new fields, give them default values when deserializing old state
Expand All @@ -13,11 +13,14 @@ pub struct DataDisplayOptions {
/// WARNING: This must be a valid index into the list as this is assumed in method implementations
emphasize_if_matching_field_idx: Option<usize>,

/// When set adds a field with this name and populates it with the row numbers
/// When set adds a field with this name and populates it with the row numbers (Skips record if field name already exists)
pub row_idx_field_name: Option<String>,

/// Controls how errors during file loading are treated
pub row_parse_error_handling: RowParseErrorHandling,

/// Used for optionally converting message levels to strings
pub level_conversion: Option<LevelConversion>,
}

#[derive(serde::Deserialize, serde::Serialize, Debug, PartialEq, Eq)]
Expand All @@ -30,6 +33,15 @@ pub enum RowParseErrorHandling {
},
}

#[derive(serde::Deserialize, serde::Serialize, Debug, PartialEq, Eq)]
pub struct LevelConversion {
/// Skips record if field name already exists
pub display_field_name: String,
/// Skips conversion if source field cannot be found
pub source_field_name: String,
pub convert_map: BTreeMap<i64, String>,
}

impl DataDisplayOptions {
pub fn main_list_fields(&self) -> &[String] {
&self.main_list_fields
Expand All @@ -46,10 +58,17 @@ impl Default for DataDisplayOptions {
fn default() -> Self {
Self {
// TODO 3: Add ability to show, select and reorder selected fields
main_list_fields: ["row#", "time", "request_id", "otel.name", "msg"]
.into_iter()
.map(String::from)
.collect(),
main_list_fields: [
"row#",
"level_str",
"time",
"request_id",
"otel.name",
"msg",
]
.into_iter()
.map(String::from)
.collect(),
common_fields: [
"elapsed_milliseconds",
"file",
Expand Down Expand Up @@ -79,6 +98,7 @@ impl Default for DataDisplayOptions {
emphasize_if_matching_field_idx: Some(2),
row_idx_field_name: Some("row#".to_string()),
row_parse_error_handling: Default::default(),
level_conversion: Some(Default::default()),
}
}
}
Expand All @@ -91,3 +111,24 @@ impl Default for RowParseErrorHandling {
}
}
}

impl Default for LevelConversion {
fn default() -> Self {
// See bunyan levels https://github.com/trentm/node-bunyan?tab=readme-ov-file#levels and note rust only goes up to Error
let convert_map = vec![
(60, "Fatal".to_string()),
(50, "Error".to_string()),
(40, "Warn".to_string()),
(30, "Info".to_string()),
(20, "Debug".to_string()),
(10, "Trace".to_string()),
]
.into_iter()
.collect();
Self {
display_field_name: "level_str".into(),
source_field_name: "level".into(),
convert_map,
}
}
}

0 comments on commit 8e41588

Please sign in to comment.