1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
//! Holochain's log implementation.
//!
//! This logger implementation is designed to be fast and to provide useful filtering combination capabilities.
//!
//! # Use
//!
//! The basic use of the log crate is through the five logging macros: [`error!`],
//! [`warn!`], [`info!`], [`debug!`] and [`trace!`]
//! where `error!` represents the highest-priority log messages
//! and `trace!` the lowest. The log messages are filtered by configuring
//! the log level to exclude messages with a lower priority.
//! Each of these macros accept format strings similarly to [`println!`].
//!
//! [`error!`]: ../log/macro.error.html
//! [`warn!`]: ../log/macro.warn.html
//! [`info!`]: ../log/macro.info.html
//! [`debug!`]: ..log/macro.debug.html
//! [`trace!`]: ../log/macro.trace.html
//! [`println!`]: https://doc.rust-lang.org/stable/std/macro.println.html
//!
//! # Quick Start
//!
//! To get you started quickly, the easiest and highest-level way to get a working logger (with the
//! [`Info`](Level::Info) log verbosity level) is to use [`init_simple`].
//!
//! ```edition2018
//! use holochain_logging::prelude::*;
//!
//! // We need a guard here in order to gracefully shutdown
//! // the logging thread
//! let mut guard = holochain_logging::init_simple().unwrap();
//! info!("Here you go champ!");
//!
//! // Warning and Error log message have their own color
//! warn!("You've been warned Sir!");
//! error!("Oh... something wrong pal.");
//!
//! // Flushes any buffered records
//! guard.flush();
//! // Flush and shutdown gracefully the logging thread
//! guard.shutdown();
//! ```
//!
//! ### Examples
//!
//! #### Simple log with Trace verbosity level.
//!
//! In order to log everything with at least the [`Debug`](Level::Debug) verbosity level:
//!
//! ```edition2018
//! use holochain_logging::prelude::*;
//!
//! // We need a guard here in order to gracefully shutdown
//! // the logging thread
//! let mut guard = FastLoggerBuilder::new()
//!     // The timestamp format is customizable as well
//!     .timestamp_format("%Y-%m-%d %H:%M:%S%.6f")
//!     .set_level_from_str("Debug")
//!     .build()
//!     .expect("Fail to init the logging factory.");
//!
//! debug!("Let's trace what that program is doing.");
//!
//! // Flushes any buffered records
//! guard.flush();
//! // Flush and shutdown gracefully the logging thread
//! guard.shutdown();
//! ```
//!
//! #### Building the logging factory from TOML configuration.
//!
//! The logger can be built from a [TOML](https://github.com/toml-lang/toml) configuration file:
//!
//! ```edition2018
//! use holochain_logging::prelude::*;
//! let toml = r#"
//!    [logger]
//!    level = "debug"
//!
//!         [[logger.rules]]
//!         pattern = "info"
//!         exclude = false
//!         color = "Blue"
//!     "#;
//! // We need a guard here in order to gracefully shutdown
//! // the logging thread
//! let mut guard = FastLoggerBuilder::from_toml(toml)
//!     .expect("Fail to instantiate the logger from toml.")
//!      .build()
//!      .expect("Fail to build logger from toml.");
//!
//! // Should NOT be logged because of the verbosity level set to Debug
//! trace!("Track me if you can.");
//! debug!("What's bugging you today?");
//!
//! // This one is colored in blue because of our rule on 'info' pattern
//! info!("Some interesting info here");
//!
//! // Flushes any buffered records
//! guard.flush();
//! // Flush and shutdown gracefully the logging thread
//! guard.shutdown();
//! ```
//!
//! #### Dependency filtering
//!
//! Filtering out every log from dependencies and putting back in everything related to a
//! particular [`target`](../log/struct.Record.html#method.target)
//!
//! ```edition2018
//! use holochain_logging::prelude::*;
//!
//! let toml = r#"
//!     [logger]
//!     level = "debug"
//!
//!         [[logger.rules]]
//!         pattern = ".*"
//!         exclude = true
//!
//!         [[logger.rules]]
//!         pattern = "^holochain"
//!         exclude = false
//!     "#;
//! // We need a guard here in order to gracefully shutdown
//! // the logging thread
//! let mut guard = FastLoggerBuilder::from_toml(toml)
//!     .expect("Fail to instantiate the logger from toml.")
//!     .build()
//!     .expect("Fail to build logger from toml.");
//!
//! // Should NOT be logged
//! debug!(target: "rpc", "This is our dependency log filtering.");
//!
//! // Should be logged each in different color. We avoid filtering by prefixing using the 'target'
//! // argument.
//! info!(target: "holochain", "Log message from Holochain Core.");
//! info!(target: "holochain-app-2", "Log message from Holochain Core with instance ID 2");
//!
//! // Flushes any buffered records
//! guard.flush();
//! // Flush and shutdown gracefully the logging thread
//! guard.shutdown();
//! ```

use log;
use chrono;
use colored::*;
use log::{Level, Metadata, Record, SetLoggerError};
use serde_derive::Deserialize;
use toml;

pub mod prelude;
pub mod color;
pub mod rule;

use color::{pick_color, ColoredLevelConfig};
use crossbeam_channel::{self, Receiver, Sender};
use rule::{Rule, RuleFilter};
use std::{
    boxed::Box,
    default::Default,
    env,
    io::{self, Write},
    str::FromStr,
    thread,
    ops::Drop,
};

/// Default format of the log's timestamp.
const DEFAULT_TIMESTAMP_FMT: &str = "%Y-%m-%d %H:%M:%S";
/// Default channel size value.
const DEFAULT_CHANNEL_SIZE: usize = 1024;
/// Default log verbosity level.
const DEFAULT_LOG_LEVEL: Level = Level::Info;
/// Default log verbosity level as a String.
const DEFAULT_LOG_LEVEL_STR: &str = "Info";

/// Helper type pointing to a trait object in order to send around a log message.
type MsgT = Box<dyn LogMessageTrait>;

/// The logging struct where we store everything we need in order to correctly log stuff.
pub struct FastLogger {
    /// Log verbosity level.
    level: Level,
    /// List of filtering [rules](RuleFilter).
    rule_filters: Vec<RuleFilter>,
    /// Color of the verbosity levels.
    level_colors: ColoredLevelConfig,
    /// Thread producer used to send log message to the log consumer.
    sender: Sender<MsgT>,
    /// Timestamp format of each log.
    timestamp_format: String,
    /// Thread handle used to gracefully shutdown the logging thread.
    handle: Option<thread::JoinHandle<()>>,
}

impl FastLogger {
    /// Returns the current log level defined. Can be one of: Trace, Debug, Info, Warn or Error.
    pub fn level(&self) -> Level {
        self.level
    }

    /// Returns all the *rules* defined.
    pub fn rule_filters(&self) -> &Vec<RuleFilter> {
        &self.rule_filters
    }

    /// Returns the color of a log message if the logger should log it, and None other wise.
    /// We want to return the last match so we can combine rules and filters.
    pub fn should_log_in(&self, args: &str) -> Option<String> {
        if self.rule_filters.is_empty() {
            Some(String::default())
        } else {
            let mut color = Some(String::default());
            for rule_filter in self.rule_filters.iter() {
                if rule_filter.is_match(args) {
                    if rule_filter.exclude() {
                        color = None;
                    } else {
                        color = Some(rule_filter.get_color());
                    }
                }
            }
            color
        }
    }

    /// Add a rule filter to the list of existing filter. This function has to be called before
    /// registering the logger or it will do nothing because the logger becomes static.
    pub fn add_rule_filter(&mut self, rule_filter: RuleFilter) {
        self.rule_filters.push(rule_filter);
    }

    /// Flush all filter from the logger. Once a logger has been registered, it becomes static and
    /// so this function doesn't do anything.
    pub fn flush_filters(&mut self) {
        self.rule_filters.clear();
    }

