Skip to content

Commit 27e4143

Browse files
committed
Add logging compatibility layer.
1 parent 13f9d24 commit 27e4143

File tree

8 files changed

+136
-58
lines changed

8 files changed

+136
-58
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
minor_changes:
2+
- "Add logging wrapper classes to simplify switch from twiggy to the standard logging framework
3+
(https://github.com/ansible-community/antsibull-core/issues/39, https://github.com/ansible-community/antsibull-core/pull/188)."

src/antsibull_core/ansible_core.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from packaging.version import Version as PypiVer
2222

2323
from . import app_context
24-
from .logging import log
24+
from .logging import get_module_logger
2525
from .subprocess_util import async_log_run
2626
from .utils.http import retry_get
2727

@@ -30,7 +30,7 @@
3030
from _typeshed import StrPath
3131

3232

33-
mlog = log.fields(mod=__name__)
33+
mlog = get_module_logger(__name__)
3434

3535

3636
class UnknownVersion(Exception):

src/antsibull_core/config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@
1414
import perky # type: ignore[import]
1515
import pydantic as p
1616

17-
from .logging import log
17+
from .logging import get_module_logger
1818
from .schemas.context import AppContext, LibContext
1919

2020
if t.TYPE_CHECKING:
2121
from _typeshed import StrPath
2222

23-
mlog = log.fields(mod=__name__)
23+
mlog = get_module_logger(__name__)
2424

2525
#: System config file location.
2626
SYSTEM_CONFIG_FILE = "/etc/antsibull.cfg"

src/antsibull_core/logging.py

Lines changed: 114 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
1919
.. seealso::
2020
21-
* :mod:`antsibull.config` to see how the antsibull scripts allow user defined configuration
21+
* :mod:`antsibull_core.config` to see how the antsibull scripts allow user defined configuration
2222
to configure logging after the bootstrap phase is over. This is the primary way that end
2323
users interact with the logging subsystem.
2424
@@ -31,32 +31,30 @@
3131
logging output is disabled. That way the library doesn't spam the user's screen with log messages.
3232
3333
An application that wishes to use the log must import the log and then call
34-
antsibull.logging.initialize_app_logging() before any other parts of antsibull are imported. See
34+
antsibull_core.logging.initialize_app_logging() before any other parts of antsibull are imported. See
3535
the :ref:`Application Logging <application_logging>`_ section for more details.
3636
3737
3838
Usage within a module
3939
=====================
4040
4141
Our convention for logging with twiggy is that the name field reflects the Python package that the
42-
code is coming from (in this case, it is already set to ``antsibull`` by :mod:`antsibull.logging`.)
42+
code is coming from (in this case, it is already set to ``antsibull`` by :mod:`antsibull_core.logging`.)
4343
At the toplevel of a module, set up a logger which has a field named ``mod`` which reflects the
4444
module name:
4545
4646
.. code-block:: python
4747
4848
# The antsibull log object with the name already set to `antsibull`.
49-
from logging import log
49+
from logging import get_module_logger
5050
51-
# mlog stands for module log. It's our convention to create a logger from the
52-
# antsibull.logging.log object in each module. `fields()` takes an arbitrary set of keyword
53-
# args and returns a new log object. Any log messages we emit with this log object (or its
54-
# children) will include the fields which were set on it. Our convention is to create mlog with
55-
# `fields(mod=__name__)` so that messages we make from mlog (or its children) have a field named
56-
# `mod` containing the name of the module.
51+
# mlog stands for module log. It's our convention to create a logger in each module.
52+
# The logger will containt the module name as the `mod` field.
53+
mlog = get_module_logger(__name__)
5754
58-
# `mod` and the value set to the module name.
59-
mlog = log.fields(mod=__name__)
55+
# `fields()` takes an arbitrary set of keyword args and returns a new log object.
56+
# Any log messages we emit with this log object (or its children) will include the fields
57+
# which were set on it.
6058
6159
TRICKY_COMPUTED_GLOBAL = [a**a for a in range(1, 4)]
6260
# Use mlog for logging interesting things that happen at the module level. Notice that we send
@@ -74,31 +72,31 @@
7472
7573
.. code-block:: python
7674
77-
def test_function(argument1):
78-
# flog stands for function log. It's our convention to use this name.
79-
# Create a new one in any function you want to log from.
80-
# By creating this from mlog, we copy any fields and other settings that we made to mlog.
81-
# Our convention is to use the `func` field to hold the name of the function we're in.
82-
flog = mlog.fields(func='test_function')
75+
def test_function(argument1):
76+
# flog stands for function log. It's our convention to use this name.
77+
# Create a new one in any function you want to log from.
78+
# By creating this from mlog, we copy any fields and other settings that we made to mlog.
79+
# Our convention is to use the `func` field to hold the name of the function we're in.
80+
flog = mlog.fields(func='test_function')
8381
84-
# This would output:
85-
# DEBUG:antsibull:func=test_function:mod=__main__|Enter
86-
flog.debug('Enter')
87-
value = do_something(argument1)
82+
# This would output:
83+
# DEBUG:antsibull:func=test_function:mod=__main__|Enter
84+
flog.debug('Enter')
85+
value = do_something(argument1)
8886
89-
flog.debug('Leave')
87+
flog.debug('Leave')
9088
91-
class FooBar:
92-
def __init__(self):
93-
flog = mlog.fields(func='FooBar.__init__')
94-
flog.debug('Enter')
89+
class FooBar:
90+
def __init__(self):
91+
flog = mlog.fields(func='FooBar.__init__')
92+
flog.debug('Enter')
9593
96-
self._x = initialize_x()
97-
self._y = initialize_y()
94+
self._x = initialize_x()
95+
self._y = initialize_y()
9896
99-
self.position = self.calculate_position(self._x, self._y)
97+
self.position = self.calculate_position(self._x, self._y)
10098
101-
flog.debug('Leave')
99+
flog.debug('Leave')
102100
103101
104102
.. _logging_levels::
@@ -146,15 +144,15 @@ def __init__(self):
146144
147145
An antsibull command (:file:`antsibull/cli/*.py`) should import the ``log`` object from this module.
148146
The log object will be configured for use within the library at first (silent) so the application
149-
should call :func:`antsibull.logging.initialize_app_logging` as soon as possible to tell the ``log``
147+
should call :func:`antsibull_core.logging.initialize_app_logging` as soon as possible to tell the ``log``
150148
that it is okay to emit messages.
151149
152150
The initial application logging configuration will log to stderr at the ``WARNING`` level or
153151
higher. If the :envvar:`ANTIBULL_EARLY_DEBUG` environment variable is set, then it will log at
154152
the ``DEBUG`` level rather than ``WARNING``.
155153
156154
The antsibull command should read the configuration settings, which may include user specified
157-
logging configuration and application defaults, and then call :twiggy:func:`twiggy.dict_config` to
155+
logging configuration and application defaults, and then call :func:`antsibull_core.logging.configure_logger` to
158156
finish the setup. At that point, logging calls will emit logs according to the user's
159157
configuration.
160158
@@ -163,10 +161,9 @@ def __init__(self):
163161
.. code-block:: python
164162
165163
# File is antsibull/cli/antsibull_command.py
166-
import twiggy
167164
# log is the toplevel log object. It is important to import this and initialize it prior to
168165
# using the log so that sane defaults can be set.
169-
from ..logging import log, initialize_app_logging
166+
from antsibull_core.logging import configure_logger, get_module_logger, initialize_app_logging
170167
171168
# By default, the log is configured to be useful within a library where the user may not have
172169
# been given the chance to configure the log. Calling initialize_app_logging() reconfigures
@@ -178,7 +175,7 @@ def __init__(self):
178175
from ..config import load_config
179176
180177
181-
mlog = log.fields(mod=__name__)
178+
mlog = get_module_logger(__name__)
182179
183180
def run(args):
184181
flog = mlog.fields(func='run')
@@ -189,12 +186,12 @@ def run(args):
189186
with app_context.app_and_lib_context(context_data) as (app_ctx, dummy_):
190187
# initialize_app_logging() sets the log's configuration with defaults appropriate for
191188
# an application but this call takes that one step further. It takes the logging
192-
# configuration from the user's config file and hands it to twiggy.dict_config() so
189+
# configuration from the user's config file and hands it to configure_logger() so
193190
# that the user has ultimate control over what log level, what format, and which file
194191
# the log is output as. See the twiggy documentation for information on the format of
195-
# the logging config. See the antsibull.app_context documentation if you want more
192+
# the logging config. See the antsibull_core.app_context documentation if you want more
196193
# information on the context object.
197-
twiggy.dict_config(app_ctx.logging_cfg.model_dump())
194+
configure_logger(app_ctx)
198195
199196
200197
Once those steps are taken, any further logging calls will obey the user's configuration.
@@ -203,11 +200,16 @@ def run(args):
203200

204201
from __future__ import annotations
205202

203+
import abc
206204
import os
205+
import typing as t
207206

208207
import twiggy # type: ignore[import]
209208
import twiggy.levels # type: ignore[import]
210209

210+
if t.TYPE_CHECKING:
211+
from .schemas.context import AppContext
212+
211213
#: The standard log to use everywhere. The name of the logger for all of the antsibull libraries
212214
#: is antsibull so that it is easy to setup an emitter for all of antsibull. For those used to
213215
#: using the module's __name__ field as the name, the idiom we use here is to set the module name
@@ -229,7 +231,7 @@ def initialize_app_logging() -> None:
229231
"""
230232
Change log settings to make sense for an application.
231233
232-
Merely importing the :mod:`antsibull.logging` module sets up the logger for use as part of
234+
Merely importing the :mod:`antsibull_core.logging` module sets up the logger for use as part of
233235
a library. Calling this function will initialize the logger for use in an application.
234236
"""
235237
# We want to see logs from the antsibull library, so the very first thing we do is turn the log
@@ -243,4 +245,75 @@ def initialize_app_logging() -> None:
243245
twiggy.quick_setup(min_level=_level)
244246

245247

246-
__all__ = ("log", "initialize_app_logging")
248+
class Logger(metaclass=abc.ABCMeta):
249+
@abc.abstractmethod
250+
def debug(self, format_spec: str, *args, **kwargs) -> None:
251+
pass
252+
253+
@abc.abstractmethod
254+
def info(self, format_spec: str, *args, **kwargs) -> None:
255+
pass
256+
257+
@abc.abstractmethod
258+
def notice(self, format_spec: str, *args, **kwargs) -> None:
259+
pass
260+
261+
@abc.abstractmethod
262+
def warning(self, format_spec: str, *args, **kwargs) -> None:
263+
pass
264+
265+
@abc.abstractmethod
266+
def error(self, format_spec: str, *args, **kwargs) -> None:
267+
pass
268+
269+
@abc.abstractmethod
270+
def critical(self, format_spec: str, *args, **kwargs) -> None:
271+
pass
272+
273+
@abc.abstractmethod
274+
def trace(self) -> None:
275+
pass
276+
277+
@abc.abstractmethod
278+
def fields(self, **kwargs) -> Logger:
279+
pass
280+
281+
282+
class TwiggyLogger(Logger):
283+
def __init__(self, logger: twiggy.logger.Logger) -> None:
284+
self.logger = logger
285+
286+
def debug(self, format_spec: str, *args, **kwargs) -> None:
287+
self.logger.debug(format_spec, *args, **kwargs)
288+
289+
def info(self, format_spec: str, *args, **kwargs) -> None:
290+
self.logger.info(format_spec, *args, **kwargs)
291+
292+
def notice(self, format_spec: str, *args, **kwargs) -> None:
293+
self.logger.notice(format_spec, *args, **kwargs)
294+
295+
def warning(self, format_spec: str, *args, **kwargs) -> None:
296+
self.logger.warning(format_spec, *args, **kwargs)
297+
298+
def error(self, format_spec: str, *args, **kwargs) -> None:
299+
self.logger.error(format_spec, *args, **kwargs)
300+
301+
def critical(self, format_spec: str, *args, **kwargs) -> None:
302+
self.logger.critical(format_spec, *args, **kwargs)
303+
304+
def trace(self) -> None:
305+
self.logger.trace()
306+
307+
def fields(self, **kwargs) -> Logger:
308+
return TwiggyLogger(self.logger.fields(**kwargs))
309+
310+
311+
def get_module_logger(module_name: str) -> Logger:
312+
return TwiggyLogger(log.fields(mod=module_name))
313+
314+
315+
def configure_logger(app_ctx: AppContext) -> None:
316+
twiggy.dict_config(app_ctx.logging_cfg.model_dump())
317+
318+
319+
__all__ = ("log", "initialize_app_logging", "get_module_logger", "Logger")

src/antsibull_core/subprocess_util.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
from twiggy.logger import Logger as TwiggyLogger # type: ignore[import]
2323

24-
from antsibull_core.logging import log
24+
from antsibull_core.logging import Logger, get_module_logger
2525

2626
if TYPE_CHECKING:
2727
from _typeshed import StrOrBytesPath
@@ -30,7 +30,7 @@
3030
_T = TypeVar("_T")
3131
_P = ParamSpec("_P")
3232

33-
mlog = log.fields(mod=__name__)
33+
mlog = get_module_logger(__name__)
3434

3535
CalledProcessError = subprocess.CalledProcessError
3636

@@ -99,7 +99,7 @@ def _get_log_func_and_prefix(
9999
else:
100100
# fmt: off
101101
func = getattr(logger, loglevel)
102-
if isinstance(logger, TwiggyLogger):
102+
if isinstance(logger, (TwiggyLogger, Logger)):
103103
def logfunc(string: str, /):
104104
func("{0}", string)
105105
elif isinstance(logger, StdLogger):
@@ -114,7 +114,7 @@ def logfunc(string: str, /):
114114

115115
async def async_log_run(
116116
args: Sequence[StrOrBytesPath],
117-
logger: TwiggyLogger | StdLogger | None = None,
117+
logger: TwiggyLogger | StdLogger | Logger | None = None,
118118
stdout_loglevel: str | OutputCallbackType | None = None,
119119
stderr_loglevel: str | OutputCallbackType | None = "debug",
120120
check: bool = True,
@@ -187,7 +187,7 @@ async def async_log_run(
187187

188188
def log_run(
189189
args: Sequence[StrOrBytesPath],
190-
logger: TwiggyLogger | StdLogger | None = None,
190+
logger: TwiggyLogger | StdLogger | Logger | None = None,
191191
stdout_loglevel: str | OutputCallbackType | None = None,
192192
stderr_loglevel: str | OutputCallbackType | None = "debug",
193193
check: bool = True,

src/antsibull_core/utils/http.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717
import aiohttp
1818

1919
from .. import app_context
20-
from ..logging import log
20+
from ..logging import get_module_logger
2121

22-
mlog = log.fields(mod=__name__)
22+
mlog = get_module_logger(__name__)
2323

2424

2525
# Since Python 3.11 asyncio.TimeoutError is a deprecated alias of TimeoutError

src/antsibull_core/utils/io.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414
from antsibull_fileutils.io import write_file as _write_file
1515

1616
from .. import app_context
17-
from ..logging import log
17+
from ..logging import get_module_logger
1818

1919
if t.TYPE_CHECKING:
2020
from _typeshed import StrOrBytesPath
2121

22-
mlog = log.fields(mod=__name__)
22+
mlog = get_module_logger(__name__)
2323

2424

2525
async def copy_file(

0 commit comments

Comments
 (0)