diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 458168dd4cf11..bfb73adbbca43 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -852,7 +852,6 @@ presetdir pretrunc prettydiff primaryfont -probot processname procid promhttp @@ -1051,6 +1050,7 @@ subfolders subfooter sublimelinter subsec +subsecond substrategies subtagline subtimer diff --git a/Cargo.lock b/Cargo.lock index 5259d73832987..d6f1ff51d6c3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2194,6 +2194,7 @@ dependencies = [ "rstest", "serde", "serde_json", + "serde_with 3.11.0", "similar-asserts", "smallvec", "snafu 0.7.5", diff --git a/changelog.d/gelf-timestamp-nanoseconds.fix.md b/changelog.d/gelf-timestamp-nanoseconds.fix.md new file mode 100644 index 0000000000000..1b4e2ddb1e5e8 --- /dev/null +++ b/changelog.d/gelf-timestamp-nanoseconds.fix.md @@ -0,0 +1,4 @@ +The `gelf` codec now correctly deserializes the subsecond portion of timestamps rather than dropping +them. + +authors: jszwedko diff --git a/lib/codecs/Cargo.toml b/lib/codecs/Cargo.toml index 691e2fd91c675..1791dab4fa8fa 100644 --- a/lib/codecs/Cargo.toml +++ b/lib/codecs/Cargo.toml @@ -24,6 +24,7 @@ prost.workspace = true prost-reflect.workspace = true regex = { version = "1.11.0", default-features = false, features = ["std", "perf"] } serde.workspace = true +serde_with = { version = "3.11.0", default-features = false, features = ["std", "macros", "chrono_0_4"] } serde_json.workspace = true smallvec = { version = "1", default-features = false, features = ["union"] } snafu = { version = "0.7.5", default-features = false, features = ["futures"] } diff --git a/lib/codecs/src/decoding/format/gelf.rs b/lib/codecs/src/decoding/format/gelf.rs index 66613cee9c679..c37924072ffa6 100644 --- a/lib/codecs/src/decoding/format/gelf.rs +++ b/lib/codecs/src/decoding/format/gelf.rs @@ -3,6 +3,7 @@ use chrono::{DateTime, Utc}; use derivative::Derivative; use lookup::{event_path, owned_value_path}; use serde::{Deserialize, Serialize}; +use serde_with::{serde_as, TimestampSecondsWithFrac}; use smallvec::{smallvec, SmallVec}; use std::collections::HashMap; use vector_config::configurable_component; @@ -133,12 +134,7 @@ impl GelfDeserializer { if let Some(timestamp_key) = log_schema().timestamp_key_target_path() { if let Some(timestamp) = parsed.timestamp { - let dt = DateTime::from_timestamp( - f64::trunc(timestamp) as i64, - f64::fract(timestamp) as u32, - ) - .expect("invalid timestamp"); - log.insert(timestamp_key, dt); + log.insert(timestamp_key, timestamp); // per GELF spec- add timestamp if not provided } else { log.insert(timestamp_key, Utc::now()); @@ -204,13 +200,15 @@ impl GelfDeserializer { } } +#[serde_as] #[derive(Serialize, Deserialize, Debug)] struct GelfMessage { version: String, host: String, short_message: String, full_message: Option, - timestamp: Option, + #[serde_as(as = "Option>")] + timestamp: Option>, level: Option, facility: Option, line: Option, @@ -301,8 +299,7 @@ mod tests { b"Backtrace here\n\nmore stuff" ))) ); - // Vector does not use the nanos - let dt = DateTime::from_timestamp(1385053862, 0).expect("invalid timestamp"); + let dt = DateTime::from_timestamp(1385053862, 307_200_000).expect("invalid timestamp"); assert_eq!(log.get(TIMESTAMP), Some(&Value::Timestamp(dt))); assert_eq!(log.get(LEVEL), Some(&Value::Integer(1))); assert_eq!(