Coverage for sources/appcore/inscription.py: 100%
45 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-13 17:16 +0000
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-13 17:16 +0000
1# vim: set filetype=python fileencoding=utf-8:
2# -*- coding: utf-8 -*-
4#============================================================================#
5# #
6# Licensed under the Apache License, Version 2.0 (the "License"); #
7# you may not use this file except in compliance with the License. #
8# You may obtain a copy of the License at #
9# #
10# http://www.apache.org/licenses/LICENSE-2.0 #
11# #
12# Unless required by applicable law or agreed to in writing, software #
13# distributed under the License is distributed on an "AS IS" BASIS, #
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
15# See the License for the specific language governing permissions and #
16# limitations under the License. #
17# #
18#============================================================================#
21''' Application inscription management.
23 Logging and, potentially, debug printing.
24'''
25# TODO? Add structured logging support (JSON formatting for log aggregation)
26# TODO? Add distributed tracing support (correlation IDs, execution IDs)
27# TODO? Add metrics collection and reporting
28# TODO? Add OpenTelemetry integration
29# TODO: Add TOML configuration support for inscription control settings
32import logging as _logging
34from . import __
35from . import state as _state
38class Modes( __.enum.Enum ): # TODO: Python 3.11: StrEnum
39 ''' Format control modes. '''
41 Null = 'null' # deferred to external management
42 Plain = 'plain' # standard
43 Rich = 'rich' # enhanced with Rich
46class Control( __.immut.DataclassObject ):
47 ''' Application inscription configuration. '''
49 mode: Modes = Modes.Plain
50 level: __.typx.Literal[
51 'debug', 'info', 'warn', 'error', 'critical' # noqa: F821
52 ] = 'info'
53 target: __.typx.TextIO = __.sys.stderr
56def prepare( auxdata: _state.Globals, /, control: Control ) -> None:
57 ''' Prepares various scribes in a sensible manner. '''
58 prepare_scribes_logging( auxdata, control )
61def prepare_scribes_logging(
62 auxdata: _state.Globals, control: Control
63) -> None:
64 ''' Prepares Python standard logging system. '''
65 level_name = _discover_inscription_level_name( auxdata, control )
66 level = getattr( _logging, level_name.upper( ) )
67 formatter = _logging.Formatter( "%(name)s: %(message)s" )
68 match control.mode:
69 case Modes.Plain:
70 _prepare_logging_plain( level, control.target, formatter )
71 case Modes.Rich:
72 _prepare_logging_rich( level, control.target, formatter )
73 case _: pass
76def _discover_inscription_level_name(
77 auxdata: _state.Globals, control: Control
78) -> str:
79 application_name = ''.join(
80 c.upper( ) if c.isalnum( ) else '_'
81 for c in auxdata.application.name )
82 for envvar_name_base in ( 'INSCRIPTION', 'LOG' ):
83 envvar_name = (
84 "{name}_{base}_LEVEL".format(
85 base = envvar_name_base, name = application_name ) )
86 if envvar_name in __.os.environ:
87 return __.os.environ[ envvar_name ]
88 return control.level
91def _prepare_logging_plain(
92 level: int, target: __.typx.TextIO, formatter: _logging.Formatter
93) -> None:
94 handler = _logging.StreamHandler( target )
95 handler.setFormatter( formatter )
96 _logging.basicConfig(
97 force = True, level = level, handlers = ( handler, ) )
100def _prepare_logging_rich(
101 level: int, target: __.typx.TextIO, formatter: _logging.Formatter
102) -> None:
103 try:
104 from rich.console import Console
105 from rich.logging import RichHandler
106 except ImportError:
107 # Gracefully degrade to plain mode
108 _prepare_logging_plain( level, target, formatter )
109 return
110 console = Console( file = target )
111 handler = RichHandler(
112 console = console,
113 rich_tracebacks = True,
114 show_time = True,
115 show_path = False
116 )
117 handler.setFormatter( formatter )
118 _logging.basicConfig(
119 force = True, level = level, handlers = ( handler, ) )