Coverage for sources/appcore/inscription.py: 100%

45 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-09 19:57 +0000

1# vim: set filetype=python fileencoding=utf-8: 

2# -*- coding: utf-8 -*- 

3 

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#============================================================================# 

19 

20 

21''' Application inscription management. 

22 

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 

30 

31 

32import logging as _logging 

33 

34from . import __ 

35from . import state as _state 

36 

37 

38class Modes( __.enum.Enum ): # TODO: Python 3.11: StrEnum 

39 ''' Format control modes. ''' 

40 

41 Null = 'null' # deferred to external management 

42 Plain = 'plain' # standard 

43 Rich = 'rich' # enhanced with Rich 

44 

45 

46class Control( __.immut.DataclassObject ): 

47 ''' Application inscription configuration. ''' 

48 

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 

54 

55 

56def prepare( auxdata: _state.Globals, /, control: Control ) -> None: 

57 ''' Prepares various scribes in a sensible manner. ''' 

58 prepare_scribes_logging( auxdata, control ) 

59 

60 

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 

74 

75 

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 

89 

90 

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, ) ) 

98 

99 

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, ) )