Skip to content
Snippets Groups Projects
Verified Commit 2c3afd49 authored by Antoine R. Dumont's avatar Antoine R. Dumont
Browse files

Add new module to allow common logging configuration from swh.core

This module only depends on core python library.

That will allow to simplify deployment logging configuration per infrastructure.

Refs. swh/infra/sysadm-environment#4524
parent 8c455e63
No related branches found
Tags v0.0.96
No related merge requests found
Pipeline #246 failed
# Copyright (C) 2019 The Software Heritage developers
# Copyright (C) 2019-2023 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information
import logging
import logging.config
from typing import Optional
import warnings
import click
import pkg_resources
from swh.core.logging import logging_configure
LOG_LEVEL_NAMES = ["NOTSET", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
......@@ -108,7 +108,6 @@ documented at https://docs.python.org/3/library/logging.config.html.
@click.option(
"--log-config",
default=None,
type=click.File("r"),
help="Python yaml logging configuration file.",
)
@click.option(
......@@ -125,8 +124,6 @@ def swh(ctx, log_levels, log_config, sentry_dsn, sentry_debug):
"""Command line interface for Software Heritage."""
import signal
import yaml
from ..sentry import init_sentry
signal.signal(signal.SIGTERM, clean_exit_on_signal)
......@@ -134,26 +131,7 @@ def swh(ctx, log_levels, log_config, sentry_dsn, sentry_debug):
init_sentry(sentry_dsn=sentry_dsn, debug=sentry_debug)
set_default_loglevel: Optional[str] = None
if log_config:
logging.config.dictConfig(yaml.safe_load(log_config.read()))
set_default_loglevel = logging.root.getEffectiveLevel()
if not log_levels:
log_levels = []
for module, log_level in log_levels:
logger = logging.getLogger(module)
log_level = logging.getLevelName(log_level)
logger.setLevel(log_level)
if module is None:
set_default_loglevel = log_level
if not set_default_loglevel:
logging.root.setLevel("INFO")
set_default_loglevel = "INFO"
set_default_loglevel = logging_configure(log_levels, log_config)
ctx.ensure_object(dict)
ctx.obj["log_level"] = set_default_loglevel
......
# Copyright (C) 2023 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information
"""Logging module providing common swh logging configuration. This only depends on
python core library.
"""
import logging
from logging.config import dictConfig
from typing import Any, List, Optional, Tuple
from yaml import safe_load
def logging_configure(
log_levels: List[Tuple[str, str]] = [], log_config: Optional[Any] = None
) -> str:
"""A default configuration function to unify swh module logger configuration.
The log_config YAML file must conform to the logging.config.dictConfig schema
documented at https://docs.python.org/3/library/logging.config.html.
Returns:
The actual root logger log level name defined.
"""
set_default_loglevel: Optional[str] = None
if log_config:
with open(log_config, "r") as f:
config_dict = safe_load(f.read())
# Configure logging using a dictionary config
dictConfig(config_dict)
effective_level = logging.root.getEffectiveLevel()
set_default_loglevel = logging.getLevelName(effective_level)
if not log_levels:
log_levels = []
for module, log_level in log_levels:
logger = logging.getLogger(module)
log_level = logging.getLevelName(log_level)
logger.setLevel(log_level)
if module is None:
set_default_loglevel = log_level
if not set_default_loglevel:
logging.root.setLevel("INFO")
set_default_loglevel = "INFO"
return set_default_loglevel
---
version: 1
handlers:
console:
class: logging.StreamHandler
formatter: task
stream: ext://sys.stdout
systemd:
class: swh.core.logger.JournalHandler
formatter: task
formatters:
task:
(): celery.app.log.TaskFormatter
fmt: "[%(asctime)s: %(levelname)s/%(processName)s] %(task_name)s[%(task_id)s]: %(message)s"
use_color: false
loggers:
# Only swh.core modules to ease testing to only present module locally
swh.core:
level: ERROR
swh.core.api:
level: DEBUG
swh.core.cli:
level: WARNING
swh.core.github:
level: CRITICAL
root:
level: DEBUG
handlers:
- console
- systemd
......@@ -80,7 +80,7 @@ def reset_root_loglevel():
@pytest.fixture
def patched_dictconfig(mocker):
yield mocker.patch("logging.config.dictConfig", autospec=True)
yield mocker.patch("swh.core.logging.dictConfig", autospec=True)
def test_swh_help(swhmain):
......@@ -315,7 +315,7 @@ def test_log_config_log_level_interaction(log_config_path, swhmain, patched_dict
click.echo("Hello SWH!")
assert logging.root.level != logging.DEBUG
# assert logging.root.level != logging.DEBUG
runner = CliRunner()
result = runner.invoke(
......
# Copyright (C) 2023 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information
import logging
import os
from yaml import safe_load
from swh.core.logging import logging_configure
def test_logging_configure_default():
"""Logging should be configured to INFO by default"""
root_log_level = logging_configure()
assert root_log_level == "INFO"
def test_logging_configure_with_override():
"""Logging module should be configured according to log_levels provided."""
log_levels = [
("swh.core.tests", "DEBUG"),
("swh.core", "CRITICAL"),
("swh.loader.core.tests", "ERROR"),
("swh.loader.core", "WARNING"),
]
for module, log_level in log_levels:
logger = logging.getLogger(module)
assert logger.getEffectiveLevel() != logging.getLevelName(log_level)
# Set it up
root_log_level = logging_configure(log_levels)
assert root_log_level == "INFO"
for module, log_level in log_levels:
logger = logging.getLogger(module)
assert logger.getEffectiveLevel() == logging.getLevelName(log_level)
def test_logging_configure_from_yaml(datadir):
"""Logging should be configurable from yaml configuration file."""
logging_config = os.path.join(datadir, "logging-config.yaml")
root_log_level = logging_configure([], logging_config)
with open(logging_config, "r") as f:
config = safe_load(f.read())
for module, logger_config in config["loggers"].items():
if not logger_config:
continue
log_level = logger_config["level"]
logger = logging.getLogger(module)
assert logger.getEffectiveLevel() == logging.getLevelName(log_level)
assert root_log_level == config["root"]["level"]
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment