syslog_rs/formatters/syslog_5424.rs
1/*-
2 * syslog-rs - a syslog client translated from libc to rust
3 *
4 * Copyright 2025 Aleksandr Morozov
5 *
6 * The syslog-rs crate can be redistributed and/or modified
7 * under the terms of either of the following licenses:
8 *
9 * 1. the Mozilla Public License Version 2.0 (the “MPL”) OR
10 *
11 * 2. The MIT License (MIT)
12 *
13 * 3. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
14 */
15
16use std::{borrow::Cow, fmt, net::IpAddr, num::NonZero, time::Duration};
17
18use chrono::{Local, SecondsFormat};
19
20use crate::
21{
22 ATSIGN, CBRACE, EQSIGN, IANA_PRIV_ENT_NUM, NILVALUE, OBRACE, QCHAR, SyslogMsgPriFac,
23 WSPACE, check_printable, error::SyRes, escape_chars, map_error, portable, throw_error,
24 truncate, truncate_n
25};
26
27use super::{SyslogFormatted, SyslogFormatter};
28
29/// A trait for the unification of the all Structured Data IDs (IANA-registered SD-IDs).
30pub trait SdIdIntrf: fmt::Display
31{
32 /// SD-ID identifier.
33 const SD_ID_TEXT: &'static str;
34
35 /// Formats the internals.
36 fn format_msg(&self) -> String;
37}
38
39
40/// RFC:
41/// > The SD-ID "timeQuality" MAY be used by the originator to describe its
42/// > notion of system time. This SD-ID SHOULD be written if the
43/// > originator is not properly synchronized with a reliable external time
44/// > source or if it does not know whether its time zone information is
45/// > correct. The main use of this structured data element is to provide
46/// > some information on the level of trust it has in the TIMESTAMP
47/// > described in Section 6.2.3. All parameters are OPTIONAL.
48///
49/// RFC:
50/// > The "tzKnown" parameter indicates whether the originator knows its
51/// > time zone. If it does, the value "1" MUST be used. If the time zone
52/// > information is in doubt, the value "0" MUST be used. If the
53/// > originator knows its time zone but decides to emit time in UTC, the
54/// > value "1" MUST be used (because the time zone is known).
55/// tz_known
56///
57/// RFC:
58/// > The "isSynced" parameter indicates whether the originator is
59/// > synchronized to a reliable external time source, e.g., via NTP. If
60/// > the originator is time synchronized, the value "1" MUST be used. If
61/// > not, the value "0" MUST be used.
62/// is_synced
63///
64/// RFC:
65/// > The "syncAccuracy" parameter indicates how accurate the originator
66/// > thinks its time synchronization is. It is an integer describing the
67/// > maximum number of microseconds that its clock may be off between
68/// > synchronization intervals.
69/// > If the value "0" is used for "isSynced", this parameter MUST NOT be
70/// > specified. If the value "1" is used for "isSynced" but the
71/// > "syncAccuracy" parameter is absent, a collector or relay can assume
72/// > that the time information provided is accurate enough to be
73/// > considered correct. The "syncAccuracy" parameter MUST be written
74/// > only if the originator actually has knowledge of the reliability of
75/// > the external time source.
76/// sync_acc
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct SdIdTimeQuality(String);
79
80impl fmt::Display for SdIdTimeQuality
81{
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
83 {
84 write!(f, "{} {}", Self::SD_ID_TEXT, self.0)
85 }
86}
87
88impl SdIdIntrf for SdIdTimeQuality
89{
90 const SD_ID_TEXT: &'static str = "timeQuality";
91
92 fn format_msg(&self) -> String
93 {
94 return self.0.clone();
95 }
96}
97
98impl SdIdTimeQuality
99{
100 pub
101 fn new(tz_known: bool, is_synced: bool, sync_acc: Option<u64>) -> Self
102 {
103 if let Some(sa) = sync_acc
104 {
105 let msg =
106 [
107 Self::SD_ID_TEXT, WSPACE,
108 "tzKnown", "=", QCHAR, tz_known.to_string().as_str(), QCHAR, WSPACE,
109 "isSynced", "=", QCHAR, is_synced.to_string().as_str(), QCHAR, WSPACE,
110 "syncAccuracy", "=", QCHAR, sa.to_string().as_str(), QCHAR,
111 ]
112 .concat();
113
114 return Self(msg);
115 }
116 else
117 {
118 let msg =
119 [
120 Self::SD_ID_TEXT, WSPACE,
121 "tzKnown", "=", QCHAR, tz_known.to_string().as_str(), QCHAR, WSPACE,
122 "isSynced", "=", QCHAR, is_synced.to_string().as_str(), QCHAR,
123 ]
124 .concat();
125
126 return Self(msg);
127 }
128 }
129}
130
131/// RFC:
132/// > The SD-ID "origin" MAY be used to indicate the origin of a syslog
133/// > message. The following parameters can be used. All parameters are
134/// > OPTIONAL.
135/// >
136/// > Specifying any of these parameters is primarily an aid to log
137/// > analyzers and similar applications.
138///
139/// RFC:
140/// > The "ip" parameter denotes an IP address that the originator knows it
141/// > had at the time of originating the message. It MUST contain the
142/// > textual representation of an IP address as outlined in Section 6.2.4.
143/// >
144/// > This parameter can be used to provide identifying information in
145/// > addition to what is present in the HOSTNAME field. It might be
146/// > especially useful if the host's IP address is included in the message
147/// > while the HOSTNAME field still contains the FQDN. It is also useful
148/// > for describing all IP addresses of a multihomed host.
149/// >
150/// > If an originator has multiple IP addresses, it MAY either list one of
151/// > its IP addresses in the "ip" parameter or it MAY include multiple
152/// > "ip" parameters in a single "origin" structured data element.
153/// ip
154///
155/// Some sh$t which grandpas from IANA invented and grabbed.
156/// enterprise_id
157///
158/// RFC:
159/// > The "software" parameter uniquely identifies the software that
160/// > generated the message. If it is used, "enterpriseId" SHOULD also be
161/// > specified, so that a specific vendor's software can be identified.
162/// > The "software" parameter is not the same as the APP-NAME header
163/// > field.
164/// > The "software" parameter is a string. It MUST NOT be longer than 48
165/// > characters.
166/// software
167///
168/// RFC:
169/// > The "swVersion" parameter uniquely identifies the version of the
170/// > software that generated the message. If it is used, the "software"
171/// > and "enterpriseId" parameters SHOULD be provided, too.
172/// >
173/// > The "swVersion" parameter is a string. It MUST NOT be longer than 32
174/// > characters.
175/// sw_version
176#[derive(Debug, Clone, PartialEq, Eq)]
177pub struct SdIdOrigin(String);
178
179impl fmt::Display for SdIdOrigin
180{
181 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
182 {
183 write!(f, "{} {}", Self::SD_ID_TEXT, self.0)
184 }
185}
186
187impl SdIdIntrf for SdIdOrigin
188{
189 const SD_ID_TEXT: &'static str = "origin";
190
191 fn format_msg(&self) -> String
192 {
193 return self.0.clone();
194 }
195}
196
197impl SdIdOrigin
198{
199 pub
200 fn new(ips: Vec<IpAddr>, enterprise_id: Option<&str>, software: Option<&str>, sw_version: Option<&str>) -> Self
201 {
202 let mut items: Vec<String> =
203 Vec::with_capacity(ips.len() + 1 + enterprise_id.map_or(0, |_| 1) +
204 software.map_or(0, |_| 1) + sw_version.map_or(0, |_| 1));
205
206 items.push(Self::SD_ID_TEXT.to_string());
207
208 ips.iter().for_each(|ip| items.push(["ip=", QCHAR, ip.to_string().as_str(), QCHAR].concat()));
209
210 if let Some(ent_id) = enterprise_id
211 {
212 items.push(["enterpriseId=", QCHAR, ent_id, QCHAR].concat());
213
214 if let Some(softw) = software
215 {
216 items.push(["software=", QCHAR, softw, QCHAR].concat());
217
218 if let Some(sw_ver) = sw_version
219 {
220 items.push(["swVersion=", QCHAR, sw_ver, QCHAR].concat());
221 }
222 else
223 {
224 items.push(["swVersion=", QCHAR, "upspecified", QCHAR].concat());
225 }
226 }
227 else
228 {
229 items.push(["software=", QCHAR, "upspecified", QCHAR].concat());
230 }
231 }
232
233 let ret = Self(items.join(WSPACE));
234
235 return ret;
236 }
237}
238
239
240/// RFC:
241/// > The SD-ID "meta" MAY be used to provide meta-information about the
242/// > message. The following parameters can be used. All parameters are
243/// > OPTIONAL. If the "meta" SD-ID is used, at least one parameter SHOULD
244/// > be specified.
245///
246/// RFC:
247/// > The "sequenceId" parameter tracks the sequence in which the
248/// > originator submits messages to the syslog transport for sending. It
249/// > is an integer that MUST be set to 1 when the syslog function is
250/// > started and MUST be increased with every message up to a maximum
251/// > value of 2147483647. If that value is reached, the next message MUST
252/// > be sent with a sequenceId of 1.
253/// sequenceId
254///
255/// RFC:
256/// > The "sysUpTime" parameter MAY be used to include the SNMP "sysUpTime"
257/// > parameter in the message. Its syntax and semantics are as defined in
258/// > [RFC3418].
259/// sysUpTime
260///
261/// RFC:
262/// > The "language" parameter MAY be specified by the originator to convey
263/// > information about the natural language used inside MSG. If it is
264/// > specified, it MUST contain a language identifier as defined in BCP 47
265/// > [RFC4646].
266/// language
267#[derive(Debug, Clone, PartialEq, Eq)]
268pub struct SdIdMeta(String);
269
270impl fmt::Display for SdIdMeta
271{
272 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
273 {
274 write!(f, "{} {}", Self::SD_ID_TEXT, self.0)
275 }
276}
277
278impl SdIdIntrf for SdIdMeta
279{
280 const SD_ID_TEXT: &'static str = "meta";
281
282 fn format_msg(&self) -> String
283 {
284 return self.0.clone();
285 }
286}
287
288impl SdIdMeta
289{
290 /// Creates new instance. This includes the uptime request and sequence number.
291 ///
292 /// This message can not be cached as it
293 pub
294 fn new(language: &str, msg: &FormatRfc5424) -> Self
295 {
296 let uptime = portable::get_uptime().unwrap_or(Duration::from_secs(0));
297
298
299
300 let msg =
301 [
302 Self::SD_ID_TEXT, WSPACE,
303 "sequenceId", "=", QCHAR, msg.seq.to_string().as_str(), QCHAR, WSPACE,
304 "sysUpTime", "=", QCHAR, uptime.as_secs().to_string().as_str(), QCHAR, WSPACE,
305 "language", "=", QCHAR, language, QCHAR,
306 ]
307 .concat();
308
309 return Self(msg);
310 }
311
312 #[cfg(test)]
313 pub
314 fn new_test(language: &str, uptime: u64, msg: &FormatRfc5424) -> Self
315 {
316 let msg =
317 [
318 Self::SD_ID_TEXT, WSPACE,
319 "sequenceId", "=", QCHAR, msg.seq.to_string().as_str(), QCHAR, WSPACE,
320 "sysUpTime", "=", QCHAR, uptime.to_string().as_str(), QCHAR, WSPACE,
321 "language", "=", QCHAR, language, QCHAR,
322 ]
323 .concat();
324
325 return Self(msg);
326 }
327}
328
329/// A RFC5424 SD-ELEMENT of the STRUCTURED-DATA.
330///
331/// i.e [exampleSDID@32473 iut="3" eventSource="Application" eventID="1011"]
332///
333/// RFC:
334/// > SD-IDs are case-sensitive and uniquely identify the type and purpose
335/// > of the SD-ELEMENT. The same SD-ID MUST NOT exist more than once in a
336/// > message.
337/// >
338/// > There are two formats for SD-ID names:
339/// >
340/// > * - Names that do not contain an at-sign ("@", ABNF %d64) are reserved
341/// > to be assigned by IETF
342/// >
343/// > * - Anyone can define additional SD-IDs using names in the format
344/// > name@<private enterprise number>, e.g., "ourSDID@32473". The
345/// > format of the part preceding the at-sign is not specified;
346/// sd_id
347///
348/// RFC:
349/// > Each SD-PARAM consists of a name, referred to as PARAM-NAME, and a
350/// > value, referred to as PARAM-VALUE. PARAM-NAME is case-sensitive.
351/// > To support international characters, the PARAM-VALUE field MUST be
352/// > encoded using UTF-8.
353/// vals
354#[derive(Debug, Clone)]
355pub struct FormatRfc5424Sd
356{
357 // SD-ID offset 0, rest - data.
358 vals: Vec<Cow<'static, str>>
359}
360
361impl fmt::Display for FormatRfc5424Sd
362{
363 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
364 {
365 write!(f, "{}", self.format_sd())
366 }
367}
368
369impl FormatRfc5424Sd
370{
371 /// Creates the second type of the SD-ID in format of
372 /// name@<private enterprise number>, e.g., "ourSDID@32473".
373 ///
374 /// RFC5424:
375 /// ```text
376 /// The format of the part preceding the at-sign is not specified;
377 /// however, these names MUST be printable US-ASCII strings, and MUST
378 /// NOT contain an at-sign ('@', ABNF %d64), an equal-sign ('=', ABNF
379 /// %d61), a closing brace (']', ABNF %d93), a quote-character ('"',
380 /// ABNF %d34), whitespace, or control characters. The part following
381 /// the at-sign MUST be a private enterprise number as specified in
382 /// Section 7.2.2. Please note that throughout this document the
383 /// value of 32473 is used for all private enterprise numbers. This
384 /// value has been reserved by IANA to be used as an example number in
385 /// documentation. Implementors will need to use their own private
386 /// enterprise number for the enterpriseId parameter, and when
387 /// creating locally extensible SD-ID names.
388 /// ```
389 ///
390 /// # Arguments
391 ///
392 /// * `sd_id` - a sd_id which is stored with static lifetime.
393 ///
394 /// * `vals_cnt` - amount of the values to be added.
395 ///
396 /// # Returns
397 ///
398 /// A [Result] in form of [SyRes] is returned.
399 pub
400 fn new(sd_id: &'static str, vals_cnt: usize) -> SyRes<Self>
401 {
402 if vals_cnt == 0
403 {
404 throw_error!("params should present.");
405 }
406
407 if sd_id.contains(ATSIGN) == false
408 {
409 throw_error!("SD-ID should contain @, otherwise see pre-defined ID's.");
410 }
411
412 let (name, ent_num) =
413 sd_id
414 .split_once(ATSIGN)
415 .ok_or_else(||
416 map_error!("incorrect SD-ID format")
417 )?;
418
419 check_printable(name)?;
420
421 let ent_num: u64 =
422 ent_num
423 .parse()
424 .map_err(|e|
425 map_error!("incorrect SD-ID entrprise number format, {}", e)
426 )?;
427
428 if ent_num == IANA_PRIV_ENT_NUM
429 {
430 throw_error!("SD-ID entrprise number can not be '{}' because it is private", ent_num);
431 }
432
433 let mut vals: Vec<Cow<'static, str>> = Vec::with_capacity(vals_cnt*3 + 2);
434 vals.push(Cow::Borrowed(sd_id));
435
436 return Ok(Self{ vals: vals });
437 }
438
439 /// Inserts the SD-PARAM into list.
440 /// An SD-PARAM MAY be repeated multiple times inside an SD-ELEMENT.
441 pub
442 fn push(&mut self, key: Cow<'static, str>, val: Cow<'static, str>) -> SyRes<()>
443 {
444 check_printable(&key)?;
445
446 let escp_val = escape_chars(val);
447 self.vals.push( Cow::Owned([WSPACE, key.as_ref(), EQSIGN, QCHAR, escp_val.as_ref(), QCHAR].concat()));
448
449 return Ok(());
450 }
451
452 fn format_sd(&self) -> String
453 {
454 return self.vals.concat();
455 }
456
457 #[inline]
458 fn get_sd_id(&self) -> &str
459 {
460 return self.vals[0].as_ref();
461 }
462}
463
464
465/// The "sequenceId" parameter tracks the sequence in which the
466/// originator submits messages to the syslog transport for sending.
467#[cfg(target_has_atomic = "64")]
468static SEQUENCE_ID: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
469
470/// The "sequenceId" parameter tracks the sequence in which the
471/// originator submits messages to the syslog transport for sending.
472#[cfg(not(target_has_atomic = "64"))]
473static SEQUENCE_ID: std::sync::Mutex<u32> = std::sync::Mutex::new(0);
474
475/// A RFC5424 formatter which formats the syslog message according to the
476/// RFC5424 requirments so it could be received by the syslog server.
477///
478/// # Usage
479///
480/// ```ignore
481/// let mut m = FormatRfc5424::from("test123");
482///
483/// m.add_msg_id_str("ATX41").unwrap();
484///
485/// let v = SdIdMeta::new_test("jp", 12345, &m);
486///
487/// m.add_internal(&v).unwrap();
488/// ```
489#[derive(Debug, Clone)]
490pub struct FormatRfc5424
491{
492 /// A sequence number of the message
493 seq: NonZero<u32>,
494
495 /// A payload message.
496 msg: Cow<'static, str>,
497
498 /// RFC: The MSGID SHOULD identify the type of message. For example, a
499 /// firewall might use the MSGID "TCPIN" for incoming TCP traffic and the
500 /// MSGID "TCPOUT" for outgoing TCP traffic. Messages with the same
501 /// MSGID should reflect events of the same semantics. The MSGID itself
502 /// is a string without further semantics. It is intended for filtering
503 //// messages on a relay or collector.
504 msg_id: Cow<'static, str>,
505
506 /// All STRUCTURED-DATA added to the current message.
507 st_data: Vec<(Cow<'static, str>, String)>,
508}
509
510impl FormatRfc5424
511{
512 /// Creates a new Rfc5424 template with provided `seq_num`. The sequence is not
513 /// verified! The seq_num generator should be implemented on the user's side.
514 ///
515 /// Use [FormatRfc5424::from] [String] or [str] to initialize the message with
516 /// internal counter.
517 ///
518 /// # Arguments
519 ///
520 /// * `value` - [String] a log message.
521 ///
522 /// * `seq_num` - [u32] a sequence number.
523 ///
524 /// # Returns
525 ///
526 /// A template is retuened.
527 pub
528 fn new(value: String, seq_num: NonZero<u32>) -> FormatRfc5424
529 {
530 return
531 Self
532 {
533 seq: seq_num,
534 msg: Cow::Owned(value),
535 msg_id: Cow::Borrowed(NILVALUE),
536 st_data: Vec::new(),
537 };
538 }
539
540 /// Generates a [NonZero] sequence number of the message. When reaching [u32::MAX]
541 /// starts from 1. Using atomics.
542 #[cfg(target_has_atomic = "64")]
543 fn generate_sequence_num() -> NonZero<u32>
544 {
545 use std::sync::atomic::{Ordering};
546
547 let mut last = SEQUENCE_ID.load(Ordering::Relaxed);
548
549 loop
550 {
551 let seq_id =
552 match last.checked_add(1)
553 {
554 Some(id) => id,
555 None =>
556 {
557 1
558 }
559 };
560
561 match SEQUENCE_ID.compare_exchange_weak(last, seq_id, Ordering::Relaxed, Ordering::Relaxed)
562 {
563 Ok(_) =>
564 return NonZero::new(seq_id).unwrap(),
565 Err(id) =>
566 last = id,
567 }
568 }
569 }
570
571 /// Generates a [NonZero] sequence number of the message. When reaching [u32::MAX]
572 /// starts from 1. Using mutex.
573 #[cfg(not(target_has_atomic = "64"))]
574 fn generate_sequence_num() -> NonZero<u32>
575 {
576 let mut seq_id_cnt =
577 SEQUENCE_ID.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
578
579 let seq_id =
580 match seq_id_cnt.checked_add(1)
581 {
582 Some(id) => id,
583 None =>
584 {
585 1
586 }
587 };
588
589 *seq_id_cnt = seq_id;
590 drop(seq_id_cnt);
591
592 return NonZero::new(seq_id).unwrap();
593 }
594
595 #[cfg(test)]
596 #[cfg(target_has_atomic = "64")]
597 fn reset_seq_counter()
598 {
599 SEQUENCE_ID.store(0, std::sync::atomic::Ordering::Relaxed);
600 }
601
602 #[cfg(test)]
603 #[cfg(not(target_has_atomic = "64"))]
604 fn reset_seq_counter()
605 {
606 *SEQUENCE_ID.lock().unwrap() = 0;
607 }
608
609 /// Adds the MSGID which identifies the type of the message. Make sure that
610 /// it does not contain any non ASCII characters or non-printable chars. Normally
611 /// it should consist from letters and digits.
612 #[inline]
613 pub
614 fn add_msg_id_str(&mut self, msg_id: &'static str) -> SyRes<()>
615 {
616 check_printable(msg_id)?;
617
618 self.msg_id = Cow::Borrowed(msg_id);
619
620 return Ok(());
621 }
622
623 /// Adds the MSGID which identifies the type of the message. Make sure that
624 /// it does not contain any non ASCII characters or non-printable chars. Normally
625 /// it should consist from letters and digits.
626 #[inline]
627 pub
628 fn add_msg_id(&mut self, msg_id: impl Into<String>) -> SyRes<()>
629 {
630 let msgid = msg_id.into();
631
632 check_printable(&msgid)?;
633
634 self.msg_id = Cow::Owned(msgid);
635
636 return Ok(());
637 }
638
639 /// Inserts the instance of the [FormatRfc5424Sd] to the list. This is a user defined
640 /// formatted message.
641 #[inline]
642 pub
643 fn add_st_data(&mut self, st_data: FormatRfc5424Sd) -> SyRes<()>
644 {
645 if self.st_data.iter().any(|(v, _)| v.as_ref() == st_data.get_sd_id()) == true
646 {
647 throw_error!("SD-ID already exists on the list: '{}'", st_data);
648 }
649
650 let items = st_data.format_sd();
651
652 self.st_data.push((Cow::Owned(st_data.get_sd_id().to_string()), items));
653
654 return Ok(());
655 }
656
657 /// Inserts the reserved type of the formatted message. The reserved types can be found
658 /// above. [] [] []
659 #[inline]
660 pub
661 fn add_reserved<I: SdIdIntrf>(&mut self, item: &I) -> SyRes<()>
662 {
663 if self.st_data.iter().any(|(v, _)| v.as_ref() == I::SD_ID_TEXT) == true
664 {
665 throw_error!("SD-ID already exists on the list: '{}'", I::SD_ID_TEXT);
666 }
667
668 let items = item.format_msg();
669
670 self.st_data.push((Cow::Borrowed(I::SD_ID_TEXT), items));
671
672 return Ok(());
673 }
674
675 #[inline]
676 fn format(&self) -> String
677 {
678 let ret =
679 self
680 .st_data
681 .iter()
682 .map(|(_, d)| [OBRACE, d.as_str(), CBRACE].concat())
683 .collect::<Vec<String>>()
684 .concat();
685
686 return ret;
687 }
688
689
690}
691
692unsafe impl Send for FormatRfc5424 {}
693
694impl From<String> for FormatRfc5424
695{
696 fn from(value: String) -> FormatRfc5424
697 {
698 return
699 Self
700 {
701 seq: Self::generate_sequence_num(),
702 msg: Cow::Owned(value),
703 msg_id: Cow::Borrowed(NILVALUE),
704 st_data: Vec::new(),
705 };
706 }
707}
708
709impl From<&'static str> for FormatRfc5424
710{
711 fn from(value: &'static str) -> FormatRfc5424
712 {
713 return
714 Self
715 {
716 seq: Self::generate_sequence_num(),
717 msg: Cow::Borrowed(value),
718 msg_id: Cow::Borrowed(NILVALUE),
719 st_data: Vec::new(),
720 };
721 }
722}
723
724
725impl SyslogFormatter for FormatRfc5424
726{
727 fn vsyslog1_format(&self, max_msg_size: usize, prifac: SyslogMsgPriFac, progname: &str, pid: Option<&str>) -> SyslogFormatted
728 {
729 // get timedate
730 let timedate =
731 Local::now().to_rfc3339_opts(SecondsFormat::Secs, false);
732
733 let hostname: String =
734 portable::portable_gethostname().map_or(NILVALUE.into(), |f| f);
735
736 // message based on RFC 5424
737 let msg_pri =
738 [
739 "<", prifac.get_val().to_string().as_str(), ">1"
740 ]
741 .concat();
742
743 let msg_st_data =
744 if self.st_data.is_empty() == false
745 {
746 Some(self.format())
747 }
748 else
749 {
750 None
751 //Cow::Borrowed(NILVALUE)
752 };
753
754 // message based on RFC 5424
755 let msg_pkt =
756 [
757 // PRI
758 //Cow::Owned(msg_pri),
759 // timedate
760 WSPACE, timedate.as_str(),
761 // hostname
762 WSPACE, hostname.as_str(),
763 // appname
764 WSPACE, progname,
765 // PID
766 WSPACE, pid.map_or(NILVALUE, |f| f),
767 // message ID
768 WSPACE, &self.msg_id,
769 // structured data
770 WSPACE, msg_st_data.as_ref().map_or(NILVALUE, |f| f.as_str()),
771 // msg
772 WSPACE,
773 /*b"\xEF\xBB\xBF",*/
774 ]
775 .concat();
776
777 // calculate the left message space
778 let msg_space_left = max_msg_size - msg_pkt.as_bytes().len();
779
780 let msg_payload = truncate_n(&self.msg, msg_space_left);
781
782 let msg_payload_final =
783 if msg_payload.ends_with("\n") == true
784 {
785 truncate(msg_payload)
786 }
787 else
788 {
789 msg_payload
790 };
791
792 // set payload
793 let msg_payload= [msg_pkt.as_str(), msg_payload_final].concat();
794
795 return
796 SyslogFormatted
797 {
798 msg_header: Some(msg_pri),
799 msg_payload: msg_payload,
800 full_msg: None
801 };
802 }
803}
804
805#[cfg(test)]
806mod tests
807{
808 use std::{borrow::Cow, time::Instant};
809
810 use crate::
811 {
812 LogFacility, Priority, SyslogMsgPriFac,
813 formatters::{FormatRfc5424, SyslogFormatter, syslog_5424::{FormatRfc5424Sd, SdIdMeta}}
814 };
815
816 #[test]
817 fn test_params1()
818 {
819 FormatRfc5424::reset_seq_counter();
820
821 let mut m = FormatRfc5424::from("test123");
822
823 m.add_msg_id_str("ATX41").unwrap();
824
825 let mut sd= FormatRfc5424Sd::new("testItem@3424", 2).unwrap();
826
827 sd.push(Cow::Borrowed("testKey"), Cow::Borrowed("testVal")).unwrap();
828 sd.push(Cow::Borrowed("testKey"), Cow::Borrowed("testVal")).unwrap();
829
830 m.add_st_data(sd).unwrap();
831
832 let fmtf = m.format();
833
834 assert_eq!(fmtf.as_str(), "[testItem@3424 testKey=\"testVal\" testKey=\"testVal\"]");
835
836
837 let mut res =
838 m.vsyslog1_format(
839 1024,
840 SyslogMsgPriFac::set_facility(Priority::LOG_CRIT, LogFacility::LOG_DAEMON),
841 "test",
842 Some("1234")
843 );
844
845 let cres = res.get_full_msg();
846
847 println!("{}", cres);
848
849 return;
850 }
851
852 #[test]
853 fn test_params2()
854 {
855 FormatRfc5424::reset_seq_counter();
856
857 let mut m = FormatRfc5424::from("test123");
858
859 m.add_msg_id_str("ATX41").unwrap();
860
861 let mut sd= FormatRfc5424Sd::new("exampleSDID@32474", 3).unwrap();
862
863 sd.push(Cow::Borrowed("iut"), Cow::Borrowed("3")).unwrap();
864 sd.push(Cow::Borrowed("eventSource"), Cow::Borrowed("Application")).unwrap();
865 sd.push(Cow::Borrowed("eventID"), Cow::Borrowed("1011")).unwrap();
866
867 m.add_st_data(sd).unwrap();
868
869 let mut sd= FormatRfc5424Sd::new("examplePriority@32474", 1).unwrap();
870
871 sd.push(Cow::Borrowed("class"), Cow::Borrowed("high")).unwrap();
872 m.add_st_data(sd).unwrap();
873
874 let s = Instant::now();
875
876 let fmtf = m.format();
877
878 let e = s.elapsed();
879
880 println!("took: {:?}", e);
881
882 println!("{}", fmtf);
883
884 assert_eq!(fmtf.as_str(), "[exampleSDID@32474 iut=\"3\" eventSource=\"Application\" eventID=\"1011\"][examplePriority@32474 class=\"high\"]");
885
886
887 let mut res =
888 m.vsyslog1_format(
889 1024,
890 SyslogMsgPriFac::set_facility(Priority::LOG_CRIT, LogFacility::LOG_DAEMON),
891 "test",
892 Some("1234")
893 );
894
895 let cres = res.get_full_msg();
896
897 println!("{}", cres);
898
899 return;
900 }
901
902 #[test]
903 fn test_params3()
904 {
905 fn test_p3(assert_tx: &str)
906 {
907 let mut m = FormatRfc5424::from("test123");
908
909 m.add_msg_id_str("ATX41").unwrap();
910
911 let v = SdIdMeta::new_test("jp", 12345, &m);
912
913 m.add_reserved(&v).unwrap();
914
915 let fmtf = m.format();
916
917 assert_eq!(fmtf.as_str(), assert_tx);
918
919 let mut res =
920 m.vsyslog1_format(
921 1024,
922 SyslogMsgPriFac::set_facility(Priority::LOG_CRIT, LogFacility::LOG_DAEMON),
923 "test",
924 Some("1234")
925 );
926
927 let cres = res.get_full_msg();
928
929 println!("{}", cres);
930 }
931
932 FormatRfc5424::reset_seq_counter();
933
934 test_p3("[meta sequenceId=\"1\" sysUpTime=\"12345\" language=\"jp\"]");
935 test_p3("[meta sequenceId=\"2\" sysUpTime=\"12345\" language=\"jp\"]");
936
937
938 return;
939 }
940
941 #[test]
942 fn test_fail_params1()
943 {
944 let mut m = FormatRfc5424::from("test123");
945
946 m.add_msg_id_str("ATX41").unwrap();
947
948 let sd= FormatRfc5424Sd::new("exampleSDID", 1);
949
950 assert_eq!(sd.is_err(), true);
951
952 let sd= FormatRfc5424Sd::new("exampleSDID@", 1);
953
954 assert_eq!(sd.is_err(), true);
955
956 let sd= FormatRfc5424Sd::new("@23", 1);
957
958 assert_eq!(sd.is_err(), true);
959
960 let sd= FormatRfc5424Sd::new("dfe@23", 0);
961
962 assert_eq!(sd.is_err(), true);
963 }
964}
965