Skip to content

Commit 119b670

Browse files
committed
Migrate AWX utility for slow method logging to DAB
1 parent 832e4a8 commit 119b670

File tree

3 files changed

+95
-0
lines changed

3 files changed

+95
-0
lines changed

ansible_base/jwt_consumer/common/auth.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from ansible_base.jwt_consumer.common.cert import JWTCert, JWTCertException
1616
from ansible_base.lib.utils.auth import get_user_by_ansible_id
1717
from ansible_base.lib.utils.translations import translatableConditionally as _
18+
from ansible_base.lib.logging.runtime import log_excess_runtime
1819
from ansible_base.resource_registry.models import Resource, ResourceType
1920
from ansible_base.resource_registry.signals.handlers import no_reverse_sync
2021

@@ -50,6 +51,7 @@ def __init__(self, user_fields=default_mapped_user_fields) -> None:
5051
self.user = None
5152
self.token = None
5253

54+
@log_excess_runtime(logger, debug_cutoff=0.01)
5355
def parse_jwt_token(self, request):
5456
"""
5557
parses the given request setting self.user and self.token
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import functools
2+
import time
3+
from collections.abc import Callable
4+
from logging import Logger, getLogger
5+
6+
7+
__all__ = ['log_excess_runtime']
8+
9+
10+
# This allows for configuring all logs from this to go to a particular destination
11+
# this is not a child of ansible_base so it can be configured separately, and default to not configured
12+
dab_logger = getLogger('dab_runtime_logger')
13+
14+
15+
DEFAULT_MSG = 'Running {name} took {delta:.2f}s'
16+
17+
18+
def log_excess_runtime(ext_logger: Logger, cutoff: float = 5.0, debug_cutoff: float =2.0, msg: str = DEFAULT_MSG, add_log_data: bool=False):
19+
"""Utility to write logs to the passed-in logger if the function it decorates takes too long.
20+
21+
Call this with the configuration options to return a decorator."""
22+
def log_excess_runtime_decorator(func: Callable):
23+
"""Instantiated decorator for the DAB excess runtime logger utility"""
24+
@functools.wraps(func)
25+
def _new_func(*args, **kwargs):
26+
log_data = {'name': repr(func.__name__)}
27+
start_time = time.time()
28+
29+
if add_log_data:
30+
return_value = func(*args, log_data=log_data, **kwargs)
31+
else:
32+
return_value = func(*args, **kwargs)
33+
34+
log_data['delta'] = time.time() - start_time
35+
36+
formatted_msg = msg.format(**log_data)
37+
38+
for logger in (dab_logger, ext_logger):
39+
if log_data['delta'] > cutoff:
40+
logger.info(formatted_msg)
41+
elif log_data['delta'] > debug_cutoff:
42+
logger.debug(formatted_msg)
43+
44+
return return_value
45+
46+
return _new_func
47+
48+
return log_excess_runtime_decorator

docs/logging.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,48 @@ Within those logs, you should be able to see the culprit:
6969
test_app-1 | File "/src/test_app/views.py", line 185, in timeout_view
7070
test_app-1 | time.sleep(60*10) # 10 minutes
7171
```
72+
73+
## Method Runtime Helper
74+
75+
As a general utility, sometimes you want to log if a particular method takes longer than a certain time.
76+
This is true in almost any case where a method is doing something known to be performance-sensitive,
77+
such as processing Ansible facts in AWX.
78+
79+
```python
80+
import logging
81+
82+
logger = logging.getLogger('my_app.tasks.cleanup')
83+
84+
@log_excess_runtime(logger, cutoff=2.0)
85+
def cleanup(self):
86+
# Do stuff that could take a long time
87+
```
88+
89+
Then if the `cleaup` method takes over 2.0 seconds, you should see a log like this:
90+
91+
```
92+
DEBUG dab_runtime_logger:runtime.py:42 Running 'cleanup' took 2.30s
93+
```
94+
95+
Because you are passing in your own logger, you can control everything about how those logs are handed.
96+
If you need to customize the thresholds or log message, you can pass kwards to
97+
the `@log_excess_runtime` call.
98+
- `cutoff` - time cutoff, any time greater than this causes an INFO level log
99+
- `debug_cutoff` - any time greater than this causes a DEBUG level log
100+
- `msg` - the log message with format strings in it
101+
- `add_log_data` - add a kwarg `log_data` to the method call as a dict that the method can attach additional log data to
102+
103+
The `msg` and `add_log_data` are intended to interact.
104+
For an example, imagine that the `cleanup` method deletes files an keeps a count, `deleted_count`.
105+
The number of deleted fields could be added to the log message with this.
106+
107+
```python
108+
import logging
109+
110+
logger = logging.getLogger('my_app.tasks.cleanup')
111+
112+
@log_excess_runtime(logger, cutoff=2.0, msg='Cleanup took {delta:.2f}s, deleted {files_deleted}')
113+
def cleanup(self, log_data):
114+
# Do stuff that could take a long time
115+
log_data['files_deleted'] = deleted_count
116+
```

0 commit comments

Comments
 (0)