Add kaya-otel package for OpenTelemetry tracing and metrics
CI / Build Pip package (push) Successful in 4m8s
CI / Build Pip package (push) Successful in 4m8s
Instrument HTTP requests and WebSocket connections via Kaya hooks, covering both ASGI and RSGI. Records handler exceptions, WebSocket close codes, optional header capture, exclusions and lifecycle hooks. Add route-template resolution and exception visibility to kaya-core so trace/metric attributes can use low-cardinality routes and failed spans can record escaped exceptions.
This commit is contained in:
@@ -250,3 +250,32 @@ class AsgiTest(unittest.TestCase):
|
||||
r = await client.put("/restaurants/42/menu")
|
||||
self.assertEqual(404, r.status_code)
|
||||
|
||||
def test_route_template(self):
|
||||
self.assertEqual('/employee/${employee_id}',
|
||||
self.app.route_template('/employee/101325', HttpMethod.GET))
|
||||
self.assertEqual('/square/${x:int}', self.app.route_template('/square/30', HttpMethod.GET))
|
||||
self.assertIsNone(self.app.route_template('/unknown', HttpMethod.GET))
|
||||
|
||||
@async_test
|
||||
async def test_exception_exposed_to_after_hooks(self):
|
||||
app = KayaApp()
|
||||
seen = []
|
||||
|
||||
async def after_request(ctx: HttpContext) -> None:
|
||||
seen.append(ctx.exception)
|
||||
|
||||
app.add_after_request_hook(after_request)
|
||||
|
||||
@app.GET('/raises')
|
||||
async def raises(ctx: HttpContext) -> None:
|
||||
raise RuntimeError('boom')
|
||||
|
||||
transport = httpx.ASGITransport(app=app)
|
||||
async with httpx.AsyncClient(transport=transport, base_url="http://127.0.0.1:80") as client:
|
||||
with self.assertRaises(RuntimeError):
|
||||
await client.get('/raises')
|
||||
|
||||
self.assertEqual(1, len(seen))
|
||||
self.assertIsInstance(seen[0], RuntimeError)
|
||||
self.assertEqual('boom', str(seen[0]))
|
||||
|
||||
|
||||
@@ -80,6 +80,27 @@ class TreeTest(unittest.TestCase):
|
||||
self.assertIs(Maybe.of(handler_num).map(self.handlers.__getitem__).or_none(),
|
||||
Maybe.of_nullable(res).map(lambda it: it[0]).or_none())
|
||||
|
||||
def test_route_template(self):
|
||||
cases: Tuple[Tuple[str, HttpMethod, Optional[str]], ...] = (
|
||||
('/home/something', HttpMethod.GET, '/home/something'),
|
||||
('/home/something_else', HttpMethod.POST, '/home/something_else'),
|
||||
('/home/README.md', HttpMethod.GET, '/home/*.md'),
|
||||
('/home/something/ciao/blah/README.md', HttpMethod.GET, '/home/something/*/blah/*.md'),
|
||||
('/home/bar/ciao/blah/README.md', HttpMethod.GET, '/home/bar/*'),
|
||||
('/unknown', HttpMethod.GET, None),
|
||||
)
|
||||
for url, method, expected in cases:
|
||||
with self.subTest(f'{method} {url}'):
|
||||
self.assertEqual(expected, self.tree.route_template(url, method))
|
||||
|
||||
def test_route_template_with_params(self):
|
||||
tree = Tree()
|
||||
tree.add((p for p in ('foo', '${id:int}')), HttpMethod.PUT, self.handlers[0])
|
||||
tree.add((p for p in ('foo', '${name}')), HttpMethod.GET, self.handlers[1])
|
||||
self.assertEqual('/foo/${id:int}', tree.route_template('/foo/42', HttpMethod.PUT))
|
||||
self.assertEqual('/foo/${name}', tree.route_template('/foo/bar', HttpMethod.GET))
|
||||
self.assertIsNone(tree.route_template('/foo/bar', HttpMethod.DELETE))
|
||||
|
||||
def test_two_method_agnostic_matchers_raise(self):
|
||||
tree = Tree()
|
||||
tree.add((p for p in ('foo', '*')), None, self.handlers[0])
|
||||
|
||||
Reference in New Issue
Block a user