annotated-logger 1.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- annotated_logger/__init__.py +966 -0
- annotated_logger/filter.py +58 -0
- annotated_logger/mocks.py +253 -0
- annotated_logger/plugins.py +208 -0
- annotated_logger/py.typed +0 -0
- annotated_logger-1.2.1.dist-info/METADATA +65 -0
- annotated_logger-1.2.1.dist-info/RECORD +10 -0
- annotated_logger-1.2.1.dist-info/WHEEL +4 -0
- annotated_logger-1.2.1.dist-info/entry_points.txt +2 -0
- annotated_logger-1.2.1.dist-info/licenses/LICENSE.txt +21 -0
|
@@ -0,0 +1,966 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import contextlib
|
|
4
|
+
import inspect
|
|
5
|
+
import logging
|
|
6
|
+
import logging.config
|
|
7
|
+
import time
|
|
8
|
+
import uuid
|
|
9
|
+
from collections.abc import Iterator
|
|
10
|
+
from copy import copy, deepcopy
|
|
11
|
+
from typing import (
|
|
12
|
+
TYPE_CHECKING,
|
|
13
|
+
Any,
|
|
14
|
+
Callable,
|
|
15
|
+
Concatenate,
|
|
16
|
+
Literal,
|
|
17
|
+
ParamSpec,
|
|
18
|
+
Protocol,
|
|
19
|
+
TypeVar,
|
|
20
|
+
cast,
|
|
21
|
+
overload,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from makefun import wraps
|
|
25
|
+
|
|
26
|
+
from annotated_logger.filter import AnnotatedFilter
|
|
27
|
+
from annotated_logger.plugins import BasePlugin
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
30
|
+
from collections.abc import MutableMapping
|
|
31
|
+
|
|
32
|
+
# Use 0.0.0.dev1 and so on when working in a PR
|
|
33
|
+
# Each push attempts to upload to testpypi, but it only works with a unique version
|
|
34
|
+
# https://test.pypi.org/project/annotated-logger/
|
|
35
|
+
# The dev versions in testpypi can then be pulled in to whatever project needed
|
|
36
|
+
# the new feature.
|
|
37
|
+
VERSION = "1.2.1" # pragma: no mutate
|
|
38
|
+
|
|
39
|
+
T = TypeVar("T")
|
|
40
|
+
P = ParamSpec("P")
|
|
41
|
+
P2 = ParamSpec("P2")
|
|
42
|
+
P3 = ParamSpec("P3")
|
|
43
|
+
R = TypeVar("R")
|
|
44
|
+
S = TypeVar("S")
|
|
45
|
+
S2 = TypeVar("S2")
|
|
46
|
+
C_co = TypeVar("C_co", covariant=True)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class AnnotatedClass(Protocol[C_co]):
|
|
50
|
+
"""Protocol for typing classes that we annotate and add the logger to."""
|
|
51
|
+
|
|
52
|
+
annotated_logger: AnnotatedAdapter
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
PreCall = Callable[Concatenate[S, "AnnotatedAdapter", P], None] | None
|
|
56
|
+
PostCall = Callable[Concatenate[S, "AnnotatedAdapter", P], None] | None
|
|
57
|
+
SelfLoggerAndParams = Callable[Concatenate[S, "AnnotatedAdapter", P], R]
|
|
58
|
+
LoggerAndParams = Callable[Concatenate["AnnotatedAdapter", P], R]
|
|
59
|
+
SelfAndParams = Callable[Concatenate[S, P], R]
|
|
60
|
+
ParamsOnly = Callable[P, R]
|
|
61
|
+
SelfAndLogger = Callable[[S, "AnnotatedAdapter"], R]
|
|
62
|
+
LoggerOnly = Callable[["AnnotatedAdapter"], R]
|
|
63
|
+
SelfOnly = Callable[[S], R]
|
|
64
|
+
Empty = Callable[[], R]
|
|
65
|
+
|
|
66
|
+
NoInjectionSelf = Callable[[SelfAndParams[S, P, R]], SelfAndParams[S, P, R]]
|
|
67
|
+
NoInjectionBare = Callable[[ParamsOnly[P, R]], ParamsOnly[P, R]]
|
|
68
|
+
InjectionSelf = Callable[[SelfLoggerAndParams[S, P, R]], SelfAndParams[S, P, R]]
|
|
69
|
+
InjectionSelfProvide = Callable[
|
|
70
|
+
[SelfLoggerAndParams[S, P, R]], SelfLoggerAndParams[S, P, R]
|
|
71
|
+
]
|
|
72
|
+
InjectionBare = Callable[[LoggerAndParams[P, R]], ParamsOnly[P, R]]
|
|
73
|
+
InjectionBareProvide = Callable[[LoggerAndParams[P, R]], LoggerAndParams[P, R]]
|
|
74
|
+
|
|
75
|
+
Function = (
|
|
76
|
+
SelfLoggerAndParams[S, P, R]
|
|
77
|
+
| SelfAndParams[S, P, R]
|
|
78
|
+
| SelfAndLogger[S, R]
|
|
79
|
+
| SelfOnly[S, R]
|
|
80
|
+
| LoggerAndParams[P, R]
|
|
81
|
+
| ParamsOnly[P, R]
|
|
82
|
+
| LoggerOnly[R]
|
|
83
|
+
| Empty[R]
|
|
84
|
+
)
|
|
85
|
+
Decorator = (
|
|
86
|
+
NoInjectionSelf[S, P, R]
|
|
87
|
+
| InjectionSelf[S, P, R]
|
|
88
|
+
| InjectionSelfProvide[S, P, R]
|
|
89
|
+
| NoInjectionBare[P, R]
|
|
90
|
+
| InjectionBare[P, R]
|
|
91
|
+
| InjectionBareProvide[P, R]
|
|
92
|
+
)
|
|
93
|
+
Annotations = dict[str, Any]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
DEFAULT_LOGGING_CONFIG = {
|
|
97
|
+
"version": 1,
|
|
98
|
+
"disable_existing_loggers": False, # pragma: no mutate
|
|
99
|
+
"filters": {
|
|
100
|
+
"annotated_filter": {
|
|
101
|
+
"annotated_filter": True, # pragma: no mutate
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"handlers": {
|
|
105
|
+
"annotated_handler": {
|
|
106
|
+
"class": "logging.StreamHandler",
|
|
107
|
+
"formatter": "annotated_formatter",
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
"formatters": {
|
|
111
|
+
"annotated_formatter": {
|
|
112
|
+
"class": "pythonjsonlogger.jsonlogger.JsonFormatter", # pragma: no mutate
|
|
113
|
+
"format": "{created} {levelname} {name} {message}", # pragma: no mutate
|
|
114
|
+
"style": "{",
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
"loggers": {
|
|
118
|
+
"annotated_logger": {
|
|
119
|
+
"level": "DEBUG",
|
|
120
|
+
"handlers": ["annotated_handler"],
|
|
121
|
+
"propagate": False, # pragma: no mutate
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class AnnotatedIterator(Iterator[T]):
|
|
128
|
+
"""Iterator that logs as it iterates."""
|
|
129
|
+
|
|
130
|
+
def __init__(
|
|
131
|
+
self,
|
|
132
|
+
logger: AnnotatedAdapter,
|
|
133
|
+
name: str,
|
|
134
|
+
wrapped: Iterator[T],
|
|
135
|
+
*,
|
|
136
|
+
value: bool,
|
|
137
|
+
level: str,
|
|
138
|
+
) -> None:
|
|
139
|
+
"""Store the wrapped iterator, the logger and note if we log the value."""
|
|
140
|
+
self.wrapped = wrapped
|
|
141
|
+
self.logger = logger
|
|
142
|
+
self.extras: dict[str, T | str] = {"iterator": name}
|
|
143
|
+
self.value = value
|
|
144
|
+
log_methods = {
|
|
145
|
+
"debug": self.logger.debug,
|
|
146
|
+
"info": self.logger.info,
|
|
147
|
+
"warning": self.logger.warning,
|
|
148
|
+
"error": self.logger.error,
|
|
149
|
+
"exception": self.logger.exception,
|
|
150
|
+
}
|
|
151
|
+
self.log_method = log_methods[level]
|
|
152
|
+
|
|
153
|
+
def __iter__(self) -> AnnotatedIterator[T]:
|
|
154
|
+
"""Log the start of the iteration."""
|
|
155
|
+
self.log_method("Starting iteration", extra=self.extras)
|
|
156
|
+
return self
|
|
157
|
+
|
|
158
|
+
def __next__(self) -> T:
|
|
159
|
+
"""Log that we are at the next iteration."""
|
|
160
|
+
try:
|
|
161
|
+
value = next(self.wrapped)
|
|
162
|
+
if self.value:
|
|
163
|
+
self.extras["value"] = value
|
|
164
|
+
except StopIteration:
|
|
165
|
+
self.log_method("Execution complete", extra=self.extras)
|
|
166
|
+
raise
|
|
167
|
+
|
|
168
|
+
self.log_method("next", extra=self.extras)
|
|
169
|
+
return value
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class AnnotatedAdapter(logging.LoggerAdapter): # pyright: ignore[reportMissingTypeArgument]
|
|
173
|
+
"""Adapter that provides extra methods."""
|
|
174
|
+
|
|
175
|
+
def __init__(
|
|
176
|
+
self,
|
|
177
|
+
logger: logging.Logger,
|
|
178
|
+
annotated_filter: AnnotatedFilter,
|
|
179
|
+
max_length: int | None = None,
|
|
180
|
+
) -> None:
|
|
181
|
+
"""Adapter that acts like a LogRecord, but allows for annotations."""
|
|
182
|
+
self.filter = annotated_filter
|
|
183
|
+
self.logger = logger
|
|
184
|
+
self.logger.addFilter(annotated_filter)
|
|
185
|
+
self.max_length = max_length
|
|
186
|
+
|
|
187
|
+
# We don't need to send in contextual information here
|
|
188
|
+
# as we do it in the filter for runtime stuff
|
|
189
|
+
super().__init__(logger)
|
|
190
|
+
|
|
191
|
+
def iterator(
|
|
192
|
+
self,
|
|
193
|
+
name: str,
|
|
194
|
+
wrapped: Iterator[T],
|
|
195
|
+
*,
|
|
196
|
+
value: bool = True,
|
|
197
|
+
level: str = "info",
|
|
198
|
+
) -> AnnotatedIterator[T]:
|
|
199
|
+
"""Return an iterator that logs as it iterates."""
|
|
200
|
+
return AnnotatedIterator(self, name, wrapped, value=value, level=level)
|
|
201
|
+
|
|
202
|
+
def process(
|
|
203
|
+
self, msg: str, kwargs: MutableMapping[str, Any]
|
|
204
|
+
) -> tuple[str, MutableMapping[str, Any]]:
|
|
205
|
+
"""Override default LoggerAdapter process behavior.
|
|
206
|
+
|
|
207
|
+
By default a LoggerAdapter replaces the extras passed in a logger call with
|
|
208
|
+
the ones given at it's initialization. That's not the behavior we want.
|
|
209
|
+
So, we just return the kwargs as provided instead.
|
|
210
|
+
|
|
211
|
+
3.13 adds a `merge_extra` argument which should make this method unneeded.
|
|
212
|
+
But, it doesn't make sense to force everyone to use python 3.13.
|
|
213
|
+
"""
|
|
214
|
+
return msg, kwargs
|
|
215
|
+
|
|
216
|
+
def log(
|
|
217
|
+
self,
|
|
218
|
+
level: int,
|
|
219
|
+
msg: object,
|
|
220
|
+
*args: object,
|
|
221
|
+
**kwargs: object,
|
|
222
|
+
) -> None:
|
|
223
|
+
"""Override log method to allow for message splitting."""
|
|
224
|
+
if not self.max_length or not isinstance(msg, str):
|
|
225
|
+
return super().log(level, msg, *args, **kwargs) # pyright: ignore[reportArgumentType]
|
|
226
|
+
|
|
227
|
+
msg_len = len(msg) # pyright: ignore[reportArgumentType]
|
|
228
|
+
if msg_len <= self.max_length:
|
|
229
|
+
return super().log(level, msg, *args, **kwargs) # pyright: ignore[reportArgumentType]
|
|
230
|
+
|
|
231
|
+
msg_chunks = []
|
|
232
|
+
while len(msg) > self.max_length: # pyright: ignore[reportArgumentType] # pragma: no mutate
|
|
233
|
+
msg_chunks.append(msg[: self.max_length]) # pyright: ignore[reportArgumentType]
|
|
234
|
+
msg = msg[self.max_length :] # pyright: ignore[reportArgumentType]
|
|
235
|
+
kwargs["extra"] = {"message_parts": len(msg_chunks) + 1, "split": True}
|
|
236
|
+
kwargs["extra"]["split_complete"] = False
|
|
237
|
+
for i, part in enumerate(msg_chunks):
|
|
238
|
+
kwargs["extra"]["message_part"] = i + 1
|
|
239
|
+
super().log(
|
|
240
|
+
level,
|
|
241
|
+
part,
|
|
242
|
+
*args,
|
|
243
|
+
**kwargs, # pyright: ignore[reportArgumentType]
|
|
244
|
+
)
|
|
245
|
+
kwargs["extra"]["message_part"] = len(msg_chunks) + 1
|
|
246
|
+
kwargs["extra"]["split_complete"] = True
|
|
247
|
+
return super().log(level, msg, *args, **kwargs) # pyright: ignore[reportArgumentType]
|
|
248
|
+
|
|
249
|
+
def annotate(self, *, persist: bool = False, **kwargs: Any) -> None:
|
|
250
|
+
"""Add an annotation to the filter."""
|
|
251
|
+
if persist:
|
|
252
|
+
self.filter.class_annotations.update(kwargs)
|
|
253
|
+
else:
|
|
254
|
+
self.filter.annotations.update(kwargs)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class AnnotatedLogger:
|
|
258
|
+
"""Class that contains settings and the decorator method.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
----
|
|
262
|
+
annotations: Dictionary of annotations to be added to every log message
|
|
263
|
+
plugins: list of plugins to use
|
|
264
|
+
|
|
265
|
+
Methods:
|
|
266
|
+
-------
|
|
267
|
+
annotate_logs: Decorator that will insert the `annotated_logger` argument if
|
|
268
|
+
asked for in the method signature or let a provided AnnotatedAdapter to be
|
|
269
|
+
passed. Creates a new AnnotatedAdapter instance for each invocation of a
|
|
270
|
+
annotated function to isolate any annotations that are set during execution.
|
|
271
|
+
|
|
272
|
+
"""
|
|
273
|
+
|
|
274
|
+
def __init__( # noqa: PLR0913
|
|
275
|
+
self,
|
|
276
|
+
annotations: dict[str, Any] | None = None,
|
|
277
|
+
plugins: list[BasePlugin] | None = None,
|
|
278
|
+
max_length: int | None = None,
|
|
279
|
+
log_level: int = logging.INFO,
|
|
280
|
+
name: str = "annotated_logger",
|
|
281
|
+
config: dict[str, Any] | Literal[False] | None = None,
|
|
282
|
+
) -> None:
|
|
283
|
+
"""Store the settings.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
----
|
|
287
|
+
annotations: Dictionary of static annotations - default None
|
|
288
|
+
plugins: List of plugins to be applied - default [BasePlugin]
|
|
289
|
+
is created and used - default None
|
|
290
|
+
max_length: Integer, maximum length of a message before it's broken into
|
|
291
|
+
multiple message and log calls. - default None
|
|
292
|
+
log_level: Integer, log level set for the shared root logger of the package.
|
|
293
|
+
- default logging.INFO (20)
|
|
294
|
+
name: Name of the shared root logger of the package. If more than one
|
|
295
|
+
`AnnotatedLogger` object is created in a project this should be set,
|
|
296
|
+
otherwise settings like level will be overwritten by the second to execute
|
|
297
|
+
- default 'annotated_logger'
|
|
298
|
+
config: Optional - logging config dictionary to be passed to
|
|
299
|
+
logging.config.dictConfig or False. If false dictConfig will not be called.
|
|
300
|
+
If not passed the DEFAULT_LOGGING_CONFIG will be used. A special
|
|
301
|
+
`annotated_filter` keyword is looked for, if present it will be
|
|
302
|
+
replaced with a `()` filter config to generate a filter for this
|
|
303
|
+
instance of `AnnotatedLogger`.
|
|
304
|
+
|
|
305
|
+
"""
|
|
306
|
+
if plugins is None:
|
|
307
|
+
plugins = []
|
|
308
|
+
|
|
309
|
+
self.log_level = log_level
|
|
310
|
+
self.logger_root_name = name
|
|
311
|
+
self.logger_base = logging.getLogger(self.logger_root_name)
|
|
312
|
+
self.logger_base.setLevel(self.log_level)
|
|
313
|
+
self.annotations = annotations or {}
|
|
314
|
+
self.plugins = [BasePlugin()]
|
|
315
|
+
self.plugins.extend(plugins)
|
|
316
|
+
|
|
317
|
+
if config is None:
|
|
318
|
+
config = deepcopy(DEFAULT_LOGGING_CONFIG)
|
|
319
|
+
if config:
|
|
320
|
+
for config_filter in config["filters"].values():
|
|
321
|
+
if config_filter.get("annotated_filter"):
|
|
322
|
+
del config_filter["annotated_filter"]
|
|
323
|
+
config_filter["()"] = self.generate_filter
|
|
324
|
+
|
|
325
|
+
# If we pass in config=False we don't want to configure.
|
|
326
|
+
# This is typically because we have another AnnotatedLogger
|
|
327
|
+
# object which did run the config and the dict config had config
|
|
328
|
+
# for both.
|
|
329
|
+
if config:
|
|
330
|
+
logging.config.dictConfig(config)
|
|
331
|
+
|
|
332
|
+
self.max_length = max_length
|
|
333
|
+
|
|
334
|
+
def _generate_logger(
|
|
335
|
+
self,
|
|
336
|
+
function: Function[S, P, R] | None = None,
|
|
337
|
+
cls: type | None = None,
|
|
338
|
+
logger_base_name: str | None = None,
|
|
339
|
+
) -> AnnotatedAdapter:
|
|
340
|
+
"""Generate a unique adapter with a unique logger object.
|
|
341
|
+
|
|
342
|
+
This is required because the AnnotatedAdapter adds a filter to the logger.
|
|
343
|
+
The filter stores the annotations inside it, so they will mix if a new filter
|
|
344
|
+
and logger are not created each time.
|
|
345
|
+
"""
|
|
346
|
+
root_name = logger_base_name or self.logger_root_name
|
|
347
|
+
logger = logging.getLogger(
|
|
348
|
+
f"{root_name}.{uuid.uuid4()}" # pragma: no mutate
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
annotated_filter = self.generate_filter(function=function, cls=cls)
|
|
352
|
+
|
|
353
|
+
return AnnotatedAdapter(logger, annotated_filter, self.max_length)
|
|
354
|
+
|
|
355
|
+
def _action_annotation(
|
|
356
|
+
self, function: Function[S, P, R], key: str = "action"
|
|
357
|
+
) -> dict[str, str]:
|
|
358
|
+
return {key: f"{function.__module__}:{function.__qualname__}"}
|
|
359
|
+
|
|
360
|
+
def generate_filter(
|
|
361
|
+
self,
|
|
362
|
+
function: Function[S, P, R] | None = None,
|
|
363
|
+
cls: type[C_co] | None = None,
|
|
364
|
+
annotations: dict[str, Any] | None = None,
|
|
365
|
+
) -> AnnotatedFilter:
|
|
366
|
+
"""Create a AnnotatedFilter with the correct annotations and plugins."""
|
|
367
|
+
annotations_passed = annotations
|
|
368
|
+
annotations = annotations or {}
|
|
369
|
+
if function:
|
|
370
|
+
annotations.update(self._action_annotation(function))
|
|
371
|
+
class_annotations = {}
|
|
372
|
+
elif cls:
|
|
373
|
+
class_annotations = {"class": f"{cls.__module__}:{cls.__qualname__}"}
|
|
374
|
+
else:
|
|
375
|
+
class_annotations = {}
|
|
376
|
+
if not annotations_passed:
|
|
377
|
+
annotations.update(self.annotations)
|
|
378
|
+
|
|
379
|
+
return AnnotatedFilter(
|
|
380
|
+
annotations=annotations,
|
|
381
|
+
class_annotations=class_annotations,
|
|
382
|
+
plugins=self.plugins,
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
#### Defaults
|
|
386
|
+
@overload
|
|
387
|
+
def annotate_logs(
|
|
388
|
+
self,
|
|
389
|
+
logger_name: str | None = None,
|
|
390
|
+
*,
|
|
391
|
+
success_info: bool = True, # pragma: no mutate
|
|
392
|
+
pre_call: PreCall[S2, P2] = None,
|
|
393
|
+
post_call: PostCall[S2, P3] = None,
|
|
394
|
+
) -> NoInjectionSelf[S, P, R]: ...
|
|
395
|
+
|
|
396
|
+
@overload
|
|
397
|
+
def annotate_logs(
|
|
398
|
+
self,
|
|
399
|
+
logger_name: str | None = None,
|
|
400
|
+
*,
|
|
401
|
+
success_info: bool = True, # pragma: no mutate
|
|
402
|
+
pre_call: PreCall[S2, P2] = None,
|
|
403
|
+
post_call: PostCall[S2, P3] = None,
|
|
404
|
+
_typing_requested: Literal[False],
|
|
405
|
+
) -> NoInjectionSelf[S, P, R]: ...
|
|
406
|
+
|
|
407
|
+
@overload
|
|
408
|
+
def annotate_logs(
|
|
409
|
+
self,
|
|
410
|
+
logger_name: str | None = None,
|
|
411
|
+
*,
|
|
412
|
+
success_info: bool = True, # pragma: no mutate
|
|
413
|
+
pre_call: PreCall[S2, P2] = None,
|
|
414
|
+
post_call: PostCall[S2, P3] = None,
|
|
415
|
+
provided: Literal[False],
|
|
416
|
+
) -> NoInjectionSelf[S, P, R]: ...
|
|
417
|
+
|
|
418
|
+
@overload
|
|
419
|
+
def annotate_logs(
|
|
420
|
+
self,
|
|
421
|
+
logger_name: str | None = None,
|
|
422
|
+
*,
|
|
423
|
+
success_info: bool = True, # pragma: no mutate
|
|
424
|
+
pre_call: PreCall[S2, P2] = None,
|
|
425
|
+
post_call: PostCall[S2, P3] = None,
|
|
426
|
+
_typing_self: Literal[True],
|
|
427
|
+
) -> NoInjectionSelf[S, P, R]: ...
|
|
428
|
+
|
|
429
|
+
@overload
|
|
430
|
+
def annotate_logs(
|
|
431
|
+
self,
|
|
432
|
+
logger_name: str | None = None,
|
|
433
|
+
*,
|
|
434
|
+
success_info: bool = True, # pragma: no mutate
|
|
435
|
+
pre_call: PreCall[S2, P2] = None,
|
|
436
|
+
post_call: PostCall[S2, P3] = None,
|
|
437
|
+
) -> NoInjectionSelf[S, P, R]: ...
|
|
438
|
+
|
|
439
|
+
@overload
|
|
440
|
+
def annotate_logs(
|
|
441
|
+
self,
|
|
442
|
+
logger_name: str | None = None,
|
|
443
|
+
*,
|
|
444
|
+
success_info: bool = True, # pragma: no mutate
|
|
445
|
+
pre_call: PreCall[S2, P2] = None,
|
|
446
|
+
post_call: PostCall[S2, P3] = None,
|
|
447
|
+
_typing_self: Literal[True],
|
|
448
|
+
_typing_requested: Literal[False],
|
|
449
|
+
) -> NoInjectionSelf[S, P, R]: ...
|
|
450
|
+
|
|
451
|
+
@overload
|
|
452
|
+
def annotate_logs(
|
|
453
|
+
self,
|
|
454
|
+
logger_name: str | None = None,
|
|
455
|
+
*,
|
|
456
|
+
success_info: bool = True, # pragma: no mutate
|
|
457
|
+
pre_call: PreCall[S2, P2] = None,
|
|
458
|
+
post_call: PostCall[S2, P3] = None,
|
|
459
|
+
provided: Literal[False],
|
|
460
|
+
_typing_requested: Literal[False],
|
|
461
|
+
) -> NoInjectionSelf[S, P, R]: ...
|
|
462
|
+
|
|
463
|
+
@overload
|
|
464
|
+
def annotate_logs(
|
|
465
|
+
self,
|
|
466
|
+
logger_name: str | None = None,
|
|
467
|
+
*,
|
|
468
|
+
success_info: bool = True, # pragma: no mutate
|
|
469
|
+
pre_call: PreCall[S2, P2] = None,
|
|
470
|
+
post_call: PostCall[S2, P3] = None,
|
|
471
|
+
_typing_self: Literal[True],
|
|
472
|
+
provided: Literal[False],
|
|
473
|
+
) -> NoInjectionSelf[S, P, R]: ...
|
|
474
|
+
|
|
475
|
+
@overload
|
|
476
|
+
def annotate_logs(
|
|
477
|
+
self,
|
|
478
|
+
logger_name: str | None = None,
|
|
479
|
+
*,
|
|
480
|
+
success_info: bool = True, # pragma: no mutate
|
|
481
|
+
pre_call: PreCall[S2, P2] = None,
|
|
482
|
+
post_call: PostCall[S2, P3] = None,
|
|
483
|
+
_typing_self: Literal[True],
|
|
484
|
+
_typing_requested: Literal[False],
|
|
485
|
+
provided: Literal[False],
|
|
486
|
+
) -> NoInjectionSelf[S, P, R]: ...
|
|
487
|
+
|
|
488
|
+
#### Class True
|
|
489
|
+
@overload
|
|
490
|
+
def annotate_logs(
|
|
491
|
+
self,
|
|
492
|
+
logger_name: str | None = None,
|
|
493
|
+
*,
|
|
494
|
+
_typing_class: Literal[True],
|
|
495
|
+
success_info: bool = True, # pragma: no mutate
|
|
496
|
+
pre_call: PreCall[S, P2] = None,
|
|
497
|
+
post_call: PostCall[S, P3] = None,
|
|
498
|
+
) -> Callable[[type[C_co]], type[C_co]]: ...
|
|
499
|
+
|
|
500
|
+
### Instance False
|
|
501
|
+
@overload
|
|
502
|
+
def annotate_logs(
|
|
503
|
+
self,
|
|
504
|
+
logger_name: str | None = None,
|
|
505
|
+
*,
|
|
506
|
+
success_info: bool = True, # pragma: no mutate
|
|
507
|
+
pre_call: PreCall[S2, P2] = None,
|
|
508
|
+
post_call: PostCall[S2, P3] = None,
|
|
509
|
+
_typing_self: Literal[False],
|
|
510
|
+
) -> NoInjectionBare[P, R]: ...
|
|
511
|
+
|
|
512
|
+
@overload
|
|
513
|
+
def annotate_logs(
|
|
514
|
+
self,
|
|
515
|
+
logger_name: str | None = None,
|
|
516
|
+
*,
|
|
517
|
+
success_info: bool = True, # pragma: no mutate
|
|
518
|
+
pre_call: PreCall[S2, P2] = None,
|
|
519
|
+
post_call: PostCall[S2, P3] = None,
|
|
520
|
+
_typing_self: Literal[False],
|
|
521
|
+
_typing_requested: Literal[False],
|
|
522
|
+
) -> NoInjectionBare[P, R]: ...
|
|
523
|
+
|
|
524
|
+
@overload
|
|
525
|
+
def annotate_logs(
|
|
526
|
+
self,
|
|
527
|
+
logger_name: str | None = None,
|
|
528
|
+
*,
|
|
529
|
+
success_info: bool = True, # pragma: no mutate
|
|
530
|
+
pre_call: PreCall[S2, P2] = None,
|
|
531
|
+
post_call: PostCall[S2, P3] = None,
|
|
532
|
+
_typing_self: Literal[False],
|
|
533
|
+
provided: Literal[False],
|
|
534
|
+
) -> NoInjectionBare[P, R]: ...
|
|
535
|
+
|
|
536
|
+
@overload
|
|
537
|
+
def annotate_logs(
|
|
538
|
+
self,
|
|
539
|
+
logger_name: str | None = None,
|
|
540
|
+
*,
|
|
541
|
+
success_info: bool = True, # pragma: no mutate
|
|
542
|
+
pre_call: PreCall[S2, P2] = None,
|
|
543
|
+
post_call: PostCall[S2, P3] = None,
|
|
544
|
+
_typing_self: Literal[False],
|
|
545
|
+
_typing_requested: Literal[False],
|
|
546
|
+
provided: Literal[False],
|
|
547
|
+
) -> NoInjectionBare[P, R]: ...
|
|
548
|
+
|
|
549
|
+
### Requested True
|
|
550
|
+
@overload
|
|
551
|
+
def annotate_logs(
|
|
552
|
+
self,
|
|
553
|
+
logger_name: str | None = None,
|
|
554
|
+
*,
|
|
555
|
+
success_info: bool = True, # pragma: no mutate
|
|
556
|
+
pre_call: PreCall[S2, P2] = None,
|
|
557
|
+
post_call: PostCall[S2, P3] = None,
|
|
558
|
+
_typing_requested: Literal[True],
|
|
559
|
+
) -> InjectionSelf[S, P, R]: ...
|
|
560
|
+
|
|
561
|
+
@overload
|
|
562
|
+
def annotate_logs(
|
|
563
|
+
self,
|
|
564
|
+
logger_name: str | None = None,
|
|
565
|
+
*,
|
|
566
|
+
success_info: bool = True, # pragma: no mutate
|
|
567
|
+
pre_call: PreCall[S2, P2] = None,
|
|
568
|
+
post_call: PostCall[S2, P3] = None,
|
|
569
|
+
_typing_self: Literal[True],
|
|
570
|
+
_typing_requested: Literal[True],
|
|
571
|
+
) -> InjectionSelf[S, P, R]: ...
|
|
572
|
+
|
|
573
|
+
@overload
|
|
574
|
+
def annotate_logs(
|
|
575
|
+
self,
|
|
576
|
+
logger_name: str | None = None,
|
|
577
|
+
*,
|
|
578
|
+
success_info: bool = True, # pragma: no mutate
|
|
579
|
+
pre_call: PreCall[S2, P2] = None,
|
|
580
|
+
post_call: PostCall[S2, P3] = None,
|
|
581
|
+
provided: Literal[False],
|
|
582
|
+
_typing_requested: Literal[True],
|
|
583
|
+
) -> InjectionSelf[S, P, R]: ...
|
|
584
|
+
|
|
585
|
+
@overload
|
|
586
|
+
def annotate_logs(
|
|
587
|
+
self,
|
|
588
|
+
logger_name: str | None = None,
|
|
589
|
+
*,
|
|
590
|
+
success_info: bool = True, # pragma: no mutate
|
|
591
|
+
pre_call: PreCall[S2, P2] = None,
|
|
592
|
+
post_call: PostCall[S2, P3] = None,
|
|
593
|
+
_typing_self: Literal[True],
|
|
594
|
+
_typing_requested: Literal[True],
|
|
595
|
+
provided: Literal[False],
|
|
596
|
+
) -> InjectionSelf[S, P, R]: ...
|
|
597
|
+
|
|
598
|
+
### Provided True, Requested True
|
|
599
|
+
# Can't provide it if it was not requested,
|
|
600
|
+
# so no overloads for not requested, but provided
|
|
601
|
+
@overload
|
|
602
|
+
def annotate_logs(
|
|
603
|
+
self,
|
|
604
|
+
logger_name: str | None = None,
|
|
605
|
+
*,
|
|
606
|
+
success_info: bool = True, # pragma: no mutate
|
|
607
|
+
pre_call: PreCall[S2, P2] = None,
|
|
608
|
+
post_call: PostCall[S2, P3] = None,
|
|
609
|
+
_typing_requested: Literal[True],
|
|
610
|
+
provided: Literal[True],
|
|
611
|
+
) -> InjectionSelfProvide[S, P, R]: ...
|
|
612
|
+
|
|
613
|
+
@overload
|
|
614
|
+
def annotate_logs(
|
|
615
|
+
self,
|
|
616
|
+
logger_name: str | None = None,
|
|
617
|
+
*,
|
|
618
|
+
success_info: bool = True, # pragma: no mutate
|
|
619
|
+
pre_call: PreCall[S2, P2] = None,
|
|
620
|
+
post_call: PostCall[S2, P3] = None,
|
|
621
|
+
_typing_self: Literal[True],
|
|
622
|
+
_typing_requested: Literal[True],
|
|
623
|
+
provided: Literal[True],
|
|
624
|
+
) -> InjectionSelfProvide[S, P, R]: ...
|
|
625
|
+
|
|
626
|
+
### Instance False, Requested True
|
|
627
|
+
@overload
|
|
628
|
+
def annotate_logs(
|
|
629
|
+
self,
|
|
630
|
+
logger_name: str | None = None,
|
|
631
|
+
*,
|
|
632
|
+
success_info: bool = True, # pragma: no mutate
|
|
633
|
+
pre_call: PreCall[S2, P2] = None,
|
|
634
|
+
post_call: PostCall[S2, P3] = None,
|
|
635
|
+
_typing_self: Literal[False],
|
|
636
|
+
_typing_requested: Literal[True],
|
|
637
|
+
) -> InjectionBare[P, R]: ...
|
|
638
|
+
|
|
639
|
+
### Instance False, Requested True, Provided True
|
|
640
|
+
# Same not as above that you can't provide if not requested
|
|
641
|
+
@overload
|
|
642
|
+
def annotate_logs(
|
|
643
|
+
self,
|
|
644
|
+
logger_name: str | None = None,
|
|
645
|
+
*,
|
|
646
|
+
success_info: bool = True, # pragma: no mutate
|
|
647
|
+
pre_call: PreCall[S2, P2] = None,
|
|
648
|
+
post_call: PostCall[S2, P2] = None,
|
|
649
|
+
_typing_self: Literal[False],
|
|
650
|
+
_typing_requested: Literal[True],
|
|
651
|
+
provided: Literal[True],
|
|
652
|
+
) -> InjectionBareProvide[P, R]: ...
|
|
653
|
+
|
|
654
|
+
# Between the overloads and the two inner method definitions,
|
|
655
|
+
# there's not much I can do to reduce the complexity more.
|
|
656
|
+
# So, ignoring the complexity metric
|
|
657
|
+
def annotate_logs( # noqa: C901
|
|
658
|
+
self,
|
|
659
|
+
logger_name: str | None = None,
|
|
660
|
+
*,
|
|
661
|
+
success_info: bool = True,
|
|
662
|
+
pre_call: PreCall[S2, P2] = None,
|
|
663
|
+
post_call: PostCall[S2, P3] = None,
|
|
664
|
+
provided: bool = False,
|
|
665
|
+
_typing_self: bool = True, # pragma: no mutate
|
|
666
|
+
_typing_requested: bool = False, # pragma: no mutate
|
|
667
|
+
_typing_class: bool = False, # pragma: no mutate
|
|
668
|
+
) -> Decorator[S, P, R] | Callable[[type[C_co]], type[C_co]]:
|
|
669
|
+
"""Log start and end of function and provide an annotated logger if requested.
|
|
670
|
+
|
|
671
|
+
Args:
|
|
672
|
+
----
|
|
673
|
+
logger_name: Optional - Specify the name of the logger attached to
|
|
674
|
+
the decorated function.
|
|
675
|
+
success_info: Log success at an info level, if falsey success will be
|
|
676
|
+
logged at debug. Default: True
|
|
677
|
+
provided: Boolean that indicates the caller will be providing it's
|
|
678
|
+
own annotated_logger. Default: False
|
|
679
|
+
pre_call: Method that takes the same arguments as the decorated function
|
|
680
|
+
and does something. Called before the function and the `start` log message.
|
|
681
|
+
post_call: Method that takes the same arguments as the decorated function
|
|
682
|
+
and does something. Called after the function and before the `success`
|
|
683
|
+
log message or in the exception handling.
|
|
684
|
+
_typing_self: Used only for type hint overloads. Indicates that the
|
|
685
|
+
decorated method is an instance method and has a self parameter.
|
|
686
|
+
Default: True
|
|
687
|
+
_typing_requested: Used only for type hint overloads. Indicates that the
|
|
688
|
+
decorated method is expecting an annotated_logger to be provided.
|
|
689
|
+
Default: False
|
|
690
|
+
|
|
691
|
+
Notes:
|
|
692
|
+
-----
|
|
693
|
+
In order to fully support type hinting, the annotated_logger argument
|
|
694
|
+
must be the first argument (after self/cls). Type hinting will only work
|
|
695
|
+
correctly if the _typing arguments are set correctly, but the code will
|
|
696
|
+
work fine at runtime without the _typing arguments.
|
|
697
|
+
|
|
698
|
+
"""
|
|
699
|
+
|
|
700
|
+
@overload
|
|
701
|
+
def decorator(
|
|
702
|
+
wrapped: SelfLoggerAndParams[S, P, R],
|
|
703
|
+
) -> SelfAndParams[S, P, R] | SelfLoggerAndParams[S, P, R]: ...
|
|
704
|
+
|
|
705
|
+
@overload
|
|
706
|
+
def decorator(
|
|
707
|
+
wrapped: LoggerAndParams[P, R],
|
|
708
|
+
) -> ParamsOnly[P, R] | LoggerAndParams[P, R]: ...
|
|
709
|
+
|
|
710
|
+
@overload
|
|
711
|
+
def decorator(
|
|
712
|
+
wrapped: SelfAndParams[S, P, R],
|
|
713
|
+
) -> SelfAndParams[S, P, R]: ...
|
|
714
|
+
|
|
715
|
+
@overload
|
|
716
|
+
def decorator(
|
|
717
|
+
wrapped: ParamsOnly[P, R],
|
|
718
|
+
) -> ParamsOnly[P, R] | Empty[R]: ...
|
|
719
|
+
|
|
720
|
+
@overload
|
|
721
|
+
def decorator(wrapped: type[C_co]) -> Callable[P, AnnotatedClass[C_co]]: ...
|
|
722
|
+
|
|
723
|
+
def decorator( # noqa: C901
|
|
724
|
+
wrapped: Function[S, P, R] | type[C_co],
|
|
725
|
+
) -> Function[S, P, R] | Callable[P, AnnotatedClass[C_co]]:
|
|
726
|
+
if isinstance(wrapped, type):
|
|
727
|
+
|
|
728
|
+
def wrap_class(
|
|
729
|
+
*args: P.args, **kwargs: P.kwargs
|
|
730
|
+
) -> AnnotatedClass[C_co]:
|
|
731
|
+
logger = self._generate_logger(
|
|
732
|
+
cls=wrapped, logger_base_name=logger_name
|
|
733
|
+
)
|
|
734
|
+
logger.debug("init")
|
|
735
|
+
new = cast(AnnotatedClass[C_co], wrapped(*args, **kwargs))
|
|
736
|
+
new.annotated_logger = logger
|
|
737
|
+
return new
|
|
738
|
+
|
|
739
|
+
return wrap_class
|
|
740
|
+
|
|
741
|
+
(remove_args, inject_logger) = self._determine_signature_adjustments(
|
|
742
|
+
wrapped, provided=provided
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
@wraps(
|
|
746
|
+
wrapped,
|
|
747
|
+
remove_args=remove_args,
|
|
748
|
+
)
|
|
749
|
+
def wrap_function(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
750
|
+
__tracebackhide__ = True # pragma: no mutate
|
|
751
|
+
|
|
752
|
+
post_call_attempted = False # pragma: no mutate
|
|
753
|
+
|
|
754
|
+
new_args, new_kwargs, logger, pre_execution_annotations = inject_logger(
|
|
755
|
+
list(args), kwargs, logger_base_name=logger_name
|
|
756
|
+
)
|
|
757
|
+
try:
|
|
758
|
+
start_time = time.perf_counter()
|
|
759
|
+
if pre_call:
|
|
760
|
+
pre_call(*new_args, **new_kwargs) # pyright: ignore[reportCallIssue]
|
|
761
|
+
logger.debug("start")
|
|
762
|
+
|
|
763
|
+
result = wrapped(*new_args, **new_kwargs) # pyright: ignore[reportCallIssue]
|
|
764
|
+
logger.annotate(success=True)
|
|
765
|
+
if post_call:
|
|
766
|
+
post_call_attempted = True
|
|
767
|
+
_attempt_post_call(post_call, logger, *new_args, **new_kwargs) # pyright: ignore[reportCallIssue]
|
|
768
|
+
end_time = time.perf_counter()
|
|
769
|
+
logger.annotate(run_time=f"{end_time - start_time :.1f}")
|
|
770
|
+
with contextlib.suppress(TypeError):
|
|
771
|
+
logger.annotate(count=len(result)) # pyright: ignore[reportArgumentType]
|
|
772
|
+
|
|
773
|
+
if success_info:
|
|
774
|
+
logger.info("success")
|
|
775
|
+
else:
|
|
776
|
+
logger.debug("success")
|
|
777
|
+
|
|
778
|
+
# If we were provided with a logger object, set the annotations
|
|
779
|
+
# back to what they were before the wrapped method was called.
|
|
780
|
+
if pre_execution_annotations:
|
|
781
|
+
logger.filter.annotations = pre_execution_annotations
|
|
782
|
+
except Exception as e:
|
|
783
|
+
for plugin in logger.filter.plugins:
|
|
784
|
+
logger = plugin.uncaught_exception(e, logger)
|
|
785
|
+
logger.exception(
|
|
786
|
+
"Uncaught Exception in logged function",
|
|
787
|
+
)
|
|
788
|
+
if post_call and not post_call_attempted:
|
|
789
|
+
_attempt_post_call(post_call, logger, *new_args, **new_kwargs) # pyright: ignore[reportCallIssue]
|
|
790
|
+
raise
|
|
791
|
+
return result
|
|
792
|
+
|
|
793
|
+
return wrap_function
|
|
794
|
+
|
|
795
|
+
return decorator
|
|
796
|
+
|
|
797
|
+
def _determine_signature_adjustments(
|
|
798
|
+
self,
|
|
799
|
+
function: Function[S, P, R],
|
|
800
|
+
*,
|
|
801
|
+
provided: bool,
|
|
802
|
+
) -> tuple[
|
|
803
|
+
list[str],
|
|
804
|
+
Callable[
|
|
805
|
+
Concatenate[list[Any], dict[str, Any], ...],
|
|
806
|
+
tuple[list[Any], dict[str, Any], AnnotatedAdapter, Annotations | None],
|
|
807
|
+
],
|
|
808
|
+
]:
|
|
809
|
+
written_signature = inspect.signature(function)
|
|
810
|
+
logger_requested = False # pragma: no mutate
|
|
811
|
+
remove_args = []
|
|
812
|
+
index, instance_method = self._check_parameters_for_self_and_cls(
|
|
813
|
+
written_signature
|
|
814
|
+
)
|
|
815
|
+
if "annotated_logger" in written_signature.parameters:
|
|
816
|
+
if list(written_signature.parameters.keys())[index] != "annotated_logger":
|
|
817
|
+
error_message = "annotated_logger must be the first argument"
|
|
818
|
+
raise TypeError(error_message)
|
|
819
|
+
|
|
820
|
+
logger_requested = True
|
|
821
|
+
if not provided:
|
|
822
|
+
remove_args = ["annotated_logger"]
|
|
823
|
+
|
|
824
|
+
def inject_logger(
|
|
825
|
+
args: list[Any],
|
|
826
|
+
kwargs: dict[str, Any],
|
|
827
|
+
logger_base_name: str | None = None,
|
|
828
|
+
) -> tuple[list[Any], dict[str, Any], AnnotatedAdapter, Annotations | None]:
|
|
829
|
+
if not logger_requested:
|
|
830
|
+
logger = self._generate_logger(
|
|
831
|
+
function, logger_base_name=logger_base_name
|
|
832
|
+
)
|
|
833
|
+
return (args, kwargs, logger, None)
|
|
834
|
+
|
|
835
|
+
by_index = False # pragma: no mutate
|
|
836
|
+
# Check for a var positional or positional only
|
|
837
|
+
# If present that means we'll have values in args when invoking
|
|
838
|
+
# but, if not everything will be in kwargs
|
|
839
|
+
for v in written_signature.parameters.values():
|
|
840
|
+
if v.kind == inspect.Parameter.VAR_POSITIONAL:
|
|
841
|
+
by_index = True
|
|
842
|
+
|
|
843
|
+
new_args = copy(args)
|
|
844
|
+
new_kwargs = copy(kwargs)
|
|
845
|
+
if by_index:
|
|
846
|
+
logger, annotations, new_args = self._inject_by_index(
|
|
847
|
+
provided=provided,
|
|
848
|
+
instance_method=instance_method,
|
|
849
|
+
args=new_args,
|
|
850
|
+
index=index,
|
|
851
|
+
function=function,
|
|
852
|
+
logger_base_name=logger_base_name,
|
|
853
|
+
)
|
|
854
|
+
else:
|
|
855
|
+
logger, annotations, new_kwargs = self._inject_by_kwarg(
|
|
856
|
+
provided=provided,
|
|
857
|
+
instance_method=instance_method,
|
|
858
|
+
kwargs=new_kwargs,
|
|
859
|
+
function=function,
|
|
860
|
+
logger_base_name=logger_base_name,
|
|
861
|
+
)
|
|
862
|
+
|
|
863
|
+
return new_args, new_kwargs, logger, annotations
|
|
864
|
+
|
|
865
|
+
return remove_args, inject_logger
|
|
866
|
+
|
|
867
|
+
def _inject_by_kwarg(
|
|
868
|
+
self,
|
|
869
|
+
*,
|
|
870
|
+
provided: bool,
|
|
871
|
+
instance_method: bool,
|
|
872
|
+
function: Function[S, P, R],
|
|
873
|
+
kwargs: dict[str, Any],
|
|
874
|
+
logger_base_name: str | None = None,
|
|
875
|
+
) -> tuple[AnnotatedAdapter, Annotations | None, dict[str, Any]]:
|
|
876
|
+
if provided:
|
|
877
|
+
instance = kwargs["annotated_logger"]
|
|
878
|
+
elif instance_method:
|
|
879
|
+
instance = kwargs["self"]
|
|
880
|
+
else:
|
|
881
|
+
instance = False # pragma: no mutate
|
|
882
|
+
logger, annotations = self._pick_correct_logger(
|
|
883
|
+
function, instance, logger_base_name=logger_base_name
|
|
884
|
+
)
|
|
885
|
+
if not provided:
|
|
886
|
+
kwargs["annotated_logger"] = logger
|
|
887
|
+
|
|
888
|
+
return logger, annotations, kwargs
|
|
889
|
+
|
|
890
|
+
def _inject_by_index( # noqa: PLR0913
|
|
891
|
+
self,
|
|
892
|
+
*,
|
|
893
|
+
provided: bool,
|
|
894
|
+
instance_method: bool,
|
|
895
|
+
function: Function[S, P, R],
|
|
896
|
+
args: list[Any],
|
|
897
|
+
index: int,
|
|
898
|
+
logger_base_name: str | None = None,
|
|
899
|
+
) -> tuple[AnnotatedAdapter, Annotations | None, list[Any]]:
|
|
900
|
+
if provided:
|
|
901
|
+
instance = args[index]
|
|
902
|
+
elif instance_method:
|
|
903
|
+
instance = args[0]
|
|
904
|
+
else:
|
|
905
|
+
instance = False # pragma: no mutate
|
|
906
|
+
logger, annotations = self._pick_correct_logger(
|
|
907
|
+
function, instance, logger_base_name=logger_base_name
|
|
908
|
+
)
|
|
909
|
+
if not provided:
|
|
910
|
+
args.insert(index, logger)
|
|
911
|
+
return logger, annotations, args
|
|
912
|
+
|
|
913
|
+
def _check_parameters_for_self_and_cls(
|
|
914
|
+
self, sig: inspect.Signature
|
|
915
|
+
) -> tuple[int, bool]:
|
|
916
|
+
parameters = sig.parameters
|
|
917
|
+
index = 0
|
|
918
|
+
instance_method = False
|
|
919
|
+
if "self" in parameters:
|
|
920
|
+
index = 1
|
|
921
|
+
instance_method = True
|
|
922
|
+
if "cls" in parameters:
|
|
923
|
+
index = 1
|
|
924
|
+
|
|
925
|
+
return index, instance_method
|
|
926
|
+
|
|
927
|
+
def _pick_correct_logger(
|
|
928
|
+
self,
|
|
929
|
+
function: Function[S, P, R],
|
|
930
|
+
instance: object | bool,
|
|
931
|
+
logger_base_name: str | None = None,
|
|
932
|
+
) -> tuple[AnnotatedAdapter, Annotations | None]:
|
|
933
|
+
"""Use the instance's logger and annotations if present."""
|
|
934
|
+
if instance and hasattr(instance, "annotated_logger"):
|
|
935
|
+
logger = instance.annotated_logger # pyright: ignore[reportAttributeAccessIssue]
|
|
936
|
+
annotations = copy(logger.filter.annotations)
|
|
937
|
+
logger.filter.annotations.update(self._action_annotation(function))
|
|
938
|
+
return (logger, annotations)
|
|
939
|
+
|
|
940
|
+
if isinstance(instance, AnnotatedAdapter):
|
|
941
|
+
logger = instance
|
|
942
|
+
annotations = copy(logger.filter.annotations)
|
|
943
|
+
logger.filter.annotations.update(
|
|
944
|
+
self._action_annotation(function, key="subaction")
|
|
945
|
+
)
|
|
946
|
+
return (logger, annotations)
|
|
947
|
+
|
|
948
|
+
return (
|
|
949
|
+
self._generate_logger(function, logger_base_name=logger_base_name),
|
|
950
|
+
None,
|
|
951
|
+
)
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
def _attempt_post_call(
|
|
955
|
+
post_call: Callable[P, None],
|
|
956
|
+
logger: AnnotatedAdapter,
|
|
957
|
+
*args: P.args,
|
|
958
|
+
**kwargs: P.kwargs,
|
|
959
|
+
) -> None:
|
|
960
|
+
try:
|
|
961
|
+
if post_call:
|
|
962
|
+
post_call(*args, **kwargs) # pyright: ignore[reportCallIssue]
|
|
963
|
+
except Exception:
|
|
964
|
+
logger.annotate(success=False)
|
|
965
|
+
logger.exception("Post call failed")
|
|
966
|
+
raise
|