#!/usr/bin/env python
-import sys
+"""
-class MoeLog:
+`Loggers` is a collection of logggers for :class:`~moe.eval.Eval`, initializing 4 subloggers (see class description).
- def __init__(self):
- self.verbosity = 0
- self.progress_file = sys.stdout
- self.log_file = None
+Use as `e.log.debug(...)` for main log that goes both to `test` and `main` log,
+`e.log.test.debug(...)` for individual logs.
- def say(self, msg):
- if self.log_file:
- self.log_file.write(msg)
+.. :data:: DDEBUG = 5
+ Very verbose debugging level
- def verbose(self, msg):
- if self.verbosity and self.log_file:
- self.log_file.write(msg)
+"""
- def progress(self, msg):
- if self.progress_file:
- self.progress_file.write(msg)
- def shout(self, msg):
- self.say(msg)
- self.progress(msg)
+import sys, os
+import logging
+from logging import Logger, StreamHandler, Formatter
-default = MoeLog()
+logging.addLevelName(5,'DDEBUG')
+
+
+class Loggers(Logger):
+ """Defines several logs:
+
+ `self`
+ root log, which sends messages to `eval` and `test` logs
+ `self.eval`
+ main log, initially a duplicated fd 2 (stderr)
+ `self.user`
+ public progress log, initially a duplicated fd 1 (stdout)
+ `self.test`
+ per-test log, initially no handler, to be directed to file like `{TEST}.log`
+
+ .. note:: Currently, the logs and fd's get never closed.
+ .. warning:: `Loggers.open_eval_log` changes global fd's 1 and 2 by default.
+ """
+ def __init__(self):
+ "Initialize loggers as described in class description."
+ Logger.__init__(self, '')
+ # Duplicate the fd's
+ self.orig_stdout_fd = os.dup(1)
+ self.orig_stdout_file = os.fdopen(self.orig_stdout_fd, 'w', 0)
+ self.orig_stderr_fd = os.dup(2)
+ self.orig_stderr_file = os.fdopen(self.orig_stderr_fd, 'w', 0)
+ # Eval main logger
+ self.eval = Logger('eval')
+ self.eval.addHandler(StreamHandler(self.orig_stderr_file))
+ # per-test logger
+ self.test = Logger('test')
+ self.test_handler = None
+ self.test_file = None
+ # user progress logger
+ self.user = Logger('user')
+ self.user.addHandler(StreamHandler(self.orig_stdout_file))
+
+ self.addHandler(self.test)
+ self.addHandler(self.eval)
+ self.debug('Logging initialized.')
+
+ def open_user_log(self, filename, level=logging.INFO):
+ """Open user (progress) logfile. Leaves logging to stdout active."""
+ h = StreamHandler(open(filename, 'w', 0))
+ h.setFormatter(Formatter('%(message)s'))
+ self.user.setLevel(level)
+ self.user.addHandler(h)
+ self.user.debug('Logging started')
+
+ def open_eval_log(self, filename, level, redirect_fds = True):
+ """Open main logfile.
+ Leaves logging to stderr active. If told to, redirects fd's 1 and 2 to this file.
+ Sets level of both `self.eval` and `self`."""
+ self.eval_file = open(filename, 'w', 0)
+ self.eval_handler = StreamHandler(self.eval_file)
+ self.eval_handler.setFormatter(Formatter('%(asctime)s [%(levelno)s] %(message)s'))
+ self.eval.addHandler(self.eval_handler)
+ if redirect_fds:
+ os.dup2(self.eval_file.fileno(), 1)
+ os.dup2(self.eval_file.fileno(), 2)
+ self.eval.setLevel(level)
+ self.setLevel(level)
+ self.eval.debug('Logging started')
+
+
+ def open_test_log(self, filename, level):
+ """Open per-test log file, like "{TEST}.log". Also set handler level to `level`."""
+ self.debug('Opening per-test log %s' % filename)
+ if self.test_handler:
+ self.close_test_log()
+ self.test_file = open(filename, 'w')
+ self.test_handler = StreamHandler(self.test_file)
+ self.test.addHandler(self.test_handler)
+ self.test.setLevel(level)
+ self.test_handler.setFormatter(Formatter('%(asctime)s [%(levelno)s] %(message)s'))
+ self.test.debug('Logging started')
+
+ def close_test_log(self):
+ """Close per-test logfile, leaving only the null handler."""
+ if self.test_handler:
+ self.test.info('Closing logfile' % filename)
+ self.test.removeHandler(self.test_handler)
+ self.test_handler = None
+ self.test_file.close()
+
+ def ddebug(self, msg, *args, **kwargs):
+ """Log with priority 5 (normal DEBUG is 10)"""
+ self.log(5, msg, *args, **kwargs)