    /// Flushes the write buffer inside the logging thread.
    pub fn flush(&self) {
        let flush_signal = Box::new(LogMessage::new_flush_signal());
        self.sender.try_send(flush_signal)
            .unwrap_or_else(|_| {}); // Let's safely handle any error from here
            // .unwrap_or_else(|_| eprintln!("Fail to send flush signal."));
    }

    /// Wrapper function to avoid collision with [flush](log::Log::flush) from the [`log`] crate trait.
    fn flush_buffer(&self) {
        self.flush();
    }

    /// Gracefully shutdown the logging thread and flush its writing buffer.
    pub fn shutdown(&mut self) {
        // Send a signal to exit the infinite loop of the logging thread
        let terminate_signal = Box::new(LogMessage::new_terminate_signal());
        self.sender.send(terminate_signal)
            .unwrap_or_else(|_| eprintln!("Fail to send thread shutdown signal. Maybe it was already sent ?"));

        // And then gracefully shutdown the logging thread
        if let Some(handle) = self.handle.take() {
            handle.join().expect("Fail to shutdown the logging thread.");
        }
    }
}

impl Drop for FastLogger {
    // /// Custom drop definition to handle a more gracefull shutdown of our logging thread.
    // /// This solution looks the best but it's currently not possible with the way unit tests are
    // /// implemented in Holochain and more broadly in Rust. Specifically every unit tests access the
    // /// same registered logger. So we fallback to a more hacky one by just giving some arbitrary
    // /// time to the logging thread to finish it's business.
    // fn drop(&mut self) {
    //     // Send a signal to exit the infinite loop of the logging thread
    //     let terminate_signal = Box::new(LogMessage::new_terminate_signal());
    //     self.sender.send(terminate_signal)
    //         .unwrap_or_else(|_| eprintln!("Fail to send thread shutdown signal. Maybe already sent?"));
    //
    //     // And then gracefully shutdown the logging thread
    //     if let Some(handle) = self.handle.take() {
    //         handle.join().expect("Fail to shutdown the logging thread.");
    //      }
    // }

    /// This a fall back solution to give some arbitrary time to the logging thread to finish it's
    /// business.
    fn drop(&mut self) {
        self.flush_buffer();
        // This one is a dilema between usability vs performance.
        // Adding a wait duration is defininatly a performance counter but it's usefull from the
        // user side because it help make those logs pop out.
        // In the end a logger should be only registered once, so it should not be a problem for
        // longer than 10ms runtime apps
        std::thread::sleep(std::time::Duration::from_millis(10));
    }
}

impl log::Log for FastLogger {
    /// Determines if a log message with the specified metadata would be logged.
    fn enabled(&self, metadata: &Metadata) -> bool {
        metadata.level() <= self.level()
    }

    /// This is where we build the log message and send it to the logging thread that is in charge
    /// of formatting and printing the log message.
    fn log(&self, record: &Record) {
        let args = record.args().to_string();
        let target = record.target().to_string();
        // Concatenate those two in order to use filtering on both
        let should_log_in = self.should_log_in(&format!("{} {}", &target, &args));

        if self.enabled(record.metadata()) && should_log_in != None {
            let msg = LogMessage {
                args,
                module: record.module_path().unwrap_or("module-name").to_string(),
                line: record.line().unwrap_or(000),
                file: record.file().unwrap_or("").to_string(),
                level: record.level(),
                level_to_print: self.level_colors.color(record.level()).to_string(),
                thread_name: std::thread::current()
                    .name()
                    // .unwrap_or("Anonymous-thread")
                    .unwrap_or(&String::default())
                    .to_string(),
                color: should_log_in,
                target: Some(target),
                timestamp_format: self.timestamp_format.to_owned(),
                ..Default::default()
            };

            self.sender
                .send(Box::new(msg))
                .expect("Fail to send message to the logging thread.");
        }
    }

    /// Flushes any buffered records.
    fn flush(&self) {
        self.flush_buffer();
    }
}

