udp_logger_rs/
lib.rs

1#![forbid(unsafe_code, future_incompatible, rust_2018_idioms)]
2#![deny(missing_debug_implementations, nonstandard_style)]
3#![warn(missing_docs, missing_doc_code_examples, unreachable_pub)]
4#![allow(dead_code)]
5
6//! This crate provides logging macros that incorporates key/value logging
7//! and a logger that sends all logs to a UDP port.
8//!
9//! # Examples
10//! ```
11//! use std::vec::Vec;
12//! use udp_logger_rs::info;
13//!
14//! let ctx: Vec<(String, String)> = vec![
15//!   ("cat_1".into(), "chashu".into()),
16//!   ("cat_2".into(), "nori".into()),
17//! ];
18//!
19//! info!(kvs: &ctx, "something to log");
20//! ```
21use log::kv::{Error, Key, Value, Visitor};
22use log::{Log, Metadata, Record, SetLoggerError};
23use std::io::Write;
24use std::net::UdpSocket;
25
26// publicly exporting so $crate::Level works.
27pub use log::Level;
28
29// publicly exporting to make configuring simpler.
30pub use log::LevelFilter;
31
32/// The statically resolved maximum log level.
33pub const STATIC_MAX_LEVEL: LevelFilter = log::STATIC_MAX_LEVEL;
34
35/// Returns the current maximum log level.
36#[inline]
37pub fn max_level() -> LevelFilter {
38    log::max_level()
39}
40
41/// The standard logging macro.
42///
43/// # Examples
44///
45/// ```no_run
46/// # use std::vec::Vec;
47/// use udp_logger_rs::info;
48///
49/// // The standard logging we know and love
50/// info!("hello");
51/// info!("hello",);
52/// info!("hello {}", "cats");
53/// info!("hello {}", "cats",);
54/// info!(target: "MyApp", "hello");
55/// info!(target: "MyApp", "hello",);
56/// info!(target: "MyApp", "hello {}", "cats");
57/// info!(target: "MyApp", "hello {}", "cats",);
58///
59/// // The kv logging we hope to love
60/// let ctx: Vec<(String, String)> = vec![
61///    ("cat_1".into(), "chashu".into()),
62///    ("cat_2".into(), "nori".into()),
63/// ];
64///
65/// // default target
66/// info!(kvs: &ctx, "hello");
67/// info!(kvs: &ctx, "hello",);
68/// info!(kvs: &ctx, "hello {}", "cats");
69/// info!(kvs: &ctx, "hello {}", "cats",);
70///
71/// // with target provided
72/// info!(target: "MyApp", kvs: &ctx, "hello");
73/// info!(target: "MyApp", kvs: &ctx, "hello",);
74/// info!(target: "MyApp", kvs: &ctx, "hello {}", "cats");
75/// info!(target: "MyApp", kvs: &ctx, "hello {}", "cats",);
76/// ```
77#[macro_export(local_inner_macros)]
78macro_rules! log {
79    (target: $target:expr, kvs: $kvs:expr, $lvl:expr, $($arg:tt)+) => ({
80        let lvl = $lvl;
81        if lvl <= $crate::STATIC_MAX_LEVEL && lvl <= $crate::max_level() {
82            $crate::__private_api_log(
83                __log_format_args!($($arg)+),
84                lvl,
85                &($target, __log_module_path!(), __log_file!(), __log_line!()),
86                Some($kvs),
87            );
88        }
89    });
90    (target: $target:expr, $lvl:expr, $($arg:tt)+) => ({
91        let lvl = $lvl;
92        if lvl <= $crate::STATIC_MAX_LEVEL && lvl <= $crate::max_level() {
93            $crate::__private_api_log(
94                __log_format_args!($($arg)+),
95                lvl,
96                &($target, __log_module_path!(), __log_file!(), __log_line!()),
97                None,
98            );
99        }
100    });
101    (kvs: $kvs:expr, $lvl:expr, $($arg:tt)+) => ({
102        (log!(target: __log_module_path!(), kvs: $kvs, $lvl, $($arg)+))    });
103
104    ($lvl:expr, $($arg:tt)+) => (log!(target: __log_module_path!(), $lvl, $($arg)+))
105}
106
107#[macro_export(local_inner_macros)]
108#[doc(hidden)]
109macro_rules! log_impl {
110    // End of macro input
111    (target: $target:expr, kvs: $kvs:expr, $lvl:expr, ($($arg:expr),*)) => {{
112        let lvl = $lvl;
113        if lvl <= $crate::STATIC_MAX_LEVEL && lvl <= $crate::max_level() {
114            $crate::__private_api_log(
115                __log_format_args!($($arg),*),
116                lvl,
117                &($target, __log_module_path!(), __log_file!(), __log_line!()),
118                $kvs,
119            );
120        }
121    }};
122}
123
124/// Logs a message at the trace level.
125#[macro_export(local_inner_macros)]
126macro_rules! trace {
127    (target: $target:expr, kvs: $kvs:expr, $($arg:tt)+) => (
128        log!(target: $target, kvs: $kvs, $crate::Level::Trace, $($arg)+);
129    );
130    (target: $target:expr, $($arg:tt)+) => (
131        log!(target: $target, $crate::Level::Trace, $($arg)+);
132    );
133    (kvs: $kvs:expr, $($arg:tt)+) => (
134        log!(kvs: $kvs, $crate::Level::Trace, $($arg)+);
135    );
136    ($($arg:tt)+) => (
137        log!($crate::Level::Trace, $($arg)+);
138    )
139}
140
141/// Logs a message at the debug level.
142#[macro_export(local_inner_macros)]
143macro_rules! debug {
144    (target: $target:expr, kvs: $kvs:expr, $($arg:tt)+) => (
145        log!(target: $target, kvs: $kvs, $crate::Level::Debug, $($arg)+);
146    );
147    (target: $target:expr, $($arg:tt)+) => (
148        log!(target: $target, $crate::Level::Debug, $($arg)+);
149    );
150    (kvs: $kvs:expr, $($arg:tt)+) => (
151        log!(kvs: $kvs, $crate::Level::Debug, $($arg)+);
152    );
153    ($($arg:tt)+) => (
154        log!($crate::Level::Debug, $($arg)+);
155    )
156}
157
158/// Logs a message at the info level.
159#[macro_export(local_inner_macros)]
160macro_rules! info {
161    (target: $target:expr, kvs: $kvs:expr, $($arg:tt)+) => (
162        log!(target: $target, kvs: $kvs, $crate::Level::Info, $($arg)+);
163    );
164    (target: $target:expr, $($arg:tt)+) => (
165        log!(target: $target, $crate::Level::Info, $($arg)+);
166    );
167    (kvs: $kvs:expr, $($arg:tt)+) => (
168        log!(kvs: $kvs, $crate::Level::Info, $($arg)+);
169    );
170    ($($arg:tt)+) => (
171        log!($crate::Level::Info, $($arg)+);
172    )
173}
174
175/// Logs a message at the warn level.
176#[macro_export(local_inner_macros)]
177macro_rules! warn {
178    (target: $target:expr, kvs: $kvs:expr, $($arg:tt)+) => (
179        log!(target: $target, kvs: $kvs, $crate::Level::Warn, $($arg)+);
180    );
181    (target: $target:expr, $($arg:tt)+) => (
182        log!(target: $target, $crate::Level::Warn, $($arg)+);
183    );
184    (kvs: $kvs:expr, $($arg:tt)+) => (
185        log!(kvs: $kvs, $crate::Level::Warn, $($arg)+);
186    );
187    ($($arg:tt)+) => (
188        log!($crate::Level::Warn, $($arg)+);
189    )
190}
191
192/// Logs a message at the error level.
193#[macro_export(local_inner_macros)]
194macro_rules! error {
195    (target: $target:expr, kvs: $kvs:expr, $($arg:tt)+) => (
196        log!(target: $target, kvs: $kvs, $crate::Level::Error, $($arg)+);
197    );
198    (target: $target:expr, $($arg:tt)+) => (
199        log!(target: $target, $crate::Level::Error, $($arg)+);
200    );
201    (kvs: $kvs:expr, $($arg:tt)+) => (
202        log!(kvs: $kvs, $crate::Level::Error, $($arg)+);
203    );
204    ($($arg:tt)+) => (
205        log!($crate::Level::Error, $($arg)+);
206    )
207}
208
209/// Determines if a message logged at the specified level in that module will
210/// be logged.
211#[macro_export(local_inner_macros)]
212macro_rules! log_enabled {
213    (target: $target:expr, $lvl:expr) => {{
214        let lvl = $lvl;
215        lvl <= $crate::STATIC_MAX_LEVEL
216            && lvl <= $crate::max_level()
217            && $crate::__private_api_enabled(lvl, $target)
218    }};
219    ($lvl:expr) => {
220        log_enabled!(target: __log_module_path!(), $lvl)
221    };
222}
223
224#[doc(hidden)]
225#[macro_export]
226macro_rules! __log_format_args {
227    ($($args:tt)*) => {
228        format_args!($($args)*)
229    };
230}
231
232#[doc(hidden)]
233#[macro_export]
234macro_rules! __log_module_path {
235    () => {
236        module_path!()
237    };
238}
239
240#[doc(hidden)]
241#[macro_export]
242macro_rules! __log_file {
243    () => {
244        file!()
245    };
246}
247
248#[doc(hidden)]
249#[macro_export]
250macro_rules! __log_line {
251    () => {
252        line!()
253    };
254}
255
256// WARNING: this is not part of the crate's public API and is subject to change at any time
257#[doc(hidden)]
258pub fn __private_api_log(
259    args: std::fmt::Arguments<'_>,
260    level: log::Level,
261    &(target, module_path, file, line): &(&str, &'static str, &'static str, u32),
262    kvs: Option<&dyn log::kv::Source>,
263) {
264    log::logger().log(
265        &log::Record::builder()
266            .args(args)
267            .level(level)
268            .target(target)
269            .module_path_static(Some(module_path))
270            .file_static(Some(file))
271            .line(Some(line))
272            .key_values(&kvs)
273            .build(),
274    );
275}
276
277// enough with the macros, on with the UDP logging
278
279/// Wire formats. Default is Uncompressed.
280///
281/// * Uncompressed, the entire payload is a string, formatted as:
282/// ```no_run
283/// # use chrono::Utc;
284/// # let record = log::Record::builder().build();
285/// # let target = "App";
286/// format!("{} {:<5} [{}] {}",
287///     Utc::now().format("%Y-%m-%d %H:%M:%S%.3f"),
288///     record.level().to_string(),
289///     target,
290///     record.args()
291/// );
292/// ```
293/// and has kv pairs, appended, as:
294/// ```no_run
295/// # let k = "key1";
296/// # let v = "value1";
297/// format!(" {}={}", k, v);
298/// ```
299/// * ByteBuffer, the entire payload is a u8 level, i64 Utc::now().timestamp_millis(), and
300/// u32 string length followed by length * utf8.
301#[derive(Debug)]
302pub enum WireFmt {
303    /// No Compression, the payload can be consistered a string of utf8 bytes.
304    Uncompressed,
305    /// 1 byte Level, 8 bytes timestamp, 4 bytes len followed by len * utf8 (string)
306    ByteBuffer,
307}
308
309/// The UdpLogger is a control structure for logging via UDP packets.
310#[derive(Debug)]
311pub struct UdpLogger {
312    default_level: LevelFilter,
313    module_levels: Vec<(String, LevelFilter)>,
314    default_source: UdpSocket,
315    sources: Vec<(LevelFilter, UdpSocket)>,
316    default_destination: String,
317    destinations: Vec<(LevelFilter, String)>,
318    wire_fmt: WireFmt,
319}
320
321impl UdpLogger {
322    /// Initializes the global logger with a UdpLogger instance with
323    /// default log level set to `Level::Trace`.
324    ///
325    /// # Examples
326    /// ```no_run
327    /// use udp_logger_rs::{UdpLogger, warn};
328    ///
329    /// UdpLogger::new().env().init().unwrap();
330    /// warn!("This is an example message.");
331    /// ```
332    ///
333    /// [`init`]: #method.init
334    #[must_use = "You must call init() to begin logging"]
335    pub fn new() -> Self {
336        let socket = UdpSocket::bind("127.0.0.1:4000").expect("unable to bind to socket");
337        socket
338            .set_nonblocking(true)
339            .expect("unable to set socket non-blocking");
340
341        Self {
342            default_level: LevelFilter::Trace,
343            module_levels: Vec::new(),
344            default_source: socket,
345            sources: Vec::new(),
346            default_destination: "127.0.0.1:4010".to_string(),
347            destinations: Vec::new(),
348            wire_fmt: WireFmt::Uncompressed,
349        }
350    }
351
352    /// Simulates env_logger behavior, which enables the user to choose log
353    /// level by setting a `RUST_LOG` environment variable. This will use
354    /// the default level set by [`with_level`] if `RUST_LOG` is not set or
355    /// can't be parsed as a standard log level.
356    ///
357    /// [`with_level`]: #method.with_level
358    #[must_use = "You must call init() to begin logging"]
359    pub fn env(mut self) -> Self {
360        if let Ok(level) = std::env::var("RUST_LOG") {
361            match level.to_lowercase().as_str() {
362                "trace" => self.default_level = log::LevelFilter::Trace,
363                "debug" => self.default_level = log::LevelFilter::Debug,
364                "info" => self.default_level = log::LevelFilter::Info,
365                "warn" => self.default_level = log::LevelFilter::Warn,
366                "error" => self.default_level = log::LevelFilter::Error,
367                _ => (),
368            }
369        };
370        self
371    }
372
373    /// Set the 'default' log level.
374    ///
375    /// You can override the default level for specific modules and their sub-modules using [`with_module_level`]
376    ///
377    /// [`with_module_level`]: #method.with_module_level
378    #[must_use = "You must call init() to begin logging"]
379    pub fn with_level(mut self, level: LevelFilter) -> Self {
380        self.default_level = level;
381        self
382    }
383
384    /// Override the log level for some specific modules.
385    ///
386    /// This sets the log level of a specific module and all its sub-modules.
387    /// When both the level for a parent module as well as a child module are set,
388    /// the more specific value is taken. If the log level for the same module is
389    /// specified twice, the resulting log level is implementation defined.
390    ///
391    /// # Examples
392    ///
393    /// Silence an overly verbose crate:
394    ///
395    /// ```no_run
396    /// use udp_logger_rs::UdpLogger;
397    /// use log::LevelFilter;
398    ///
399    /// UdpLogger::new().with_module_level("chatty_dependency", LevelFilter::Warn).init().unwrap();
400    /// ```
401    ///
402    /// Disable logging for all dependencies:
403    ///
404    /// ```no_run
405    /// use udp_logger_rs::UdpLogger;
406    /// use log::LevelFilter;
407    ///
408    /// UdpLogger::new()
409    ///     .with_level(LevelFilter::Off)
410    ///     .with_module_level("my_crate", LevelFilter::Info)
411    ///     .init()
412    ///     .unwrap();
413    /// ```
414    #[must_use = "You must call init() to begin logging"]
415    pub fn with_module_level(mut self, target: &str, level: LevelFilter) -> Self {
416        self.module_levels.push((target.to_string(), level));
417
418        /* Normally this is only called in `init` to avoid redundancy, but we can't initialize the logger in tests */
419        #[cfg(test)]
420        self.module_levels
421            .sort_by_key(|(name, _level)| name.len().wrapping_neg());
422
423        self
424    }
425
426    /// Override the default source socket.
427    ///
428    /// This sets the default source socket, which otherwise defaults to "127.0.0.1:4000".
429    ///
430    /// # Examples
431    ///
432    /// Log from UDP port "127.0.0.1:4444"
433    ///
434    /// ```no_run
435    /// use udp_logger_rs::UdpLogger;
436    ///
437    /// UdpLogger::new()
438    ///     .with_source("127.0.0.1:4444")
439    ///     .init()
440    ///     .unwrap();
441    /// ```
442    #[must_use = "You must call init() to begin logging"]
443    pub fn with_source(mut self, source: &str) -> Self {
444        let socket = UdpSocket::bind(source).expect("unable to bind to socket");
445        socket
446            .set_nonblocking(true)
447            .expect("unable to set socket non-blocking");
448        self.default_source = socket;
449
450        self
451    }
452
453    /// Provide a level specific source address.
454    ///
455    /// This sets the source address, for log messages matching the level.
456    ///
457    /// # Examples
458    ///
459    /// Log from UDP port "127.0.0.1:4001" all Info log messages. Trace and Debug messages
460    /// will log from the default source address. If with_source_level() for
461    /// Warn or Error aren't set, those levels will also send from "127.0.0.1:4001".
462    ///
463    /// ```no_run
464    /// use udp_logger_rs::UdpLogger;
465    /// use log::LevelFilter;
466    ///
467    /// UdpLogger::new()
468    ///     .with_source_level("127.0.0.1:4001", LevelFilter::Info)
469    ///     .init()
470    ///     .unwrap();
471    /// ```
472    #[must_use = "You must call init() to begin logging"]
473    pub fn with_source_level(mut self, source: &str, level: LevelFilter) -> Self {
474        let socket = UdpSocket::bind(source).expect("unable to bind to socket");
475        socket
476            .set_nonblocking(true)
477            .expect("unable to set socket non-blocking");
478        self.sources.push((level, socket));
479
480        self
481    }
482
483    /// Override the default destination address.
484    ///
485    /// This sets the default destination address, which otherwise defaults to "127.0.0.1:4010".
486    ///
487    /// # Examples
488    ///
489    /// Log to UDP port "127.0.0.1:4040"
490    ///
491    /// ```no_run
492    /// use udp_logger_rs::UdpLogger;
493    ///
494    /// UdpLogger::new()
495    ///     .with_destination("127.0.0.1:4040")
496    ///     .init()
497    ///     .unwrap();
498    /// ```
499    #[must_use = "You must call init() to begin logging"]
500    pub fn with_destination(mut self, destination: &str) -> Self {
501        self.default_destination = destination.to_string();
502
503        self
504    }
505
506    /// Provide a level specific destination address.
507    ///
508    /// This sets the destination address, for log messages matching the level.
509    ///
510    /// # Examples
511    ///
512    /// Log to UDP port "127.0.0.1:4040" all Info log messages. Trace and Debug messages
513    /// will send to the default destination address. If with_destination_level() for
514    /// Warn or Error aren't set, those levels will also send to "127.0.0.1:4040".
515    ///
516    /// ```no_run
517    /// use udp_logger_rs::UdpLogger;
518    /// use log::LevelFilter;
519    /// UdpLogger::new()
520    ///     .with_destination_level("127.0.0.1:4040", LevelFilter::Info)
521    ///     .init()
522    ///     .unwrap();
523    /// ```
524    #[must_use = "You must call init() to begin logging"]
525    pub fn with_destination_level(mut self, destination: &str, level: LevelFilter) -> Self {
526        self.destinations.push((level, destination.to_string()));
527
528        self
529    }
530
531    /// Set the wire format for logging.
532    #[must_use = "You must call init() to begin logging"]
533    pub fn with_wire_fmt(mut self, wire_fmt: WireFmt) -> Self {
534        self.wire_fmt = wire_fmt;
535
536        self
537    }
538
539    #[doc(hidden)]
540    // partial_init is used internally in init() and in testing.
541    pub fn partial_init(mut self) -> Self {
542        /* Sort all module levels from most specific to least specific. The length of the module
543         * name is used instead of its actual depth to avoid module name parsing.
544         */
545        self.module_levels
546            .sort_by_key(|(name, _level)| name.len().wrapping_neg());
547        let max_level = self
548            .module_levels
549            .iter()
550            .map(|(_name, level)| level)
551            .copied()
552            .max();
553        let max_level = max_level
554            .map(|lvl| lvl.max(self.default_level))
555            .unwrap_or(self.default_level);
556
557        self.sources.sort_by_key(|(level, _socket)| *level);
558        self.destinations.sort_by_key(|(level, _socket)| *level);
559        log::set_max_level(max_level);
560
561        self
562    }
563    /// 'Init' the actual logger, instantiate it and configure it,
564    /// this method MUST be called in order for the logger to be effective.
565    pub fn init(self) -> Result<(), SetLoggerError> {
566        let logger = self.partial_init();
567        log::set_boxed_logger(Box::new(logger))?;
568        Ok(())
569    }
570}
571
572impl Default for UdpLogger {
573    /// See [this](struct.UdpLogger.html#method.new)
574    fn default() -> Self {
575        UdpLogger::new()
576    }
577}
578
579#[derive(Default)]
580struct KVAccumulator(String);
581
582impl<'kvs> Visitor<'kvs> for KVAccumulator {
583    fn visit_pair(&mut self, key: Key<'kvs>, value: Value<'kvs>) -> Result<(), Error> {
584        self.0.push_str(&format!(" {}={}", key, value));
585        Ok(())
586    }
587}
588
589impl Log for UdpLogger {
590    fn enabled(&self, metadata: &Metadata<'_>) -> bool {
591        &metadata.level().to_level_filter()
592            <= self
593                .module_levels
594                .iter()
595                /* At this point the Vec is already sorted so that we can simply take
596                 * the first match
597                 */
598                .find(|(name, _level)| metadata.target().starts_with(name))
599                .map(|(_name, level)| level)
600                .unwrap_or(&self.default_level)
601    }
602
603    fn log(&self, record: &Record<'_>) {
604        if self.enabled(record.metadata()) {
605            let socket = self
606                .sources
607                .iter()
608                .find(|(level, _socket)| level >= &record.level())
609                .map(|(_level, socket)| socket)
610                .unwrap_or_else(|| &self.default_source);
611
612            let remote_addr = self
613                .destinations
614                .iter()
615                .find(|(level, _socket)| level >= &record.level())
616                .map(|(_level, socket)| socket)
617                .unwrap_or_else(|| &self.default_destination);
618
619            let target = if !record.target().is_empty() {
620                record.target()
621            } else {
622                record.module_path().unwrap_or_default()
623            };
624            let source = record.key_values();
625            let mut visitor = KVAccumulator::default();
626            let _result = source.visit(&mut visitor);
627
628            let result = match self.wire_fmt {
629                WireFmt::Uncompressed => {
630                    let payload = format!(
631                        "{} {:<5} [{}] {}{}",
632                        chrono::Utc::now().format("%Y-%m-%d %H:%M:%S%.3f"),
633                        record.level().to_string(),
634                        target,
635                        record.args(),
636                        visitor.0
637                    );
638                    socket.send_to(payload.as_bytes(), remote_addr)
639                }
640                WireFmt::ByteBuffer => {
641                    let mut encoder = bytebuffer::ByteBuffer::new();
642                    let level: [u8; 1] = match record.level() {
643                        Level::Error => [1],
644                        Level::Warn => [2],
645                        Level::Info => [3],
646                        Level::Debug => [4],
647                        Level::Trace => [5],
648                    };
649                    let now = chrono::Utc::now().timestamp_millis().to_be_bytes();
650                    let text = format!("[{}] {}{}", target, record.args(), visitor.0);
651                    encoder
652                        .write(&level)
653                        .and_then(|_count| encoder.write(&now))
654                        .and_then(|_count| {
655                            encoder.write_string(&text);
656                            socket.send_to(&encoder.to_bytes(), remote_addr)
657                        })
658                }
659            };
660            match result {
661                Ok(_) => (),
662                Err(err) => {
663                    println!("error sending payload, err={}", err)
664                }
665            };
666        }
667    }
668
669    fn flush(&self) {}
670}