Skip to content

Commit 862b6e1

Browse files
authored
add wrapper support for Starlette (#1830)
* add wrapper support for Starlette The instrumentation itself is fairly trivial, we wrap `Starlette.build_middleware_stack` to insert our middleware. Testing was a bit more difficult. Ultimately, I resorted to only test the wrapping, but without use of the wrapper script. * update changelog
1 parent dfb4936 commit 862b6e1

File tree

5 files changed

+121
-1
lines changed

5 files changed

+121
-1
lines changed

CHANGELOG.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ endif::[]
3939
* Add lambda layer for instrumenting AWS Lambda functions {pull}1826[#1826]
4040
* Implement instrumentation of Azure Functions {pull}1766[#1766]
4141
* Add support for Django to wrapper script {pull}1780[#1780]
42+
* Add support for Starlette to wrapper script {pull}1830[#1830]
4243
* Add `transport_json_serializer` configuration option {pull}1777[#1777]
4344
* Add S3 bucket and key name to OTel attributes {pull}1790[#1790]
4445
* Implement partial transaction support in AWS lambda {pull}1784[#1784]

docs/wrapper.asciidoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ no-code-changes instrumentation:
88

99
* Django
1010
* Flask
11+
* Starlette
1112

1213
Please keep in mind that these instrumentations are a work in progress! We'd
1314
love to have feedback on our
@@ -54,4 +55,4 @@ You can also pass config options as arguments to the script:
5455
[source,bash]
5556
----
5657
$ elasticapm-run --config "service_name=my_flask_app" --config "debug=true" flask run
57-
----
58+
----
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# BSD 3-Clause License
2+
#
3+
# Copyright (c) 2022, Elasticsearch BV
4+
# All rights reserved.
5+
#
6+
# Redistribution and use in source and binary forms, with or without
7+
# modification, are permitted provided that the following conditions are met:
8+
#
9+
# * Redistributions of source code must retain the above copyright notice, this
10+
# list of conditions and the following disclaimer.
11+
#
12+
# * Redistributions in binary form must reproduce the above copyright notice,
13+
# this list of conditions and the following disclaimer in the documentation
14+
# and/or other materials provided with the distribution.
15+
#
16+
# * Neither the name of the copyright holder nor the names of its
17+
# contributors may be used to endorse or promote products derived from
18+
# this software without specific prior written permission.
19+
#
20+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
31+
import weakref
32+
from typing import TYPE_CHECKING
33+
34+
from elasticapm.instrumentation.packages.base import AbstractInstrumentedModule
35+
36+
if TYPE_CHECKING:
37+
from starlette.applications import Starlette
38+
39+
40+
class StarletteInstrumentation(AbstractInstrumentedModule):
41+
name = "starlette"
42+
43+
instrument_list = [("starlette.applications", "Starlette.build_middleware_stack")]
44+
45+
instrumented_apps = []
46+
47+
creates_transactions = True
48+
49+
def call(self, module, method, wrapped, instance: "Starlette", args, kwargs):
50+
from elasticapm.contrib.starlette import ElasticAPM
51+
52+
if ElasticAPM not in instance.user_middleware:
53+
instance.add_middleware(ElasticAPM, client=None)
54+
self.instrumented_apps.append(weakref.ref(instance))
55+
return wrapped(*args, **kwargs)

elasticapm/instrumentation/register.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
_wrapper_register = {
102102
"elasticapm.instrumentation.packages.flask.FlaskInstrumentation",
103103
"elasticapm.instrumentation.packages.django.DjangoAutoInstrumentation",
104+
"elasticapm.instrumentation.packages.starlette.StarletteInstrumentation",
104105
}
105106

106107

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# BSD 3-Clause License
2+
#
3+
# Copyright (c) 2023, Elasticsearch BV
4+
# All rights reserved.
5+
#
6+
# Redistribution and use in source and binary forms, with or without
7+
# modification, are permitted provided that the following conditions are met:
8+
#
9+
# * Redistributions of source code must retain the above copyright notice, this
10+
# list of conditions and the following disclaimer.
11+
#
12+
# * Redistributions in binary form must reproduce the above copyright notice,
13+
# this list of conditions and the following disclaimer in the documentation
14+
# and/or other materials provided with the distribution.
15+
#
16+
# * Neither the name of the copyright holder nor the names of its
17+
# contributors may be used to endorse or promote products derived from
18+
# this software without specific prior written permission.
19+
#
20+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
31+
import pytest # isort:skip
32+
33+
pytest.importorskip("starlette") # isort:skip
34+
35+
from starlette.applications import Starlette
36+
from starlette.responses import PlainTextResponse
37+
from starlette.routing import Route
38+
from starlette.testclient import TestClient
39+
40+
from elasticapm import async_capture_span
41+
from elasticapm.conf import constants
42+
from elasticapm.contrib.starlette import ElasticAPM
43+
44+
45+
def test_starlette_app_instrumented(wrapper_instrument, elasticapm_client):
46+
async def hi(request):
47+
body = await request.body()
48+
with async_capture_span("test"):
49+
pass
50+
return PlainTextResponse(str(len(body)))
51+
52+
routes = [Route("/", hi)]
53+
app = Starlette(routes=routes)
54+
test_client = TestClient(app)
55+
56+
app.build_middleware_stack()
57+
assert any(middleware.cls is ElasticAPM for middleware in app.user_middleware)
58+
test_client.get("/")
59+
transaction = elasticapm_client.events[constants.TRANSACTION][0]
60+
assert transaction["name"] == "GET /"
61+
assert transaction["result"] == "HTTP 2xx"
62+
assert transaction["outcome"] == "success"

0 commit comments

Comments
 (0)