From c3415b25cfc41f121beeb3606b2c7759af5f00ef Mon Sep 17 00:00:00 2001 From: Patrick Haun Date: Tue, 21 May 2024 13:54:28 +0200 Subject: [PATCH] add number timestamps --- Cargo.lock | 137 ++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + sample.json.log | 1 + src/config.rs | 2 +- src/log.rs | 3 +- src/main.rs | 1 + src/time.rs | 77 +++++++++++++++++++++++++++ 7 files changed, 220 insertions(+), 2 deletions(-) create mode 100644 src/time.rs diff --git a/Cargo.lock b/Cargo.lock index 7b83fec..0eabb56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.13" @@ -90,6 +105,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "cc" version = "1.0.90" @@ -102,6 +123,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.4", +] + [[package]] name = "clap" version = "4.5.4" @@ -157,6 +192,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + [[package]] name = "cpufeatures" version = "0.2.12" @@ -233,6 +274,7 @@ dependencies = [ name = "fblog" version = "4.9.0" dependencies = [ + "chrono", "clap", "clap_complete", "dirs", @@ -302,6 +344,29 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "indexmap" version = "2.2.6" @@ -318,6 +383,15 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -716,6 +790,60 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + [[package]] name = "which" version = "6.0.1" @@ -728,6 +856,15 @@ dependencies = [ "winsafe", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.4", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index de03d4e..726a475 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ clap_complete = "4" serde = { version = "1", features = ["derive"] } toml = "0.8" dirs = "5" +chrono = "0.4.38" [dependencies.clap] version = "4" diff --git a/sample.json.log b/sample.json.log index d5b67ec..4d21cd4 100644 --- a/sample.json.log +++ b/sample.json.log @@ -3,5 +3,6 @@ with prefix and not parsable | {"level with prefix | {"level":"xyz","msg":"<>Trust key rsa-43fe6c3d-6242-11e7-8b0c-02420a000007 found in cache","package":"trust","process":"gatekeeper","store":"cache","time":"2017-07-06T15:21:16Z","version":"0.1.20170620v0125"} with prefix | {"level":"debug","msg":"Trust key rsa-43fe6c3d-6242-11e7-8b0c-02420a000007 found in cache","package":"trust","process":"gatekeeper","store":"cache","time":"2017-07-06T15:21:16Z","version":"0.1.20170620v0125"} {"level":"info","msg":"<>Trust key rsa-43fe6c3d-6242-11e7-8b0c-02420a000007 found in cache","package":"trust","process":"gatekeeper","store":"cache","time":"2017-07-06T15:21:16Z","version":"0.1.20170620v0125"} +{"level":"info","msg":"<>Trust key rsa-43fe6c3d-6242-11e7-8b0c-02420a000007 found in cache","package":"trust","process":"gatekeeper","store":"cache","time":"1716292213381","version":"-1.1.20170620v0125"} {"timestamp":"<>2017-07-06T15:21:16.378+00:00","@version":1,"short_message":"Content-Type set both in header (application/json) and attached to entity (text/plain; charset=utf-8), ignoring content type from entity. To remove this warning, use Result.as(...) to set the content type, rather than setting the header manually.","logger_name":"play.core.server.netty.NettyModelConversion","thread_name":"application-akka.actor.default-dispatcher-114","severity":"WARN","level_value":30000,"HOSTNAME":"b54f64d212ac","application.home":"/opt/docker","process":"play"} {"bytes":0,"fields.time":"13.288673ms","flow_id":"c1114163-625e-11e7-90fe-02420a000016","level":"info","method":"GET","millis":13.288673,"msg":"Request: Success","package":"proxy","process":"gatekeeper","status":200,"time":"2017-07-06T15:21:16Z","type":"access","uri":"/internal/metrics","version":"0.1.20170620v0125"} diff --git a/src/config.rs b/src/config.rs index 0b23411..822202b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -122,7 +122,7 @@ mod tests { "#, ) .unwrap(); - assert_eq!(config.level_map.is_empty(), true); + assert!(config.level_map.is_empty()); } #[test] diff --git a/src/log.rs b/src/log.rs index e3b0180..5b8c44f 100644 --- a/src/log.rs +++ b/src/log.rs @@ -1,4 +1,5 @@ use crate::log_settings::LogSettings; +use crate::time::try_convert_timestamp_to_readable; use handlebars::Handlebars; use serde_json::{Map, Value}; use std::borrow::ToOwned; @@ -21,7 +22,7 @@ pub fn print_log_line( let trimmed_prefix = maybe_prefix.map(|p| p.trim()).unwrap_or_else(|| "").to_string(); let mut message = get_string_value_or_default(&string_log_entry, &log_settings.message_keys, ""); - let timestamp = get_string_value_or_default(&string_log_entry, &log_settings.time_keys, ""); + let timestamp = try_convert_timestamp_to_readable(get_string_value_or_default(&string_log_entry, &log_settings.time_keys, "")); if let Some(message_template) = &log_settings.substitution { if let Some(templated_message) = message_template.apply(&message, log_entry) { diff --git a/src/main.rs b/src/main.rs index 053aee6..bb0b931 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ mod log_settings; mod process; mod substitution; mod template; +mod time; use crate::log_settings::LogSettings; use clap_complete::{generate, Shell}; diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000..578cc4e --- /dev/null +++ b/src/time.rs @@ -0,0 +1,77 @@ + +use chrono::{LocalResult, TimeZone, Utc}; + +fn timestamp_to_iso8601(timestamp: i64) -> Option { + let now = Utc::now(); + + let dt_secs = Utc.timestamp_opt(timestamp, 0); + let dt_millis = Utc.timestamp_millis_opt(timestamp); + + match (dt_secs, dt_millis) { + (LocalResult::Single(dt_secs), LocalResult::Single(dt_millis)) => { + let diff_secs = (now - dt_secs).num_milliseconds().abs(); + let diff_millis = (now - dt_millis).num_milliseconds().abs(); + + if diff_secs < diff_millis { + Some(dt_secs.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)) + } else { + Some(dt_millis.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)) + } + } + (LocalResult::Single(dt_secs), _) => Some(dt_secs.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)), + (_, LocalResult::Single(dt_millis)) => Some(dt_millis.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)), + _ => None, // Neither is valid (or ambiguous due to time zone transitions) + } +} + +fn is_only_digits(input: &str) -> bool { + input.chars().all(char::is_numeric) +} + +pub fn try_convert_timestamp_to_readable(input: String) ->String { + if input.is_empty() { + return input + } + + if is_only_digits(&input) { + if let Some(parsed) = input.parse::().ok().and_then(timestamp_to_iso8601) { + return parsed; + } + } + + input +} + +#[cfg(test)] +mod tests { + use chrono::DateTime; + + use super::*; + + #[test] + fn test_valid_second_timestamp() { + + let iso = "2024-05-21T11:21:29.000Z"; + let timestamp = DateTime::parse_from_rfc3339(iso).expect("should be valid").timestamp(); + let result = timestamp_to_iso8601(timestamp).unwrap(); + assert_eq!(iso, result) + } + + #[test] + fn test_valid_millis_timestamp() { + let iso = "2024-05-21T11:21:29.536Z"; + let timestamp = DateTime::parse_from_rfc3339(iso).expect("should be valid").timestamp_millis(); + let result = timestamp_to_iso8601(timestamp).unwrap(); + assert_eq!(iso, result) + } + + #[test] + fn test_try_convert() { + assert_eq!(try_convert_timestamp_to_readable("1716292213381".to_string()), "2024-05-21T11:50:13.381Z"); + assert_eq!(try_convert_timestamp_to_readable("1716292213".to_string()), "2024-05-21T11:50:13.000Z"); + assert_eq!(try_convert_timestamp_to_readable("bla".to_string()), "bla"); + assert_eq!(try_convert_timestamp_to_readable("".to_string()), ""); + } + +} +