import logging
import ConfigParser

"""
author:Martin Robinson
program:python loggin facility decorator
done:20 feb 2014

use with config file like this
[log]
level = debug

[file]
level = error
filename = error.log

[console]
level = debug

and you can decorate your function with

@error(exceptionName)
def functionThatThrowException():
  ...

@debug
def functionYouWantToMonitor():
  ....

@info
def functionYouWantToNoticeOnExecution():
  ...

you can daisychain decorator
@info
@error(exception1)
@error(exception2)
def dangerousFunction():
  ...
"""

def init(configfilename=None):
  """
  will load file and configure logger for you
  if no file is specified or the file cant be opened, 
  then default setting are used
  """
  levelMap = {
  'critical':logging.CRITICAL,
  'error':logging.ERROR,
  'warn':logging.WARNING,
  'info':logging.INFO,
  'debug':logging.DEBUG,
  '':logging.NOTSET
  };
  
  loglevel = 'warn';
  filelevel = 'error';
  filename = None;
  consolelevel = 'warn';
  
  if configfilename:
    config = ConfigParser.ConfigParser();
    config.read(configfilename)
  
    if 'log' in config.sections():
      if 'level' in config.options('log'):
        levelname = config.get('log','level');
        if levelname in levelMap:
          loglevel = levelname;

    if 'file' in config.sections():
      if 'level' in config.options('file'):
        levelname = config.get('file','level');
        if levelname in levelMap:
          filelevel = levelname;
      if 'filename' in config.options('file'):
        filename = config.get('file','filename');
  
    if 'console' in config.sections():
      if 'level' in config.options('console'):
        levelname = config.get('console','level');
        if levelname in levelMap:
          consolelevel = levelname;
  
  logger = logging.getLogger()
  logger.setLevel(levelMap[loglevel]);
  
  if filename:
    fileHandler = logging.FileHandler(filename);
    fileHandler.setLevel(levelMap[filelevel]);
    fileHandler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'));
    logger.addHandler(fileHandler);
  
  consoleHandler = logging.StreamHandler();
  consoleHandler.setLevel(levelMap[consolelevel]);
  consoleHandler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'));
  logger.addHandler(consoleHandler);

def info(func):
  """
  logging decorator level info
  will pop function name on log when executed
  """
  logger = logging.getLogger()
  def log(*args, **kwargs):
    logger.info(func.__name__);
    return func(*args, **kwargs);
  log.__name__ = func.__name__;
  return log;

def debug(func):
  """
  logging decorator level debug
  will tell function name, parameter and return value on execution
  """
  logger = logging.getLogger()
  def log(*args, **kwargs):
    ret = func(*args, **kwargs);
    logger.debug(func.__name__+" param "+str(args)+str(kwargs)+" Return "+str(ret));
    return ret;
  log.__name__ = func.__name__;
  return log;

def warn(exception):
  """
  logging decorator level warning
  will capture specified exception if raised
  log all information about execption
  and throw it back
  """
  logger = logging.getLogger()
  def decorate(func):
    def log(*args, **kwargs):
      try:
        ret = func(*args, **kwargs);
      except exception as e:
        logger.warn(func.__name__+" param "+str(args)+str(kwargs)+" Raise "+exception.__name__+e.__doc__+"("+e.message+")");
        #raise e;
      return ret;
    log.__name__ = func.__name__;
    return log;
  return decorate;

if __name__ == "__main__":
  #not a real unit test
  init("log.cfg");

  @debug
  def test2(a):
    return a;

  @debug
  @info
  def test(a):
    return test2(a);

  @warn(KeyboardInterrupt)
  def test3():
    raise KeyboardInterrupt

  test(2);
  try:
    test3();
  except:
    pass;


