<commit_before>// Copyright 2018 Pants project contributors (see CONTRIBUTORS.md).
// Licensed under the Apache License, Version 2.0 (see LICENSE).

use crate::PythonLogLevel;

use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use std::fs::File;
use std::fs::OpenOptions;
use std::future::Future;
use std::io::{stderr, Stderr, Write};
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};

use lazy_static::lazy_static;
use log::{debug, log, set_logger, set_max_level, LevelFilter, Log, Metadata, Record};
use parking_lot::Mutex;
use simplelog::{ConfigBuilder, LevelPadding, WriteLogger};
use tokio::task_local;
use uuid::Uuid;

const TIME_FORMAT_STR: &str = "%H:%M:%S:%3f";

pub type StdioHandler = Box<dyn Fn(&str) -> () + Send>;

lazy_static! {
  pub static ref LOGGER: Logger = Logger::new();
}

pub struct Logger {
  pantsd_log: Mutex<MaybeWriteLogger<File>>,
  stderr_log: Mutex<MaybeWriteLogger<Stderr>>,
  show_rust_3rdparty_logs: AtomicBool,
  stderr_handlers: Mutex<HashMap<Uuid, StdioHandler>>,
}

impl Logger {
  pub fn new() -> Logger {
    Logger {
      pantsd_log: Mutex::new(MaybeWriteLogger::empty()),
      stderr_log: Mutex::new(MaybeWriteLogger::empty()),
      show_rust_3rdparty_logs: AtomicBool::new(true),
      stderr_handlers: Mutex::new(HashMap::new()),
    }
  }

  pub fn init(max_level: u64, show_rust_3rdparty_logs: bool) {
    let max_python_level: Result<PythonLogLevel, _> = max_level.try_into();
    match max_python_level {
      Ok(python_level) => {
        let level: log::LevelFilter = python_level.into();
        set_max_level(level);
        LOGGER
          .show_rust_3rdparty_logs
          .store(show_rust_3rdparty_logs, Ordering::SeqCst);
        if set_logger(&*LOGGER).is_err() {
          debug!("Logging already initialized.");
        }
      }
      Err(err) => panic!("Unrecognised log level from python: {}: {}", max_level, err),
    };
  }

  fn maybe_increase_global_verbosity(&self, new_level: log::LevelFilter) {
    if log::max_level() < new_level {
      set_max_level(new_level);
    }
  }

  pub fn set_stderr_logger(&self, python_level: u64) -> Result<(), String> {
    python_level
      .try_into()
      .map_err(|err| format!("{}", err))
      .map(|level: PythonLogLevel| {
        self.maybe_increase_global_verbosity(level.into());
        *self.stderr_log.lock() = MaybeWriteLogger::new(
          stderr(),
          level.into(),
          self.show_rust_3rdparty_logs.load(Ordering::SeqCst),
        )
      })
  }

  ///
  /// Set up a file logger which logs at python_level to log_file_path.
  /// Returns the file descriptor of the log file.
  ///
  #[cfg(unix)]
  pub fn set_pantsd_logger(
    &self,
    log_file_path: PathBuf,
    python_level: u64,
  ) -> Result<std::os::unix::io::RawFd, String> {
    use std::os::unix::io::AsRawFd;
    python_level
      .try_into()
      .map_err(|err| format!("{}", err))
      .and_then(|level: PythonLogLevel| {
        {
          // Maybe close open file by dropping the existing logger
          *self.pantsd_log.lock() = MaybeWriteLogger::empty();
        }
        OpenOptions::new()
          .create(true)
          .append(true)
          .open(log_file_path)
          .map(|file| {
            let fd = file.as_raw_fd();
            self.maybe_increase_global_verbosity(level.into());
            *self.pantsd_log.lock() = MaybeWriteLogger::new(
              file,
              level.into(),
              self.show_rust_3rdparty_logs.load(Ordering::SeqCst),
            );
            fd
          })
          .map_err(|err| format!("Error opening pantsd logfile: {}", err))
      })
  }

