1
0
mirror of https://github.com/mariadb-corporation/mariadb-columnstore-engine.git synced 2025-11-03 17:13:17 +03:00
Files
mariadb-columnstore-engine/cmapi/cmapi_server/logging_management.py
Alexander Presnyakov a0b4bcd1ce Basic request tracer
Tracing requests

Custom log factory adds all trace values as one log record parameter (it will be empty if trace values are empty, like in MainThread where there are no incoming requests)
2025-09-03 20:32:03 +04:00

163 lines
5.7 KiB
Python

import json
import logging
import logging.config
from functools import partial, partialmethod
import cherrypy
from cherrypy import _cperror
from cmapi_server.constants import CMAPI_LOG_CONF_PATH
from cmapi_server.tracer import get_tracer
class AddIpFilter(logging.Filter):
"""Filter to add IP address to logging record."""
def filter(self, record):
record.ip = cherrypy.request.remote.name or cherrypy.request.remote.ip
return True
def install_trace_record_factory() -> None:
"""Install a LogRecord factory that adds 'trace_params' field.
'trace_params' will be an empty string if there is no active trace/span
(like in MainThread, where there is no incoming requests).
Otherwise it will contain trace parameters.
"""
current_factory = logging.getLogRecordFactory()
def factory(*args, **kwargs): # type: ignore[no-untyped-def]
record = current_factory(*args, **kwargs)
try:
trace_id, span_id, parent_span_id = get_tracer().current_trace_ids()
record.trace_params = (
f" rid={trace_id} sid={span_id} psid={parent_span_id}"
)
except Exception:
record.trace_params = " rid= sid= psid="
return record
logging.setLogRecordFactory(factory)
def custom_cherrypy_error(
self, msg='', context='', severity=logging.INFO, traceback=False
):
"""Write the given ``msg`` to the error log. [now without hardcoded time]
This is not just for errors! [looks awful, but cherrypy realisation as is]
Applications may call this at any time to log application-specific
information.
If ``traceback`` is True, the traceback of the current exception
(if any) will be appended to ``msg``.
..Note:
All informatio
"""
exc_info = None
if traceback:
exc_info = _cperror._exc_info()
self.error_log.log(severity, ' '.join((context, msg)), exc_info=exc_info)
def dict_config(config_filepath: str):
with open(config_filepath, 'r', encoding='utf-8') as json_config:
config_dict = json.load(json_config)
logging.config.dictConfig(config_dict)
def add_logging_level(level_name, level_num, method_name=None):
"""
Comprehensively adds a new logging level to the `logging` module and the
currently configured logging class.
`level_name` becomes an attribute of the `logging` module with the value
`level_num`.
`methodName` becomes a convenience method for both `logging` itself
and the class returned by `logging.getLoggerClass()` (usually just
`logging.Logger`).
If `methodName` is not specified, `levelName.lower()` is used.
To avoid accidental clobberings of existing attributes, this method will
raise an `AttributeError` if the level name is already an attribute of the
`logging` module or if the method name is already present
Example
-------
>>> add_logging_level('TRACE', logging.DEBUG - 5)
>>> logging.getLogger(__name__).setLevel('TRACE')
>>> logging.getLogger(__name__).trace('that worked')
>>> logging.trace('so did this')
>>> logging.TRACE
5
"""
if not method_name:
method_name = level_name.lower()
if hasattr(logging, level_name):
raise AttributeError(f'{level_name} already defined in logging module')
if hasattr(logging, method_name):
raise AttributeError(
f'{method_name} already defined in logging module'
)
if hasattr(logging.getLoggerClass(), method_name):
raise AttributeError(f'{method_name} already defined in logger class')
# This method was inspired by the answers to Stack Overflow post
# http://stackoverflow.com/q/2183233/2988730, especially
# https://stackoverflow.com/a/35804945
# https://stackoverflow.com/a/55276759
logging.addLevelName(level_num, level_name)
setattr(logging, level_name, level_num)
setattr(
logging.getLoggerClass(), method_name,
partialmethod(logging.getLoggerClass().log, level_num)
)
setattr(logging, method_name, partial(logging.log, level_num))
def enable_console_logging(logger: logging.Logger) -> None:
"""Enable logging to console for passed logger by adding a StreamHandler to it"""
console_handler = logging.StreamHandler()
logger.addHandler(console_handler)
def config_cmapi_server_logging():
# add custom level TRACE only for develop purposes
# could be activated using API endpoints or cli tool without relaunching
add_logging_level('TRACE', 5)
cherrypy._cplogging.LogManager.error = custom_cherrypy_error
# reconfigure cherrypy.access log message format
# Default access_log_format '{h} {l} {u} {t} "{r}" {s} {b} "{f}" "{a}"'
# h - remote.name or remote.ip, l - "-",
# u - getattr(request, 'login', None) or '-', t - self.time(),
# r - request.request_line, s - status,
# b - dict.get(outheaders, 'Content-Length', '') or '-',
# f - dict.get(inheaders, 'Referer', ''),
# a - dict.get(inheaders, 'User-Agent', ''),
# o - dict.get(inheaders, 'Host', '-'),
# i - request.unique_id, z - LazyRfc3339UtcTime()
cherrypy._cplogging.LogManager.access_log_format = (
'{h} ACCESS "{r}" code {s}, bytes {b}, user-agent "{a}"'
)
# Ensure trace_params is available on every record
install_trace_record_factory()
dict_config(CMAPI_LOG_CONF_PATH)
def change_loggers_level(level: str):
"""Set level for each custom logger except cherrypy library.
:param level: logging level to set
:type level: str
"""
loggers = [
logging.getLogger(name) for name in logging.root.manager.loggerDict
if 'cherrypy' not in name
]
loggers.append(logging.getLogger()) # add RootLogger
for logger in loggers:
logger.setLevel(level)