/// Logger Builder used to correctly set up our logging capability.
pub struct FastLoggerBuilder {
    /// Verbosity level of the logger.
    level: Level,
    /// List of filtering [rules](RuleFilter).
    rule_filters: Vec<RuleFilter>,
    /// Color of the verbosity levels.
    level_colors: ColoredLevelConfig,
    /// Size of the channel used to send log message. Currently defaulting to 512.
    channel_size: usize,
    /// The path of the file where the log will be dump in the optional case we redirect logs to a file.
    file_path: Option<String>,
    /// Timestamp format of each log.
    timestamp_format: String,
}

///
/// ```rust
/// use holochain_logging::FastLoggerBuilder;
///
/// let logger = FastLoggerBuilder::new()
///     .set_level_from_str("Debug")
///     .set_channel_size(512)
///     .build();
///
/// assert!(logger.is_ok());
/// ```

impl<'a> FastLoggerBuilder {
    /// It will init a [FastLogger] with the default argument (log level set to
    /// [Info](log::Level::Info) by default).
    pub fn new() -> Self {
        FastLoggerBuilder::default()
    }

    /// Instantiate a logger builder from a config file in TOML format.
    /// ```rust
    /// use holochain_logging::FastLoggerBuilder;
    ///
    /// let toml = r#"
    /// [logger]
    /// level = "debug"
    ///     [[logger.rules]]
    ///     pattern = ".*"
    ///     color = "red"
    /// "#;
    ///
    /// let logger = FastLoggerBuilder::from_toml(toml)
    ///                 .expect("Fail to instantiate the logger from toml.");
    /// assert!(logger.build().is_ok());
    /// ```
    pub fn from_toml(toml_str: &str) -> Result<Self, toml::de::Error> {
        let logger_conf: LoggerConfig = toml::from_str(toml_str)?;
        let logger: Option<Logger> = logger_conf.logger;
        assert!(
            logger.is_some(),
            "The 'logger' part might be missing in the toml."
        );

        Ok(logger.unwrap().into())
    }

    /// Returns the [verbosity level](log::Level) of logger to build. Can be one of:
    /// [Trace](Level::Trace), [Debug](Level::Debug), [Info](Level::Info),
    /// [Warn](Level::Warn) or [Error](Level::Error).
    pub fn level(&self) -> Level {
        self.level
    }
    /// Set the [verbosity level](log::Level) of the logger.
    /// Value can be one of: [Trace](Level::Trace), [Debug](Level::Debug), [Info](Level::Info),
    /// [Warn](Level::Warn) or [Error](Level::Error).
    pub fn set_level(&mut self, level: Level) -> &mut Self {
        self.level = level;
        self
    }

    /// Set the [verbosity level](log::Level) of the logger from a string value: Trace, Debug,
    /// Info, Warn or Error.
    pub fn set_level_from_str(&mut self, level: &str) -> &mut Self {
        self.level = Level::from_str(level).unwrap_or_else(|_| {
            eprintln!("Fail to parse the logging level from string: '{}'.", level);
            self.level
        });
        self
    }

    /// Sets the capacity of our bounded message queue (i.e. there is a limit to how many messages
    /// it can hold at a time.). By default we use a queue of 1024.
    pub fn set_channel_size(&mut self, channel_size: usize) -> &mut Self {
        self.channel_size = channel_size;
        self
    }

    /// Customize our logging timestamp.
    pub fn timestamp_format(&mut self, timestamp_fmt: &str) -> &mut Self {
        self.timestamp_format = timestamp_fmt.to_owned();
        self
    }

    /// Add filtering [rules](rule::RuleFilter) to the logging facility.
    pub fn add_rule_filter(&mut self, rule_filter: RuleFilter) -> &mut Self {
        self.rule_filters.push(rule_filter);
        self
    }

    /// Returns all the [rules](Rule) that will be applied to the logger.
    pub fn rule_filters(&self) -> &[RuleFilter] {
        &self.rule_filters
    }

    /// Redirect log message to the provided file path.
    pub fn redirect_to_file(&mut self, file_path: &str) -> &mut Self {
        self.file_path = Some(String::from(file_path));
        self
    }

    /// Returns the file path of the logs in the case we want to redirect them to a file.
    pub fn file_path(&self) -> Option<String> {
        self.file_path.clone()
    }

    /// Registers a [FastLogger] as the comsumer of [log] facade so it becomes static and any further
    /// mutation are discarded.
    pub fn build(&self) -> Result<FastLogger, SetLoggerError> {
        // Let's create the logging thread that will be responsable for all the heavy work of
        // building and printing the log messages
        let (s, r): (Sender<MsgT>, Receiver<MsgT>) = crossbeam_channel::bounded(self.channel_size);

        let logger = FastLogger {
            level: self.level,
            rule_filters: self.rule_filters.to_owned(),
            level_colors: self.level_colors,
            sender: s.clone(),
            timestamp_format: self.timestamp_format.to_owned(),
            handle: None,
        };

        let handle = match log::set_boxed_logger(Box::new(logger))
            .map(|_| log::set_max_level(self.level.to_level_filter()))
        {
            Ok(_v) => {
                // This is a hacky way to do it, because it cannot work using the Write trait object:
                // `dyn std::io::Write` cannot be sent between threads safely
                // Also
                // Here we use `writeln!` instead of println! in order to avoid
                // unnecessary flush.
                // Currently we use `BufWriter` which has a sized buffer of about
                // 8kb by default
                if self.file_path.is_some() {
                    let mut buffer = {
                        let fp = match &self.file_path {
                            Some(fp) => fp.to_string(),
                            None => String::from("dummy.log"),
                        };
                        let file_path = std::path::PathBuf::from(&fp);
                        let file_stream = std::fs::OpenOptions::new()
                            .create(true)
                            .append(true)
                            .open(&file_path)
                            .unwrap_or_else(|_| panic!("Fail to log to {:?}.", &file_path));

                        io::BufWriter::new(file_stream)
                    };
                    thread::spawn(move || {
                        while let Ok(msg) = r.recv() {
                            if msg.should_terminate() {
                                drop(r);
                                buffer.flush().expect("Fail to flush the logging buffer.");
                                break
                            } else if msg.should_flush() {
                                buffer.flush().expect("Fail to flush the logging buffer.")
                            } else {
                                writeln!(&mut buffer, "{}", msg.build())
                                    .expect("Fail to log to file.")
                            }
                        }
                    })
                } else {
                    let mut buffer = io::BufWriter::new(io::stderr());
                    thread::spawn(move || {
                        while let Ok(msg) = r.recv() {
                            if msg.should_terminate() {
                                drop(r);
                                buffer.flush().expect("Fail to flush the logging buffer.");
                                break
                            } else if msg.should_flush() {
                                buffer.flush().expect("Fail to flush the logging buffer.");
                            } else {
                                writeln!(&mut buffer, "{}", msg.build())
                                    .expect("Fail to log to the stderr.")
                            }
                        }
                    })
                }
            }
            Err(e) => {
                eprintln!("Attempt to initialize the Logger more than once. '{}'.", e);
                thread::spawn(move || {})
            }
        };

        // We recreate a FastLogger here because the previous one is moved to the logger register
        // and we cannot make it derive clone because thread::JoinHandle doesn't implement clone
        Ok(FastLogger {
            level: self.level,
            rule_filters: self.rule_filters.to_owned(),
            level_colors: self.level_colors,
            sender: s.clone(),
            timestamp_format: self.timestamp_format.to_owned(),
            handle: Some(handle),
        })
    }

    /// Dull log build, only used for test purposes because it actually doesn't log anything by not
    /// registering the logger.
    #[allow(dead_code)]
    pub fn build_test(&self) -> Result<FastLogger, SetLoggerError> {
        // Let's create the logging thread that will be responsable for all the heavy work of
        // building and printing the log messages
        let (s, _): (Sender<MsgT>, Receiver<MsgT>) = crossbeam_channel::bounded(self.channel_size);

        let logger = FastLogger {
            level: self.level,
            rule_filters: self.rule_filters.to_owned(),
            level_colors: self.level_colors,
            sender: s,
            timestamp_format: self.timestamp_format.to_owned(),
            handle: None,
        };

        Ok(logger)
    }
}

impl Default for FastLoggerBuilder {
    fn default() -> Self {
        // Get the log verbosity from the command line
        let level = env::var("RUST_LOG").unwrap_or_else(|_| DEFAULT_LOG_LEVEL_STR.to_string());

        Self {
            level: Level::from_str(&level).unwrap_or(DEFAULT_LOG_LEVEL),
            rule_filters: Vec::new(),
            level_colors: ColoredLevelConfig::new(),
            channel_size: DEFAULT_CHANNEL_SIZE,
            file_path: None,
            timestamp_format: String::from(DEFAULT_TIMESTAMP_FMT)
        }
    }
}

/// Initialize a simple logging instance with [Info](Level::Info) log level verbosity or retrieve
/// the level from the *RUST_LOG* environment variable and no rule filtering.
pub fn init_simple() -> Result<FastLogger, SetLoggerError> {
    FastLoggerBuilder::new().build()
}

/// This is our log message data structure. Useful especially for performance reasons.
struct LogMessage {
    /// The actual log message.
    args: String,
    /// The module name of the caller log message.
    module: String,
    /// Additional information provided by the log caller. In HC Core it correspond to the instance
    /// id of the Conductor.
    target: Option<String>,
    /// Line number of the issued log message.
    line: u32,
    /// The source file containing the message.
    file: String,
    /// Log verbosity level.
    level: Level,
    /// Log verbosity level to print with color.
    level_to_print: String,
    /// Thread name of the log message issuer. Default to `Anonymous Thread`.
    thread_name: String,
    /// The color of the log message defined by the user using [RuleFilter]. Default to color based
    /// on the thread name and the module name if not present.
    color: Option<String>,
    /// Timestamp format.
    timestamp_format: String,
    /// Whether the logging thread should gracefully shutdown.
    should_terminate: bool,
    /// Whether we should flush the write buffer.
    should_flush: bool,
}

