Coverage for gws-app/gws/core/log.py: 63%
118 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-16 22:59 +0200
« prev ^ index » next coverage.py v7.11.0, created at 2025-10-16 22:59 +0200
1"""Logging facility."""
3import os
4import sys
5import traceback
8class Level:
9 """Log level."""
10 CRITICAL = 50
11 ERROR = 40
12 WARN = 30
13 WARNING = 30
14 INFO = 20
15 DEBUG = 10
16 NOTSET = 0
17 ALL = 0
20def set_level(level: int | str | None):
21 global _current_level
22 if level is None:
23 _current_level = Level.INFO
24 elif isinstance(level, int) or level.isdigit():
25 _current_level = int(level)
26 else:
27 _current_level = getattr(Level, level.upper())
30def get_level() -> str:
31 global _current_level
32 for k, n in vars(Level).items():
33 if n == _current_level:
34 return k
35 return 'ALL'
38def log(level: int, msg: str, *args, **kwargs):
39 _raw(level, msg, args, kwargs)
42def critical(msg: str, *args, **kwargs):
43 _raw(Level.CRITICAL, msg, args, kwargs)
46def error(msg: str, *args, **kwargs):
47 _raw(Level.ERROR, msg, args, kwargs)
50def warning(msg: str, *args, **kwargs):
51 _raw(Level.WARNING, msg, args, kwargs)
54def info(msg: str, *args, **kwargs):
55 _raw(Level.INFO, msg, args, kwargs)
58def debug(msg: str, *args, **kwargs):
59 _raw(Level.DEBUG, msg, args, kwargs)
62def exception(msg: str = '', *args, **kwargs):
63 _, exc, _ = sys.exc_info()
64 ls = exception_backtrace(exc)
65 _raw(Level.ERROR, msg or ls[0], args, kwargs)
66 for s in ls[1:]:
67 _raw(Level.ERROR, 'EXCEPTION :: ' + s)
70def if_debug(fn, *args):
71 """If debugging, apply the function to args and log the result."""
73 if Level.DEBUG < _current_level:
74 return
75 try:
76 msg = fn(*args)
77 except Exception as exc:
78 msg = repr(exc)
79 _raw(Level.DEBUG, msg)
82def exception_backtrace(exc: BaseException | None) -> list:
83 """Exception backtrace as a list of strings."""
85 head = _name(exc)
86 messages = []
88 lines = []
89 pfx = ''
91 while exc:
92 subhead = _name(exc)
93 msg = _message(exc)
94 if msg:
95 subhead += ': ' + msg
96 messages.append(msg)
97 if pfx:
98 subhead = pfx + ' ' + subhead
100 lines.append(subhead)
102 for f in traceback.extract_tb(exc.__traceback__, limit=100):
103 lines.append(f' in {f[2]} ({f[0]}:{f[1]})')
105 if exc.__cause__:
106 exc = exc.__cause__
107 pfx = 'caused by'
108 elif exc.__context__:
109 exc = exc.__context__
110 pfx = 'during handling of'
111 else:
112 break
114 if messages:
115 head += ': ' + messages[0]
116 if len(lines) > 1:
117 head += ' ' + lines[1].strip()
119 lines.insert(0, head)
120 return lines
123##
125def _name(exc):
126 typ = type(exc) or Exception
127 # if typ == Error:
128 # return 'Error'
129 name = getattr(typ, '__name__', '')
130 mod = getattr(typ, '__module__', '')
131 if mod in {'exceptions', 'builtins'}:
132 return name
133 return mod + '.' + name
136def _message(exc):
137 try:
138 return repr(exc.args[0])
139 except:
140 return ''
143##
146_current_level = Level.INFO
148_out_stream = sys.stdout
150_PREFIX = {
151 Level.CRITICAL: 'CRITICAL',
152 Level.ERROR: 'ERROR',
153 Level.WARNING: 'WARNING',
154 Level.INFO: 'INFO',
155 Level.DEBUG: 'DEBUG',
156}
159def _raw(level, msg, args=None, kwargs=None):
160 if level < _current_level:
161 return
163 if args:
164 if len(args) == 1 and args[0] and isinstance(args[0], dict):
165 args = args[0]
166 msg = msg % args
168 pid = os.getpid()
169 loc = ' '
170 if _current_level <= Level.DEBUG:
171 stacklevel = kwargs.get('stacklevel', 1) if kwargs else 1
172 loc = ' ' + _location(2 + stacklevel) + ' '
173 pfx = '[' + str(pid) + ']' + loc + _PREFIX[level] + ' :: '
175 try:
176 _out_stream.write(f'{pfx}{msg}\n')
177 except UnicodeEncodeError:
178 _out_stream.write(f'{pfx}{msg!r}\n')
180 _out_stream.flush()
183def _location(stacklevel):
184 frames = traceback.extract_stack()
185 for fname, line, func, text in reversed(frames):
186 if stacklevel == 0:
187 return f'{fname}:{line}'
188 stacklevel -= 1
189 return '???'