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
use processor::{OutputProcessor, ConfigurableFilter};

use std::collections::HashMap;
use std::sync::mpsc::Receiver;
use std::thread::JoinHandle;
use std::path::PathBuf;
use std::io::prelude::*;
use std::fs;
use std::fs::File;
use std::fs::create_dir_all;
use std::fs::OpenOptions;


use time;

/// # File output
///
/// - sends output into a rotating file
///
/// ### catapult.conf
///
/// ```
/// output {
/// 	file {
/// 		directory = "./logs/"
/// 	}
/// }
/// ```
/// ### Parameters
///
/// - **directory**: Base directory into which logs are created. Can be a strftime pattern.

pub struct RotatingFile {
  name: String
}

impl RotatingFile {
  pub fn new(name: String) -> RotatingFile {
    RotatingFile{ name: name }
  }
}

impl ConfigurableFilter for RotatingFile {
  fn human_name(&self) -> &str {
    self.name.as_ref()
  }

  fn mandatory_fields(&self) -> Vec<&str> {
    vec!["directory"]
  }


}

impl OutputProcessor for RotatingFile {
  fn start(&self, rx: Receiver<String>, config: &Option<HashMap<String,String>>) -> Result<JoinHandle<()>, String> {
    self.requires_fields(config, self.mandatory_fields());
    self.invoke(rx, config, RotatingFile::handle_func)
  }
  fn handle_func(rx: Receiver<String>, oconfig: Option<HashMap<String,String>>) {
    let config = oconfig.expect("Need a configuration");
    let mut basefile_format = config.get("directory").expect("Need a log directory").clone();

    basefile_format.push_str("%Y-%m-%d-%H:00.log");

    let mut parent_dir =  PathBuf::from("/");
    let mut log_path =  PathBuf::from("");
    let mut log_file: Option<File> = None;

    loop {

      match rx.recv() {
        Ok(mut l) => {
          let now = time::now();
          let basefile = time::strftime(basefile_format.as_ref(), &now).ok().unwrap();
          let new_log_path = PathBuf::from(basefile);
          let new_parent_dir = new_log_path.parent().unwrap().to_path_buf();
          if new_parent_dir != parent_dir {
            match fs::metadata(new_parent_dir.as_path()) {
              Err(_) => {create_dir_all(new_parent_dir.as_path()).ok();},
              _ => {}
            }
            parent_dir = new_parent_dir.clone();
          }

          // First time we see this file, open it, or create it
          if new_log_path != log_path {
            log_path = new_log_path.clone();
            match fs::metadata(log_path.as_path()) {
              Err(_) => {
                log_file = File::create(log_path.clone()).ok()
              },
              Ok(f) => {
                if f.is_file() {
                  log_file = OpenOptions::new().write(true).append(true).open(log_path.clone()).ok();
                } else {
                  panic!("File {:?} exists and is not a file.", log_path);
                }
              }
            }
          }
          l.push_str("\n");
          if let Some(ref mut writable) = log_file {
            let _count = writable.write_all(l.as_bytes());
          } else {
            println!("No file to write to, discarding line: {}", l);
          }
        }
        Err(e) => { panic!(e) }
      }
    }
  }
}