impl LogMessage {
    /// Create a terminate signal to be passed to the logging thread in order to shutdown
    /// it gracefully.
    fn new_terminate_signal() -> Self {
        Self {
            should_terminate: true,
            ..Default::default()
        }
    }

    /// Create a special signal in order to flush the wite buffer of the logging thread.
    fn new_flush_signal() -> Self {
        Self {
            should_flush: true,
            ..Default::default()
        }
    }

    /// Returns whether this message is a 'terminate signal' or not.
    fn should_terminate(&self) -> bool {
        self.should_terminate
    }

    /// Returns whether this message is a 'flush signal' or not.
    fn should_flush(&self) -> bool {
        self.should_flush
    }
}

impl Default for LogMessage {
    fn default() -> Self {
        Self {
            args: String::default(),
            module: String::default(),
            target: None,
            line: 0,
            file: String::default(),
            level: DEFAULT_LOG_LEVEL,
            level_to_print : DEFAULT_LOG_LEVEL_STR.to_owned(),
            thread_name: String::default(),
            color: None,
            timestamp_format: String::default(),
            should_terminate: false,
            should_flush: false,
        }
    }
}

/// For performance purpose, we build the logging message in the logging thread instead of the
/// calling one. It's primarily to deal with the potential slowness of retrieving the timestamp
/// from the OS.
trait LogMessageTrait: Send {
    fn build(&self) -> String;
    fn shutdown(&mut self);
    fn should_terminate(&self) -> bool;
    fn should_flush(&self) -> bool;
}

