<commit_before>#
# This file is part of pygimplib.
#
# Copyright (C) 2014, 2015 khalim19 <<EMAIL>>
#
# pygimplib is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pygimplib is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pygimplib.  If not, see <http://www.gnu.org/licenses/>.
#

"""
This module:
* provides logging of unhandled exceptions and debug information (if
debugging is enabled),
* defines a class to duplicate ("tee") standard output or error output.
"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

str = unicode

import datetime
import logging
import os
import sys

from . import constants
from . import pgpath
from . import pgpdb

#===============================================================================


def log_output(log_mode, log_path_dirnames, log_stdout_filename, log_stderr_filename, log_header_title=""):
  """
  Enable logging of output.
  
  Parameters:
  
  * `log_mode` - log mode. Possible values:
  
    * EXCEPTIONS_ONLY - only log exceptions to the error log file.
    * DEBUG_FILE - redirect stdout and stderr to log files.
    * DEBUG_GIMP_CONSOLE - redirect stdout and stderr to the GIMP error console.
  
  * `log_path_dirnames` - list of directory paths for log files. If the first
    path is invalid or permission to write is denied, subsequent directories are
    used. For `DEBUG_FILE` mode, only the first directory is used. For
    `DEBUG_GIMP_CONSOLE` mode, this parameter has no effect.
  
  * `log_stdout_filename` - filename of the log file to write standard output
    to. Applies to `DEBUG_FILE` mode only.
  
  * `log_stderr_filename` - filename of the log file to write error output to.
    Applies to `EXCEPTIONS_ONLY` and `DEBUG_FILE` modes only.
  
  * `log_header_title` - optional title in the log header, written before the
    first write to the log files. Applies to `EXCEPTIONS_ONLY` and `DEBUG_FILE`
    modes only.
  """
  
  if log_mode == constants.LOG_EXCEPTIONS_ONLY:
    logger = logging.getLogger(log_stderr_filename)
    logger.setLevel(logging.DEBUG)
    
    can_log = _logger_add_file_handler(
      logger, [os.path.join(log_path_dirname, log_stderr_filename) for log_path_dirname in log_path_dirnames])
    if can_log:
      # Pass the `logger` instance to the function to make sure it is not None.
      # More information at:
      # http://stackoverflow.com/questions/5451746/sys-excepthook-doesnt-work-in-imported-modules/5477639
      # http://bugs.python.org/issue11705
      def log_exceptions(exctype, value, traceback, logger=logger):
        logger.error(get_log_header(log_header_title).encode(), exc_info=(exctype, value, traceback))
      
      sys.excepthook = log_exceptions
  
  elif log_mode == constants.LOG_OUTPUT_FILES:
    sys.stdout = SimpleLogger(
      os.path.join(log_path_dirnames[0], log_stdout_filename), "a", log_header_title=log_header_title)
    sys.stderr = SimpleLogger(
      os.path.join(log_path_dirnames[0], log_stderr_filename), "a", log_header_title=log_header_title)
  elif log_mode == constants.LOG_OUTPUT_GIMP_CONSOLE:
    sys.stdout = pgpdb.GimpMessageFile(message_delay_milliseconds=20)
    sys.stderr = pgpdb.GimpMessageFile(message_prefix="Error: ", message_delay_milliseconds=20)


def _logger_add_file_handler(logger, log_paths):
  """
  If the first log path in `log_paths` cannot be used (e.g. due to denied write
  permission), try out subsequent paths.
  
  Do not log if directories cannot be created or any of the log files cannot be
  created.
  """
  
  can_log = True
  for log_path in log_paths:
    try:
      pgpath.make_dirs(os.path.dirname(log_path))
    except OSError:
      can_log = False
      break
    
    try:
      logger.addHandler(logging.FileHandler(log_path))
    except (OSError, IOError):
      if log_path == log_paths[-1]:
        can_log = False
    else:
      break
  
  return can_log


def get_log_header(log_header_title):
  return "\n".join(("", "=" * 80, log_header_title, str(datetime.datetime.now()), "\n"))


#===============================================================================


class SimpleLogger(object):
  
  def __init__(self, filename, mode, log_header_title=None):
    self._log_header_title = log_header_title
    
    self._log_file = open(filename, mode)
  
  def write(self, data):
    if self._log_header_title:
      self._write(get_log_header(self._log_header_title).encode())<commit_msg>log_output: Refactoring and clarification<commit_after>#
# This file is part of pygimplib.
#
# Copyright (C) 2014, 2015 khalim19 <<EMAIL>>
#
# pygimplib is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pygimplib is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with pygimplib.  If not, see <http://www.gnu.org/licenses/>.
#

"""
This module:
* provides logging of unhandled exceptions and debug information (if
debugging is enabled),
* defines a class to duplicate ("tee") standard output or error output.
"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