  /// log_from_python is only used in the Python FFI, which in turn is only called within the
  /// Python `NativeHandler` class. Every logging call from Python should get proxied through this
  /// function, which translates the log message into the Rust log paradigm provided by
  /// the `log` crate.
  pub fn log_from_python(message: &str, python_level: u64, target: &str) -> Result<(), String> {
    python_level
      .try_into()
      .map_err(|err| format!("{}", err))
      .map(|level: PythonLogLevel| {
        log!(target: target, level.into(), "{}", message);
      })
  }

  pub fn register_stderr_handler(&self, callback: StdioHandler) -> Uuid {
    let mut handlers = self.stderr_handlers.lock();
    let unique_id = Uuid::new_v4();
    handlers.insert(unique_id, callback);
    unique_id
  }

  pub fn deregister_stderr_handler(&self, unique_id: Uuid) {
    let mut handlers = self.stderr_handlers.lock();
    handlers.remove(&unique_id);
  }
}

impl Log for Logger {
  fn enabled(&self, _metadata: &Metadata) -> bool {
    // Individual log levels are handled by each sub-logger,
    // And a global filter is applied to set_max_level.
    // No need to filter here.
    true
  }

  fn log(&self, record: &Record) {
    let destination = get_destination();
    match destination {
      Destination::Stderr => {
        let cur_time = chrono::Utc::now().format(TIME_FORMAT_STR);
        let level = record.level();
        let log_string: String = format!("{} [{}] {}", cur_time, level, record.args());

        {
          let handlers_map = self.stderr_handlers.lock();
          if handlers_map.len() == 0 {
            self.stderr_log.lock().log(record);
          } else {
            for callback in handlers_map.values() {
              callback(&log_string);
            }
          }
        }
      }
      Destination::Pantsd => self.pantsd_log.lock().log(record),
    }
  }

  fn flush(&self) {
    self.stderr_log.lock().flush();
    self.pantsd_log.lock().flush();
  }
}

struct MaybeWriteLogger<W: Write + Send + 'static> {
  level: LevelFilter,<commit_msg>Log timestamps should be in the local time zone (#9927)<commit_after>// Copyright 2018 Pants project contributors (see CONTRIBUTORS.md).
// Licensed under the Apache License, Version 2.0 (see LICENSE).

use crate::PythonLogLevel;

use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use std::fs::File;
use std::fs::OpenOptions;
use std::future::Future;
use std::io::{stderr, Stderr, Write};
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};

use lazy_static::lazy_static;
use log::{debug, log, set_logger, set_max_level, LevelFilter, Log, Metadata, Record};
use parking_lot::Mutex;
use simplelog::{ConfigBuilder, LevelPadding, WriteLogger};
use tokio::task_local;
use uuid::Uuid;

const TIME_FORMAT_STR: &str = "%H:%M:%S";

pub type StdioHandler = Box<dyn Fn(&str) -> () + Send>;

lazy_static! {
  pub static ref LOGGER: Logger = Logger::new();
}

pub struct Logger {
  pantsd_log: Mutex<MaybeWriteLogger<File>>,
  stderr_log: Mutex<MaybeWriteLogger<Stderr>>,
  show_rust_3rdparty_logs: AtomicBool,
  stderr_handlers: Mutex<HashMap<Uuid, StdioHandler>>,
}

impl Logger {
  pub fn new() -> Logger {
    Logger {
      pantsd_log: Mutex::new(MaybeWriteLogger::empty()),
      stderr_log: Mutex::new(MaybeWriteLogger::empty()),
      show_rust_3rdparty_logs: AtomicBool::new(true),
      stderr_handlers: Mutex::new(HashMap::new()),
    }
  }

  pub fn init(max_level: u64, show_rust_3rdparty_logs: bool) {
    let max_python_level: Result<PythonLogLevel, _> = max_level.try_into();
    match max_python_level {
      Ok(python_level) => {
        let level: log::LevelFilter = python_level.into();
        set_max_level(level);
        LOGGER
          .show_rust_3rdparty_logs
          .store(show_rust_3rdparty_logs, Ordering::SeqCst);
        if set_logger(&*LOGGER).is_err() {
          debug!("Logging already initialized.");
        }
      }
      Err(err) => panic!("Unrecognised log level from python: {}: {}", max_level, err),
    };
  }

  fn maybe_increase_global_verbosity(&self, new_level: log::LevelFilter) {
    if log::max_level() < new_level {
      set_max_level(new_level);
    }
  }

