########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Server/Server/Lib/LogUtil.py,v 1.9 2005/03/14 07:38:20 mbrown Exp $
"""
Functions and classes related to message logging

Copyright 2004 Fourthought, Inc. (USA).
Detailed license and copyright information: http://4suite.org/COPYRIGHT
Project home, documentation, distributions: http://4suite.org/
"""

import sys, threading, time, os, socket

LOG_EMERG = ('emerg', 0)
LOG_ALERT = ('alert', 1)
LOG_CRIT = ('crit', 2)
LOG_ERROR = LOG_ERR = ('error', 3)
LOG_WARNING = LOG_WARN = ('warn', 4)
LOG_NOTICE = ('notice', 5)
LOG_INFO = ('info', 6)
LOG_DEBUG = ('debug', 7)

_levels = {}
for kword, value in locals().items():
    if kword.startswith('LOG_'):
        _levels[value[0]] = value

_file_locks_lock = threading.Lock()
_file_locks = {}


def FromString(level):
    if not level:
        raise ValueError('must not be empty')

    value = _levels.get(level.lower())
    if value is None:
        kwords = '|'.join(_levels.keys())
        raise ValueError('must be one of ' + kwords)

    return value


class ThreadSafeFile:

    def __init__(self, name):
        name = os.path.abspath(name)
        _file_locks_lock.acquire()
        try:
            if not _file_locks.has_key(name):
                _file_locks[name] = threading.Lock()
        finally:
            _file_locks_lock.release()
        self.name = name
        self._lock = _file_locks[name]
        return

    def __str__(self):
        return 'ThreadSafeFile(%s)' % self.name

    def write(self, data):
        self._lock.acquire()
        try:
            fd = open(self.name, 'a')
            fd.write(data)
            fd.close()
        finally:
            self._lock.release()
        return

DEFAULT_LOGGER_BUFSIZE = 600 # lines

class Logger:
    def __init__(self, ident, logFile, maxLevel=LOG_INFO, showPid=0):
        self.buffer = []
        self.buffer_maxsize = DEFAULT_LOGGER_BUFSIZE
        self.bufferIsFull = False
        self.ident = ident
        if isinstance(logFile, (file, ThreadSafeFile)):
            # An existing file-like object
            self.logFile = logFile.name
            self.stream = logFile
        else:
            # Assume it is a filename
            self.logFile = logFile
            self.stream = ThreadSafeFile(logFile)
        self.maxPriority, self.maxLevel = self.logLevel = maxLevel
        self.showPid = showPid
        return

    def __str__(self):
        level = 'LOG_' + self.maxPriority.upper()
        return "<Logger %s, file %s, maxlevel %s>" % (self.ident,
                                                      self.logFile,
                                                      level)

    def clone(self, ident, logLevel=None, showPid=None):
        # Create a new Logger instance with the given identifier
        if logLevel is None:
            logLevel = self.logLevel
        if showPid is None:
            showPid = self.showPid
        return self.__class__(ident, self.stream, logLevel, showPid)

    def log(self, (priority, level), message):
        if level > self.maxLevel:
            # Ignore this message, more detail than we want to display
            return

        # Create the message header: "mmm dd HH:MM:SS ident[pid]: <message>"
        strtime = time.strftime('%b %d %H:%M:%S', time.localtime(time.time()))
        if self.showPid:
            ident = '%s[%d]' % (self.ident, os.getpid())
        else:
            ident = self.ident
        header = '%s %s: [%s]' % (strtime, ident, priority)

        if message.endswith('\n'):
            # strip single trailing newline
            message = message[:-1]

        # Map the header to each line of the message
        data = reduce(lambda data, line, header=header:
                      data + '%s %s\n' % (header, line),
                      message.split('\n'), '')

        # attempt to write buffered messages
        if self.buffer:
            try:
                self.stream.write(''.join(self.buffer))
                self.buffer = []
                self.bufferIsFull = False
            except (IOError, socket.error):
                pass

        # Write it out
        try:
            self.stream.write(data)
        except (IOError, socket.error):
            # if log temporarily unwritable, buffer the data
            if self.bufferIsFull:
                pass
            else:
                lines = data.split('\n')
                if len(self.buffer) + len(lines) > self.buffer_maxsize:
                    self.bufferIsFull = True
                    self.buffer.append('%s Additional messages exist but were not logged (buffer full)\n' % header)
                else:
                    self.buffer += lines
        return

    def emergency(self, msg):
        return self.log(LOG_EMERG, msg)

    def alert(self, msg):
        return self.log(LOG_ALERT, msg)

    def critical(self, msg):
        return self.log(LOG_CRIT, msg)

    def error(self, msg):
        return self.log(LOG_ERR, msg)

    def warning(self, msg):
        return self.log(LOG_WARNING, msg)

    def notice(self, msg):
        return self.log(LOG_NOTICE, msg)

    def info(self, msg):
        return self.log(LOG_INFO, msg)

    def debug(self, msg):
        return self.log(LOG_DEBUG, msg)


class StreamLogger:
    """
    A wrapper around a Logger instance which allows the log facility
    to be used in place of a stream object.
    """
    def __init__(self, logger, priority):
        self._logger = logger
        self._priority = priority
        self._lock = threading.Lock()
        self._buffer = []

    def flush(self):
        if self._buffer:
            self._lock.acquire()
            try:
                msg = ''.join(self._buffer)
                self._buffer = self._buffer[:0]
            finally:
                self._lock.release()
            self._logger.log(self._priority, msg)
        return

    def write(self, str):
        if '\n' in str:
            parts = str.split('\n')
            self._lock.acquire()
            try:
                parts[0] = ''.join(self._buffer) + parts[0]
                self._buffer = parts[-1:]
            finally:
                self._lock.release()
            for msg in parts[:-1]:
                self._logger.log(self._priority, msg)
        else:
            self._buffer.append(str)
        return

    def isatty(self):
        return False


class NullLogger:
    def emergency(self, msg):
        pass

    def alert(self, msg):
        pass

    def critical(self, msg):
        pass

    def error(self, msg):
        pass

    def warning(self, msg):
        pass

    def notice(self, msg):
        pass

    def info(self, msg):
        pass

    def debug(self, msg):
        pass


