# -*- encoding: utf-8 -*-

import os
import logging, logging.handlers
import traceback

DEBUG	= logging.DEBUG
INFO	= logging.INFO
WARNING = logging.WARNING
ERROR	= logging.ERROR
CRITICAL= logging.CRITICAL

class Logger(object):
	"""This class is adapter for standard logging library - it has function for both service-wise logging
		and tas-wise logging. The interface is ver similliar to standard logging package."""
	def __init__(self, lvl, main_log_path, task_logs_path, log_to_console):
		"""Constructor
			lvl - logging level (string), possible values: "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"
			main_log_path - path to service-wise logfile
			task_logs_path - path to directory containing task specific logfiles
		"""
		self._task_logs_path = task_logs_path
		self._main_log_path = main_log_path
		self._log_to_console = log_to_console
		self.default_logging_lvl = lvl
		logger_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
		self._default_formatter = logging.Formatter(logger_format)

		self._main_logger = None

	def log(self, lvl, exception):
		"""Log message in main logfile
			lvl - message level, as in constructor
			exception - string message or exception to log
			If the exception is WARNING, ERROR or CRITICAL, than logs traceback as well.
		"""
		if self._main_logger is None:
			self._main_logger = self._get_main_logger(self._main_log_path, self._log_to_console)

		if lvl in [WARNING, ERROR, CRITICAL]:
			self._log_traceback(self._main_logger, lvl, exception)
		self._log(self._main_logger, lvl, exception)

	def debug(exception):
		self.log(DEBUG, exception)
	def info(exception):
		self.log(INFO, exception)
	def warning(exception):
		self.log(WARNING, exception)
	def error(exception):
		self.log(ERROR, exception)

	def task_log(self, task_token, lvl, exception):
		"""Log message in task specific logfile
			lvl - message level, as in constructor
			exception - string message or exception to log
			If the exception is WARNING, ERROR or CRITICAL, than logs traceback with main logger.
		"""
		if lvl in [WARNING, ERROR, CRITICAL]:
			self._log_traceback(self._main_logger, lvl, exception)
		task_logger, task_logger_file_descriptor = self._get_task_logger(task_token)
		self._log(task_logger, lvl, exception)

	def task_debug(task_token, exception):
		self.task_log(task_token, DEBUG, exception)
	def task_info(task_token, exception):
		self.task_log(task_token, INFO, exception)
	def task_warning(task_token, exception):
		self.task_log(task_token, WARNING, exception)
	def task_error(task_token, exception):
		self.task_log(task_token, ERROR, exception)

	def shutdown(self):
		logging.shutdown()

	@staticmethod
	def str2logging_lvl(str_level):
		"""Translates logging level in string into logging module const"""
		str_level = str_level.lower()
		return {"debug"		: DEBUG,
				"info"		: INFO,
				"warning"	: WARNING,
				"error"		: ERROR,
				"critical"	: CRITICAL}[str_level]

	def _get_main_logger(self, main_log_path, log_to_console):
		"""Creates /retrieves main logger - global for whole service and a parent to all task loggers,
			which writes to console and main logfile."""
		logger, descriptor = self._get_logger(self.default_logging_lvl, 'service', main_log_path)
		self.main_logfile_descr = descriptor

		if log_to_console and not self._check_logger_inited('service'):
			console_handler = logging.StreamHandler()
			console_handler.setLevel(self.default_logging_lvl)
			console_handler.setFormatter(self._default_formatter)
			logger.addHandler(console_handler)
		return logger

	def _get_task_logger(self, task_token):
		"""Creates/retrieves logging.Logger instance with certain level for task with supplied token"""
		task_logger_name = 'service.task-' + task_token
		task_log_file = os.path.join(self._task_logs_path, task_token)
		return self._get_logger(self.default_logging_lvl, task_logger_name, task_log_file, delay=True)

	def _get_logger(self, logging_lvl, name, filepath, delay=False):
		"""Creates/retreives logging.Logger instance with certain level and name that writes into files
			given by filepath (rotating).Returns logger and it's file descriptor."""
		logger = logging.getLogger(name)

		if not self._check_logger_inited(name):
			handler = logging.handlers.RotatingFileHandler(filepath, delay=delay, maxBytes=1024000, backupCount=10)
			handler.setLevel(logging_lvl)
			handler.setFormatter(self._default_formatter)
			logger.setLevel(logging_lvl)
			logger.addHandler(handler)
		else:
			handler = logger.handlers[0]

		descriptor = handler.stream.fileno() if handler.stream else None
		return logger, descriptor

	def _log(self, logger, lvl, exception):
		"""Logs given exception to given logger with given level."""
		logger.log(lvl, str(exception))

	def _log_traceback(self, logger, lvl, exception):
		"""If exception's traceback can be extracted, it logs this traceback."""
		if isinstance(exception, Exception):
			if hasattr(exception, 'traceback'):
				traceback_str = exception.traceback
			else:
				traceback_str = traceback.format_exc()

			if traceback_str is not None:
				self._log(self._main_logger, lvl, traceback_str)

	def _check_logger_inited(self, name):
		logger = logging.getLogger(name)
		return len(logger.handlers) != 0