impl LogMessageTrait for LogMessage {
    /// Build the log message as a string. Applying custom color if needed.
    fn build(&self) -> String {
        // Prioritizing `target` as a tag name and falling back to the module name if missing.
        let tag_name = self
            .target
            .to_owned()
            .unwrap_or_else(|| format!("{}{}", &self.thread_name, &self.module).to_owned());
        let base_color_on = &tag_name.to_owned();
        let pseudo_rng_color = pick_color(&base_color_on);

        // Let's colorize our logging messages
        let msg_color = match &self.color {
            Some(color) => {
                if color.is_empty() {
                    pseudo_rng_color
                } else {
                    color
                }
            },
            None => pseudo_rng_color,
        };

        // Force color on "special" log level
        let msg_color = match self.level {
            Level::Error => "Red",
            Level::Warn => "Yellow",
            _ => msg_color,
        };

        let msg = format!(
            "{level} {timestamp} [{tag}] {thread_name} {line} {args}",
            args = self.args.color(msg_color),
            tag = tag_name.bold().color(pseudo_rng_color),
            line = format!("{}:{}", self.file, self.line).italic(),
            // We might consider retrieving the timestamp once and proceed logging
            // in batch in the future, if this ends up being performance critical
            timestamp = chrono::Local::now().format(&self.timestamp_format),
            level = self.level_to_print.bold(),
            thread_name = self.thread_name.underline(),
        );
        msg.to_string()
    }

    /// Tells the logging thread to gracefully shutdown.
    fn shutdown(&mut self) {
        self.should_terminate = true;
    }

    /// Returns weather we should gracefully terminate the logging thread or keep going on.
    fn should_terminate(&self) -> bool {
        self.should_terminate()
    }

    /// Returns weather we should flush our buffer writer inside the logging thread.
    fn should_flush(&self) -> bool {
        self.should_flush()
    }
}

/// This is a helper for [TOML](toml) deserialization into [Logger].
#[derive(Debug, Deserialize)]
struct LoggerConfig {
    pub logger: Option<Logger>,
}

/// This structure is a helper for the [TOML](toml) logging configuration.
#[derive(Clone, Debug, Deserialize)]
struct Logger {
    /// Verbosity level of the logger.
    level: String,
    /// The path to the file in the case where we want to redirect our log to a file.
    file: Option<String>,
    /// List of filtering [rules](RuleFilter).
    rules: Option<Vec<Rule>>,
    /// Timestamp format.
    timestamp_format: Option<String>,
}

impl From<Logger> for FastLoggerBuilder {
    fn from(logger: Logger) -> Self {
        let rule_filters: Vec<RuleFilter> = logger
            .rules
            .unwrap_or_else(|| vec![])
            .into_iter()
            .map(|rule| rule.into())
            .collect();

        FastLoggerBuilder {
            level: Level::from_str(&logger.level).unwrap_or(Level::Info),
            rule_filters,
            file_path: logger.file,
            timestamp_format: logger.timestamp_format.unwrap_or_else(|| String::from(DEFAULT_TIMESTAMP_FMT)),
            ..FastLoggerBuilder::default()
        }
    }
}

#[test]
fn should_log_test() {
    use rule::RuleFilterBuilder;

    let mut logger = FastLoggerBuilder::new()
        .set_level_from_str("Debug")
        .add_rule_filter(
            RuleFilterBuilder::new()
                .set_pattern("foo")
                .set_exclusion(false)
                .set_color("Blue")
                .build(),
        )
        .build_test()
        .unwrap();

    assert_eq!(logger.should_log_in("bar"), Some(String::from("")));

    assert_eq!(logger.should_log_in("xfooy"), Some(String::from("Blue")));

    // rule to reject anything with baz
    logger.add_rule_filter(RuleFilter::new("baz", true, "White"));
    assert_eq!(logger.should_log_in("baz"), None);

    // rule to accept anything with b
    logger.add_rule_filter(RuleFilter::new("b", false, "Green"));
    assert_eq!(logger.should_log_in("xboy"), Some(String::from("Green")));
}

