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