Geal / rust-syslog

Send syslog messages from Rust
MIT License
110 stars 55 forks source link

Invalid timestamp format for RFC 5424 #73

Closed cipriancraciun closed 6 months ago

cipriancraciun commented 1 year ago

According to the RFC 5424 section 6 the timestamp second should have at most 6 digits after the dot (see TIME-SECFRAC):

      TIMESTAMP       = NILVALUE / FULL-DATE "T" FULL-TIME
      FULL-DATE       = DATE-FULLYEAR "-" DATE-MONTH "-" DATE-MDAY
      DATE-FULLYEAR   = 4DIGIT
      DATE-MONTH      = 2DIGIT  ; 01-12
      DATE-MDAY       = 2DIGIT  ; 01-28, 01-29, 01-30, 01-31 based on
                                ; month/year
      FULL-TIME       = PARTIAL-TIME TIME-OFFSET
      PARTIAL-TIME    = TIME-HOUR ":" TIME-MINUTE ":" TIME-SECOND
                        [TIME-SECFRAC]
      TIME-HOUR       = 2DIGIT  ; 00-23
      TIME-MINUTE     = 2DIGIT  ; 00-59
      TIME-SECOND     = 2DIGIT  ; 00-59
      TIME-SECFRAC    = "." 1*6DIGIT
      TIME-OFFSET     = "Z" / TIME-NUMOFFSET
      TIME-NUMOFFSET  = ("+" / "-") TIME-HOUR ":" TIME-MINUTE

Here is an example of what the library produces with RFC 5424 format:

2023-05-15T14:38:30.099254959Z

Moreover, citing from RFC 5424, section 6.2.3:

The TIMESTAMP field is a formalized timestamp derived from [[RFC3339](https://datatracker.ietf.org/doc/html/rfc3339)].

   Whereas [[RFC3339](https://datatracker.ietf.org/doc/html/rfc3339)] makes allowances for multiple syntaxes, this
   document imposes further restrictions.  The TIMESTAMP value MUST
   follow these restrictions:

[...]

   Example 5 - An Invalid TIMESTAMP

         2003-08-24T05:14:15.000000003-07:00

   This example is nearly the same as Example 4, but it is specifying
   TIME-SECFRAC in nanoseconds.  This results in TIME-SECFRAC being
   longer than the allowed 6 digits, which invalidates it.

The last example, cited from the RFC explicitly states that nanosecond resolution is not allowed according to the specification.

cipriancraciun commented 1 year ago

Here is the minimal patch that solves the issue.

diff --git i/src/format.rs w/src/format.rs
index b684ae8..43bff7e 100644
--- i/src/format.rs
+++ w/src/format.rs
@@ -162,25 +162,32 @@ impl Formatter5424 {

 impl<T: Display> LogFormat<(u32, StructuredData, T)> for Formatter5424 {
     fn format<W: Write>(
         &self,
         w: &mut W,
         severity: Severity,
         log_message: (u32, StructuredData, T),
     ) -> Result<()> {
         let (message_id, data, message) = log_message;

+        let timestamp = time::OffsetDateTime::now_utc();
+
+        // Trim timestamp to millisecond resolution according to RFC5424.
+        let timestamp = timestamp
+            .replace_nanosecond(timestamp.nanosecond() / 1000 * 1000)
+            .unwrap();
+
         write!(
             w,
             "<{}>1 {} {} {} {} {} {} {}", // v1
             encode_priority(severity, self.facility),
-            time::OffsetDateTime::now_utc()
+            timestamp
                 .format(&time::format_description::well_known::Rfc3339)
                 .unwrap(),
             self.hostname
                 .as_ref()
                 .map(|x| &x[..])
                 .unwrap_or("localhost"),
             self.process,
             self.pid,
             message_id,
             self.format_5424_structured_data(data),