#[test]
fn filtering_back_log_test() {
    let toml = r#"
        [logger]
        level = "debug"

            [[logger.rules]]
            pattern = ".*"
            exclude = true

            [[logger.rules]]
            pattern = "^holochain"
            exclude = false

            [[logger.rules]]
            pattern = "Cyan"
            exclude = false
            color = "Cyan"

            [[logger.rules]]
            pattern = "app-6"
            exclude = false
            color = "Green"
    "#;

    let logger_conf: LoggerConfig =
        toml::from_str(toml).expect("Fail to deserialize logger from toml.");
    let logger: Option<Logger> = logger_conf.logger;
    assert!(logger.is_some());

    let flb: FastLoggerBuilder = logger.unwrap().into();
    let logger = flb.build_test().unwrap();

    // This log entry should be filtered: 'debug!(target: "rpc", "...")'
    assert_eq!(logger.should_log_in("rpc"), None);

    // This one should be logged: 'info!(target: "holochain-app-2", "...")' because of 2nd rule
    assert_ne!(logger.should_log_in("holochain"), None);

    // This next one should be logged in red: 'debug!(target: "holochain-app-6", "...'Red'...")'
    assert_eq!(logger.should_log_in("app-6"), Some(String::from("Green")));

}

#[test]
fn logger_conf_deserialization_test() {
    let toml = r#"
        [logger]
        level = "debug"
        file = "humpty_dumpty.log"

            [[logger.rules]]
            pattern = ".*"
            color = "red"
    "#;

    let logger_conf: LoggerConfig =
        toml::from_str(toml).expect("Fail to deserialize logger from toml.");
    let logger: Option<Logger> = logger_conf.logger;
    assert!(logger.is_some());

    let flb: FastLoggerBuilder = logger.unwrap().into();

    // Log verbosity check
    assert_eq!(flb.level(), Level::Debug);

    // File dump check
    assert_eq!(flb.file_path(), Some(String::from("humpty_dumpty.log")));
}

#[test]
fn fastloggerbuilder_conf_deserialization_test() {
    let toml = r#"
        [logger]
        level = "debug"
        file = "humpty_dumpty.log"

            [[logger.rules]]
            pattern = ".*"
            color = "red"
    "#;

    let flb =
        FastLoggerBuilder::from_toml(&toml).expect("Fail to init `FastLoggerBuilder` from toml.");

    // Log verbosity check
    assert_eq!(flb.level(), Level::Debug);

    // File dump check
    assert_eq!(flb.file_path(), Some(String::from("humpty_dumpty.log")));
}

#[test]
fn log_rules_deserialization_test() {
    use rule;

    let toml = r#"
        [logger]
        level = "debug"

            [[logger.rules]]
            pattern = ".*"
            color = "red"
            [[logger.rules]]
            pattern = "twice"
            exclude = true
            color = "magenta"
    "#;

    let logger_conf: LoggerConfig =
        toml::from_str(toml).expect("Fail to deserialize logger from toml.");
    let logger: Option<Logger> = logger_conf.logger;
    assert!(logger.is_some());

    let rule0 = rule::Rule {
        pattern: String::from(".*"),
        exclude: Some(false),
        color: Some(String::from("red")),
    };
    let rule1 = rule::Rule {
        pattern: String::from("twice"),
        exclude: Some(true),
        color: Some(String::from("magenta")),
    };

    let flb: FastLoggerBuilder = logger.unwrap().into();

    let rule0_from_toml: Rule = flb.rule_filters()[0].clone().into();
    let rule1_from_toml: Rule = flb.rule_filters()[1].clone().into();
    // File dump check
    assert_eq!(rule0_from_toml, rule0);
    assert_eq!(rule1_from_toml, rule1);
}

#[test]
fn configure_log_level_from_env_test() {
    env::set_var("RUST_LOG", "warn");
    let flb = FastLoggerBuilder::new();

    assert_eq!(flb.level(), Level::Warn);

    let logger = flb.build_test().unwrap();
    assert_eq!(logger.level(), Level::Warn)
}

#[test]
fn log_to_file_test() {
    let toml = r#"
        [logger]
        level = "debug"
        file = "logger_dump.log"

            [[logger.rules]]
            pattern = ".*"
            color = "Yellow"
    "#;

    FastLoggerBuilder::from_toml(toml).expect("Fail to load logging conf from toml.");
}