194 lines
4.8 KiB
Python
194 lines
4.8 KiB
Python
import logging
|
|
import re
|
|
import traceback
|
|
import uuid
|
|
from enum import IntEnum, unique
|
|
|
|
from ._journal import send, syslog_priorities
|
|
|
|
|
|
try:
|
|
from collections.abc import Mapping
|
|
except ImportError:
|
|
from collections import Mapping
|
|
|
|
|
|
_priorities = syslog_priorities()
|
|
|
|
|
|
__all__ = "write", "send", "Priority", "JournaldLogHandler", "Facility"
|
|
|
|
|
|
@unique
|
|
class Priority(IntEnum):
|
|
PANIC = _priorities["panic"]
|
|
WARNING = _priorities["warn"]
|
|
ALERT = _priorities["alert"]
|
|
NONE = _priorities["none"]
|
|
CRITICAL = _priorities["crit"]
|
|
DEBUG = _priorities["debug"]
|
|
INFO = _priorities["info"]
|
|
ERROR = _priorities["error"]
|
|
NOTICE = _priorities["notice"]
|
|
|
|
|
|
@unique
|
|
class Facility(IntEnum):
|
|
KERN = 0
|
|
USER = 1
|
|
MAIL = 2
|
|
DAEMON = 3
|
|
AUTH = 4
|
|
SYSLOG = 5
|
|
LPR = 6
|
|
NEWS = 7
|
|
UUCP = 8
|
|
CLOCK_DAEMON = 9
|
|
AUTHPRIV = 10
|
|
FTP = 11
|
|
NTP = 12
|
|
AUDIT = 13
|
|
ALERT = 14
|
|
CRON = 15
|
|
LOCAL0 = 16
|
|
LOCAL1 = 17
|
|
LOCAL2 = 18
|
|
LOCAL3 = 19
|
|
LOCAL4 = 20
|
|
LOCAL5 = 21
|
|
LOCAL6 = 22
|
|
LOCAL7 = 23
|
|
|
|
|
|
def write(message, priority=Priority.INFO):
|
|
""" Write message into systemd journal
|
|
:type priority: Priority
|
|
:type message: str
|
|
"""
|
|
|
|
priority = int(Priority(int(priority)))
|
|
|
|
send(priority=priority, message=message)
|
|
|
|
|
|
class JournaldLogHandler(logging.Handler):
|
|
FIELD_BADCHAR_RE = re.compile(r'\W')
|
|
LEVELS = {
|
|
logging.CRITICAL: Priority.CRITICAL.value,
|
|
logging.FATAL: Priority.PANIC.value,
|
|
logging.ERROR: Priority.ERROR.value,
|
|
logging.WARNING: Priority.WARNING.value,
|
|
logging.WARN: Priority.WARNING.value,
|
|
logging.INFO: Priority.INFO.value,
|
|
logging.DEBUG: Priority.DEBUG.value,
|
|
logging.NOTSET: Priority.NONE.value,
|
|
}
|
|
|
|
__slots__ = ("__facility",)
|
|
|
|
def __init__(self, identifier=None, facility=Facility.DAEMON):
|
|
"""
|
|
|
|
:type identifier: Override default journald identifier
|
|
:type facility: Facility
|
|
"""
|
|
logging.Handler.__init__(self)
|
|
self.__identifier = identifier
|
|
self.__facility = int(facility)
|
|
|
|
@staticmethod
|
|
def _to_microsecond(ts):
|
|
"""
|
|
|
|
:type ts: float
|
|
"""
|
|
return int(ts * 1000 * 1000)
|
|
|
|
def emit(self, record):
|
|
message = str(record.getMessage())
|
|
|
|
tb_message = ""
|
|
if record.exc_info:
|
|
tb_message = "\n".join(
|
|
traceback.format_exception(*record.exc_info)
|
|
)
|
|
|
|
message += "\n"
|
|
message += tb_message
|
|
|
|
ts = self._to_microsecond(record.created)
|
|
|
|
hash_fields = (
|
|
message,
|
|
record.funcName,
|
|
record.levelno,
|
|
record.process,
|
|
record.processName,
|
|
record.levelname,
|
|
record.pathname,
|
|
record.name,
|
|
record.thread,
|
|
record.lineno,
|
|
ts,
|
|
tb_message,
|
|
)
|
|
|
|
message_id = uuid.uuid3(
|
|
uuid.NAMESPACE_OID, "$".join(str(x) for x in hash_fields)
|
|
).hex
|
|
|
|
data = {
|
|
key: value
|
|
for key, value in record.__dict__.items()
|
|
if not key.startswith("_") and value is not None
|
|
}
|
|
data["message"] = self.format(record)
|
|
data["priority"] = self.LEVELS[data.pop("levelno")]
|
|
data["syslog_facility"] = self.__facility
|
|
|
|
data["code_file"] = data.pop("filename")
|
|
data["code_line"] = data.pop("lineno")
|
|
data["code_func"] = data.pop("funcName")
|
|
if self.__identifier:
|
|
data["syslog_identifier"] = self.__identifier
|
|
else:
|
|
data["syslog_identifier"] = data["name"]
|
|
|
|
if "msg" in data:
|
|
data["message_raw"] = data.pop("msg")
|
|
|
|
data["message_id"] = message_id
|
|
data["code_module"] = data.pop("module")
|
|
data["logger_name"] = data.pop("name")
|
|
data["pid"] = data.pop("process")
|
|
data["proccess_name"] = data.pop("processName")
|
|
data["errno"] = 0 if not record.exc_info else 255
|
|
data["relative_ts"] = self._to_microsecond(data.pop("relativeCreated"))
|
|
data["thread_name"] = data.pop("threadName")
|
|
|
|
args = data.pop("args", [])
|
|
if isinstance(args, Mapping):
|
|
for key, value in args.items():
|
|
key = self.FIELD_BADCHAR_RE.sub('_', key)
|
|
data["argument_%s" % key] = value
|
|
else:
|
|
for idx, item in enumerate(args):
|
|
data["argument_%d" % idx] = str(item)
|
|
|
|
if tb_message:
|
|
data["traceback"] = tb_message
|
|
|
|
send(**data)
|
|
|
|
|
|
handler = JournaldLogHandler()
|
|
|
|
|
|
class JournaldLogger(logging.Logger):
|
|
def __init__(self, level, name="root"):
|
|
super(JournaldLogger, self).__init__(name, level)
|
|
self.addHandler(handler)
|
|
|
|
|
|
Logger = JournaldLogger(logging.WARNING)
|