  pub fn set_stderr_logger(&self, python_level: u64) -> Result<(), String> {
    python_level
      .try_into()
      .map_err(|err| format!("{}", err))
      .map(|level: PythonLogLevel| {
        self.maybe_increase_global_verbosity(level.into());
        *self.stderr_log.lock() = MaybeWriteLogger::new(
          stderr(),
          level.into(),
          self.show_rust_3rdparty_logs.load(Ordering::SeqCst),
        )
      })
  }

  ///
  /// Set up a file logger which logs at python_level to log_file_path.
  /// Returns the file descriptor of the log file.
  ///
  #[cfg(unix)]
  pub fn set_pantsd_logger(
    &self,
    log_file_path: PathBuf,
    python_level: u64,
  ) -> Result<std::os::unix::io::RawFd, String> {
    use std::os::unix::io::AsRawFd;
    python_level
      .try_into()
      .map_err(|err| format!("{}", err))
      .and_then(|level: PythonLogLevel| {
        {
          // Maybe close open file by dropping the existing logger
          *self.pantsd_log.lock() = MaybeWriteLogger::empty();
        }
        OpenOptions::new()
          .create(true)
          .append(true)
          .open(log_file_path)
          .map(|file| {
            let fd = file.as_raw_fd();
            self.maybe_increase_global_verbosity(level.into());
            *self.pantsd_log.lock() = MaybeWriteLogger::new(
              file,
              level.into(),
              self.show_rust_3rdparty_logs.load(Ordering::SeqCst),
            );
            fd
          })
          .map_err(|err| format!("Error opening pantsd logfile: {}", err))
      })
  }

  /// log_from_python is only used in the Python FFI, which in turn is only called within the
  /// Python `NativeHandler` class. Every logging call from Python should get proxied through this
  /// function, which translates the log message into the Rust log paradigm provided by
  /// the `log` crate.
  pub fn log_from_python(message: &str, python_level: u64, target: &str) -> Result<(), String> {
    python_level
      .try_into()
      .map_err(|err| format!("{}", err))
      .map(|level: PythonLogLevel| {
        log!(target: target, level.into(), "{}", message);
      })
  }

  pub fn register_stderr_handler(&self, callback: StdioHandler) -> Uuid {
    let mut handlers = self.stderr_handlers.lock();
    let unique_id = Uuid::new_v4();
    handlers.insert(unique_id, callback);
    unique_id
  }

  pub fn deregister_stderr_handler(&self, unique_id: Uuid) {
    let mut handlers = self.stderr_handlers.lock();
    handlers.remove(&unique_id);
  }
}

impl Log for Logger {
  fn enabled(&self, _metadata: &Metadata) -> bool {
    // Individual log levels are handled by each sub-logger,
    // And a global filter is applied to set_max_level.
    // No need to filter here.
    true
  }

  fn log(&self, record: &Record) {
    use chrono::Timelike;
    let destination = get_destination();
    match destination {
      Destination::Stderr => {
        let cur_date = chrono::Local::now();
        let time_str = format!(
          "{}.{:02}",
          cur_date.format(TIME_FORMAT_STR),
          cur_date.time().nanosecond() / 10_000_000 // two decimal places of precision
        );
        let level = record.level();
        let log_string: String = format!("{} [{}] {}", time_str, level, record.args());

        {
          let handlers_map = self.stderr_handlers.lock();
          if handlers_map.len() == 0 {
            self.stderr_log.lock().log(record);
          } else {
            for callback in handlers_map.values() {
              callback(&log_string);
            }
          }
        }
      }
      Destination::Pantsd => self.pantsd_log.lock().log(record),
    }
  }

  fn flush(&self) {
    self.stderr_log.lock().flush();
    self.pantsd_log.lock().flush();
  }
}

struct MaybeWriteLogger<W: Write + Send + 'static> {
  level: LevelFilter,
{'fraction_non_alphanumeric': 0.14021702271956596, 'fraction_numerical': 0.005171244489657511, 'mean_word_length': 2.911472148541114, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 49, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4424908', 'n_tokens_mistral': 3867, 'n_tokens_neox': 3812, 'n_words': 846}