1
0
Fork 0
mirror of https://github.com/Kozea/Radicale.git synced 2025-06-26 16:45:52 +00:00

Upgrade to journald's native journal protocol

This commit is contained in:
Unrud 2023-03-21 00:03:29 +01:00
parent 360484e2d5
commit 9276c65462

View file

@ -25,11 +25,16 @@ Log messages are sent to the first available target of:
""" """
import contextlib
import logging import logging
import os import os
import socket
import struct
import sys import sys
import threading import threading
from typing import Any, Callable, ClassVar, Dict, Iterator, Union import time
from typing import (Any, Callable, ClassVar, Dict, Iterator, Optional, Tuple,
Union)
from radicale import types from radicale import types
@ -65,6 +70,9 @@ class IdentLogRecordFactory:
if current_thread.name and main_thread != current_thread: if current_thread.name and main_thread != current_thread:
ident += "/%s" % current_thread.name ident += "/%s" % current_thread.name
record.ident = ident # type:ignore[attr-defined] record.ident = ident # type:ignore[attr-defined]
record.tid = None # type:ignore[attr-defined]
if sys.version_info >= (3, 8):
record.tid = current_thread.native_id
return record return record
@ -75,14 +83,76 @@ class ThreadedStreamHandler(logging.Handler):
terminator: ClassVar[str] = "\n" terminator: ClassVar[str] = "\n"
_streams: Dict[int, types.ErrorStream] _streams: Dict[int, types.ErrorStream]
_journal_stream_id: Optional[Tuple[int, int]]
_journal_socket: Optional[socket.socket]
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self._streams = {} self._streams = {}
self._journal_stream_id = None
with contextlib.suppress(TypeError, ValueError):
dev, inode = os.environ.get("JOURNAL_STREAM", "").split(":", 1)
self._journal_stream_id = int(dev), int(inode)
self._journal_socket = None
if self._journal_stream_id and hasattr(socket, "AF_UNIX"):
journal_socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
try:
journal_socket.connect("/run/systemd/journal/socket")
except OSError:
journal_socket.close()
else:
self._journal_socket = journal_socket
def _detect_journal(self, stream):
if not self._journal_stream_id:
return False
try:
stat = os.fstat(stream.fileno())
except Exception:
return False
return self._journal_stream_id == (stat.st_dev, stat.st_ino)
@staticmethod
def _encode_journal(data):
msg = b""
for key, value in data.items():
if key is None:
continue
key = key.encode()
value = str(value).encode()
if b"\n" in value:
msg += (key + b"\n" +
struct.pack("<Q", len(value)) + value + b"\n")
else:
msg += key + b"=" + value + b"\n"
return msg
def _emit_journal(self, record):
priority = {"DEBUG": 7,
"INFO": 6,
"WARNING": 4,
"ERROR": 3,
"CRITICAL": 2}.get(record.levelname, 4)
timestamp = time.strftime("%Y-%m-%dT%H:%M:%S.%%03dZ",
time.gmtime(record.created)) % record.msecs
data = {"PRIORITY": priority,
"TID": record.tid,
"SYSLOG_IDENTIFIER": record.name,
"SYSLOG_FACILITY": 1,
"SYSLOG_PID": record.process,
"SYSLOG_TIMESTAMP": timestamp,
"CODE_FILE": record.pathname,
"CODE_LINE": record.lineno,
"CODE_FUNC": record.funcName,
"MESSAGE": self.format(record)}
self._journal_socket.sendall(self._encode_journal(data))
def emit(self, record: logging.LogRecord) -> None: def emit(self, record: logging.LogRecord) -> None:
try: try:
stream = self._streams.get(threading.get_ident(), sys.stderr) stream = self._streams.get(threading.get_ident(), sys.stderr)
if self._journal_socket and self._detect_journal(stream):
self._emit_journal(record)
return
msg = self.format(record) msg = self.format(record)
stream.write(msg) stream.write(msg)
stream.write(self.terminator) stream.write(self.terminator)