str = unicode

import datetime
import logging
import os
import sys

from . import constants
from . import pgpath
from . import pgpdb

#===============================================================================


def log_output(log_mode, log_path_dirnames, log_stdout_filename, log_stderr_filename, log_header_title=""):
  """
  Enable logging of output.
  
  Parameters:
  
  * `log_mode` - log mode. Possible values:
  
    * EXCEPTIONS_ONLY - only log exceptions to the error log file.
    * DEBUG_FILE - redirect stdout and stderr to log files.
    * DEBUG_GIMP_CONSOLE - redirect stdout and stderr to the GIMP error console.
  
  * `log_path_dirnames` - list of directory paths for log files. If the first
    path is invalid or permission to write is denied, subsequent directories are
    used. For `DEBUG_FILE` mode, only the first directory is used. For
    `DEBUG_GIMP_CONSOLE` mode, this parameter has no effect.
  
  * `log_stdout_filename` - filename of the log file to write standard output
    to. Applies to `DEBUG_FILE` mode only.
  
  * `log_stderr_filename` - filename of the log file to write error output to.
    Applies to `EXCEPTIONS_ONLY` and `DEBUG_FILE` modes only.
  
  * `log_header_title` - optional title in the log header, written before the
    first output to the log files. Applies to `EXCEPTIONS_ONLY` and `DEBUG_FILE`
    modes only.
  """
  
  if log_mode == constants.LOG_EXCEPTIONS_ONLY:
    logger = logging.getLogger(log_stderr_filename)
    logger.setLevel(logging.DEBUG)
    
    can_log = _logger_add_file_handler(
      logger, [os.path.join(log_path_dirname, log_stderr_filename) for log_path_dirname in log_path_dirnames])
    if can_log:
      # Pass the `logger` instance to the function to make sure it is not None.
      # More information at:
      # http://stackoverflow.com/questions/5451746/sys-excepthook-doesnt-work-in-imported-modules/5477639
      # http://bugs.python.org/issue11705
      def log_exceptions(exctype, value, traceback, logger=logger):
        logger.error(get_log_header(log_header_title).encode(), exc_info=(exctype, value, traceback))
      
      sys.excepthook = log_exceptions
  
  elif log_mode == constants.LOG_OUTPUT_FILES:
    sys.stdout = SimpleLogger(os.path.join(log_path_dirnames[0], log_stdout_filename), "a", log_header_title)
    sys.stderr = SimpleLogger(os.path.join(log_path_dirnames[0], log_stderr_filename), "a", log_header_title)
  elif log_mode == constants.LOG_OUTPUT_GIMP_CONSOLE:
    sys.stdout = pgpdb.GimpMessageFile(message_delay_milliseconds=20)
    sys.stderr = pgpdb.GimpMessageFile(message_prefix="Error: ", message_delay_milliseconds=20)


def _logger_add_file_handler(logger, log_paths):
  """
  If the first log path in `log_paths` cannot be used (e.g. due to denied write
  permission), try out subsequent paths.
  
  Do not log if directories cannot be created or any of the log files cannot be
  created.
  """
  
  can_log = True
  for log_path in log_paths:
    try:
      pgpath.make_dirs(os.path.dirname(log_path))
    except OSError:
      can_log = False
      break
    
    try:
      logger.addHandler(logging.FileHandler(log_path))
    except (OSError, IOError):
      if log_path == log_paths[-1]:
        can_log = False
    else:
      break
  
  return can_log


def get_log_header(log_header_title):
  return "\n".join(("", "=" * 80, log_header_title, str(datetime.datetime.now()), "\n"))


#===============================================================================


class SimpleLogger(object):
  
  """
  This class wraps a file object to write a header before the first output.
  """
  
  def __init__(self, filename, mode, log_header_title):
    self._log_header_title = log_header_title
    self._log_file = open(filename, mode)
  
  def write(self, data):
    if self._log_header_title:
      self._write(get_log_header(self._log_header_title).encode())
{'fraction_non_alphanumeric': 0.11190428312889604, 'fraction_numerical': 0.007842348682887593, 'mean_word_length': 4.001005530417295, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, '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': '958728', 'n_tokens_mistral': 3082, 'n_tokens_neox': 2961, 'n_words': 966}