Initial open source commit
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
import logging
|
||||
import asyncio
|
||||
import sys
|
||||
import warnings
|
||||
from logging import StreamHandler, DEBUG, INFO, ERROR, WARNING, CRITICAL, raiseExceptions
|
||||
|
||||
|
||||
class AsyncHandler(StreamHandler):
|
||||
""" An async wrapper around logging.StreamHandler for async log streams like Redis PUB/SUB"""
|
||||
def __init__(self, stream=None, loop=None):
|
||||
"""
|
||||
Initialize the handler.
|
||||
|
||||
If stream is not specified, sys.stderr is used.
|
||||
"""
|
||||
super().__init__(stream)
|
||||
self.loop = loop
|
||||
|
||||
async def flush(self):
|
||||
"""
|
||||
Flushes the stream.
|
||||
"""
|
||||
await self.stream.flush()
|
||||
|
||||
async def emit(self, record):
|
||||
"""
|
||||
Emit a record.
|
||||
|
||||
If a formatter is specified, it is used to format the record.
|
||||
The record is then written to the stream with a trailing newline. If
|
||||
exception information is present, it is formatted using
|
||||
traceback.print_exception and appended to the stream. If the stream
|
||||
has an 'encoding' attribute, it is used to determine how to do the
|
||||
output to the stream.
|
||||
"""
|
||||
try:
|
||||
msg = self.format(record)
|
||||
await self.stream.write(msg + self.terminator)
|
||||
await self.flush()
|
||||
except Exception:
|
||||
self.handleError(record)
|
||||
|
||||
async def handle(self, record):
|
||||
"""
|
||||
Conditionally emit the specified logging record.
|
||||
|
||||
Emission depends on filters which may have been added to the handler.
|
||||
Wrap the actual emission of the record with acquisition/release of
|
||||
the I/O thread lock. Returns whether the filter passed the record for
|
||||
emission.
|
||||
"""
|
||||
rv = self.filter(record)
|
||||
if rv:
|
||||
self.acquire()
|
||||
try:
|
||||
await self.emit(record)
|
||||
finally:
|
||||
self.release()
|
||||
return rv
|
||||
|
||||
async def close(self):
|
||||
if self.stream is not None:
|
||||
await self.flush()
|
||||
await self.stream.close()
|
||||
super().close()
|
||||
|
||||
|
||||
class AsyncLogger(logging.Logger):
|
||||
""" An async wrapper around logging.Logger for async log streams like Redis PUB/SUB """
|
||||
def __init__(self, name, level=logging.ERROR, loop=asyncio.get_event_loop()):
|
||||
super().__init__(name, level=level)
|
||||
self.loop = loop
|
||||
|
||||
async def debug(self, msg, *args, **kwargs):
|
||||
"""
|
||||
Log 'msg % args' with severity 'DEBUG'.
|
||||
|
||||
To pass exception information, use the keyword argument exc_info with
|
||||
a true value, e.g.
|
||||
|
||||
logger.debug("Houston, we have a %s", "thorny problem", exc_info=1)
|
||||
"""
|
||||
if self.isEnabledFor(DEBUG):
|
||||
await self._log(DEBUG, msg, args, **kwargs)
|
||||
|
||||
async def info(self, msg, *args, **kwargs):
|
||||
"""
|
||||
Log 'msg % args' with severity 'INFO'.
|
||||
|
||||
To pass exception information, use the keyword argument exc_info with
|
||||
a true value, e.g.
|
||||
|
||||
logger.info("Houston, we have a %s", "interesting problem", exc_info=1)
|
||||
"""
|
||||
if self.isEnabledFor(INFO):
|
||||
await self._log(INFO, msg, args, **kwargs)
|
||||
|
||||
async def warning(self, msg, *args, **kwargs):
|
||||
"""
|
||||
Log 'msg % args' with severity 'WARNING'.
|
||||
|
||||
To pass exception information, use the keyword argument exc_info with
|
||||
a true value, e.g.
|
||||
|
||||
logger.warning("Houston, we have a %s", "bit of a problem", exc_info=1)
|
||||
"""
|
||||
if self.isEnabledFor(WARNING):
|
||||
await self._log(WARNING, msg, args, **kwargs)
|
||||
|
||||
async def warn(self, msg, *args, **kwargs):
|
||||
warnings.warn("The 'warn' method is deprecated, use 'warning' instead", DeprecationWarning, 2)
|
||||
await self.warning(msg, *args, **kwargs)
|
||||
|
||||
async def error(self, msg, *args, **kwargs):
|
||||
"""
|
||||
Log 'msg % args' with severity 'ERROR'.
|
||||
|
||||
To pass exception information, use the keyword argument exc_info with
|
||||
a true value, e.g.
|
||||
|
||||
logger.error("Houston, we have a %s", "major problem", exc_info=1)
|
||||
"""
|
||||
if self.isEnabledFor(ERROR):
|
||||
await self._log(ERROR, msg, args, **kwargs)
|
||||
|
||||
async def exception(self, msg, *args, exc_info=True, **kwargs):
|
||||
"""
|
||||
Convenience method for logging an ERROR with exception information.
|
||||
"""
|
||||
await self.error(msg, *args, exc_info=exc_info, **kwargs)
|
||||
|
||||
async def critical(self, msg, *args, **kwargs):
|
||||
"""
|
||||
Log 'msg % args' with severity 'CRITICAL'.
|
||||
|
||||
To pass exception information, use the keyword argument exc_info with
|
||||
a true value, e.g.
|
||||
|
||||
logger.critical("Houston, we have a %s", "major disaster", exc_info=1)
|
||||
"""
|
||||
if self.isEnabledFor(CRITICAL):
|
||||
await self._log(CRITICAL, msg, args, **kwargs)
|
||||
|
||||
fatal = critical
|
||||
|
||||
async def log(self, level, msg, *args, **kwargs):
|
||||
"""
|
||||
Log 'msg % args' with the integer severity 'level'.
|
||||
|
||||
To pass exception information, use the keyword argument exc_info with
|
||||
a true value, e.g.
|
||||
|
||||
logger.log(level, "We have a %s", "mysterious problem", exc_info=1)
|
||||
"""
|
||||
if not isinstance(level, int):
|
||||
if raiseExceptions:
|
||||
raise TypeError("level must be an integer")
|
||||
else:
|
||||
return
|
||||
if self.isEnabledFor(level):
|
||||
await self._log(level, msg, args, **kwargs)
|
||||
|
||||
async def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False):
|
||||
"""
|
||||
Low-level logging routine which creates a LogRecord and then calls
|
||||
all the handlers of this logger to handle the record.
|
||||
"""
|
||||
sinfo = None
|
||||
if logging._srcfile:
|
||||
try:
|
||||
fn, lno, func, sinfo = self.findCaller(stack_info)
|
||||
except ValueError:
|
||||
fn, lno, func = "(unknown file)", 0, "(unknown function)"
|
||||
else:
|
||||
fn, lno, func = "(unknown file)", 0, "(unknown function)"
|
||||
if exc_info:
|
||||
if isinstance(exc_info, BaseException):
|
||||
exc_info = (type(exc_info), exc_info, exc_info.__traceback__)
|
||||
elif not isinstance(exc_info, tuple):
|
||||
exc_info = sys.exc_info()
|
||||
record = self.makeRecord(self.name, level, fn, lno, msg, args,
|
||||
exc_info, func, extra, sinfo)
|
||||
await self.handle(record)
|
||||
|
||||
async def handle(self, record):
|
||||
"""
|
||||
Call the handlers for the specified record.
|
||||
|
||||
This method is used for unpickled records received from a socket, as
|
||||
well as those created locally. Logger-level filtering is applied.
|
||||
"""
|
||||
if (not self.disabled) and self.filter(record):
|
||||
await self.callHandlers(record)
|
||||
|
||||
async def callHandlers(self, record):
|
||||
"""
|
||||
Pass a record to all relevant handlers.
|
||||
|
||||
Loop through all handlers for this logger and its parents in the
|
||||
logger hierarchy. If no handler was found, output a one-off error
|
||||
message to sys.stderr. Stop searching up the hierarchy whenever a
|
||||
logger with the "propagate" attribute set to zero is found - that
|
||||
will be the last logger whose handlers are called.
|
||||
"""
|
||||
c = self
|
||||
found = 0
|
||||
while c:
|
||||
for hdlr in c.handlers:
|
||||
found += 1
|
||||
if record.levelno >= hdlr.level:
|
||||
await hdlr.handle(record)
|
||||
if not c.propagate:
|
||||
c = None
|
||||
else:
|
||||
c = c.parent
|
||||
if found == 0:
|
||||
if logging.lastResort:
|
||||
if record.levelno >= logging.lastResort.level:
|
||||
logging.lastResort.handle(record)
|
||||
elif logging.raiseExceptions and not self.manager.emittedNoHandlerWarning:
|
||||
sys.stderr.write("No handlers could be found for logger"
|
||||
" \"%s\"\n" % self.name)
|
||||
self.manager.emittedNoHandlerWarning = True
|
||||
|
||||
async def shutdown(self):
|
||||
"""
|
||||
Perform any cleanup actions in the logging system (e.g. flushing
|
||||
buffers).
|
||||
|
||||
Should be called at application exit.
|
||||
"""
|
||||
for handler in reversed(self.handlers):
|
||||
# errors might occur, for example, if files are locked
|
||||
# we just ignore them if raiseExceptions is not set
|
||||
try:
|
||||
if handler:
|
||||
try:
|
||||
# await handler.acquire() # do we need to lock?
|
||||
await handler.flush()
|
||||
await handler.close()
|
||||
except (OSError, ValueError):
|
||||
# Ignore errors which might be caused
|
||||
# because handlers have been closed but
|
||||
# references to them are still around at
|
||||
# application exit.
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
# handler.release() # We need to release if we decide to lock I guess
|
||||
self.removeHandler(handler)
|
||||
except Exception: # ignore everything, as we're shutting down
|
||||
pass
|
||||
Reference in New Issue
Block a user