Skip to content

Instantly share code, notes, and snippets.

@sveinse
Last active January 4, 2018 18:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sveinse/041c69b6bbdde83cf5b00fecae3dcd50 to your computer and use it in GitHub Desktop.
Save sveinse/041c69b6bbdde83cf5b00fecae3dcd50 to your computer and use it in GitHub Desktop.
Proposed syslog observer for Twisted
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Syslog log observer.
"""
syslog = __import__('syslog')
from zope.interface import implementer
from ._observer import ILogObserver
from ._format import formatEvent
from ._logger import LogLevel
# These defaults come from the Python syslog docs.
DEFAULT_OPTIONS = 0
DEFAULT_FACILITY = syslog.LOG_USER
# Map the twisted LogLevels up against the syslog values
LOGLEVEL_MAP = {
LogLevel.debug: syslog.LOG_DEBUG,
LogLevel.info: syslog.LOG_INFO,
LogLevel.warn: syslog.LOG_WARNING,
LogLevel.error: syslog.LOG_ERR,
LogLevel.critical: syslog.LOG_CRIT,
}
@implementer(ILogObserver)
class SyslogObserver(object):
"""
A log observer for logging to syslog.
See L{twisted.logger} for context.
The observer will use 'C{log_level}' to determine syslog loglevel. If no
'C{log_level}' is in the event, it will use 'C{critical}' for failures,
and 'C{info}' for others. The syslog facility can be set using
'C{log_facility}', otherwise the default value in the constructor will be
used.
"""
openlog = syslog.openlog
syslog = syslog.syslog
def __init__(self, prefix, options=DEFAULT_OPTIONS,
facility=DEFAULT_FACILITY, dumpTraceback=False):
"""
@type prefix: C{str}
@param prefix: The syslog prefix to use.
@type options: C{int}
@param options: A bitvector represented as an integer of the syslog
options to use.
@type facility: C{int}
@param facility: An indication to the syslog daemon of what sort of
program this is (essentially, an additional arbitrary metadata
classification for messages sent to syslog by this observer).
@type dumpTraceback: C{bool}
@param dumpTraceback: If the traceback shall be printed to the syslog
in the case of failure log events.
"""
self.openlog(prefix, options, facility)
self.dumpTraceback = dumpTraceback
def __call__(self, event):
"""
Write event to syslog.
@param event: An event.
@type event: L{dict}
"""
# Figure out what the message-text is.
text = formatEvent(event)
if self.dumpTraceback and "log_failure" in event:
try:
traceback = event["log_failure"].getTraceback()
except:
traceback = u"(UNABLE TO OBTAIN TRACEBACK FROM EVENT)\n"
text = u"\n".join((text, traceback))
if text is None:
return
# Figure out what syslog parameters we might need to use.
level = event.get("log_level", None)
if level is None:
if 'log_failure' in event:
level = LogLevel.critical
else:
level = LogLevel.info
priority = LOGLEVEL_MAP[level]
facility = int(event.get('log_facility', DEFAULT_FACILITY))
# Break the message up into lines and send them.
lines = text.split('\n')
while lines[-1:] == ['']:
lines.pop()
firstLine = True
for line in lines:
if firstLine:
firstLine = False
else:
line = ' ' + line
self.syslog(priority | facility,
'[%s] %s' % (event.get('log_system', '-'), line))
#-*- python -*-
"""
Log functions.
"""
import sys
from zope.interface import implementer
from twisted.logger import Logger
from twisted.logger import ILogObserver
from twisted.logger import formatTime
from twisted.logger import formatEvent
from twisted.logger import globalLogBeginner
from twisted.logger import globalLogPublisher
from twisted.logger import FileLogObserver
from twisted.python.compat import unicode
from _syslog import SyslogObserver
__all__ = ["start", "Logger"]
# Mod of twisted.logger.formatEventAsClassicLogText()
def formatLuminaLogText(event, formatTime=formatTime):
"""
Format an event as a line of human-readable text for, e.g. traditional log
file output.
"""
eventText = formatEvent(event)
if "log_failure" in event:
try:
traceback = event["log_failure"].getTraceback()
except:
traceback = u"(UNABLE TO OBTAIN TRACEBACK FROM EVENT)\n"
eventText = u"\n".join((eventText, traceback))
if not eventText:
return None
eventText = eventText.replace(u"\n", u"\n\t")
timeStamp = formatTime(event.get("log_time", None))
level = event.get("log_level", None)
if level is None:
levelName = u"-"
else:
levelName = level.name
system = event.get("log_system", None)
return u"{timeStamp} {level:<8} [{system}] {event}\n".format(
timeStamp=timeStamp,
level=levelName,
system=system,
event=eventText,
)
@implementer(ILogObserver)
class LuminaLogFormatter(object):
''' Logging observer for Lumina '''
def __call__(self, event):
''' Main dispatcher for log-events '''
# We want log_system to always be valid (SyslogObserver uses it)
system = event.get("log_system", None)
if system is None:
system = event.get("log_namespace", u"-")
else:
try:
system = unicode(system)
except Exception:
system = u"UNFORMATTABLE"
event['log_system'] = system
def start(syslog=False, logfile=None, syslog_prefix='lumina', redirect_stdio=True):
''' Start the custom logger '''
# System defaults from twisted.logger._global.py:
# globalLogPublisher = LogPublisher()
# globalLogBeginner = LogBeginner(globalLogPublisher, sys.stderr, sys, warnings)
if logfile is None:
logfile = sys.stdout
# Lumina log observers
if syslog:
out_observer = SyslogObserver(prefix=syslog_prefix, dumpTraceback=True)
else:
out_observer = FileLogObserver(sys.stdout, formatLuminaLogText)
observers = [
LuminaLogFormatter(),
out_observer,
]
# This logger will take over the system (the default LogPublisher). It will
# iterate over any messages that has already been logged prior to
# this registration. However, any errors in the observers will be silently
# ignored because the observers are no longer run through the
# LogPublisher()
globalLogBeginner.beginLoggingTo(observers,
redirectStandardIO=redirect_stdio)
# Alternatively, register the observers using the default (global)
# LogPublisher (found in twisted.logger._observer.py) . The upside with
# this approach is that any errors in the observes will be catched and
# printed. The downside is that it is not possible to prevent redirection
# of stdio and it will not replay pre-logger entries prior to installation.
# It will also print failure messages >=critical to sys.stderr because
# _temporaryObserver is still installed. (see self._temporaryObserver in
# LogBeginner)
#for obs in observers:
# globalLogPublisher.addObserver(obs)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment