Skip to content

Commit 154582f

Browse files
authored
Merge pull request #285 from getappmap/flask-exc_20240227
Make sure an Exception in a Flask route gets captured, test Flask 3
2 parents dbbfc14 + 498d988 commit 154582f

25 files changed

+201
-167
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ python:
77
- "3.10"
88
- "3.9.14"
99
- "3.8"
10-
- "3.7"
1110

1211
# https://github.com/travis-ci/travis-ci/issues/1147#issuecomment-441393807
1312
if: type != push OR branch = master OR branch =~ /^v\d+\.\d+(\.\d+)?(-\S*)?$/
@@ -21,6 +20,7 @@ install: pip -q install --upgrade "tox < 4" tox-travis
2120
script: tox
2221

2322
cache:
23+
cargo: true
2424
pip: true
2525
directories:
2626
- $TRAVIS_BUILD_DIR/.tox/

_appmap/configuration.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,15 @@ def find_top_packages(rootdir):
9696
def excluded(d):
9797
excluded = d == "node_modules" or d[0] == "."
9898
if excluded:
99-
logger.debug("excluding dir %s", d)
99+
logger.trace("excluding dir %s", d)
100100
return excluded
101101

102102
sys_prefix = _get_sys_prefix()
103103

104104
for d, dirs, files in os.walk(rootdir):
105-
logger.debug("dir %s dirs %s", d, dirs)
105+
logger.trace("dir %s dirs %s", d, dirs)
106106
if realpath(d) == sys_prefix:
107-
logger.debug("skipping sys.prefix %s", sys_prefix)
107+
logger.trace("skipping sys.prefix %s", sys_prefix)
108108
dirs.clear()
109109
continue
110110

@@ -124,7 +124,7 @@ class Config:
124124

125125
def __new__(cls):
126126
if cls._instance is None:
127-
logger.debug("Creating the Config object")
127+
logger.trace("Creating the Config object")
128128
cls._instance = super(Config, cls).__new__(cls)
129129

130130
cls._instance._initialized = False
@@ -143,7 +143,7 @@ def __init__(self):
143143
self._load_config()
144144
self._load_functions()
145145
logger.info("config: %s", self._config)
146-
logger.info("package_functions: %s", self.package_functions)
146+
logger.debug("package_functions: %s", self.package_functions)
147147

148148
if "labels" in self._config:
149149
self.labels.append(self._config["labels"])
@@ -299,7 +299,7 @@ def matches(self, filterable):
299299
logger.info("%r excluded", fqname)
300300
else:
301301
result = False
302-
logger.debug("%r.matches(%r) -> %r", self, fqname, result)
302+
logger.trace("%r.matches(%r) -> %r", self, fqname, result)
303303
return result
304304

305305
def __repr__(self):
@@ -319,9 +319,7 @@ def __init__(self, dist, *args, **kwargs):
319319
def matches(self, filterable):
320320
try:
321321
obj = filterable.obj
322-
logger.debug(
323-
"%r.matches(%r): %s in %r", self, obj, inspect.getfile(obj), self.files
324-
)
322+
logger.trace("%r.matches(%r): %s in %r", self, obj, inspect.getfile(obj), self.files)
325323
if inspect.getfile(obj) not in self.files:
326324
return False
327325
except TypeError:
@@ -347,7 +345,7 @@ def filter(self, filterable):
347345
result = any(
348346
m.matches(filterable) for m in self.matchers
349347
) or self.next_filter.filter(filterable)
350-
logger.debug("ConfigFilter.filter(%r) -> %r", filterable.fqname, result)
348+
logger.trace("ConfigFilter.filter(%r) -> %r", filterable.fqname, result)
351349
return result
352350

353351
def wrap(self, filterable):
@@ -364,13 +362,13 @@ def wrap(self, filterable):
364362
rule = self.match(filterable)
365363
wrapped = getattr(filterable.obj, "_appmap_wrapped", None)
366364
if wrapped is None:
367-
logger.debug(" wrapping %s", filterable.fqname)
365+
logger.trace(" wrapping %s", filterable.fqname)
368366
Config().labels.apply(filterable)
369367
ret = instrument(filterable)
370368
if rule and rule.shallow:
371369
setattr(ret, "_appmap_shallow", rule)
372370
else:
373-
logger.debug(" already wrapped %s", filterable.fqname)
371+
logger.trace(" already wrapped %s", filterable.fqname)
374372
ret = filterable.obj
375373
return ret
376374

_appmap/django.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,7 @@ def __call__(self, request: HttpRequest):
3030
def not_allowed():
3131
return "", HTTPStatus.METHOD_NOT_ALLOWED
3232

33+
assert request.method is not None
3334
body, status = handlers.get(request.method, not_allowed)()
35+
3436
return HttpResponse(body, status=status, content_type="application/json")

_appmap/env.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
"""Initialize from the environment"""
22

3-
from contextlib import contextmanager
43
import logging
54
import logging.config
65
import os
6+
from contextlib import contextmanager
77
from os import environ
88
from pathlib import Path
9+
from typing import cast
10+
11+
from . import trace_logger
912

1013
_cwd = Path.cwd()
1114
_bootenv = environ.copy()
@@ -115,10 +118,12 @@ def is_appmap_repo(self):
115118
def display_params(self):
116119
return self.get("APPMAP_DISPLAY_PARAMS", "true").lower() == "true"
117120

118-
def getLogger(self, name):
119-
return logging.getLogger(name)
121+
def getLogger(self, name) -> trace_logger.TraceLogger:
122+
return cast(trace_logger.TraceLogger, logging.getLogger(name))
120123

121124
def _configure_logging(self):
125+
trace_logger.install()
126+
122127
log_level = self.get("APPMAP_LOG_LEVEL", "warning").upper()
123128

124129
log_config = self.get("APPMAP_LOG_CONFIG")

_appmap/importer.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -167,14 +167,14 @@ def do_import(cls, *args, **kwargs):
167167
if mod.__name__.startswith(cls._skip_instrumenting):
168168
return
169169

170-
logger.debug("do_import, mod %s args %s kwargs %s", mod, args, kwargs)
170+
logger.trace("do_import, mod %s args %s kwargs %s", mod, args, kwargs)
171171
if not cls.filter_chain:
172172
cls.filter_chain = reduce(lambda acc, e: e(acc), cls.filter_stack, NullFilter(None))
173173

174174
def instrument_functions(filterable, selected_functions=None):
175-
logger.debug(" looking for members of %s", filterable.obj)
175+
logger.trace(" looking for members of %s", filterable.obj)
176176
functions = get_members(filterable.obj)
177-
logger.debug(" functions %s", functions)
177+
logger.trace(" functions %s", functions)
178178

179179
for fn_name, static_fn, fn in functions:
180180
# Only instrument the function if it was specifically called out for the package
@@ -198,11 +198,11 @@ def instrument_functions(filterable, selected_functions=None):
198198
instrument_functions(fm)
199199

200200
classes = get_classes(mod)
201-
logger.debug(" classes %s", classes)
201+
logger.trace(" classes %s", classes)
202202
for c in classes:
203203
fc = FilterableCls(c)
204204
if fc.fqname.startswith(cls._skip_instrumenting):
205-
logger.debug(" not instrumenting %s", fc.fqname)
205+
logger.trace(" not instrumenting %s", fc.fqname)
206206
continue
207207
if fc.fqname in package_functions:
208208
instrument_functions(fc, package_functions.get(fc.fqname))
@@ -221,18 +221,18 @@ def wrap_finder_function(fn, decorator):
221221
obj = fn.__self__ if hasattr(fn, "__self__") else fn
222222

223223
if getattr(obj, marker, None) is None:
224-
logger.debug("wrapping %r", fn)
224+
logger.trace("wrapping %r", fn)
225225
ret = decorator(fn)
226226
setattr(obj, marker, True)
227227
else:
228-
logger.debug("already wrapped, %r", fn)
228+
logger.trace("already wrapped, %r", fn)
229229

230230
return ret
231231

232232

233233
@wrapt.decorator
234234
def wrapped_exec_module(exec_module, _, args, kwargs):
235-
logger.debug("exec_module %r args %s kwargs %s", exec_module, args, kwargs)
235+
logger.trace("exec_module %r args %s kwargs %s", exec_module, args, kwargs)
236236
exec_module(*args, **kwargs)
237237
# Only process imports if we're currently enabled. This
238238
# handles the case where we previously hooked the finders, but
@@ -264,7 +264,7 @@ def wrapped_find_spec(find_spec, _, args, kwargs):
264264
else:
265265
loader.exec_module = wrap_exec_module(loader.exec_module)
266266
else:
267-
logger.debug("no exec_module for loader %r", spec.loader)
267+
logger.trace("no exec_module for loader %r", spec.loader)
268268
return spec
269269

270270

@@ -275,14 +275,14 @@ def wrap_finder_find_spec(finder):
275275
if sys.version_info[1] < 11:
276276
find_spec = getattr(finder, "find_spec", None)
277277
if find_spec is None:
278-
logger.debug("no find_spec for finder %r", finder)
278+
logger.trace("no find_spec for finder %r", finder)
279279
return
280280

281281
finder.find_spec = wrap_finder_function(find_spec, wrapped_find_spec)
282282
else:
283283
find_spec = inspect.getattr_static(finder, "find_spec", None)
284284
if find_spec is None:
285-
logger.debug("no find_spec for finder %r", finder)
285+
logger.trace("no find_spec for finder %r", finder)
286286
return
287287

288288
if isinstance(find_spec, (classmethod, staticmethod)):
@@ -319,7 +319,7 @@ def initialize():
319319
Importer.initialize()
320320
# If we're not enabled, there's no reason to hook the finders.
321321
if Env.current.enabled:
322-
logger.debug("sys.metapath: %s", sys.meta_path)
322+
logger.trace("sys.metapath: %s", sys.meta_path)
323323
for finder in sys.meta_path:
324324
wrap_finder_find_spec(finder)
325325

_appmap/instrument.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def track_shallow(fn):
4848
"""
4949
tls = appmap_tls()
5050
rule = getattr(fn, "_appmap_shallow", None)
51-
logger.debug("track_shallow(%r) [%r]", fn, rule)
51+
logger.trace("track_shallow(%r) [%r]", fn, rule)
5252
result = rule and tls.get("last_rule", None) == rule
5353
tls["last_rule"] = rule
5454
return result
@@ -82,7 +82,7 @@ def call_instrumented(f, instance, args, kwargs):
8282
return f.fn(*args, **kwargs)
8383

8484
with recording_disabled():
85-
logger.debug("%s args %s kwargs %s", f.fn, args, kwargs)
85+
logger.trace("%s args %s kwargs %s", f.fn, args, kwargs)
8686
params = CallEvent.set_params(f.params, instance, args, kwargs)
8787
call_event = f.make_call_event(parameters=params)
8888
Recorder.add_event(call_event)

_appmap/py_version_check.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def _get_platform_version():
1717

1818

1919
def check_py_version():
20-
req = (3, 6)
20+
req = (3, 8)
2121
actual = _get_platform_version()
2222
if _get_py_version() < req:
2323
raise AppMapPyVerException(

_appmap/test/data/flask/app.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,8 @@ def show_user_post(username, post_id):
4545
@app.route("/<int:org>/posts/<username>")
4646
def show_org_user_posts(org, username):
4747
return f"org {org} username {username}"
48+
49+
50+
@app.route("/exception")
51+
def raise_exception():
52+
raise Exception("An exception")

_appmap/test/test_command.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,4 @@ def test_flask_version(self, capsys, mocker):
134134
side_effect=lambda d: "1.0" if d == "flask" else version(d),
135135
)
136136

137-
self.check_errors(capsys, 1, 1, "flask must have version >= 1.1, found 1.0")
137+
self.check_errors(capsys, 1, 1, "flask must have version >= 2.0, found 1.0")

_appmap/test/test_django.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ def raise_on_call(*args):
152152
{"request_method": "GET", "path_info": "/exception", "protocol": "HTTP/1.1"}
153153
)
154154

155+
assert events[1].event == "return"
155156
assert events[1].parent_id == events[0].id
156157
assert events[1].exceptions == [
157158
DictIncluding({"class": "builtins.RuntimeError", "message": "An error"})

0 commit comments

Comments
 (0)