Source code for shinken.log

#!/usr/bin/env python

# -*- coding: utf-8 -*-

# Copyright (C) 2009-2014:
#     Gabes Jean, naparuba@gmail.com
#     Gerhard Lausser, Gerhard.Lausser@consol.de
#     Gregory Starck, g.starck@gmail.com
#     Hartmut Goebel, h.goebel@goebel-consult.de
#
# This file is part of Shinken.
#
# Shinken is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Shinken 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Shinken.  If not, see <http://www.gnu.org/licenses/>.

import logging
import sys
import os
import stat
from logging import Handler, Formatter, StreamHandler, NOTSET, FileHandler
from logging.handlers import TimedRotatingFileHandler

from brok import Brok

try:
    from shinken.misc.termcolor import cprint
except (SyntaxError, ImportError), exp:
    # Outch can't import a cprint, do a simple print
    def cprint(s, color='', end=''):
        print s


#obj = None
#name = None
human_timestamp_log = False

_brokhandler_ = None


defaultFormatter = Formatter('[%(created)i] %(levelname)s: %(message)s')
defaultFormatter_named = Formatter('[%(created)i] %(levelname)s: [%(name)s] %(message)s')
humanFormatter = Formatter('[%(asctime)s] %(levelname)s: %(message)s', '%a %b %d %H:%M:%S %Y')
humanFormatter_named = Formatter('[%(asctime)s] %(levelname)s: [%(name)s] %(message)s', '%a %b %d %H:%M:%S %Y')
nagFormatter = Formatter('[%(created)i] %(message)s')

[docs]class BrokHandler(Handler): """ This log handler is forwarding log messages as broks to the broker. Only messages of level higher than DEBUG are send to other satellite to not risk overloading them. """ def __init__(self, broker): # Only messages of level INFO or higher are passed on to the # broker. Other handlers have a different level. Handler.__init__(self, logging.INFO) self._broker = broker
[docs] def emit(self, record): try: msg = self.format(record) brok = Brok('log', {'log': msg + '\n'}) self._broker.add(brok) except Exception: self.handleError(record)
[docs]class ColorStreamHandler(StreamHandler):
[docs] def emit(self, record): try: msg = self.format(record) colors = {'DEBUG': 'cyan', 'INFO': 'magenta', 'WARNING': 'yellow', 'CRITICAL': 'magenta', 'ERROR': 'red'} cprint(msg, colors[record.levelname]) except UnicodeEncodeError: print msg.encode('ascii', 'ignore') except Exception: self.handleError(record)
[docs]class Log(logging.Logger): """ Shinken logger class, wrapping access to Python logging standard library. See : https://docs.python.org/2/howto/logging.html#logging-flow for more detail about how log are handled""" def __init__(self, name="Shinken", level=NOTSET, log_set=False): logging.Logger.__init__(self, name, level) self.pre_log_buffer = [] self.log_set = log_set
[docs] def setLevel(self, level): """ Set level of logger and handlers. The logger need the lowest level (see link above) """ if not isinstance(level, int): level = getattr(logging, level, None) if not level or not isinstance(level, int): raise TypeError('log level must be an integer') # Not very useful, all we have to do is no to set the level > info for the brok handler self.level = min(level, logging.INFO) # Only set level to file and/or console handler for handler in self.handlers: if isinstance(handler, BrokHandler): continue handler.setLevel(level)
[docs] def load_obj(self, object, name_=None): """ We load the object where we will put log broks with the 'add' method """ global _brokhandler_ _brokhandler_ = BrokHandler(object) if name_ is not None or self.name is not None: if name_ is not None: self.name = name_ # We need to se the name format to all other handlers for handler in self.handlers: handler.setFormatter(defaultFormatter_named) _brokhandler_.setFormatter(defaultFormatter_named) else: _brokhandler_.setFormatter(defaultFormatter) self.addHandler(_brokhandler_)
[docs] def register_local_log(self, path, level=None, purge_buffer=True): """The shinken logging wrapper can write to a local file if needed and return the file descriptor so we can avoid to close it. Add logging to a local log-file. The file will be rotated once a day """ self.log_set = True # Todo : Create a config var for backup count if os.path.exists(path) and not stat.S_ISREG(os.stat(path).st_mode): # We don't have a regular file here. Rotate may fail # It can be one of the stat.S_IS* (FIFO? CHR?) handler = FileHandler(path) else: handler = TimedRotatingFileHandler(path, 'midnight', backupCount=5) if level is not None: handler.setLevel(level) if self.name is not None: handler.setFormatter(defaultFormatter_named) else: handler.setFormatter(defaultFormatter) self.addHandler(handler) # Ok now unstack all previous logs if purge_buffer: self._destack() # Todo : Do we need this now we use logging? return handler.stream.fileno()
[docs] def set_human_format(self, on=True): """ Set the output as human format. If the optional parameter `on` is False, the timestamps format will be reset to the default format. """ global human_timestamp_log human_timestamp_log = bool(on) # Apply/Remove the human format to all handlers except the brok one. for handler in self.handlers: if isinstance(handler, BrokHandler): continue if self.name is not None: handler.setFormatter(human_timestamp_log and humanFormatter_named or defaultFormatter_named) else: handler.setFormatter(human_timestamp_log and humanFormatter or defaultFormatter) # Stack logs if we don't open a log file so we will be able to flush them # Stack max 500 logs (no memory leak please...)
def _stack(self, level, args, kwargs): if self.log_set: return self.pre_log_buffer.append( (level, args, kwargs) ) if len(self.pre_log_buffer) > 500: self.pre_log_buffer = self.pre_log_buffer[2:] # Ok, we are opening a log file, flush all the logs now def _destack(self): for (level, args, kwargs) in self.pre_log_buffer: f = getattr(logging.Logger, level, None) if f is None: self.warning('Missing level for a log? %s', level) continue f(self, *args, **kwargs)
[docs] def debug(self, *args, **kwargs): self._stack('debug', args, kwargs) logging.Logger.debug(self, *args, **kwargs)
[docs] def info(self, *args, **kwargs): self._stack('info', args, kwargs) #super(logging.Logger, self).info(*args, **kwargs) logging.Logger.info(self, *args, **kwargs)
[docs] def warning(self, *args, **kwargs): self._stack('warning', args, kwargs) logging.Logger.warning(self, *args, **kwargs)
[docs] def error(self, *args, **kwargs): self._stack('error', args, kwargs) logging.Logger.error(self, *args, **kwargs) #--- create the main logger ---
logging.setLoggerClass(Log) logger = logging.getLogger('Shinken') if hasattr(sys.stdout, 'isatty'): csh = ColorStreamHandler(sys.stdout) if logger.name is not None: csh.setFormatter(defaultFormatter_named) else: csh.setFormatter(defaultFormatter) logger.addHandler(csh)
[docs]def naglog_result(level, result, *args): """ Function use for old Nag compatibility. We to set format properly for this call only. Dirty Hack to keep the old format, we should have another logger and use one for Shinken logs and another for monitoring data """ prev_formatters = [] for handler in logger.handlers: prev_formatters.append(handler.formatter) handler.setFormatter(nagFormatter) log_fun = getattr(logger, level) if log_fun: log_fun(result) for index, handler in enumerate(logger.handlers): handler.setFormatter(prev_formatters[index])
Read the Docs v: latest
Versions
latest
documentation
Downloads
PDF
HTML
Epub
On Read the Docs
Project Home
Builds

Free document hosting provided by Read the Docs.