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
3131logging output is disabled. That way the library doesn't spam the user's screen with log messages.
3232
3333An 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
35- the :ref:`Application Logging <application_logging>`_ section for more details.
34+ antsibull_core .logging.initialize_app_logging() before any other parts of antsibull are imported.
35+ See the :ref:`Application Logging <application_logging>`_ section for more details.
3636
3737
3838Usage within a module
3939=====================
4040
4141Our 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
43+ :mod:`antsibull_core.logging`.)
4344At the toplevel of a module, set up a logger which has a field named ``mod`` which reflects the
4445module name:
4546
4647.. code-block:: python
4748
4849 # The antsibull log object with the name already set to `antsibull`.
49- from logging import log
50+ from logging import get_module_logger
5051
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.
52+ # mlog stands for module log. It's our convention to create a logger in each module.
53+ # The logger will containt the module name as the `mod` field.
54+ mlog = get_module_logger(__name__)
5755
58- # `mod` and the value set to the module name.
59- mlog = log.fields(mod=__name__)
56+ # `fields()` takes an arbitrary set of keyword args and returns a new log object.
57+ # Any log messages we emit with this log object (or its children) will include the fields
58+ # which were set on it.
6059
6160 TRICKY_COMPUTED_GLOBAL = [a**a for a in range(1, 4)]
6261 # Use mlog for logging interesting things that happen at the module level. Notice that we send
7473
7574.. code-block:: python
7675
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')
76+ def test_function(argument1):
77+ # flog stands for function log. It's our convention to use this name.
78+ # Create a new one in any function you want to log from.
79+ # By creating this from mlog, we copy any fields and other settings that we made to mlog.
80+ # Our convention is to use the `func` field to hold the name of the function we're in.
81+ flog = mlog.fields(func='test_function')
8382
84- # This would output:
85- # DEBUG:antsibull:func=test_function:mod=__main__|Enter
86- flog.debug('Enter')
87- value = do_something(argument1)
83+ # This would output:
84+ # DEBUG:antsibull:func=test_function:mod=__main__|Enter
85+ flog.debug('Enter')
86+ value = do_something(argument1)
8887
89- flog.debug('Leave')
88+ flog.debug('Leave')
9089
91- class FooBar:
92- def __init__(self):
93- flog = mlog.fields(func='FooBar.__init__')
94- flog.debug('Enter')
90+ class FooBar:
91+ def __init__(self):
92+ flog = mlog.fields(func='FooBar.__init__')
93+ flog.debug('Enter')
9594
96- self._x = initialize_x()
97- self._y = initialize_y()
95+ self._x = initialize_x()
96+ self._y = initialize_y()
9897
99- self.position = self.calculate_position(self._x, self._y)
98+ self.position = self.calculate_position(self._x, self._y)
10099
101- flog.debug('Leave')
100+ flog.debug('Leave')
102101
103102
104103.. _logging_levels::
@@ -144,29 +143,28 @@ def __init__(self):
144143Logging Setup
145144=============
146145
147- An antsibull command (:file:`antsibull /cli/*.py`) should import the ``log`` object from this module.
146+ An antsibull command (:file:`antsibull_* /cli/*.py`) should import this module.
148147The 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``
150- that it is okay to emit messages.
148+ should call :func:`antsibull_core .logging.initialize_app_logging` as soon as possible to tell the
149+ ``log`` that it is okay to emit messages.
151150
152151The initial application logging configuration will log to stderr at the ``WARNING`` level or
153152higher. If the :envvar:`ANTIBULL_EARLY_DEBUG` environment variable is set, then it will log at
154153the ``DEBUG`` level rather than ``WARNING``.
155154
156155The 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
158- finish the setup. At that point, logging calls will emit logs according to the user's
159- configuration.
156+ logging configuration and application defaults, and then call
157+ :func:`antsibull_core. logging.configure_logger` to finish the setup.
158+ At that point, logging calls will emit logs according to the user's configuration.
160159
161160Here's a sample of the relevant portions of an antsibull command to show how this will look:
162161
163162.. code-block:: python
164163
165164 # File is antsibull/cli/antsibull_command.py
166- import twiggy
167165 # log is the toplevel log object. It is important to import this and initialize it prior to
168166 # using the log so that sane defaults can be set.
169- from .. logging import log , initialize_app_logging
167+ from antsibull_core. logging import configure_logger, get_module_logger , initialize_app_logging
170168
171169 # By default, the log is configured to be useful within a library where the user may not have
172170 # been given the chance to configure the log. Calling initialize_app_logging() reconfigures
@@ -178,7 +176,7 @@ def __init__(self):
178176 from ..config import load_config
179177
180178
181- mlog = log.fields(mod= __name__)
179+ mlog = get_module_logger( __name__)
182180
183181 def run(args):
184182 flog = mlog.fields(func='run')
@@ -189,12 +187,12 @@ def run(args):
189187 with app_context.app_and_lib_context(context_data) as (app_ctx, dummy_):
190188 # initialize_app_logging() sets the log's configuration with defaults appropriate for
191189 # 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
190+ # configuration from the user's config file and hands it to configure_logger () so
193191 # that the user has ultimate control over what log level, what format, and which file
194192 # 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
193+ # the logging config. See the antsibull_core .app_context documentation if you want more
196194 # information on the context object.
197- twiggy.dict_config (app_ctx.logging_cfg.model_dump() )
195+ configure_logger (app_ctx)
198196
199197
200198Once those steps are taken, any further logging calls will obey the user's configuration.
@@ -203,11 +201,16 @@ def run(args):
203201
204202from __future__ import annotations
205203
204+ import abc
206205import os
206+ import typing as t
207207
208208import twiggy # type: ignore[import]
209209import twiggy .levels # type: ignore[import]
210210
211+ if t .TYPE_CHECKING :
212+ from .schemas .context import AppContext
213+
211214#: The standard log to use everywhere. The name of the logger for all of the antsibull libraries
212215#: is antsibull so that it is easy to setup an emitter for all of antsibull. For those used to
213216#: using the module's __name__ field as the name, the idiom we use here is to set the module name
@@ -229,7 +232,7 @@ def initialize_app_logging() -> None:
229232 """
230233 Change log settings to make sense for an application.
231234
232- Merely importing the :mod:`antsibull .logging` module sets up the logger for use as part of
235+ Merely importing the :mod:`antsibull_core .logging` module sets up the logger for use as part of
233236 a library. Calling this function will initialize the logger for use in an application.
234237 """
235238 # We want to see logs from the antsibull library, so the very first thing we do is turn the log
@@ -243,4 +246,75 @@ def initialize_app_logging() -> None:
243246 twiggy .quick_setup (min_level = _level )
244247
245248
246- __all__ = ("log" , "initialize_app_logging" )
249+ class Logger (metaclass = abc .ABCMeta ):
250+ @abc .abstractmethod
251+ def debug (self , format_spec : str , * args , ** kwargs ) -> None :
252+ pass
253+
254+ @abc .abstractmethod
255+ def info (self , format_spec : str , * args , ** kwargs ) -> None :
256+ pass
257+
258+ @abc .abstractmethod
259+ def notice (self , format_spec : str , * args , ** kwargs ) -> None :
260+ pass
261+
262+ @abc .abstractmethod
263+ def warning (self , format_spec : str , * args , ** kwargs ) -> None :
264+ pass
265+
266+ @abc .abstractmethod
267+ def error (self , format_spec : str , * args , ** kwargs ) -> None :
268+ pass
269+
270+ @abc .abstractmethod
271+ def critical (self , format_spec : str , * args , ** kwargs ) -> None :
272+ pass
273+
274+ @abc .abstractmethod
275+ def trace (self ) -> None :
276+ pass
277+
278+ @abc .abstractmethod
279+ def fields (self , ** kwargs ) -> Logger :
280+ pass
281+
282+
283+ class TwiggyLogger (Logger ):
284+ def __init__ (self , logger : twiggy .logger .Logger ) -> None :
285+ self .logger = logger
286+
287+ def debug (self , format_spec : str , * args , ** kwargs ) -> None :
288+ self .logger .debug (format_spec , * args , ** kwargs )
289+
290+ def info (self , format_spec : str , * args , ** kwargs ) -> None :
291+ self .logger .info (format_spec , * args , ** kwargs )
292+
293+ def notice (self , format_spec : str , * args , ** kwargs ) -> None :
294+ self .logger .notice (format_spec , * args , ** kwargs )
295+
296+ def warning (self , format_spec : str , * args , ** kwargs ) -> None :
297+ self .logger .warning (format_spec , * args , ** kwargs )
298+
299+ def error (self , format_spec : str , * args , ** kwargs ) -> None :
300+ self .logger .error (format_spec , * args , ** kwargs )
301+
302+ def critical (self , format_spec : str , * args , ** kwargs ) -> None :
303+ self .logger .critical (format_spec , * args , ** kwargs )
304+
305+ def trace (self ) -> None :
306+ self .logger .trace ()
307+
308+ def fields (self , ** kwargs ) -> Logger :
309+ return TwiggyLogger (self .logger .fields (** kwargs ))
310+
311+
312+ def get_module_logger (module_name : str ) -> Logger :
313+ return TwiggyLogger (log .fields (mod = module_name ))
314+
315+
316+ def configure_logger (app_ctx : AppContext ) -> None :
317+ twiggy .dict_config (app_ctx .logging_cfg .model_dump ())
318+
319+
320+ __all__ = ("log" , "initialize_app_logging" , "get_module_logger" , "Logger" )
0 commit comments