From 5ad35341ab141ba0bf942e69598c69f8a39a2a8b Mon Sep 17 00:00:00 2001 From: Iuri de Silvio Date: Mon, 27 Nov 2023 20:08:05 -0300 Subject: [PATCH 001/409] Fix asyncpg tests to make it compatible with asyncpg>=0.29 (#1935) * Fix asyncpg get_query to make it compatible with asyncpg>=0.29 * Keep testing with asyncpg<=0.28 * Upgrade flake8 in pre-commit * Remove 0.28 test from PR framework list --------- Co-authored-by: Colton Myers --- .ci/.matrix_framework_full.yml | 1 + .pre-commit-config.yaml | 2 +- elasticapm/contrib/asgi.py | 4 +++- elasticapm/instrumentation/packages/asyncio/asyncpg.py | 9 ++++++--- tests/requirements/reqs-asyncpg-0.28.txt | 2 ++ 5 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 tests/requirements/reqs-asyncpg-0.28.txt diff --git a/.ci/.matrix_framework_full.yml b/.ci/.matrix_framework_full.yml index 17d7198fe..7b1ee213e 100644 --- a/.ci/.matrix_framework_full.yml +++ b/.ci/.matrix_framework_full.yml @@ -67,6 +67,7 @@ FRAMEWORK: - aiohttp-3.0 - aiohttp-newest - aiopg-newest + - asyncpg-0.28 - asyncpg-newest - tornado-newest - starlette-0.13 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f3341b37e..07f59f368 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ repos: language_version: python3 exclude: "(tests/utils/stacks/linenos.py|tests/utils/stacks/linenos2.py|tests/contrib/grpc/grpc_app/.*pb2.*.py)" - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 + rev: 6.1.0 hooks: - id: flake8 exclude: "(tests/utils/stacks/linenos.py|tests/utils/stacks/linenos2.py|tests/contrib/grpc/grpc_app/.*pb2.*.py)" diff --git a/elasticapm/contrib/asgi.py b/elasticapm/contrib/asgi.py index 701ea3d0e..096fed36a 100644 --- a/elasticapm/contrib/asgi.py +++ b/elasticapm/contrib/asgi.py @@ -92,13 +92,15 @@ async def __call__(self, scope: "Scope", receive: "ASGIReceiveCallable", send: " body = str(body_raw, errors="ignore") # Dispatch to the ASGI callable - async def wrapped_receive(): + async def new_wrapped_receive(): if messages: return messages.pop(0) # Once that's done we can just await any other messages. return await receive() + wrapped_receive = new_wrapped_receive + await set_context(lambda: self.get_data_from_request(scope, constants.TRANSACTION, body), "request") try: diff --git a/elasticapm/instrumentation/packages/asyncio/asyncpg.py b/elasticapm/instrumentation/packages/asyncio/asyncpg.py index e76dd2bf1..4dcf93311 100644 --- a/elasticapm/instrumentation/packages/asyncio/asyncpg.py +++ b/elasticapm/instrumentation/packages/asyncio/asyncpg.py @@ -55,14 +55,17 @@ class AsyncPGInstrumentation(AsyncAbstractInstrumentedModule): ("asyncpg.protocol.protocol", "Protocol.copy_out"), ] - def get_query(self, method, args): + def get_query(self, method, args, kwargs=None): if method in ["Protocol.query", "Protocol.copy_in", "Protocol.copy_out"]: return args[0] - else: + elif args: return args[0].query + else: + # asyncpg>=0.29 pass data as kwargs + return kwargs["state"].query async def call(self, module, method, wrapped, instance, args, kwargs): - query = self.get_query(method, args) + query = self.get_query(method, args, kwargs) name = extract_signature(query) sql_string = shorten(query, string_length=10000) context = {"db": {"type": "sql", "statement": sql_string}} diff --git a/tests/requirements/reqs-asyncpg-0.28.txt b/tests/requirements/reqs-asyncpg-0.28.txt new file mode 100644 index 000000000..2dd69e3a5 --- /dev/null +++ b/tests/requirements/reqs-asyncpg-0.28.txt @@ -0,0 +1,2 @@ +asyncpg==0.28 +-r reqs-base.txt From cda2feff70773221a729c246a8787c498545635d Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Tue, 28 Nov 2023 23:02:24 +0100 Subject: [PATCH 002/409] Remove redundant semicolon in code example (#1940) Co-authored-by: Colton Myers --- docs/opentelemetry.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/opentelemetry.asciidoc b/docs/opentelemetry.asciidoc index 531fd09d4..ee3800376 100644 --- a/docs/opentelemetry.asciidoc +++ b/docs/opentelemetry.asciidoc @@ -36,7 +36,7 @@ pip install opentelemetry-api opentelemetry-sdk ---- from elasticapm.contrib.opentelemetry import Tracer -tracer = Tracer(__name__); +tracer = Tracer(__name__) with tracer.start_as_current_span("test"): # Do some work ---- From bc068d430e7c6a7dde8d3068a634059b181fd882 Mon Sep 17 00:00:00 2001 From: Igor Malinovskiy Date: Tue, 28 Nov 2023 23:03:44 +0100 Subject: [PATCH 003/409] Fix code examples in Starlette/FastAPI doc (#1939) - Add required 'SERVER_URL' config param - Fix import in FastAPI code example - Use the correct variable when referring to APM client Co-authored-by: Colton Myers --- docs/starlette.asciidoc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/starlette.asciidoc b/docs/starlette.asciidoc index 8b82ee1f2..77aaca0d4 100644 --- a/docs/starlette.asciidoc +++ b/docs/starlette.asciidoc @@ -57,6 +57,7 @@ from elasticapm.contrib.starlette import make_apm_client, ElasticAPM apm = make_apm_client({ 'SERVICE_NAME': '', 'SECRET_TOKEN': '', + 'SERVER_URL': '', }) app = Starlette() app.add_middleware(ElasticAPM, client=apm) @@ -72,7 +73,7 @@ is almost exactly the same as with Starlette: [source,python] ---- from fastapi import FastAPI -from elasticapm.contrib.starlette ElasticAPM +from elasticapm.contrib.starlette import ElasticAPM app = FastAPI() app.add_middleware(ElasticAPM) @@ -93,14 +94,14 @@ Capture an arbitrary exception by calling try: 1 / 0 except ZeroDivisionError: - apm.client.capture_exception() + apm.capture_exception() ---- Log a generic message with <>: [source,python] ---- -apm.client.capture_message('hello, world!') +apm.capture_message('hello, world!') ---- [float] From 60e9f4285d467f0250cd8d6311e9cedd85ee3a5b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Nov 2023 17:07:40 -0500 Subject: [PATCH 004/409] Bump certifi from 2023.7.22 to 2023.11.17 in /dev-utils (#1936) Bumps [certifi](https://github.com/certifi/python-certifi) from 2023.7.22 to 2023.11.17. - [Commits](https://github.com/certifi/python-certifi/compare/2023.07.22...2023.11.17) --- updated-dependencies: - dependency-name: certifi dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dev-utils/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-utils/requirements.txt b/dev-utils/requirements.txt index 353f9d8dc..21258b2ad 100644 --- a/dev-utils/requirements.txt +++ b/dev-utils/requirements.txt @@ -1,4 +1,4 @@ # These are the pinned requirements for the lambda layer/docker image -certifi==2023.7.22 +certifi==2023.11.17 urllib3==1.26.18 wrapt==1.14.1 From 0adfefc53a04676e945bbdab267535351fe6eee6 Mon Sep 17 00:00:00 2001 From: apmmachine <58790750+apmmachine@users.noreply.github.com> Date: Wed, 29 Nov 2023 19:33:09 -0500 Subject: [PATCH 005/409] chore: container_metadata_discovery.json (#1943) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: apmmachine --- .../container_metadata_discovery.json | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/upstream/json-specs/container_metadata_discovery.json b/tests/upstream/json-specs/container_metadata_discovery.json index 510fd19d2..d1797ca0e 100644 --- a/tests/upstream/json-specs/container_metadata_discovery.json +++ b/tests/upstream/json-specs/container_metadata_discovery.json @@ -60,6 +60,41 @@ }, "containerId": "6548c6863fb748e72d1e2a4f824fde92f720952d062dede1318c2d6219a672d6", "podId": null + }, + "gardener": { + "files": { + "/proc/self/mountinfo": [ + "10112 5519 0:864 / / ro,relatime master:1972 - overlay overlay rw,lowerdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/35235/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/27346/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/27345/fs:/var/lib/containerd/io.containerd.snapsh", + "10113 10112 0:884 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw", + "10301 10112 0:926 / /dev rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64", + "10302 10301 0:930 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666", + "10519 10301 0:820 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw", + "10520 10112 0:839 / /sys ro,nosuid,nodev,noexec,relatime - sysfs sysfs ro", + "10716 10520 0:26 /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod121157b5_c67d_4c3e_9052_cb27bbb711fb.slice/cri-containerd-1cd3449e930b8a28c7595240fa32ba20c84f36d059e5fbe63104ad40057992d1.scope /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw", + "10736 10112 8:3 /var/lib/kubelet/pods/121157b5-c67d-4c3e-9052-cb27bbb711fb/volumes/kubernetes.io~empty-dir/tmpdir /tmp rw,relatime - ext4 /dev/sda3 rw,discard,prjquota,errors=remount-ro", + "10737 10112 0:786 / /vault/tls ro,relatime - tmpfs tmpfs rw,size=4194304k,inode64", + "10738 10112 8:3 /var/lib/kubelet/pods/121157b5-c67d-4c3e-9052-cb27bbb711fb/etc-hosts /etc/hosts rw,relatime - ext4 /dev/sda3 rw,discard,prjquota,errors=remount-ro", + "10739 10301 8:3 /var/lib/kubelet/pods/121157b5-c67d-4c3e-9052-cb27bbb711fb/containers/application-search-indexer/9bf2b38c /dev/termination-log rw,relatime - ext4 /dev/sda3 rw,discard,prjquota,errors=remount-ro", + "10740 10112 8:3 /var/lib/containerd/io.containerd.grpc.v1.cri/sandboxes/26a006f558da58874bc37863efe9d2b5d715afc54453d95b22a7809a4e65566c/hostname /etc/hostname ro,relatime - ext4 /dev/sda3 rw,discard,prjquota,errors=remount-ro", + "10741 10112 8:3 /var/lib/containerd/io.containerd.grpc.v1.cri/sandboxes/26a006f558da58874bc37863efe9d2b5d715afc54453d95b22a7809a4e65566c/resolv.conf /etc/resolv.conf ro,relatime - ext4 /dev/sda3 rw,discard,prjquota,errors=remount-ro", + "10761 10301 0:788 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k,inode64", + "10762 10112 0:787 / /var/run/secrets/kubernetes.io/serviceaccount ro,relatime - tmpfs tmpfs rw,size=4194304k,inode64", + "5630 10113 0:884 /bus /proc/bus ro,nosuid,nodev,noexec,relatime - proc proc rw", + "5631 10113 0:884 /fs /proc/fs ro,nosuid,nodev,noexec,relatime - proc proc rw", + "5632 10113 0:884 /irq /proc/irq ro,nosuid,nodev,noexec,relatime - proc proc rw", + "5633 10113 0:884 /sys /proc/sys ro,nosuid,nodev,noexec,relatime - proc proc rw", + "5634 10113 0:931 / /proc/acpi ro,relatime - tmpfs tmpfs ro,inode64", + "5635 10113 0:926 /null /proc/kcore rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64", + "5636 10113 0:926 /null /proc/keys rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64", + "5637 10113 0:926 /null /proc/timer_list rw,nosuid - tmpfs tmpfs rw,size=65536k,mode=755,inode64", + "5639 10520 0:932 / /sys/firmware ro,relatime - tmpfs tmpfs ro,inode64" + ], + "/proc/self/cgroup": [ + "0::/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod121157b5_c67d_4c3e_9052_cb27bbb711fb.slice/cri-containerd-1cd3449e930b8a28c7595240fa32ba20c84f36d059e5fbe63104ad40057992d1.scope" + ] + }, + "containerId": "1cd3449e930b8a28c7595240fa32ba20c84f36d059e5fbe63104ad40057992d1", + "podId": "121157b5-c67d-4c3e-9052-cb27bbb711fb" } } From 8ee719b9c562bc3ade3dd4bac761a5157b068552 Mon Sep 17 00:00:00 2001 From: Philippe Labat Date: Thu, 30 Nov 2023 19:21:35 -0500 Subject: [PATCH 006/409] Added Async support for DBAPI2 w/ implementation for psycopg (#1944) * Introducing AsyncIO DBAPIv2 + Psycopg async support * Introducing AsyncIO DBAPIv2 + Psycopg async support * Fixed incorrect import --- .../packages/asyncio/dbapi2_asyncio.py | 126 ++++++++++++++++++ .../packages/asyncio/psycopg_async.py | 104 +++++++++++++++ elasticapm/instrumentation/register.py | 1 + 3 files changed, 231 insertions(+) create mode 100644 elasticapm/instrumentation/packages/asyncio/dbapi2_asyncio.py create mode 100644 elasticapm/instrumentation/packages/asyncio/psycopg_async.py diff --git a/elasticapm/instrumentation/packages/asyncio/dbapi2_asyncio.py b/elasticapm/instrumentation/packages/asyncio/dbapi2_asyncio.py new file mode 100644 index 000000000..4345731b7 --- /dev/null +++ b/elasticapm/instrumentation/packages/asyncio/dbapi2_asyncio.py @@ -0,0 +1,126 @@ +# BSD 3-Clause License +# +# Copyright (c) 2019, Elasticsearch BV +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +"""Provides classes to instrument dbapi2 providers + +https://www.python.org/dev/peps/pep-0249/ +""" + +import wrapt + +from elasticapm.contrib.asyncio.traces import async_capture_span +from elasticapm.instrumentation.packages.asyncio.base import AsyncAbstractInstrumentedModule +from elasticapm.instrumentation.packages.dbapi2 import EXEC_ACTION, QUERY_ACTION +from elasticapm.utils.encoding import shorten + + +class AsyncCursorProxy(wrapt.ObjectProxy): + provider_name = None + DML_QUERIES = ("INSERT", "DELETE", "UPDATE") + + def __init__(self, wrapped, destination_info=None): + super(AsyncCursorProxy, self).__init__(wrapped) + self._self_destination_info = destination_info or {} + + async def callproc(self, procname, params=None): + return await self._trace_sql(self.__wrapped__.callproc, procname, params, action=EXEC_ACTION) + + async def execute(self, sql, params=None): + return await self._trace_sql(self.__wrapped__.execute, sql, params) + + async def executemany(self, sql, param_list): + return await self._trace_sql(self.__wrapped__.executemany, sql, param_list) + + def _bake_sql(self, sql): + """ + Method to turn the "sql" argument into a string. Most database backends simply return + the given object, as it is already a string + """ + return sql + + async def _trace_sql(self, method, sql, params, action=QUERY_ACTION): + sql_string = self._bake_sql(sql) + if action == EXEC_ACTION: + signature = sql_string + "()" + else: + signature = self.extract_signature(sql_string) + + # Truncate sql_string to 10000 characters to prevent large queries from + # causing an error to APM server. + sql_string = shorten(sql_string, string_length=10000) + + async with async_capture_span( + signature, + span_type="db", + span_subtype=self.provider_name, + span_action=action, + extra={ + "db": {"type": "sql", "statement": sql_string, "instance": getattr(self, "_self_database", None)}, + "destination": self._self_destination_info, + }, + skip_frames=1, + leaf=True, + ) as span: + if params is None: + result = await method(sql) + else: + result = await method(sql, params) + # store "rows affected", but only for DML queries like insert/update/delete + if span and self.rowcount not in (-1, None) and signature.startswith(self.DML_QUERIES): + span.update_context("db", {"rows_affected": self.rowcount}) + return result + + def extract_signature(self, sql): + raise NotImplementedError() + + +class AsyncConnectionProxy(wrapt.ObjectProxy): + cursor_proxy = AsyncCursorProxy + + def __init__(self, wrapped, destination_info=None): + super(AsyncConnectionProxy, self).__init__(wrapped) + self._self_destination_info = destination_info + + def cursor(self, *args, **kwargs): + return self.cursor_proxy(self.__wrapped__.cursor(*args, **kwargs), self._self_destination_info) + + +class AsyncDbApi2Instrumentation(AsyncAbstractInstrumentedModule): + connect_method = None + + async def call(self, module, method, wrapped, instance, args, kwargs): + return AsyncConnectionProxy(await wrapped(*args, **kwargs)) + + async def call_if_sampling(self, module, method, wrapped, instance, args, kwargs): + # Contrasting to the superclass implementation, we *always* want to + # return a proxied connection, even if there is no ongoing elasticapm + # transaction yet. This ensures that we instrument the cursor once + # the transaction started. + return await self.call(module, method, wrapped, instance, args, kwargs) diff --git a/elasticapm/instrumentation/packages/asyncio/psycopg_async.py b/elasticapm/instrumentation/packages/asyncio/psycopg_async.py new file mode 100644 index 000000000..0ef565119 --- /dev/null +++ b/elasticapm/instrumentation/packages/asyncio/psycopg_async.py @@ -0,0 +1,104 @@ +# BSD 3-Clause License +# +# Copyright (c) 2019, Elasticsearch BV +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +from __future__ import absolute_import + +from elasticapm.contrib.asyncio.traces import async_capture_span +from elasticapm.instrumentation.packages.asyncio.dbapi2_asyncio import ( + AsyncConnectionProxy, + AsyncCursorProxy, + AsyncDbApi2Instrumentation, +) +from elasticapm.instrumentation.packages.dbapi2 import extract_signature +from elasticapm.instrumentation.packages.psycopg2 import get_destination_info + + +class PGAsyncCursorProxy(AsyncCursorProxy): + provider_name = "postgresql" + + def _bake_sql(self, sql): + # If this is a Composable object, use its `as_string` method. + # See https://www.psycopg.org/psycopg3/docs/api/sql.html + if hasattr(sql, "as_string"): + sql = sql.as_string(self.__wrapped__) + # If the sql string is already a byte string, we need to decode it using the connection encoding + if isinstance(sql, bytes): + sql = sql.decode(self.connection.info.encoding) + return sql + + def extract_signature(self, sql): + return extract_signature(sql) + + async def __aenter__(self): + return PGAsyncCursorProxy(await self.__wrapped__.__aenter__(), destination_info=self._self_destination_info) + + async def __aexit__(self, *args): + return PGAsyncCursorProxy(await self.__wrapped__.__aexit__(*args), destination_info=self._self_destination_info) + + @property + def _self_database(self): + return self.connection.info.dbname or "" + + +class PGAsyncConnectionProxy(AsyncConnectionProxy): + cursor_proxy = PGAsyncCursorProxy + + async def __aenter__(self): + return PGAsyncConnectionProxy(await self.__wrapped__.__aenter__(), destination_info=self._self_destination_info) + + async def __aexit__(self, *args): + return PGAsyncConnectionProxy( + await self.__wrapped__.__aexit__(*args), destination_info=self._self_destination_info + ) + + +class AsyncPsycopgInstrumentation(AsyncDbApi2Instrumentation): + name = "psycopg_async" + + instrument_list = [("psycopg.connection_async", "AsyncConnection.connect")] + + async def call(self, module, method, wrapped, instance, args, kwargs): + signature = "psycopg.connect_async" + + host, port = get_destination_info(kwargs.get("host"), kwargs.get("port")) + database = kwargs.get("dbname") + signature = f"{signature} {host}:{port}" # noqa: E231 + destination_info = { + "address": host, + "port": port, + } + async with async_capture_span( + signature, + span_type="db", + span_subtype="postgresql", + span_action="connect", + leaf=True, + extra={"destination": destination_info, "db": {"type": "sql", "instance": database}}, + ): + return PGAsyncConnectionProxy(await wrapped(*args, **kwargs), destination_info=destination_info) diff --git a/elasticapm/instrumentation/register.py b/elasticapm/instrumentation/register.py index 2c3ab71a9..b37aff1e9 100644 --- a/elasticapm/instrumentation/register.py +++ b/elasticapm/instrumentation/register.py @@ -94,6 +94,7 @@ "elasticapm.instrumentation.packages.asyncio.starlette.StarletteServerErrorMiddlewareInstrumentation", "elasticapm.instrumentation.packages.asyncio.redis_asyncio.RedisAsyncioInstrumentation", "elasticapm.instrumentation.packages.asyncio.redis_asyncio.RedisPipelineInstrumentation", + "elasticapm.instrumentation.packages.asyncio.psycopg_async.AsyncPsycopgInstrumentation", "elasticapm.instrumentation.packages.grpc.GRPCAsyncServerInstrumentation", ] ) From 1654dd5e83ed160f3dfe457ee7179729a5bd7b43 Mon Sep 17 00:00:00 2001 From: Johan Andersson Date: Fri, 1 Dec 2023 02:08:34 +0100 Subject: [PATCH 007/409] feat(dbapi2): include object name in procedure call spans (#1938) * feat(dbapi2): include object name in procedure call spans Include the name of the procedure in the name of the span. Examples: CALL procedure() EXECUTE procedure Issue: #1937 * feat(dbapi2): use span action exec for procedure calls Set span action to exec instead of query for procedure calls not using callproc(). Issue: #1937 --- elasticapm/instrumentation/packages/dbapi2.py | 31 ++++++++++---- tests/instrumentation/dbapi2_tests.py | 42 ++++++++++++++++++- 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/elasticapm/instrumentation/packages/dbapi2.py b/elasticapm/instrumentation/packages/dbapi2.py index 8315bd8be..cbe34be59 100644 --- a/elasticapm/instrumentation/packages/dbapi2.py +++ b/elasticapm/instrumentation/packages/dbapi2.py @@ -170,30 +170,46 @@ def extract_signature(sql): keyword = "INTO" if sql_type == "INSERT" else "FROM" sql_type = sql_type + " " + keyword - table_name = look_for_table(sql, keyword) + object_name = look_for_table(sql, keyword) elif sql_type in ["CREATE", "DROP"]: # 2nd word is part of SQL type sql_type = sql_type + sql[first_space:second_space] - table_name = "" + object_name = "" elif sql_type == "UPDATE": - table_name = look_for_table(sql, "UPDATE") + object_name = look_for_table(sql, "UPDATE") elif sql_type == "SELECT": # Name is first table try: sql_type = "SELECT FROM" - table_name = look_for_table(sql, "FROM") + object_name = look_for_table(sql, "FROM") except Exception: - table_name = "" + object_name = "" + elif sql_type in ["EXEC", "EXECUTE"]: + sql_type = "EXECUTE" + end = second_space if second_space > first_space else len(sql) + object_name = sql[first_space + 1 : end] + elif sql_type == "CALL": + first_paren = sql.find("(", first_space) + end = first_paren if first_paren > first_space else len(sql) + procedure_name = sql[first_space + 1 : end].rstrip(";") + object_name = procedure_name + "()" else: # No name - table_name = "" + object_name = "" - signature = " ".join(filter(bool, [sql_type, table_name])) + signature = " ".join(filter(bool, [sql_type, object_name])) return signature QUERY_ACTION = "query" EXEC_ACTION = "exec" +PROCEDURE_STATEMENTS = ["EXEC", "EXECUTE", "CALL"] + + +def extract_action_from_signature(signature, default): + if signature.split(" ")[0] in PROCEDURE_STATEMENTS: + return EXEC_ACTION + return default class CursorProxy(wrapt.ObjectProxy): @@ -226,6 +242,7 @@ def _trace_sql(self, method, sql, params, action=QUERY_ACTION): signature = sql_string + "()" else: signature = self.extract_signature(sql_string) + action = extract_action_from_signature(signature, action) # Truncate sql_string to 10000 characters to prevent large queries from # causing an error to APM server. diff --git a/tests/instrumentation/dbapi2_tests.py b/tests/instrumentation/dbapi2_tests.py index 7519b0801..d2fc84aab 100644 --- a/tests/instrumentation/dbapi2_tests.py +++ b/tests/instrumentation/dbapi2_tests.py @@ -29,7 +29,13 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import pytest -from elasticapm.instrumentation.packages.dbapi2 import Literal, extract_signature, scan, tokenize +from elasticapm.instrumentation.packages.dbapi2 import ( + Literal, + extract_action_from_signature, + extract_signature, + scan, + tokenize, +) def test_scan_simple(): @@ -114,3 +120,37 @@ def test_extract_signature_bytes(): actual = extract_signature(sql) expected = "HELLO" assert actual == expected + + +@pytest.mark.parametrize( + ["sql", "expected"], + [ + ( + "EXEC AdventureWorks2022.dbo.uspGetEmployeeManagers 50;", + "EXECUTE AdventureWorks2022.dbo.uspGetEmployeeManagers", + ), + ("EXECUTE sp_who2", "EXECUTE sp_who2"), + ("EXEC sp_updatestats @@all_schemas = 'true'", "EXECUTE sp_updatestats"), + ("CALL get_car_stats_by_year(2017, @number, @min, @avg, @max);", "CALL get_car_stats_by_year()"), + ("CALL get_car_stats_by_year", "CALL get_car_stats_by_year()"), + ("CALL get_car_stats_by_year;", "CALL get_car_stats_by_year()"), + ("CALL get_car_stats_by_year();", "CALL get_car_stats_by_year()"), + ], +) +def test_extract_signature_for_procedure_call(sql, expected): + actual = extract_signature(sql) + assert actual == expected + + +@pytest.mark.parametrize( + ["sql", "expected"], + [ + ("SELECT FROM table", "query"), + ("EXEC sp_who", "exec"), + ("EXECUTE sp_updatestats", "exec"), + ("CALL me_maybe", "exec"), + ], +) +def test_extract_action_from_signature(sql, expected): + actual = extract_action_from_signature(sql, "query") + assert actual == expected From 46b0c525f1099e66b470c5eed39ff5dc1fff3140 Mon Sep 17 00:00:00 2001 From: Alessio Izzo Date: Wed, 6 Dec 2023 00:59:43 +0100 Subject: [PATCH 008/409] fix extract_signature dbapi2: handling square brakets in table name (#1947) --- elasticapm/instrumentation/packages/dbapi2.py | 4 ++-- tests/instrumentation/dbapi2_tests.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/elasticapm/instrumentation/packages/dbapi2.py b/elasticapm/instrumentation/packages/dbapi2.py index cbe34be59..fb49723c2 100644 --- a/elasticapm/instrumentation/packages/dbapi2.py +++ b/elasticapm/instrumentation/packages/dbapi2.py @@ -76,8 +76,8 @@ def _scan_for_table_with_tokens(tokens, keyword): def tokenize(sql): - # split on anything that is not a word character, excluding dots - return [t for t in re.split(r"([^\w.])", sql) if t != ""] + # split on anything that is not a word character or a square bracket, excluding dots + return [t for t in re.split(r"([^\w.\[\]])", sql) if t != ""] def scan(tokens): diff --git a/tests/instrumentation/dbapi2_tests.py b/tests/instrumentation/dbapi2_tests.py index d2fc84aab..3d72b6632 100644 --- a/tests/instrumentation/dbapi2_tests.py +++ b/tests/instrumentation/dbapi2_tests.py @@ -154,3 +154,18 @@ def test_extract_signature_for_procedure_call(sql, expected): def test_extract_action_from_signature(sql, expected): actual = extract_action_from_signature(sql, "query") assert actual == expected + + +@pytest.mark.parametrize( + ["sql", "expected"], + [ + ("SELECT username FROM user", "SELECT FROM user"), + ("SELECT username FROM [user]", "SELECT FROM [user]"), + ("SELECT username FROM [db].[user]", "SELECT FROM [db].[user]"), + ("SELECT username FROM db.[user]", "SELECT FROM db.[user]"), + ("SELECT username FROM [db].user", "SELECT FROM [db].user"), + ], +) +def test_extract_signature_when_using_square_brackets(sql, expected): + actual = extract_signature(sql) + assert actual == expected From 01f16247a5bae8d4da9e4f3e7688eb9202af3a9b Mon Sep 17 00:00:00 2001 From: Colton Myers Date: Wed, 10 Jan 2024 09:12:35 -0700 Subject: [PATCH 009/409] Add note about log ingestion via filebeat (#1876) --- docs/logging.asciidoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/logging.asciidoc b/docs/logging.asciidoc index 9e0b24922..943de8f64 100644 --- a/docs/logging.asciidoc +++ b/docs/logging.asciidoc @@ -6,6 +6,10 @@ Elastic Python APM Agent provides the following log features: - <> : Automatically inject correlation IDs that allow navigation between logs, traces and services. - <> : Automatically reformat plaintext logs in {ecs-logging-ref}/intro.html[ECS logging] format. +NOTE: Elastic Python APM Agent does not send the logs to Elasticsearch. It only +injects correlation IDs and reformats the logs. You must use another ingestion +strategy. We recommend https://www.elastic.co/beats/filebeat[Filebeat] for that purpose. + Those features are part of {observability-guide}/application-logs.html[Application log ingestion strategies]. The {ecs-logging-python-ref}/intro.html[`ecs-logging-python`] library can also be used to use the {ecs-logging-ref}/intro.html[ECS logging] format without an APM agent. From 4f5661277becc1034ee588bae4b018a4b22cc02b Mon Sep 17 00:00:00 2001 From: Colton Myers Date: Wed, 10 Jan 2024 09:45:10 -0700 Subject: [PATCH 010/409] update CHANGELOG and bump version to 6.20.0 (#1949) --- CHANGELOG.asciidoc | 29 +++++++++++++++++------------ elasticapm/handlers/logging.py | 6 +++--- elasticapm/version.py | 2 +- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 97c5abdfc..fb87b1107 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -29,24 +29,29 @@ endif::[] //===== Bug fixes // -=== Unreleased +[[release-notes-6.x]] +=== Python Agent version 6.x -// Unreleased changes go here -// When the next release happens, nest these changes under the "Python Agent version 6.x" heading -//[float] -//===== Features -// -//[float] -//===== Bug fixes +[[release-notes-6.20.0]] +==== 6.20.0 - 2024-01-10 [float] -===== Pending Deprecations +===== Features -* The log shipping LoggingHandler will be removed in version 7.0.0 of the agent. +* Async support for dbapi2 (starting with psycopg) {pull}1944[#1944] +* Add object name to procedure call spans in dbapi2 {pull}1938[#1938] +* Add support for python 3.10 and 3.11 lambda runtimes +[float] +===== Bug fixes -[[release-notes-6.x]] -=== Python Agent version 6.x +* Fix asyncpg support for 0.29+ {pull}1935[#1935] +* Fix dbapi2 signature extraction to handle square brackets in table name {pull}1947[#1947] + +[float] +===== Pending Deprecations + +* The log shipping LoggingHandler will be removed in version 7.0.0 of the agent. [[release-notes-6.19.0]] ==== 6.19.0 - 2023-10-11 diff --git a/elasticapm/handlers/logging.py b/elasticapm/handlers/logging.py index 0738df16d..4407f0f87 100644 --- a/elasticapm/handlers/logging.py +++ b/elasticapm/handlers/logging.py @@ -47,9 +47,9 @@ class LoggingHandler(logging.Handler): def __init__(self, *args, **kwargs) -> None: warnings.warn( - "The LoggingHandler will be deprecated in v7.0 of the agent. " - "Please use `log_ecs_reformatting` and ship the logs with Elastic " - "Agent or Filebeat instead. " + "The LoggingHandler is deprecated and will be removed in v7.0 of " + "the agent. Please use `log_ecs_reformatting` and ship the logs " + "with Elastic Agent or Filebeat instead. " "https://www.elastic.co/guide/en/apm/agent/python/current/logs.html", PendingDeprecationWarning, ) diff --git a/elasticapm/version.py b/elasticapm/version.py index e817f4ef1..ea64e853b 100644 --- a/elasticapm/version.py +++ b/elasticapm/version.py @@ -28,5 +28,5 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = (6, 19, 0) +__version__ = (6, 20, 0) VERSION = ".".join(map(str, __version__)) From a23afc677b0dbaa812006eb742581dedc314e17c Mon Sep 17 00:00:00 2001 From: Som <111349830+someshwaranM@users.noreply.github.com> Date: Fri, 19 Jan 2024 04:22:05 +0530 Subject: [PATCH 011/409] added a note on centralized agent config support for AWS Lambda (#1954) --- docs/serverless-lambda.asciidoc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/serverless-lambda.asciidoc b/docs/serverless-lambda.asciidoc index 48c091390..732abb2b4 100644 --- a/docs/serverless-lambda.asciidoc +++ b/docs/serverless-lambda.asciidoc @@ -5,6 +5,11 @@ The Python APM Agent can be used with AWS Lambda to monitor the execution of your AWS Lambda functions. +``` +Note: The Centralized Agent Configuration on the Elasticsearch APM currently does NOT support AWS Lambda. +``` + + [float] ==== Prerequisites From 1ad759aec5a1b31cb59324ecf6dc0ba5914994ce Mon Sep 17 00:00:00 2001 From: Gabriel Figueiredo <53541827+gabriel-f-santos@users.noreply.github.com> Date: Mon, 22 Jan 2024 17:32:18 -0300 Subject: [PATCH 012/409] fix: setup middleware without client argument for starlette (#1952) --- .ci/.matrix_exclude.yml | 2 ++ elasticapm/contrib/starlette/__init__.py | 2 +- tests/contrib/asyncio/starlette_tests.py | 8 ++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.ci/.matrix_exclude.yml b/.ci/.matrix_exclude.yml index eaf0fa82b..d0c69e15c 100644 --- a/.ci/.matrix_exclude.yml +++ b/.ci/.matrix_exclude.yml @@ -279,3 +279,5 @@ exclude: FRAMEWORK: pyodbc-newest # error on wheel - VERSION: python-3.12 FRAMEWORK: cassandra-newest # c extension issue + - VERSION: python-3.12 + FRAMEWORK: starlette-newest # waiting for 3.12.2 for this fix: https://github.com/python/cpython/pull/111221 diff --git a/elasticapm/contrib/starlette/__init__.py b/elasticapm/contrib/starlette/__init__.py index a6262ba86..fcc5dc3b4 100644 --- a/elasticapm/contrib/starlette/__init__.py +++ b/elasticapm/contrib/starlette/__init__.py @@ -105,7 +105,7 @@ class ElasticAPM: >>> elasticapm.capture_message('hello, world!') """ - def __init__(self, app: ASGIApp, client: Optional[Client], **kwargs) -> None: + def __init__(self, app: ASGIApp, client: Optional[Client] = None, **kwargs) -> None: """ Args: diff --git a/tests/contrib/asyncio/starlette_tests.py b/tests/contrib/asyncio/starlette_tests.py index 5f4c070bd..fcd7d0dee 100644 --- a/tests/contrib/asyncio/starlette_tests.py +++ b/tests/contrib/asyncio/starlette_tests.py @@ -534,3 +534,11 @@ def test_transaction_active_in_base_exception_handler(app, elasticapm_client): assert exc.transaction_id assert len(elasticapm_client.events[constants.TRANSACTION]) == 1 + + +def test_middleware_without_client_arg(): + with mock.patch.dict("os.environ", {"ELASTIC_APM_SERVICE_NAME": "foo"}): + app = Starlette() + elasticapm = ElasticAPM(app) + + assert elasticapm.client.config.service_name == "foo" From dd1298fe29d34ba72cc858302528b43c1ab899ea Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Fri, 26 Jan 2024 09:38:24 +0100 Subject: [PATCH 013/409] updatecli: dynamic specs (#1957) --- .ci/updatecli.d/update-gherkin-specs.yml | 117 ----------------- .ci/updatecli.d/update-json-specs.yml | 122 ------------------ .ci/updatecli.d/update-specs.yml | 104 --------------- .../updatecli.d/update-gherkin-specs.yml | 84 ++++++++++++ .../updatecli.d/update-json-specs.yml | 84 ++++++++++++ .ci/updatecli/updatecli.d/update-specs.yml | 86 ++++++++++++ .ci/updatecli/values.yml | 14 ++ .github/workflows/updatecli.yml | 3 +- 8 files changed, 270 insertions(+), 344 deletions(-) delete mode 100644 .ci/updatecli.d/update-gherkin-specs.yml delete mode 100644 .ci/updatecli.d/update-json-specs.yml delete mode 100644 .ci/updatecli.d/update-specs.yml create mode 100644 .ci/updatecli/updatecli.d/update-gherkin-specs.yml create mode 100644 .ci/updatecli/updatecli.d/update-json-specs.yml create mode 100644 .ci/updatecli/updatecli.d/update-specs.yml create mode 100644 .ci/updatecli/values.yml diff --git a/.ci/updatecli.d/update-gherkin-specs.yml b/.ci/updatecli.d/update-gherkin-specs.yml deleted file mode 100644 index 8deb269fc..000000000 --- a/.ci/updatecli.d/update-gherkin-specs.yml +++ /dev/null @@ -1,117 +0,0 @@ -name: update-gherkin-specs -pipelineid: update-gherkin-specs -title: synchronize gherkin specs - -scms: - default: - kind: github - spec: - user: '{{ requiredEnv "GIT_USER" }}' - email: '{{ requiredEnv "GIT_EMAIL" }}' - owner: elastic - repository: apm-agent-python - token: '{{ requiredEnv "GITHUB_TOKEN" }}' - username: '{{ requiredEnv "GIT_USER" }}' - branch: main - -sources: - sha: - kind: file - spec: - file: 'https://github.com/elastic/apm/commit/main.patch' - matchpattern: "^From\\s([0-9a-f]{40})\\s" - transformers: - - findsubmatch: - pattern: "[0-9a-f]{40}" - - api_key.feature: - kind: file - spec: - file: https://raw.githubusercontent.com/elastic/apm/main/tests/agents/gherkin-specs/api_key.feature - azure_app_service_metadata.feature: - kind: file - spec: - file: https://raw.githubusercontent.com/elastic/apm/main/tests/agents/gherkin-specs/azure_app_service_metadata.feature - azure_functions_metadata.feature: - kind: file - spec: - file: https://raw.githubusercontent.com/elastic/apm/main/tests/agents/gherkin-specs/azure_functions_metadata.feature - otel_bridge.feature: - kind: file - spec: - file: https://raw.githubusercontent.com/elastic/apm/main/tests/agents/gherkin-specs/otel_bridge.feature - outcome.feature: - kind: file - spec: - file: https://raw.githubusercontent.com/elastic/apm/main/tests/agents/gherkin-specs/outcome.feature - user_agent.feature: - kind: file - spec: - file: https://raw.githubusercontent.com/elastic/apm/main/tests/agents/gherkin-specs/user_agent.feature - -actions: - pr: - kind: "github/pullrequest" - scmid: default - title: '[Automation] Update Gherkin specs' - spec: - automerge: false - draft: false - labels: - - "automation" - description: |- - ### What - APM agent Gherkin specs automatic sync - ### Why - *Changeset* - * https://github.com/elastic/apm/commit/{{ source "sha" }} - -targets: - api_key.feature: - name: api_key.feature - scmid: default - sourceid: api_key.feature - kind: file - spec: - file: tests/bdd/features/api_key.feature - forcecreate: true - azure_app_service_metadata.feature: - name: azure_app_service_metadata.feature - scmid: default - sourceid: azure_app_service_metadata.feature - kind: file - spec: - file: tests/bdd/features/azure_app_service_metadata.feature - forcecreate: true - azure_functions_metadata.feature: - name: azure_functions_metadata.feature - scmid: default - sourceid: azure_functions_metadata.feature - kind: file - spec: - file: tests/bdd/features/azure_functions_metadata.feature - forcecreate: true - otel_bridge.feature: - name: otel_bridge.feature - scmid: default - sourceid: otel_bridge.feature - kind: file - spec: - file: tests/bdd/features/otel_bridge.feature - forcecreate: true - outcome.feature: - name: outcome.feature - scmid: default - sourceid: outcome.feature - kind: file - spec: - file: tests/bdd/features/outcome.feature - forcecreate: true - user_agent.feature: - name: user_agent.feature - scmid: default - sourceid: user_agent.feature - kind: file - spec: - file: tests/bdd/features/user_agent.feature - forcecreate: true diff --git a/.ci/updatecli.d/update-json-specs.yml b/.ci/updatecli.d/update-json-specs.yml deleted file mode 100644 index 13d25c834..000000000 --- a/.ci/updatecli.d/update-json-specs.yml +++ /dev/null @@ -1,122 +0,0 @@ -name: update-json-specs -pipelineid: update-json-specs -title: synchronize json specs - -scms: - default: - kind: github - spec: - user: '{{ requiredEnv "GIT_USER" }}' - email: '{{ requiredEnv "GIT_EMAIL" }}' - owner: elastic - repository: apm-agent-python - token: '{{ requiredEnv "GITHUB_TOKEN" }}' - username: '{{ requiredEnv "GIT_USER" }}' - branch: main - -sources: - sha: - kind: file - spec: - file: 'https://github.com/elastic/apm/commit/main.patch' - matchpattern: "^From\\s([0-9a-f]{40})\\s" - transformers: - - findsubmatch: - pattern: "[0-9a-f]{40}" - - container_metadata_discovery.json: - kind: file - spec: - file: https://raw.githubusercontent.com/elastic/apm/main/tests/agents/json-specs/container_metadata_discovery.json - service_resource_inference.json: - kind: file - spec: - file: https://raw.githubusercontent.com/elastic/apm/main/tests/agents/json-specs/service_resource_inference.json - span_types.json: - kind: file - spec: - file: https://raw.githubusercontent.com/elastic/apm/main/tests/agents/json-specs/span_types.json - sql_signature_examples.json: - kind: file - spec: - file: https://raw.githubusercontent.com/elastic/apm/main/tests/agents/json-specs/sql_signature_examples.json - sql_token_examples.json: - kind: file - spec: - file: https://raw.githubusercontent.com/elastic/apm/main/tests/agents/json-specs/sql_token_examples.json - w3c_distributed_tracing.json: - kind: file - spec: - file: https://raw.githubusercontent.com/elastic/apm/main/tests/agents/json-specs/w3c_distributed_tracing.json - wildcard_matcher_tests.json: - kind: file - spec: - file: https://raw.githubusercontent.com/elastic/apm/main/tests/agents/json-specs/wildcard_matcher_tests.json - -actions: - pr: - kind: "github/pullrequest" - scmid: default - title: '[Automation] Update JSON specs' - spec: - automerge: false - draft: false - labels: - - "automation" - description: |- - ### What - APM agent specs automatic sync - ### Why - *Changeset* - * https://github.com/elastic/apm/commit/{{ source "sha" }} - -targets: - container_metadata_discovery.json: - name: container_metadata_discovery.json - scmid: default - sourceid: container_metadata_discovery.json - kind: file - spec: - file: tests/upstream/json-specs/container_metadata_discovery.json - service_resource_inference.json: - name: service_resource_inference.json - scmid: default - sourceid: service_resource_inference.json - kind: file - spec: - file: tests/upstream/json-specs/service_resource_inference.json - span_types.json: - name: span_types.json - scmid: default - sourceid: span_types.json - kind: file - spec: - file: tests/upstream/json-specs/span_types.json - sql_signature_examples.json: - name: sql_signature_examples.json - scmid: default - sourceid: sql_signature_examples.json - kind: file - spec: - file: tests/upstream/json-specs/sql_signature_examples.json - sql_token_examples.json: - name: sql_token_examples.json - scmid: default - sourceid: sql_token_examples.json - kind: file - spec: - file: tests/upstream/json-specs/sql_token_examples.json - w3c_distributed_tracing.json: - name: w3c_distributed_tracing.json - scmid: default - sourceid: w3c_distributed_tracing.json - kind: file - spec: - file: tests/upstream/json-specs/w3c_distributed_tracing.json - wildcard_matcher_tests.json: - name: wildcard_matcher_tests.json - scmid: default - sourceid: wildcard_matcher_tests.json - kind: file - spec: - file: tests/upstream/json-specs/wildcard_matcher_tests.json diff --git a/.ci/updatecli.d/update-specs.yml b/.ci/updatecli.d/update-specs.yml deleted file mode 100644 index ab3bd34c7..000000000 --- a/.ci/updatecli.d/update-specs.yml +++ /dev/null @@ -1,104 +0,0 @@ -name: update-specs -pipelineid: update-schema-specs -title: synchronize schema specs - -scms: - default: - kind: github - spec: - user: '{{ requiredEnv "GIT_USER" }}' - email: '{{ requiredEnv "GIT_EMAIL" }}' - owner: elastic - repository: apm-agent-python - token: '{{ requiredEnv "GITHUB_TOKEN" }}' - username: '{{ requiredEnv "GIT_USER" }}' - branch: main - -sources: - sha: - kind: file - spec: - file: 'https://github.com/elastic/apm-data/commit/main.patch' - matchpattern: "^From\\s([0-9a-f]{40})\\s" - transformers: - - findsubmatch: - pattern: "[0-9a-f]{40}" - error.json: - kind: file - spec: - file: https://raw.githubusercontent.com/elastic/apm-data/main/input/elasticapm/docs/spec/v2/error.json - metadata.json: - kind: file - spec: - file: https://raw.githubusercontent.com/elastic/apm-data/main/input/elasticapm/docs/spec/v2/metadata.json - metricset.json: - kind: file - spec: - file: https://raw.githubusercontent.com/elastic/apm-data/main/input/elasticapm/docs/spec/v2/metricset.json - span.json: - kind: file - spec: - file: https://raw.githubusercontent.com/elastic/apm-data/main/input/elasticapm/docs/spec/v2/span.json - transaction.json: - kind: file - spec: - file: https://raw.githubusercontent.com/elastic/apm-data/main/input/elasticapm/docs/spec/v2/transaction.json - -actions: - pr: - kind: "github/pullrequest" - scmid: default - title: '[Automation] Update JSON schema specs' - spec: - automerge: false - draft: false - labels: - - "automation" - description: |- - ### What - APM agent json schema automatic sync - ### Why - *Changeset* - * https://github.com/elastic/apm-data/commit/{{ source "sha" }} - -targets: - error.json: - name: error.json - scmid: default - sourceid: error.json - kind: file - spec: - file: tests/upstream/json-specs/error.json - forcecreate: true - metadata.json: - name: metadata.json - scmid: default - sourceid: metadata.json - kind: file - spec: - file: tests/upstream/json-specs/metadata.json - forcecreate: true - metricset.json: - name: metricset.json - scmid: default - sourceid: metricset.json - kind: file - spec: - file: tests/upstream/json-specs/metricset.json - forcecreate: true - span.json: - name: span.json - scmid: default - sourceid: span.json - kind: file - spec: - file: tests/upstream/json-specs/span.json - forcecreate: true - transaction.json: - name: transaction.json - scmid: default - sourceid: transaction.json - kind: file - spec: - file: tests/upstream/json-specs/transaction.json - forcecreate: true diff --git a/.ci/updatecli/updatecli.d/update-gherkin-specs.yml b/.ci/updatecli/updatecli.d/update-gherkin-specs.yml new file mode 100644 index 000000000..f12ece861 --- /dev/null +++ b/.ci/updatecli/updatecli.d/update-gherkin-specs.yml @@ -0,0 +1,84 @@ +name: update-gherkin-specs +pipelineid: update-gherkin-specs + +scms: + default: + kind: github + spec: + user: '{{ requiredEnv "GIT_USER" }}' + email: '{{ requiredEnv "GIT_EMAIL" }}' + owner: "{{ .github.owner }}" + repository: "{{ .github.repository }}" + token: '{{ requiredEnv "GITHUB_TOKEN" }}' + username: '{{ requiredEnv "GIT_USER" }}' + branch: "{{ .github.branch }}" + apm: + kind: github + spec: + user: '{{ requiredEnv "GIT_USER" }}' + email: '{{ requiredEnv "GIT_EMAIL" }}' + owner: "{{ .github.owner }}" + repository: "{{ .github.apm_repository }}" + token: '{{ requiredEnv "GITHUB_TOKEN" }}' + username: '{{ requiredEnv "GIT_USER" }}' + branch: "{{ .github.branch }}" + +sources: + sha: + kind: file + spec: + file: 'https://github.com/{{ .github.owner }}/{{ .github.apm_repository }}/commit/{{ .github.branch }}.patch' + matchpattern: "^From\\s([0-9a-f]{40})\\s" + transformers: + - findsubmatch: + pattern: "[0-9a-f]{40}" + pull_request: + kind: shell + dependson: + - sha + spec: + command: gh api /repos/{{ .github.owner }}/{{ .github.apm_repository }}/commits/{{ source "sha" }}/pulls --jq '.[].html_url' + environments: + - name: GITHUB_TOKEN + - name: PATH + agents-gherkin-specs-tarball: + kind: shell + scmid: apm + dependson: + - sha + spec: + command: tar cvzf {{ requiredEnv "GITHUB_WORKSPACE" }}/gherkin-specs.tgz . + environments: + - name: PATH + workdir: "{{ .specs.apm_gherkin_path }}" + +actions: + pr: + kind: "github/pullrequest" + scmid: default + spec: + automerge: false + draft: false + labels: + - "automation" + description: |- + ### What + APM agent Gherkin specs automatic sync + + ### Why + *Changeset* + * {{ source "pull_request" }} + * https://github.com/elastic/apm/commit/{{ source "sha" }} + title: '[Automation] Update Gherkin specs' + +targets: + agent-gherkin-specs: + name: APM agent gherkin specs {{ source "sha" }} + scmid: default + disablesourceinput: true + kind: shell + spec: + # git diff helps to print what it changed, If it is empty, then updatecli report a success with no changes applied. + # See https://www.updatecli.io/docs/plugins/resource/shell/#_shell_target + command: 'tar -xzf {{ requiredEnv "GITHUB_WORKSPACE" }}/gherkin-specs.tgz && git --no-pager diff' + workdir: "{{ .apm_agent.gherkin_specs_path }}" diff --git a/.ci/updatecli/updatecli.d/update-json-specs.yml b/.ci/updatecli/updatecli.d/update-json-specs.yml new file mode 100644 index 000000000..e05aaecdb --- /dev/null +++ b/.ci/updatecli/updatecli.d/update-json-specs.yml @@ -0,0 +1,84 @@ +name: update-json-specs +pipelineid: update-json-specs + +scms: + default: + kind: github + spec: + user: '{{ requiredEnv "GIT_USER" }}' + email: '{{ requiredEnv "GIT_EMAIL" }}' + owner: "{{ .github.owner }}" + repository: "{{ .github.repository }}" + token: '{{ requiredEnv "GITHUB_TOKEN" }}' + username: '{{ requiredEnv "GIT_USER" }}' + branch: "{{ .github.branch }}" + apm: + kind: github + spec: + user: '{{ requiredEnv "GIT_USER" }}' + email: '{{ requiredEnv "GIT_EMAIL" }}' + owner: "{{ .github.owner }}" + repository: "{{ .github.apm_repository }}" + token: '{{ requiredEnv "GITHUB_TOKEN" }}' + username: '{{ requiredEnv "GIT_USER" }}' + branch: "{{ .github.branch }}" + +sources: + sha: + kind: file + spec: + file: 'https://github.com/{{ .github.owner }}/{{ .github.apm_repository }}/commit/{{ .github.branch }}.patch' + matchpattern: "^From\\s([0-9a-f]{40})\\s" + transformers: + - findsubmatch: + pattern: "[0-9a-f]{40}" + pull_request: + kind: shell + dependson: + - sha + spec: + command: gh api /repos/{{ .github.owner }}/{{ .github.apm_repository }}/commits/{{ source "sha" }}/pulls --jq '.[].html_url' + environments: + - name: GITHUB_TOKEN + - name: PATH + agents-json-specs-tarball: + kind: shell + scmid: apm + dependson: + - sha + spec: + command: tar cvzf {{ requiredEnv "GITHUB_WORKSPACE" }}/json-specs.tgz . + environments: + - name: PATH + workdir: "{{ .specs.apm_json_path }}" + +actions: + pr: + kind: "github/pullrequest" + scmid: default + spec: + automerge: false + draft: false + labels: + - "automation" + description: |- + ### What + APM agent specs automatic sync + + ### Why + *Changeset* + * {{ source "pull_request" }} + * https://github.com/{{ .github.owner }}/{{ .github.apm_repository }}/commit/{{ source "sha" }} + title: '[Automation] Update JSON specs' + +targets: + agent-json-specs: + name: APM agent json specs {{ source "sha" }} + scmid: default + disablesourceinput: true + kind: shell + spec: + # git diff helps to print what it changed, If it is empty, then updatecli report a success with no changes applied. + # See https://www.updatecli.io/docs/plugins/resource/shell/#_shell_target + command: 'tar -xzf {{ requiredEnv "GITHUB_WORKSPACE" }}/json-specs.tgz && git --no-pager diff' + workdir: "{{ .apm_agent.json_specs_path }}" diff --git a/.ci/updatecli/updatecli.d/update-specs.yml b/.ci/updatecli/updatecli.d/update-specs.yml new file mode 100644 index 000000000..554140da2 --- /dev/null +++ b/.ci/updatecli/updatecli.d/update-specs.yml @@ -0,0 +1,86 @@ +name: update-specs +pipelineid: update-schema-specs + +scms: + default: + kind: github + spec: + user: '{{ requiredEnv "GIT_USER" }}' + email: '{{ requiredEnv "GIT_EMAIL" }}' + owner: "{{ .github.owner }}" + repository: "{{ .github.repository }}" + token: '{{ requiredEnv "GITHUB_TOKEN" }}' + username: '{{ requiredEnv "GIT_USER" }}' + branch: "{{ .github.branch }}" + + apm-data: + kind: github + spec: + user: '{{ requiredEnv "GIT_USER" }}' + email: '{{ requiredEnv "GIT_EMAIL" }}' + owner: "{{ .github.owner }}" + repository: "{{ .github.apm_data_repository }}" + token: '{{ requiredEnv "GITHUB_TOKEN" }}' + username: '{{ requiredEnv "GIT_USER" }}' + branch: "{{ .github.branch }}" + +sources: + sha: + kind: file + spec: + file: 'https://github.com/{{ .github.owner }}/{{ .github.apm_data_repository }}/commit/{{ .github.branch }}.patch' + matchpattern: "^From\\s([0-9a-f]{40})\\s" + transformers: + - findsubmatch: + pattern: "[0-9a-f]{40}" + pull_request: + kind: shell + dependson: + - sha + spec: + command: gh api /repos/{{ .github.owner }}/{{ .github.apm_data_repository }}/commits/{{ source "sha" }}/pulls --jq '.[].html_url' + environments: + - name: GITHUB_TOKEN + - name: PATH + agent-specs-tarball: + kind: shell + scmid: apm-data + dependson: + - sha + spec: + command: tar cvzf {{ requiredEnv "GITHUB_WORKSPACE" }}/json-schema.tgz . + environments: + - name: PATH + workdir: "{{ .specs.apm_data_path }}" + +actions: + pr: + kind: "github/pullrequest" + scmid: default + sourceid: sha + spec: + automerge: false + draft: false + labels: + - "automation" + description: |- + ### What + APM agent json server schema automatic sync + + ### Why + *Changeset* + * {{ source "pull_request" }} + * https://github.com/{{ .github.owner }}/{{ .github.apm_data_repository }}/commit/{{ source "sha" }} + title: '[Automation] Update JSON server schema specs' + +targets: + agent-json-schema: + name: APM agent json server schema {{ source "sha" }} + scmid: default + disablesourceinput: true + kind: shell + spec: + # git diff helps to print what it changed, If it is empty, then updatecli report a success with no changes applied. + # See https://www.updatecli.io/docs/plugins/resource/shell/#_shell_target + command: 'tar -xzf {{ requiredEnv "GITHUB_WORKSPACE" }}/json-schema.tgz && git --no-pager diff' + workdir: "{{ .apm_agent.server_schema_specs_path }}" diff --git a/.ci/updatecli/values.yml b/.ci/updatecli/values.yml new file mode 100644 index 000000000..b0b58d73e --- /dev/null +++ b/.ci/updatecli/values.yml @@ -0,0 +1,14 @@ +github: + owner: "elastic" + repository: "apm-agent-python" + apm_repository: "apm" + apm_data_repository: "apm-data" + branch: "main" +specs: + apm_data_path: "input/elasticapm/docs/spec/v2" + apm_json_path: "tests/agents/json-specs" + apm_gherkin_path: "tests/agents/gherkin-specs" +apm_agent: + gherkin_specs_path: "tests/bdd/features" + json_specs_path: "tests/upstream/json-specs" + server_schema_specs_path: "tests/upstream/json-specs" \ No newline at end of file diff --git a/.github/workflows/updatecli.yml b/.github/workflows/updatecli.yml index 2101ec798..22598200b 100644 --- a/.github/workflows/updatecli.yml +++ b/.github/workflows/updatecli.yml @@ -18,7 +18,8 @@ jobs: vaultUrl: ${{ secrets.VAULT_ADDR }} vaultRoleId: ${{ secrets.VAULT_ROLE_ID }} vaultSecretId: ${{ secrets.VAULT_SECRET_ID }} - pipeline: .ci/updatecli.d + pipeline: .ci/updatecli/updatecli.d + values: .ci/updatecli/values.yml - if: failure() uses: elastic/apm-pipeline-library/.github/actions/notify-build-status@current with: From 8a691909417d7a510621ddd983ad9fb90e49897f Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Mon, 29 Jan 2024 08:40:37 +0100 Subject: [PATCH 014/409] Add ability to run full matrix manually and on push (#1958) * Add ability to run full matrix manually and on releases * Fix typo * Run full matrix on push instead in the release * Re-add workflow_call trigger again --- .github/workflows/test.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 363dec4a6..0246189b0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,6 +14,12 @@ on: - "**/*.asciidoc" schedule: - cron: "0 2 * * *" + workflow_dispatch: + inputs: + full-matrix: + description: "Run the full matrix" + required: true + type: boolean jobs: build-distribution: @@ -31,9 +37,9 @@ jobs: uses: elastic/apm-pipeline-library/.github/actions/version-framework@current with: # Use .ci/.matrix_python_full.yml if it's a scheduled workflow, otherwise use .ci/.matrix_python.yml - versionsFile: .ci/.matrix_python${{ github.event_name == 'schedule' && '_full' || '' }}.yml + versionsFile: .ci/.matrix_python${{ (github.event_name == 'schedule' || github.event_name == 'push' || inputs.full-matrix) && '_full' || '' }}.yml # Use .ci/.matrix_framework_full.yml if it's a scheduled workflow, otherwise use .ci/.matrix_framework.yml - frameworksFile: .ci/.matrix_framework${{ github.event_name == 'schedule' && '_full' || '' }}.yml + frameworksFile: .ci/.matrix_framework${{ (github.event_name == 'schedule' || github.event_name == 'push' || inputs.full-matrix) && '_full' || '' }}.yml excludedFile: .ci/.matrix_exclude.yml - name: Split matrix shell: python From 9d94ab38fcfb87f661f698ff071d4808e342e37d Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Mon, 29 Jan 2024 21:31:03 +0100 Subject: [PATCH 015/409] Add notification if scheduled job or main fails (#1959) * Add notification if scheduled job fails * Update .github/workflows/test.yml --- .github/workflows/test.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0246189b0..7fab3fb26 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -148,7 +148,19 @@ jobs: - chunks-3 - windows steps: - - run: test $(echo '${{ toJSON(needs) }}' | jq -s 'map(.[].result) | all(.=="success")') = 'true' + - id: check + uses: elastic/apm-pipeline-library/.github/actions/check-dependent-jobs@current + with: + needs: ${{ toJSON(needs) }} + - run: ${{ steps.check.outputs.isSuccess }} + - if: failure() && (github.event_name == 'schedule' || github.event_name == 'push') + uses: elastic/apm-pipeline-library/.github/actions/notify-build-status@current + with: + status: ${{ steps.check.outputs.status }} + vaultUrl: ${{ secrets.VAULT_ADDR }} + vaultRoleId: ${{ secrets.VAULT_ROLE_ID }} + vaultSecretId: ${{ secrets.VAULT_SECRET_ID }} + slackChannel: "#apm-agent-python" coverage: name: Combine & check coverage. From 97aaab47694bd38f0163f8ffcfa1dd154f694eed Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Mon, 29 Jan 2024 21:32:04 +0100 Subject: [PATCH 016/409] Add PR review comment command (#1960) * Add PR review comment command * Add to README.md * Update .github/workflows/README.md Co-authored-by: Victor Martinez * Update .github/workflows/test.yml --------- Co-authored-by: Victor Martinez --- .github/workflows/README.md | 1 + .github/workflows/matrix-command.yml | 49 ++++++++++++++++++++++++++++ .github/workflows/test.yml | 23 ++++++++++--- 3 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/matrix-command.yml diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 3cdfe70f0..c224d62b8 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -39,6 +39,7 @@ Once a PR has been opened then there are two different ways you can trigger buil 1. Commit based 1. UI based, any Elasticians can force a build through the GitHub UI +1. PR review comment-based, any Elastic employees can force a full matrix test run through a PR review comment with the following syntax: `/test matrix`. #### Branches diff --git a/.github/workflows/matrix-command.yml b/.github/workflows/matrix-command.yml new file mode 100644 index 000000000..f2c32658f --- /dev/null +++ b/.github/workflows/matrix-command.yml @@ -0,0 +1,49 @@ +name: matrix-command + +on: + pull_request_review: + types: + - submitted + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +permissions: + contents: read + +jobs: + command-validation: + if: startsWith(github.event.review.body, '/test matrix') + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + pull-requests: write + steps: + - name: Is comment allowed? + uses: actions/github-script@v7 + with: + script: | + const actorPermission = (await github.rest.repos.getCollaboratorPermissionLevel({ + ...context.repo, + username: context.actor + })).data.permission + const isPermitted = ['write', 'admin'].includes(actorPermission) + if (!isPermitted) { + const errorMessage = 'Only users with write permission to the repository can run GitHub commands' + await github.rest.issues.createComment({ + ...context.repo, + issue_number: context.issue.number, + body: errorMessage, + }) + core.setFailed(errorMessage) + return + } + + test: + needs: + - command-validation + uses: ./.github/workflows/test.yml + with: + full-matrix: true + ref: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7fab3fb26..b57b8a023 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,7 +1,16 @@ name: test # The name must be the same as in test-docs.yml on: - workflow_call: ~ + workflow_call: + inputs: + full-matrix: + description: "Run the full matrix" + required: true + type: boolean + ref: + description: "The git ref of elastic/apm-agent-python to run test workflow from." + required: false + type: string pull_request: paths-ignore: - "**/*.md" @@ -32,7 +41,9 @@ jobs: data: ${{ steps.split.outputs.data }} chunks: ${{ steps.split.outputs.chunks }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref || github.ref }} - id: generate uses: elastic/apm-pipeline-library/.github/actions/version-framework@current with: @@ -113,7 +124,9 @@ jobs: FRAMEWORK: ${{ matrix.framework }} ASYNCIO: ${{ matrix.asyncio }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref || github.ref }} - uses: actions/setup-python@v4 with: python-version: ${{ matrix.version }} @@ -168,7 +181,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref || github.ref }} - uses: actions/setup-python@v4 with: From 7e04856e380f4d65f140864ec8f969078c5d54e5 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Tue, 30 Jan 2024 14:20:51 +0100 Subject: [PATCH 017/409] github-action: use wildcards for discovering all the workflows (#1961) --- .github/workflows/opentelemetry.yml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/opentelemetry.yml b/.github/workflows/opentelemetry.yml index ea858e655..84a6209ff 100644 --- a/.github/workflows/opentelemetry.yml +++ b/.github/workflows/opentelemetry.yml @@ -1,18 +1,16 @@ --- +# Look up results at https://ela.st/oblt-ci-cd-stats. +# There will be one service per GitHub repository, including the org name, and one Transaction per Workflow. name: OpenTelemetry Export Trace on: workflow_run: - workflows: - - pre-commit - - test - - test-reporter - - snapshoty - - release - - packages - - updatecli + workflows: [ "*" ] types: [completed] +permissions: + contents: read + jobs: otel-export-trace: runs-on: ubuntu-latest From 509f19478044c311193187dfe28071192001e063 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 10:03:45 -0700 Subject: [PATCH 018/409] Bump certifi from 2023.11.17 to 2024.2.2 in /dev-utils (#1963) Bumps [certifi](https://github.com/certifi/python-certifi) from 2023.11.17 to 2024.2.2. - [Commits](https://github.com/certifi/python-certifi/compare/2023.11.17...2024.02.02) --- updated-dependencies: - dependency-name: certifi dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dev-utils/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-utils/requirements.txt b/dev-utils/requirements.txt index 21258b2ad..59008afc2 100644 --- a/dev-utils/requirements.txt +++ b/dev-utils/requirements.txt @@ -1,4 +1,4 @@ # These are the pinned requirements for the lambda layer/docker image -certifi==2023.11.17 +certifi==2024.2.2 urllib3==1.26.18 wrapt==1.14.1 From c7bde0ae2cb509f84d4605400917229828b2dc2e Mon Sep 17 00:00:00 2001 From: Colton Myers Date: Mon, 5 Feb 2024 11:33:38 -0700 Subject: [PATCH 019/409] Fixup test suite (#1964) --- .ci/.matrix_exclude.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.ci/.matrix_exclude.yml b/.ci/.matrix_exclude.yml index d0c69e15c..d7d05f2af 100644 --- a/.ci/.matrix_exclude.yml +++ b/.ci/.matrix_exclude.yml @@ -184,8 +184,12 @@ exclude: # asyncpg - VERSION: pypy-3 FRAMEWORK: asyncpg-newest + - VERSION: pypy-3 + FRAMEWORK: asyncpg-0.28 - VERSION: python-3.6 FRAMEWORK: asyncpg-newest + - VERSION: python-3.6 + FRAMEWORK: asyncpg-0.28 # sanic - VERSION: pypy-3 FRAMEWORK: sanic-newest @@ -281,3 +285,7 @@ exclude: FRAMEWORK: cassandra-newest # c extension issue - VERSION: python-3.12 FRAMEWORK: starlette-newest # waiting for 3.12.2 for this fix: https://github.com/python/cpython/pull/111221 + - VERSION: python-3.12 + FRAMEWORK: starlette-0.14 # waiting for 3.12.2 for this fix: https://github.com/python/cpython/pull/111221 + - VERSION: python-3.12 + FRAMEWORK: starlette-0.13 # waiting for 3.12.2 for this fix: https://github.com/python/cpython/pull/111221 From 460f1410e5fc7213c34abc75fd1e599e2262f115 Mon Sep 17 00:00:00 2001 From: psydok <47638600+psydok@users.noreply.github.com> Date: Tue, 13 Feb 2024 06:00:12 +0500 Subject: [PATCH 020/409] Fix blocking of gRPC stream-to-stream requests when elasticapm is enabled (#1967) * Fix gprc support with streaming requests (#1966) * Update version.py --- .../contrib/grpc/async_server_interceptor.py | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/elasticapm/contrib/grpc/async_server_interceptor.py b/elasticapm/contrib/grpc/async_server_interceptor.py index 5af0c1372..e7c9b659f 100644 --- a/elasticapm/contrib/grpc/async_server_interceptor.py +++ b/elasticapm/contrib/grpc/async_server_interceptor.py @@ -33,20 +33,18 @@ import grpc import elasticapm -from elasticapm.contrib.grpc.server_interceptor import _ServicerContextWrapper, _wrap_rpc_behavior, get_trace_parent +from elasticapm.contrib.grpc.server_interceptor import _ServicerContextWrapper, get_trace_parent class _AsyncServerInterceptor(grpc.aio.ServerInterceptor): async def intercept_service(self, continuation, handler_call_details): - def transaction_wrapper(behavior, request_streaming, response_streaming): - async def _interceptor(request_or_iterator, context): - if request_streaming or response_streaming: # only unary-unary is supported - return behavior(request_or_iterator, context) + def wrap_unary_unary(behavior): + async def _interceptor(request, context): tp = get_trace_parent(handler_call_details) client = elasticapm.get_client() transaction = client.begin_transaction("request", trace_parent=tp) try: - result = behavior(request_or_iterator, _ServicerContextWrapper(context, transaction)) + result = behavior(request, _ServicerContextWrapper(context, transaction)) # This is so we can support both sync and async rpc functions if inspect.isawaitable(result): @@ -65,4 +63,12 @@ async def _interceptor(request_or_iterator, context): return _interceptor - return _wrap_rpc_behavior(await continuation(handler_call_details), transaction_wrapper) + handler = await continuation(handler_call_details) + if handler.request_streaming or handler.response_streaming: + return handler + + return grpc.unary_unary_rpc_method_handler( + wrap_unary_unary(handler.unary_unary), + request_deserializer=handler.request_deserializer, + response_serializer=handler.response_serializer, + ) From e0b3deb0eb17f94ad45115b41b3f23894e3e237c Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Thu, 15 Feb 2024 12:14:29 +0100 Subject: [PATCH 021/409] chore: enable dependabot version updates of github-actions yaml files (#1930) --- .github/dependabot.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index eb8155b22..eb1cff95b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,8 +8,24 @@ updates: # Check for updates once a week schedule: interval: "weekly" + day: "sunday" + time: "22:00" reviewers: - "elastic/apm-agent-python" ignore: - dependency-name: "urllib3" # ignore until lambda runtimes use OpenSSL 1.1.1+ versions: [">=2.0.0"] + + # GitHub actions + - package-ecosystem: "github-actions" + directory: "/" + reviewers: + - "elastic/observablt-ci" + schedule: + interval: "weekly" + day: "sunday" + time: "22:00" + groups: + github-actions: + patterns: + - "*" From a1bde22a8c4652e9c3d7e4b4a1a0eaf050563e9c Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 16 Feb 2024 12:11:26 +0100 Subject: [PATCH 022/409] contrib/starlette: take into account body reading time (#1970) Create the transaction always before the body is read to avoid discrepancies between cases where the capture_body config on or off. Fix #1948 --- elasticapm/contrib/starlette/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/elasticapm/contrib/starlette/__init__.py b/elasticapm/contrib/starlette/__init__.py index fcc5dc3b4..ad26d7a0a 100644 --- a/elasticapm/contrib/starlette/__init__.py +++ b/elasticapm/contrib/starlette/__init__.py @@ -36,6 +36,7 @@ from typing import Dict, Optional import starlette +from starlette.datastructures import Headers from starlette.requests import Request from starlette.routing import Match, Mount from starlette.types import ASGIApp, Message @@ -151,6 +152,10 @@ async def wrapped_send(message) -> None: _mocked_receive = None _request_receive = None + # begin the transaction before capturing the body to get that time accounted + trace_parent = TraceParent.from_headers(dict(Headers(scope=scope))) + self.client.begin_transaction("request", trace_parent=trace_parent) + if self.client.config.capture_body != "off": # When we consume the body from receive, we replace the streaming @@ -234,9 +239,6 @@ async def _request_started(self, request: Request) -> None: if self.client.config.capture_body != "off": await get_body(request) - trace_parent = TraceParent.from_headers(dict(request.headers)) - self.client.begin_transaction("request", trace_parent=trace_parent) - await set_context(lambda: get_data_from_request(request, self.client.config, constants.TRANSACTION), "request") transaction_name = self.get_route_name(request) or request.url.path elasticapm.set_transaction_name("{} {}".format(request.method, transaction_name), override=False) From 6be16a40aa19bd64dc09ae7c72ebc3eafe0964d9 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 16 Feb 2024 12:11:37 +0100 Subject: [PATCH 023/409] tests: make urlllib3 transport tests more robust against local env (#1969) When mocking os.environ pass clear=True to avoid getting host configurations. Fix #1968 --- tests/transports/test_urllib3.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/transports/test_urllib3.py b/tests/transports/test_urllib3.py index 42a21c1e9..b24408e54 100644 --- a/tests/transports/test_urllib3.py +++ b/tests/transports/test_urllib3.py @@ -115,38 +115,46 @@ def test_generic_error(mock_urlopen, elasticapm_client): def test_http_proxy_environment_variable(elasticapm_client): - with mock.patch.dict("os.environ", {"HTTP_PROXY": "http://example.com"}): + with mock.patch.dict("os.environ", {"HTTP_PROXY": "http://example.com"}, clear=True): transport = Transport("http://localhost:9999", client=elasticapm_client) assert isinstance(transport.http, urllib3.ProxyManager) def test_https_proxy_environment_variable(elasticapm_client): - with mock.patch.dict("os.environ", {"HTTPS_PROXY": "https://example.com"}): + with mock.patch.dict( + "os.environ", + { + "HTTPS_PROXY": "https://example.com", + }, + clear=True, + ): transport = Transport("http://localhost:9999", client=elasticapm_client) assert isinstance(transport.http, urllib3.poolmanager.ProxyManager) def test_https_proxy_environment_variable_is_preferred(elasticapm_client): - with mock.patch.dict("os.environ", {"https_proxy": "https://example.com", "HTTP_PROXY": "http://example.com"}): + with mock.patch.dict( + "os.environ", {"https_proxy": "https://example.com", "HTTP_PROXY": "http://example.com"}, clear=True + ): transport = Transport("http://localhost:9999", client=elasticapm_client) assert isinstance(transport.http, urllib3.poolmanager.ProxyManager) assert transport.http.proxy.scheme == "https" def test_no_proxy_star(elasticapm_client): - with mock.patch.dict("os.environ", {"HTTPS_PROXY": "https://example.com", "NO_PROXY": "*"}): + with mock.patch.dict("os.environ", {"HTTPS_PROXY": "https://example.com", "NO_PROXY": "*"}, clear=True): transport = Transport("http://localhost:9999", client=elasticapm_client) assert not isinstance(transport.http, urllib3.poolmanager.ProxyManager) def test_no_proxy_host(elasticapm_client): - with mock.patch.dict("os.environ", {"HTTPS_PROXY": "https://example.com", "NO_PROXY": "localhost"}): + with mock.patch.dict("os.environ", {"HTTPS_PROXY": "https://example.com", "NO_PROXY": "localhost"}, clear=True): transport = Transport("http://localhost:9999", client=elasticapm_client) assert not isinstance(transport.http, urllib3.poolmanager.ProxyManager) def test_no_proxy_all(elasticapm_client): - with mock.patch.dict("os.environ", {"HTTPS_PROXY": "https://example.com", "NO_PROXY": "*"}): + with mock.patch.dict("os.environ", {"HTTPS_PROXY": "https://example.com", "NO_PROXY": "*"}, clear=True): transport = Transport("http://localhost:9999", client=elasticapm_client) assert not isinstance(transport.http, urllib3.poolmanager.ProxyManager) From bdc0eaccfbfefb94975d4e630790c154d15e5779 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 16:36:30 +0100 Subject: [PATCH 024/409] Bump the github-actions group with 9 updates (#1971) * Bump the github-actions group with 9 updates | Package | From | To | | --- | --- | --- | | [actions/checkout](https://github.com/actions/checkout) | `3` | `4` | | [actions/setup-python](https://github.com/actions/setup-python) | `3` | `5` | | [actions/upload-artifact](https://github.com/actions/upload-artifact) | `3` | `4` | | [elastic/get-user-teams-membership](https://github.com/elastic/get-user-teams-membership) | `1.0.4` | `1.1.0` | | [pre-commit/action](https://github.com/pre-commit/action) | `3.0.0` | `3.0.1` | | [actions/download-artifact](https://github.com/actions/download-artifact) | `3` | `4` | | [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) | `1.8.7` | `1.8.11` | | [hashicorp/vault-action](https://github.com/hashicorp/vault-action) | `2.7.2` | `2.8.0` | | [geekyeggo/delete-artifact](https://github.com/geekyeggo/delete-artifact) | `2.0.0` | `4.1.0` | --- .github/workflows/build-distribution.yml | 6 +++--- .github/workflows/labeler.yml | 2 +- .github/workflows/packages.yml | 6 +++--- .github/workflows/pre-commit.yml | 6 +++--- .github/workflows/release.yml | 22 +++++++++++----------- .github/workflows/run-matrix.yml | 2 +- .github/workflows/snapshoty.yml | 4 ++-- .github/workflows/test.yml | 8 ++++---- .github/workflows/updatecli.yml | 2 +- 9 files changed, 29 insertions(+), 29 deletions(-) diff --git a/.github/workflows/build-distribution.yml b/.github/workflows/build-distribution.yml index 986632acd..8b728c3bf 100644 --- a/.github/workflows/build-distribution.yml +++ b/.github/workflows/build-distribution.yml @@ -7,13 +7,13 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: "3.10" - name: Build lambda layer zip run: ./dev-utils/make-distribution.sh - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: build-distribution path: ./build/ diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index df219658c..377caaa5c 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -17,7 +17,7 @@ jobs: configuration-path: .github/labeler-config.yml enable-versioned-regex: 0 - name: Check team membership for user - uses: elastic/get-user-teams-membership@v1.0.4 + uses: elastic/get-user-teams-membership@1.1.0 id: checkUserMember with: username: ${{ github.actor }} diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 148110c7f..af485c455 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -17,8 +17,8 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: "3.10" - name: Install wheel @@ -28,7 +28,7 @@ jobs: - name: Building source distribution run: python setup.py sdist - name: Upload Packages - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: packages path: | diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index c2f7e71fc..65947d33b 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -9,6 +9,6 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v3 - - uses: pre-commit/action@v3.0.0 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + - uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 03a77ce47..0e297bb95 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,13 +24,13 @@ jobs: permissions: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 with: name: packages path: dist - name: Upload - uses: pypa/gh-action-pypi-publish@f5622bde02b04381239da3573277701ceca8f6a0 + uses: pypa/gh-action-pypi-publish@2f6f737ca5f74c637829c0f5c3acd0e29ea5e8bf with: repository-url: https://upload.pypi.org/legacy/ @@ -42,8 +42,8 @@ jobs: - build-distribution runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: hashicorp/vault-action@v2.7.2 + - uses: actions/checkout@v4 + - uses: hashicorp/vault-action@v2.8.0 with: url: ${{ secrets.VAULT_ADDR }} method: approle @@ -52,7 +52,7 @@ jobs: secrets: | secret/observability-team/ci/service-account/apm-agent-python access_key_id | AWS_ACCESS_KEY_ID ; secret/observability-team/ci/service-account/apm-agent-python secret_access_key | AWS_SECRET_ACCESS_KEY - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: build-distribution path: ./build @@ -63,7 +63,7 @@ jobs: VERSION=${VERSION//./-} ELASTIC_LAYER_NAME="elastic-apm-python-${VERSION}" .ci/publish-aws.sh - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: arn-file path: ".arn-file.md" @@ -74,7 +74,7 @@ jobs: - build-distribution runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: elastic/apm-pipeline-library/.github/actions/docker-login@current with: registry: docker.elastic.co @@ -82,7 +82,7 @@ jobs: url: ${{ secrets.VAULT_ADDR }} roleId: ${{ secrets.VAULT_ROLE_ID }} secretId: ${{ secrets.VAULT_SECRET_ID }} - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: build-distribution path: ./build @@ -115,8 +115,8 @@ jobs: - publish-lambda-layers runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 with: name: arn-file - name: Create GitHub Draft Release diff --git a/.github/workflows/run-matrix.yml b/.github/workflows/run-matrix.yml index 811f68dd9..827212527 100644 --- a/.github/workflows/run-matrix.yml +++ b/.github/workflows/run-matrix.yml @@ -18,7 +18,7 @@ jobs: matrix: include: ${{ fromJSON(inputs.include) }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Run tests run: ./tests/scripts/docker/run_tests.sh ${{ matrix.version }} ${{ matrix.framework }} env: diff --git a/.github/workflows/snapshoty.yml b/.github/workflows/snapshoty.yml index 3f91e2213..49d1b3423 100644 --- a/.github/workflows/snapshoty.yml +++ b/.github/workflows/snapshoty.yml @@ -21,8 +21,8 @@ jobs: - packages runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 with: name: packages path: dist diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b57b8a023..4638ab5d3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -127,7 +127,7 @@ jobs: - uses: actions/checkout@v4 with: ref: ${{ inputs.ref || github.ref }} - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.version }} cache: pip @@ -185,7 +185,7 @@ jobs: with: ref: ${{ inputs.ref || github.ref }} - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: # Use latest Python, so it understands all syntax. python-version: 3.11 @@ -208,10 +208,10 @@ jobs: python -Im coverage report --fail-under=84 - name: Upload HTML report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: html-coverage-report path: htmlcov - - uses: geekyeggo/delete-artifact@54ab544f12cdb7b71613a16a2b5a37a9ade990af + - uses: geekyeggo/delete-artifact@65041433121f7239077fa20be14c0690f70569de with: name: coverage-reports diff --git a/.github/workflows/updatecli.yml b/.github/workflows/updatecli.yml index 22598200b..4fc00bd71 100644 --- a/.github/workflows/updatecli.yml +++ b/.github/workflows/updatecli.yml @@ -12,7 +12,7 @@ jobs: bump: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: elastic/apm-pipeline-library/.github/actions/updatecli@current with: vaultUrl: ${{ secrets.VAULT_ADDR }} From 58c916bf4ab91cba27a19acb1a0b669a22a1afe2 Mon Sep 17 00:00:00 2001 From: Mario Candela Date: Wed, 21 Feb 2024 23:17:35 +0100 Subject: [PATCH 025/409] Fix:Update starlette.asciidoc, incorrect middleware order (#1956) * Update starlette.asciidoc Fix typo * Update starlette.asciidoc --------- Co-authored-by: Colton Myers --- docs/starlette.asciidoc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/starlette.asciidoc b/docs/starlette.asciidoc index 77aaca0d4..941bf6d7a 100644 --- a/docs/starlette.asciidoc +++ b/docs/starlette.asciidoc @@ -42,10 +42,12 @@ app = Starlette() app.add_middleware(ElasticAPM) ---- -WARNING: If you are using any `BaseHTTPMiddleware` middleware, you must add them -*before* the ElasticAPM middleware. This is because `BaseHTTPMiddleware` breaks -`contextvar` propagation, as noted -https://www.starlette.io/middleware/#limitations[here]. +WARNING: `BaseHTTPMiddleware` breaks `contextvar` propagation, as noted +https://www.starlette.io/middleware/#limitations[here]. This means the +ElasticAPM middleware must be above any `BaseHTTPMiddleware` in the final +middleware list. If you're calling `add_middleware` repeatedly, add the +ElasticAPM middleware last. If you're passing in a list of middleware, +ElasticAPM should be first on that list. To configure the agent using initialization arguments: From 47fad62b5230b9b00922ab4d22381911cb2201e8 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 23 Feb 2024 09:58:59 +0100 Subject: [PATCH 026/409] instrumentation/dbapi2: make query scanning for dollar quotes a bit more correct (#1976) Dollar quotes follow the same rules as SQL identifiers so: SQL identifiers and key words must begin with a letter (a-z, but also letters with diacritical marks and non-Latin letters) or an underscore (_). Subsequent characters in an identifier or key word can be letters, underscores, digits (0-9), or dollar signs ($). Given we are not going to write a compliant SQL parser at least handle query parameters that are simple to catch since they have a digit right after the opening $. Refs #1851. --- elasticapm/instrumentation/packages/dbapi2.py | 7 +++++++ tests/instrumentation/dbapi2_tests.py | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/elasticapm/instrumentation/packages/dbapi2.py b/elasticapm/instrumentation/packages/dbapi2.py index fb49723c2..fa1d0f31e 100644 --- a/elasticapm/instrumentation/packages/dbapi2.py +++ b/elasticapm/instrumentation/packages/dbapi2.py @@ -34,6 +34,7 @@ """ import re +import string import wrapt @@ -85,6 +86,7 @@ def scan(tokens): literal_started = None prev_was_escape = False lexeme = [] + digits = set(string.digits) i = 0 while i < len(tokens): @@ -114,6 +116,11 @@ def scan(tokens): literal_start_idx = i literal_started = token elif token == "$": + # exclude query parameters that have a digit following the dollar + if True and len(tokens) > i + 1 and tokens[i + 1] in digits: + yield i, token + i += 1 + continue # Postgres can use arbitrary characters between two $'s as a # literal separation token, e.g.: $fish$ literal $fish$ # This part will detect that and skip over the literal. diff --git a/tests/instrumentation/dbapi2_tests.py b/tests/instrumentation/dbapi2_tests.py index 3d72b6632..089571715 100644 --- a/tests/instrumentation/dbapi2_tests.py +++ b/tests/instrumentation/dbapi2_tests.py @@ -122,6 +122,20 @@ def test_extract_signature_bytes(): assert actual == expected +def test_extract_signature_pathological(): + # tune for performance testing + multiplier = 10 + values = [] + for chunk in range(multiplier): + i = chunk * 3 + values.append(f" (${1+i}::varchar, ${2+i}::varchar, ${3+i}::varchar), ") + + sql = f"SELECT * FROM (VALUES {''.join(values)})\n" + actual = extract_signature(sql) + expected = "SELECT FROM" + assert actual == expected + + @pytest.mark.parametrize( ["sql", "expected"], [ From 01336c41a498e825bc5558a2c64c4abaefdd0c41 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Sat, 24 Feb 2024 09:49:40 +0100 Subject: [PATCH 027/409] Cleanup python2 vestiges (#1977) Remove python2 vestiges --- elasticapm/instrumentation/packages/urllib.py | 5 ++--- elasticapm/utils/__init__.py | 10 ++-------- tests/client/client_tests.py | 6 +----- tests/fixtures.py | 7 +------ tests/instrumentation/urllib_tests.py | 2 +- tests/utils/tests.py | 10 +--------- 6 files changed, 8 insertions(+), 32 deletions(-) diff --git a/elasticapm/instrumentation/packages/urllib.py b/elasticapm/instrumentation/packages/urllib.py index b40932a55..2b0dae16e 100644 --- a/elasticapm/instrumentation/packages/urllib.py +++ b/elasticapm/instrumentation/packages/urllib.py @@ -97,10 +97,9 @@ def call(self, module, method, wrapped, instance, args, kwargs): leaf_span.dist_tracing_propagated = True response = wrapped(*args, **kwargs) if response: - status = getattr(response, "status", None) or response.getcode() # Python 2 compat if span.context: - span.context["http"]["status_code"] = status - span.set_success() if status < 400 else span.set_failure() + span.context["http"]["status_code"] = response.status + span.set_success() if response.status < 400 else span.set_failure() return response def mutate_unsampled_call_args(self, module, method, wrapped, instance, args, kwargs, transaction): diff --git a/elasticapm/utils/__init__.py b/elasticapm/utils/__init__.py index 58a302960..0f7b52c0d 100644 --- a/elasticapm/utils/__init__.py +++ b/elasticapm/utils/__init__.py @@ -33,20 +33,14 @@ import re import socket import urllib.parse -from functools import partial +from functools import partial, partialmethod from types import FunctionType from typing import Pattern from elasticapm.conf import constants from elasticapm.utils import encoding -try: - from functools import partialmethod - - partial_types = (partial, partialmethod) -except ImportError: - # Python 2 - partial_types = (partial,) +partial_types = (partial, partialmethod) default_ports = {"https": 443, "http": 80, "postgresql": 5432, "mysql": 3306, "mssql": 1433} diff --git a/tests/client/client_tests.py b/tests/client/client_tests.py index 6cec88205..af266b710 100644 --- a/tests/client/client_tests.py +++ b/tests/client/client_tests.py @@ -77,11 +77,7 @@ def test_service_info_node_name(elasticapm_client): def test_process_info(elasticapm_client): process_info = elasticapm_client.get_process_info() assert process_info["pid"] == os.getpid() - if hasattr(os, "getppid"): - assert process_info["ppid"] == os.getppid() - else: - # Windows + Python 2.7 - assert process_info["ppid"] is None + assert process_info["ppid"] == os.getppid() assert "argv" not in process_info elasticapm_client.config.update("1", include_process_args=True) with mock.patch.object(sys, "argv", ["a", "b", "c"]): diff --git a/tests/fixtures.py b/tests/fixtures.py index 94e89f961..ddeaa1f5b 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -46,6 +46,7 @@ import zlib from collections import defaultdict from typing import Optional +from urllib.request import pathname2url import jsonschema import mock @@ -61,12 +62,6 @@ from elasticapm.transport.http_base import HTTPTransportBase from elasticapm.utils.threading import ThreadManager -try: - from urllib.request import pathname2url -except ImportError: - # Python 2 - from urllib import pathname2url - cur_dir = os.path.dirname(os.path.realpath(__file__)) ERRORS_SCHEMA = os.path.join(cur_dir, "upstream", "json-specs", "error.json") diff --git a/tests/instrumentation/urllib_tests.py b/tests/instrumentation/urllib_tests.py index 3f2796483..fbf5fa44f 100644 --- a/tests/instrumentation/urllib_tests.py +++ b/tests/instrumentation/urllib_tests.py @@ -114,7 +114,7 @@ def test_urllib_error(instrument, elasticapm_client, waiting_httpserver, status_ @mock.patch(request_method) @mock.patch(getresponse_method) def test_urllib_standard_port(mock_getresponse, mock_request, instrument, elasticapm_client): - # "code" is needed for Python 3, "status" for Python 2 + # Python internally used both "code" and "status" mock_getresponse.return_value = mock.Mock(code=200, status=200) url = "http://example.com/" diff --git a/tests/utils/tests.py b/tests/utils/tests.py index 5f073862d..bd09eef26 100644 --- a/tests/utils/tests.py +++ b/tests/utils/tests.py @@ -30,7 +30,7 @@ import os import socket -from functools import partial +from functools import partial, partialmethod import pytest @@ -48,12 +48,6 @@ ) from elasticapm.utils.deprecation import deprecated -try: - from functools import partialmethod -except ImportError: - # Python 2 - partialmethod = None - @deprecated("alternative") def deprecated_function(): @@ -164,7 +158,6 @@ def x(x): assert "partial(tests.utils.tests.x)" == get_name_from_func(p) -@pytest.mark.skipif(partialmethod is None, reason="partialmethod not available on Python 2") def test_get_name_from_func_partialmethod_unbound(): class X(object): def x(self, x): @@ -175,7 +168,6 @@ def x(self, x): assert "partial(tests.utils.tests.x)" == get_name_from_func(X.p) -@pytest.mark.skipif(partialmethod is None, reason="partialmethod not available on Python 2") def test_get_name_from_func_partialmethod_bound(): class X(object): def x(self, x): From fd8f8590d6e6ff581113756de05ec367887f46e0 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Sat, 24 Feb 2024 09:50:20 +0100 Subject: [PATCH 028/409] ci: re-enable more python 3.12 testing (#1978) --- .ci/.matrix_exclude.yml | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/.ci/.matrix_exclude.yml b/.ci/.matrix_exclude.yml index d7d05f2af..5eb71a09f 100644 --- a/.ci/.matrix_exclude.yml +++ b/.ci/.matrix_exclude.yml @@ -263,29 +263,9 @@ exclude: - VERSION: python-3.7 FRAMEWORK: celery-4-flask-1.0 # TODO py3.12 - - VERSION: python-3.12 - FRAMEWORK: pymssql-newest # no wheels available yet - - VERSION: python-3.12 - FRAMEWORK: aiohttp-newest # no wheels available yet - - VERSION: python-3.12 - FRAMEWORK: elasticsearch-7 # relies on aiohttp - - VERSION: python-3.12 - FRAMEWORK: elasticsearch-8 # relies on aiohttp - - VERSION: python-3.12 - FRAMEWORK: aiobotocore-newest # relies on aiohttp - VERSION: python-3.12 FRAMEWORK: sanic-20.12 # no wheels available yet - - VERSION: python-3.12 - FRAMEWORK: sanic-newest # no wheels available yet - VERSION: python-3.12 FRAMEWORK: kafka-python-newest # https://github.com/dpkp/kafka-python/pull/2376 - - VERSION: python-3.12 - FRAMEWORK: pyodbc-newest # error on wheel - VERSION: python-3.12 FRAMEWORK: cassandra-newest # c extension issue - - VERSION: python-3.12 - FRAMEWORK: starlette-newest # waiting for 3.12.2 for this fix: https://github.com/python/cpython/pull/111221 - - VERSION: python-3.12 - FRAMEWORK: starlette-0.14 # waiting for 3.12.2 for this fix: https://github.com/python/cpython/pull/111221 - - VERSION: python-3.12 - FRAMEWORK: starlette-0.13 # waiting for 3.12.2 for this fix: https://github.com/python/cpython/pull/111221 From 9bcc72d32ef9cd3d547894833966648edb0107c6 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 5 Mar 2024 09:23:25 +0100 Subject: [PATCH 029/409] Remove hound integration (#1983) We have already pre-commit running on CI and it doesn't look to be running at the moment. --- .hound.yml | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .hound.yml diff --git a/.hound.yml b/.hound.yml deleted file mode 100644 index 0745a960a..000000000 --- a/.hound.yml +++ /dev/null @@ -1,5 +0,0 @@ -flake8: - enabled: true - config_file: .flake8 - -fail_on_violations: true From 0f5c531e7c9570ee19f6819d948673eefb8e7298 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 5 Mar 2024 09:28:18 +0100 Subject: [PATCH 030/409] contrib/serverless/aws: normalize headers with v1 event format (#1982) If the event has been generated from elb or api gateway and is in the v1 format normalize headers even if they already should per https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html Event discrimination logic courtesy of nodejs apm client. Fix #1980 --- elasticapm/contrib/serverless/aws.py | 20 ++++++++++++++++- .../contrib/serverless/aws_elb_test_data.json | 1 + tests/contrib/serverless/aws_tests.py | 22 ++++++++++++++++++- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/elasticapm/contrib/serverless/aws.py b/elasticapm/contrib/serverless/aws.py index 26f37bdfb..9f5f7b133 100644 --- a/elasticapm/contrib/serverless/aws.py +++ b/elasticapm/contrib/serverless/aws.py @@ -135,6 +135,18 @@ def prep_kwargs(kwargs=None): return kwargs +def should_normalize_headers(event: dict) -> bool: + """ + Helper to decide if we should normalize headers or not depending on the event + + Even if the documentation says that headers are lowercased it's not always the case for format version 1.0 + https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html + """ + + request_context = event.get("requestContext", {}) + return ("elb" in request_context or "requestId" in request_context) and "http" not in request_context + + class _lambda_transaction(object): """ Context manager for creating transactions around AWS Lambda functions. @@ -162,7 +174,13 @@ def __enter__(self): # service like Step Functions, and is unlikely to be standardized # in any way. We just have to rely on our defaults in this case. self.event = {} - trace_parent = TraceParent.from_headers(self.event.get("headers") or {}) + + headers = self.event.get("headers") or {} + if headers and should_normalize_headers(self.event): + normalized_headers = {k.lower(): v for k, v in headers.items()} + else: + normalized_headers = headers + trace_parent = TraceParent.from_headers(normalized_headers) global COLD_START cold_start = COLD_START diff --git a/tests/contrib/serverless/aws_elb_test_data.json b/tests/contrib/serverless/aws_elb_test_data.json index 87e05ac85..79b4dc6dd 100644 --- a/tests/contrib/serverless/aws_elb_test_data.json +++ b/tests/contrib/serverless/aws_elb_test_data.json @@ -15,6 +15,7 @@ "connection": "Keep-Alive", "host": "blabla.com", "user-agent": "Apache-HttpClient/4.5.13 (Java/11.0.15)", + "TraceParent": "00-12345678901234567890123456789012-1234567890123456-01", "x-amzn-trace-id": "Root=1-xxxxxxxxxxxxxx", "x-forwarded-for": "199.99.99.999", "x-forwarded-port": "443", diff --git a/tests/contrib/serverless/aws_tests.py b/tests/contrib/serverless/aws_tests.py index 9f4a7253f..df062a378 100644 --- a/tests/contrib/serverless/aws_tests.py +++ b/tests/contrib/serverless/aws_tests.py @@ -36,7 +36,12 @@ from elasticapm import capture_span from elasticapm.conf import constants -from elasticapm.contrib.serverless.aws import capture_serverless, get_data_from_request, get_data_from_response +from elasticapm.contrib.serverless.aws import ( + capture_serverless, + get_data_from_request, + get_data_from_response, + should_normalize_headers, +) @pytest.fixture @@ -300,6 +305,7 @@ def test_func(event, context): assert transaction["context"]["request"]["headers"] assert transaction["context"]["response"]["status_code"] == 200 assert transaction["context"]["service"]["origin"]["name"] == "lambda-279XGJDqGZ5rsrHC2Fjr" + assert transaction["trace_id"] == "12345678901234567890123456789012" def test_capture_serverless_s3(event_s3, context, elasticapm_client): @@ -477,3 +483,17 @@ def test_func(event, context): test_func(event_api2, context) assert len(elasticapm_client.events[constants.TRANSACTION]) == 1 + + +def test_should_normalize_headers_true(event_api, event_elb): + assert should_normalize_headers(event_api) is True + assert should_normalize_headers(event_elb) is True + + +def test_should_normalize_headers_false(event_api2, event_lurl, event_s3, event_s3_batch, event_sqs, event_sns): + assert should_normalize_headers(event_api2) is False + assert should_normalize_headers(event_lurl) is False + assert should_normalize_headers(event_s3) is False + assert should_normalize_headers(event_s3_batch) is False + assert should_normalize_headers(event_sqs) is False + assert should_normalize_headers(event_sns) is False From 080b879257935903adb30ca4a0616b8bd1914d1d Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 6 Mar 2024 17:22:27 +0100 Subject: [PATCH 031/409] update CHANGELOG and bump version to 6.21.0 (#1986) --- CHANGELOG.asciidoc | 16 +++++++++++++++- elasticapm/version.py | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index fb87b1107..c94edf53a 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -5,7 +5,7 @@ endif::[] //// [[release-notes-x.x.x]] -==== x.x.x - YYYY/MM/DD +==== x.x.x - YYYY-MM-DD [float] ===== Breaking changes @@ -32,6 +32,20 @@ endif::[] [[release-notes-6.x]] === Python Agent version 6.x +[[release-notes-6.21.0]] +==== 6.21.0 - 2024-03-06 + +[float] +===== Bug fixes + +* Fix starlette middleware setup without client argument {pull}1952[#1952] +* Fix blocking of gRPC stream-to-stream requests {pull}1967[#1967] +* Always take into account body reading time for starlette requests {pull}1970[#1970] +* Make urllib3 transport tests more robust against local env {pull}1969[#1969] +* Clarify starlette integration documentation {pull}1956[#1956] +* Make dbapi2 query scanning for dollar quotes a bit more correct {pull}1976[#1976] +* Normalize headers in AWS Lambda integration on API Gateway v1 requests {pull}1982[#1982] + [[release-notes-6.20.0]] ==== 6.20.0 - 2024-01-10 diff --git a/elasticapm/version.py b/elasticapm/version.py index ea64e853b..a48ac9368 100644 --- a/elasticapm/version.py +++ b/elasticapm/version.py @@ -28,5 +28,5 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = (6, 20, 0) +__version__ = (6, 21, 0) VERSION = ".".join(map(str, __version__)) From b9306477320f0e418259bd64df8b5dcdb8cdadd5 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 7 Mar 2024 10:09:37 +0100 Subject: [PATCH 032/409] ci: add missing full-matrix input when using test workflow in release (#1990) Should fix the following error when tagging a release: The workflow is not valid. .github/workflows/release.yml (Line: 13, Col: 11): Input full-matrix is required, but not provided while calling. --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0e297bb95..8d22d37aa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,6 +11,8 @@ permissions: jobs: test: uses: ./.github/workflows/test.yml + with: + full-matrix: true packages: uses: ./.github/workflows/packages.yml From ad2e34667d1937626cef635e2360999bebb10baf Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 7 Mar 2024 16:59:41 +0100 Subject: [PATCH 033/409] Update CHANGELOG and bump version to 6.21.1 (#1992) --- CHANGELOG.asciidoc | 8 ++++++++ elasticapm/version.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index c94edf53a..b8ad0d90c 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -32,6 +32,14 @@ endif::[] [[release-notes-6.x]] === Python Agent version 6.x +[[release-notes-6.21.1]] +==== 6.21.1 - 2024-03-07 + +[float] +===== Bug fixes + +* Fix CI release workflow {pull}1990[#1990] + [[release-notes-6.21.0]] ==== 6.21.0 - 2024-03-06 diff --git a/elasticapm/version.py b/elasticapm/version.py index a48ac9368..7cba6757b 100644 --- a/elasticapm/version.py +++ b/elasticapm/version.py @@ -28,5 +28,5 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = (6, 21, 0) +__version__ = (6, 21, 1) VERSION = ".".join(map(str, __version__)) From 50c098ceaa3e0ed98fb53216b67cc14d47c3bae3 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 7 Mar 2024 17:53:10 +0100 Subject: [PATCH 034/409] ci: revert to actions/upload-artifact@v3 in build-distribution workflow (#1993) It looks like since v4 only one upload for the same dir will work. See: https://github.com/actions/upload-artifact/issues/478#issuecomment-1860038048 In the meantime we sort this out revert to v3. Should fix: https://github.com/elastic/apm-agent-python/actions/runs/8191161913/job/22399752533 --- .github/workflows/build-distribution.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-distribution.yml b/.github/workflows/build-distribution.yml index 8b728c3bf..fd3e11ed7 100644 --- a/.github/workflows/build-distribution.yml +++ b/.github/workflows/build-distribution.yml @@ -13,7 +13,7 @@ jobs: python-version: "3.10" - name: Build lambda layer zip run: ./dev-utils/make-distribution.sh - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v3 with: name: build-distribution path: ./build/ From 5bffc41e67589396e3118a3df68c746527e5315e Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 7 Mar 2024 19:19:30 +0100 Subject: [PATCH 035/409] update CHANGELOG and bump version to 6.21.2 (#1995) --- CHANGELOG.asciidoc | 8 ++++++++ elasticapm/version.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index b8ad0d90c..956f90d9a 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -32,6 +32,14 @@ endif::[] [[release-notes-6.x]] === Python Agent version 6.x +[[release-notes-6.21.2]] +==== 6.21.2 - 2024-03-07 + +[float] +===== Bug fixes + +* Fix artifacts upload in CI build-distribution workflow {pull}1993[#1993] + [[release-notes-6.21.1]] ==== 6.21.1 - 2024-03-07 diff --git a/elasticapm/version.py b/elasticapm/version.py index 7cba6757b..38a43e781 100644 --- a/elasticapm/version.py +++ b/elasticapm/version.py @@ -28,5 +28,5 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = (6, 21, 1) +__version__ = (6, 21, 2) VERSION = ".".join(map(str, __version__)) From 739b0080fd48999cb08c37e2839cc8473548588d Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 8 Mar 2024 11:38:18 +0100 Subject: [PATCH 036/409] ci: switch build-distribution downloadArtifact to v3 (#1996) Since v4 can't find them if uploaded with uploadArtifact v3. --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8d22d37aa..a2a48b62b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -54,7 +54,7 @@ jobs: secrets: | secret/observability-team/ci/service-account/apm-agent-python access_key_id | AWS_ACCESS_KEY_ID ; secret/observability-team/ci/service-account/apm-agent-python secret_access_key | AWS_SECRET_ACCESS_KEY - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v3 with: name: build-distribution path: ./build @@ -84,7 +84,7 @@ jobs: url: ${{ secrets.VAULT_ADDR }} roleId: ${{ secrets.VAULT_ROLE_ID }} secretId: ${{ secrets.VAULT_SECRET_ID }} - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v3 with: name: build-distribution path: ./build From bab896de8579bbf1c3aaec37b13619ec06ea8a01 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 8 Mar 2024 12:00:32 +0100 Subject: [PATCH 037/409] update CHANGELOG and bump version to 6.21.3 (#1997) --- CHANGELOG.asciidoc | 8 ++++++++ elasticapm/version.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 956f90d9a..57a6cb95f 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -32,6 +32,14 @@ endif::[] [[release-notes-6.x]] === Python Agent version 6.x +[[release-notes-6.21.3]] +==== 6.21.3 - 2024-03-08 + +[float] +===== Bug fixes + +* Fix artifacts download in CI workflows {pull}1996[#1996] + [[release-notes-6.21.2]] ==== 6.21.2 - 2024-03-07 diff --git a/elasticapm/version.py b/elasticapm/version.py index 38a43e781..6da6c370d 100644 --- a/elasticapm/version.py +++ b/elasticapm/version.py @@ -28,5 +28,5 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = (6, 21, 2) +__version__ = (6, 21, 3) VERSION = ".".join(map(str, __version__)) From fd550df44a2e5d9d1ef0eca921324e3dbfe0c857 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 13 Mar 2024 10:40:46 +0100 Subject: [PATCH 038/409] instrumentation/urllib3: fix urllib3 2.0.1+ crash with many args (#2002) In c1dd69e0a5b9d1ec020666a97e0b7cb788b86c4f we changed the logic of update_headers taking into account a new body arg before the one for headers. The problem is that HTTPConnectionPool.urlopen did not change at all, only HTTPConnectionPool.request did so the old login update_headers was fine. Fix #1928 --- elasticapm/instrumentation/packages/urllib3.py | 7 +------ tests/instrumentation/urllib3_tests.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/elasticapm/instrumentation/packages/urllib3.py b/elasticapm/instrumentation/packages/urllib3.py index cc7206e83..93d9c3392 100644 --- a/elasticapm/instrumentation/packages/urllib3.py +++ b/elasticapm/instrumentation/packages/urllib3.py @@ -61,12 +61,7 @@ def update_headers(args, kwargs, instance, transaction, trace_parent): :param trace_parent: the TraceParent object :return: an (args, kwargs) tuple """ - from urllib3._version import __version__ as urllib3_version - - if urllib3_version.startswith("2") and len(args) >= 5 and args[4]: - headers = args[4].copy() - args = tuple(itertools.chain((args[:4]), (headers,), args[5:])) - elif len(args) >= 4 and args[3]: + if len(args) >= 4 and args[3]: headers = args[3].copy() args = tuple(itertools.chain((args[:3]), (headers,), args[4:])) elif "headers" in kwargs and kwargs["headers"]: diff --git a/tests/instrumentation/urllib3_tests.py b/tests/instrumentation/urllib3_tests.py index 8cc21ceb0..1fa03fa43 100644 --- a/tests/instrumentation/urllib3_tests.py +++ b/tests/instrumentation/urllib3_tests.py @@ -294,3 +294,20 @@ def test_instance_headers_are_respected( assert "kwargs" in request_headers if instance_headers and not (header_arg or header_kwarg): assert "instance" in request_headers + + +def test_connection_pool_urlopen_does_not_crash_with_many_args(instrument, elasticapm_client, waiting_httpserver): + """Mimics ConnectionPool.urlopen error path with broken connection, see #1928""" + waiting_httpserver.serve_content("") + url = waiting_httpserver.url + "/hello_world" + parsed_url = urllib.parse.urlparse(url) + pool = urllib3.HTTPConnectionPool( + parsed_url.hostname, + parsed_url.port, + maxsize=1, + block=True, + ) + retry = urllib3.util.Retry(10) + elasticapm_client.begin_transaction("transaction") + r = pool.urlopen("GET", url, None, {"args": "true"}, retry, False, False) + assert r.status == 200 From 022080fe9aadf1b4c168e05aefaea4d2e8b270cc Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 13 Mar 2024 17:58:17 +0100 Subject: [PATCH 039/409] setup: take a post version from environment variable (#2000) Make it possible to add a post version (per PEP-440) to the current agent version at build time. This will be useful because we want to continuously build the agent on CI and avoid version conflicts when publishing to test pypi. So when returning the agent version add anything that has been passed in ELASTIC_CI_POST_VERSION environment variable as post version. Refs #1994 --- setup.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 23ebec33e..a88cdeb5b 100644 --- a/setup.py +++ b/setup.py @@ -100,7 +100,11 @@ def get_version(): for line in version_file: if line.startswith("__version__"): version_tuple = ast.literal_eval(line.split(" = ")[1]) - return ".".join(map(str, version_tuple)) + version_str = ".".join(map(str, version_tuple)) + post_version = os.getenv("ELASTIC_CI_POST_VERSION") + if post_version: + return f"{version_str}.post{post_version}" + return version_str return "unknown" From a982eb8c2d46c8685c1248c8601e0f45713a9d43 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 13 Mar 2024 18:01:17 +0100 Subject: [PATCH 040/409] Deprecate Python < 3.2 logging code and and remove documentation (#1984) * docs: remove Python < 3.2 specific documentation * handlers/logging: deprecate Python<3.2 LoggingFilter Since we don't support Python < 3.6 there's not reason to keep code for Python versions older than that. * Update elasticapm/handlers/logging.py Co-authored-by: Colton Myers --------- Co-authored-by: Colton Myers --- docs/logging.asciidoc | 21 +-------------------- elasticapm/handlers/logging.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/docs/logging.asciidoc b/docs/logging.asciidoc index 943de8f64..8f51edd50 100644 --- a/docs/logging.asciidoc +++ b/docs/logging.asciidoc @@ -36,7 +36,7 @@ as well as http://www.structlog.org/en/stable/[`structlog`]. [[logging]] ===== `logging` -For Python 3.2+, we use https://docs.python.org/3/library/logging.html#logging.setLogRecordFactory[`logging.setLogRecordFactory()`] +We use https://docs.python.org/3/library/logging.html#logging.setLogRecordFactory[`logging.setLogRecordFactory()`] to decorate the default LogRecordFactory to automatically add new attributes to each LogRecord object: @@ -51,25 +51,6 @@ You can disable this automatic behavior by using the <> setting in your configuration. -For Python versions <3.2, we also provide a -https://docs.python.org/3/library/logging.html#filter-objects[filter] which will -add the same new attributes to any filtered `LogRecord`: - -[source,python] ----- -import logging -from elasticapm.handlers.logging import LoggingFilter - -console = logging.StreamHandler() -console.addFilter(LoggingFilter()) -# add the handler to the root logger -logging.getLogger("").addHandler(console) ----- - -NOTE: Because https://docs.python.org/3/library/logging.html#filter-objects[filters -are not propagated to descendent loggers], you should add the filter to each of -your log handlers, as handlers are propagated, along with their attached filters. - [float] [[structlog]] ===== `structlog` diff --git a/elasticapm/handlers/logging.py b/elasticapm/handlers/logging.py index 4407f0f87..ed4db87ac 100644 --- a/elasticapm/handlers/logging.py +++ b/elasticapm/handlers/logging.py @@ -194,6 +194,16 @@ class LoggingFilter(logging.Filter): automatically. """ + def __init__(self, name=""): + super().__init__(name=name) + warnings.warn( + "The LoggingFilter is deprecated and will be removed in v7.0 of " + "the agent. On Python 3.2+, by default we add a LogRecordFactory to " + "your root logger automatically" + "https://www.elastic.co/guide/en/apm/agent/python/current/logs.html", + PendingDeprecationWarning, + ) + def filter(self, record): """ Add elasticapm attributes to `record`. From 84688df62560ceea588225b71891a85d971cb607 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Wed, 13 Mar 2024 18:08:27 +0100 Subject: [PATCH 041/409] ci: run the release when merges to main (#1998) * ci: run the release when merges to main * avoid wrong docker-tag name * use the opposite * fix yaml * enable test, slack message conditional and fix names * Update .github/workflows/release.yml * skip test for branches in the release workflow * workaround * fix * Update .github/workflows/release.yml * support releases in test.pypi for commits on main * set environment variable in the reusable workflow instead env propagation between reusable workflow is not supported * setup: take a post version from environment variable * Update .github/workflows/release.yml --------- Co-authored-by: Riccardo Magliocchetti --- .github/workflows/packages.yml | 4 +++ .github/workflows/release.yml | 39 +++++++++++++++++++++-------- .github/workflows/test-release.yml | 40 ++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/test-release.yml diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index af485c455..f2d9a4f83 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -13,6 +13,10 @@ on: - '**/*.md' - '**/*.asciidoc' +# Override the version if there is no tag release. +env: + ELASTIC_CI_POST_VERSION: ${{ startsWith(github.ref, 'refs/tags') && '' || github.run_id }} + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a2a48b62b..b1d0dff48 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,15 +4,18 @@ on: push: tags: - "v*.*.*" + branches: + - main permissions: contents: read jobs: test: - uses: ./.github/workflows/test.yml + uses: ./.github/workflows/test-release.yml with: full-matrix: true + enabled: ${{ startsWith(github.ref, 'refs/tags') }} packages: uses: ./.github/workflows/packages.yml @@ -31,10 +34,16 @@ jobs: with: name: packages path: dist - - name: Upload + - name: Upload pypi.org + if: startsWith(github.ref, 'refs/tags') uses: pypa/gh-action-pypi-publish@2f6f737ca5f74c637829c0f5c3acd0e29ea5e8bf with: repository-url: https://upload.pypi.org/legacy/ + - name: Upload test.pypi.org + if: ${{ ! startsWith(github.ref, 'refs/tags') }} + uses: pypa/gh-action-pypi-publish@2f6f737ca5f74c637829c0f5c3acd0e29ea5e8bf + with: + repository-url: https://test.pypi.org/legacy/ build-distribution: uses: ./.github/workflows/build-distribution.yml @@ -59,6 +68,7 @@ jobs: name: build-distribution path: ./build - name: Publish lambda layers to AWS + if: startsWith(github.ref, 'refs/tags') run: | # Convert v1.2.3 to ver-1-2-3 VERSION=${GITHUB_REF_NAME/v/ver-} @@ -66,6 +76,7 @@ jobs: ELASTIC_LAYER_NAME="elastic-apm-python-${VERSION}" .ci/publish-aws.sh - uses: actions/upload-artifact@v4 + if: startsWith(github.ref, 'refs/tags') with: name: arn-file path: ".arn-file.md" @@ -75,6 +86,8 @@ jobs: needs: - build-distribution runs-on: ubuntu-latest + env: + DOCKER_IMAGE_NAME: docker.elastic.co/observability/apm-agent-python steps: - uses: actions/checkout@v4 - uses: elastic/apm-pipeline-library/.github/actions/docker-login@current @@ -91,30 +104,35 @@ jobs: - id: setup-docker name: Set up docker variables run: |- - # version without v prefix (e.g. 1.2.3) - echo "tag=${GITHUB_REF_NAME/v/}" >> "${GITHUB_OUTPUT}" - echo "name=docker.elastic.co/observability/apm-agent-python" >> "${GITHUB_OUTPUT}" + if [ "${{ startsWith(github.ref, 'refs/tags') }}" == "false" ] ; then + # for testing purposes + echo "tag=test" >> "${GITHUB_OUTPUT}" + else + # version without v prefix (e.g. 1.2.3) + echo "tag=${GITHUB_REF_NAME/v/}" >> "${GITHUB_OUTPUT}" + fi - name: Docker build run: >- docker build - -t ${{ steps.setup-docker.outputs.name }}:${{ steps.setup-docker.outputs.tag }} + -t ${{ env.DOCKER_IMAGE_NAME }}:${{ steps.setup-docker.outputs.tag }} --build-arg AGENT_DIR=./build/dist/package/python . - name: Docker retag run: >- docker tag - ${{ steps.setup-docker.outputs.name }}:${{ steps.setup-docker.outputs.tag }} - ${{ steps.setup-docker.outputs.name }}:latest + ${{ env.DOCKER_IMAGE_NAME }}:${{ steps.setup-docker.outputs.tag }} + ${{ env.DOCKER_IMAGE_NAME }}:latest - name: Docker push + if: startsWith(github.ref, 'refs/tags') run: |- - docker push ${{ steps.setup-docker.outputs.name }}:${{ steps.setup-docker.outputs.tag }} - docker push ${{ steps.setup-docker.outputs.name }}:latest + docker push --all-tags ${{ env.DOCKER_IMAGE_NAME }} github-draft: permissions: contents: write needs: - publish-lambda-layers + if: startsWith(github.ref, 'refs/tags') runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -145,6 +163,7 @@ jobs: with: needs: ${{ toJSON(needs) }} - uses: elastic/apm-pipeline-library/.github/actions/notify-build-status@current + if: startsWith(github.ref, 'refs/tags') with: status: ${{ steps.check.outputs.status }} vaultUrl: ${{ secrets.VAULT_ADDR }} diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml new file mode 100644 index 000000000..c873f9eb7 --- /dev/null +++ b/.github/workflows/test-release.yml @@ -0,0 +1,40 @@ +name: test-release + +on: + workflow_call: + inputs: + full-matrix: + description: "Run the full matrix" + required: true + type: boolean + ref: + description: "The git ref of elastic/apm-agent-python to run test workflow from." + required: false + type: string + enabled: + description: "Whether to run the workfow" + required: true + type: boolean + workflow_dispatch: + inputs: + full-matrix: + description: "Run the full matrix" + required: true + type: boolean + enabled: + description: "Whether to run the workfow" + required: true + type: boolean + +jobs: + test: + if: ${{ inputs.enabled }} + uses: ./.github/workflows/test.yml + with: + full-matrix: ${{ inputs.full-matrix }} + + run-if-disabled: + if: ${{ ! inputs.enabled }} + runs-on: ubuntu-latest + steps: + - run: echo "do something to help with the reusable workflows with needs" From 11b59b235d37cc48dc75d809643592ab76ca7d21 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 14 Mar 2024 09:55:47 +0100 Subject: [PATCH 042/409] Bump the github-actions group with 2 updates (#1999) Bumps the github-actions group with 2 updates: [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) and [hashicorp/vault-action](https://github.com/hashicorp/vault-action). Updates `pypa/gh-action-pypi-publish` from 1.8.11 to 1.8.14 - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/2f6f737ca5f74c637829c0f5c3acd0e29ea5e8bf...81e9d935c883d0b210363ab89cf05f3894778450) Updates `hashicorp/vault-action` from 2.8.0 to 3.0.0 - [Release notes](https://github.com/hashicorp/vault-action/releases) - [Changelog](https://github.com/hashicorp/vault-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/vault-action/compare/v2.8.0...v3.0.0) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: hashicorp/vault-action dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b1d0dff48..5f91014b4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,12 +36,12 @@ jobs: path: dist - name: Upload pypi.org if: startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@2f6f737ca5f74c637829c0f5c3acd0e29ea5e8bf + uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 with: repository-url: https://upload.pypi.org/legacy/ - name: Upload test.pypi.org if: ${{ ! startsWith(github.ref, 'refs/tags') }} - uses: pypa/gh-action-pypi-publish@2f6f737ca5f74c637829c0f5c3acd0e29ea5e8bf + uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 with: repository-url: https://test.pypi.org/legacy/ @@ -54,7 +54,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: hashicorp/vault-action@v2.8.0 + - uses: hashicorp/vault-action@v3.0.0 with: url: ${{ secrets.VAULT_ADDR }} method: approle From 165a73bb7a8cda705abde534c13149af29d5541c Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 14 Mar 2024 14:50:57 +0100 Subject: [PATCH 043/409] ci: update issue labeler (#2003) * ci: update issue labeler workflow Stop using the apm group and custom github action to check if issues are from internal members or not. While at it remove posting internal pull requests on the old APM agent board. Fixes #2001 * Update .github/workflows/labeler.yml Co-authored-by: Jan Calanog --------- Co-authored-by: Jan Calanog --- .github/community-label.yml | 5 ---- .github/labeler-config.yml | 3 --- .github/workflows/labeler.yml | 49 +++++++++++++---------------------- 3 files changed, 18 insertions(+), 39 deletions(-) delete mode 100644 .github/community-label.yml delete mode 100644 .github/labeler-config.yml diff --git a/.github/community-label.yml b/.github/community-label.yml deleted file mode 100644 index 8872df2d5..000000000 --- a/.github/community-label.yml +++ /dev/null @@ -1,5 +0,0 @@ -# add 'community' label to all new issues and PRs created by the community -community: - - '.*' -triage: - - '.*' \ No newline at end of file diff --git a/.github/labeler-config.yml b/.github/labeler-config.yml deleted file mode 100644 index a1e4dbc29..000000000 --- a/.github/labeler-config.yml +++ /dev/null @@ -1,3 +0,0 @@ -# add 'agent-python' label to all new issues -agent-python: - - '.*' diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 377caaa5c..564cc7d31 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -4,43 +4,30 @@ on: types: [opened] pull_request_target: types: [opened] -env: - MY_GITHUB_TOKEN: ${{ secrets.APM_TECH_USER_TOKEN }} + +# 'issues: write' for https://docs.github.com/en/rest/issues/labels?apiVersion=2022-11-28#add-labels-to-an-issue +permissions: + contents: read + issues: write + pull-requests: write + jobs: triage: runs-on: ubuntu-latest steps: - name: Add agent-python label - uses: AlexanderWert/issue-labeler@v2.3 + uses: actions-ecosystem/action-add-labels@v1 with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" - configuration-path: .github/labeler-config.yml - enable-versioned-regex: 0 - - name: Check team membership for user - uses: elastic/get-user-teams-membership@1.1.0 - id: checkUserMember + labels: agent-python + - id: is_elastic_member + uses: elastic/apm-pipeline-library/.github/actions/is-member-elastic-org@current with: username: ${{ github.actor }} - team: 'apm' - usernamesToExclude: | - apmmachine - dependabot - GITHUB_TOKEN: ${{ secrets.APM_TECH_USER_TOKEN }} - - name: Show team membership - run: | - echo "::debug::isTeamMember: ${{ steps.checkUserMember.outputs.isTeamMember }}" - echo "::debug::isExcluded: ${{ steps.checkUserMember.outputs.isExcluded }}" - - name: Add community and triage lables - if: steps.checkUserMember.outputs.isTeamMember != 'true' && steps.checkUserMember.outputs.isExcluded != 'true' - uses: AlexanderWert/issue-labeler@v2.3 + token: ${{ secrets.APM_TECH_USER_TOKEN }} + - name: Add community and triage labels + if: contains(steps.is_elastic_member.outputs.result, 'false') && github.actor != 'dependabot[bot]' && github.actor != 'apmmachine' + uses: actions-ecosystem/action-add-labels@v1 with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" - configuration-path: .github/community-label.yml - enable-versioned-regex: 0 - - name: Assign new internal pull requests to project - uses: elastic/assign-one-project-github-action@1.2.2 - if: (steps.checkUserMember.outputs.isTeamMember == 'true' || steps.checkUserMember.outputs.isExcluded == 'true') && github.event.pull_request - with: - project: 'https://github.com/orgs/elastic/projects/454' - project_id: '5882982' - column_name: 'In Progress' + labels: + - community + - triage From d9c9020a586b7355fad0e1e3a542389e449cfc19 Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Thu, 14 Mar 2024 15:36:24 +0100 Subject: [PATCH 044/409] security: add permissions block to workflows (#1972) * security: add permissions block to workflows * Update .github/workflows/test-reporter.yml Co-authored-by: Victor Martinez * Update .github/workflows/labeler.yml * Remove permissions This will be removed in another PR --------- Co-authored-by: Victor Martinez --- .github/workflows/build-distribution.yml | 3 +++ .github/workflows/packages.yml | 3 +++ .github/workflows/pre-commit.yml | 3 +++ .github/workflows/run-matrix.yml | 3 +++ .github/workflows/test-reporter.yml | 5 +++++ .github/workflows/test.yml | 3 +++ 6 files changed, 20 insertions(+) diff --git a/.github/workflows/build-distribution.yml b/.github/workflows/build-distribution.yml index fd3e11ed7..ba8497e0c 100644 --- a/.github/workflows/build-distribution.yml +++ b/.github/workflows/build-distribution.yml @@ -3,6 +3,9 @@ name: build-distribution on: workflow_call: ~ +permissions: + contents: read + jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index f2d9a4f83..3f63467fe 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -13,6 +13,9 @@ on: - '**/*.md' - '**/*.asciidoc' +permissions: + contents: read + # Override the version if there is no tag release. env: ELASTIC_CI_POST_VERSION: ${{ startsWith(github.ref, 'refs/tags') && '' || github.run_id }} diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 65947d33b..926c21be6 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -5,6 +5,9 @@ on: push: branches: [main] +permissions: + contents: read + jobs: pre-commit: runs-on: ubuntu-latest diff --git a/.github/workflows/run-matrix.yml b/.github/workflows/run-matrix.yml index 827212527..d5db311d6 100644 --- a/.github/workflows/run-matrix.yml +++ b/.github/workflows/run-matrix.yml @@ -8,6 +8,9 @@ on: description: Matrix include JSON string type: string +permissions: + contents: read + jobs: docker: name: "docker (version: ${{ matrix.version }}, framework: ${{ matrix.framework }})" diff --git a/.github/workflows/test-reporter.yml b/.github/workflows/test-reporter.yml index 4b0b7620d..1060771c5 100644 --- a/.github/workflows/test-reporter.yml +++ b/.github/workflows/test-reporter.yml @@ -8,6 +8,11 @@ on: types: - completed +permissions: + contents: read + actions: read + checks: write + jobs: report: runs-on: ubuntu-latest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4638ab5d3..cae9a846e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,6 +30,9 @@ on: required: true type: boolean +permissions: + contents: read + jobs: build-distribution: uses: ./.github/workflows/build-distribution.yml From 09d40b902cc73c9856497fb1e33b01eb2c82e16d Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 14 Mar 2024 15:37:04 +0100 Subject: [PATCH 045/409] ci: remove adding issues to the old APM agents board (#2004) --- .github/workflows/addToProject.yml | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 .github/workflows/addToProject.yml diff --git a/.github/workflows/addToProject.yml b/.github/workflows/addToProject.yml deleted file mode 100644 index 0a3b76924..000000000 --- a/.github/workflows/addToProject.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Auto Assign to Project(s) - -on: - issues: - types: [opened, edited, milestoned] -env: - MY_GITHUB_TOKEN: ${{ secrets.APM_TECH_USER_TOKEN }} - -jobs: - assign_one_project: - runs-on: ubuntu-latest - name: Assign milestoned to Project - steps: - - name: Assign issues with milestones to project - uses: elastic/assign-one-project-github-action@1.2.2 - if: github.event.issue && github.event.issue.milestone - with: - project: 'https://github.com/orgs/elastic/projects/454' - project_id: '5882982' - column_name: 'Planned' From adff59cb29214f661b3569725aafb1a670d986a9 Mon Sep 17 00:00:00 2001 From: Brandon Morelli Date: Mon, 18 Mar 2024 16:19:29 -0700 Subject: [PATCH 046/409] docs: Update flask.asciidoc (#2006) --- docs/flask.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/flask.asciidoc b/docs/flask.asciidoc index 9fb8f7e3e..b99ddd198 100644 --- a/docs/flask.asciidoc +++ b/docs/flask.asciidoc @@ -76,7 +76,7 @@ apm = ElasticAPM(app, service_name='', secret_token='') ===== Debug mode NOTE: Please note that errors and transactions will only be sent to the APM Server if your app is *not* in -http://flask.pocoo.org/docs/2.3.x/quickstart/#debug-mode[Flask debug mode]. +https://flask.palletsprojects.com/en/3.0.x/quickstart/#debug-mode[Flask debug mode]. To force the agent to send data while the app is in debug mode, set the value of `DEBUG` in the `ELASTIC_APM` dictionary to `True`: From 49b4727dba4e278e94b5a04f9b48c99bdd0c6ff2 Mon Sep 17 00:00:00 2001 From: Trent Mick Date: Tue, 19 Mar 2024 01:16:39 -0700 Subject: [PATCH 047/409] ci: fix labeler workflow syntax (#2007) Refs: #2003 --- .github/workflows/labeler.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 564cc7d31..a14b036c0 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -5,7 +5,7 @@ on: pull_request_target: types: [opened] -# 'issues: write' for https://docs.github.com/en/rest/issues/labels?apiVersion=2022-11-28#add-labels-to-an-issue +# '*: write' permissions for https://docs.github.com/en/rest/issues/labels?apiVersion=2022-11-28#add-labels-to-an-issue permissions: contents: read issues: write @@ -28,6 +28,6 @@ jobs: if: contains(steps.is_elastic_member.outputs.result, 'false') && github.actor != 'dependabot[bot]' && github.actor != 'apmmachine' uses: actions-ecosystem/action-add-labels@v1 with: - labels: - - community - - triage + labels: | + community + triage From 2383dc2d8c6797d835ca2b1c29ef640a0be728ca Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 19 Mar 2024 10:35:35 +0100 Subject: [PATCH 048/409] Add Django 4.2 LTS and Django 5.0 to test matrix (#1985) * ci: add Django 4.2 LTS and Django 5.0 to test matrix Closes #1946 * tests: remove django tests for old and not tested versions --- .ci/.matrix_exclude.yml | 22 ++++++ .ci/.matrix_framework.yml | 5 +- .ci/.matrix_framework_full.yml | 3 + tests/contrib/django/django_tests.py | 70 +------------------ tests/requirements/reqs-celery-5-django-5.txt | 2 + tests/requirements/reqs-django-4.2.txt | 3 + tests/requirements/reqs-django-5.0.txt | 3 + 7 files changed, 39 insertions(+), 69 deletions(-) create mode 100644 tests/requirements/reqs-celery-5-django-5.txt create mode 100644 tests/requirements/reqs-django-4.2.txt create mode 100644 tests/requirements/reqs-django-5.0.txt diff --git a/.ci/.matrix_exclude.yml b/.ci/.matrix_exclude.yml index 5eb71a09f..b3e735566 100644 --- a/.ci/.matrix_exclude.yml +++ b/.ci/.matrix_exclude.yml @@ -9,12 +9,34 @@ exclude: FRAMEWORK: django-4.0 - VERSION: python-3.7 FRAMEWORK: django-4.0 + # Django 4.2 requires Python 3.8+ + - VERSION: python-3.6 + FRAMEWORK: django-4.2 + - VERSION: python-3.7 + FRAMEWORK: django-4.2 + # Django 5.0 requires Python 3.10+ + - VERSION: python-3.6 + FRAMEWORK: django-5.0 + - VERSION: python-3.7 + FRAMEWORK: django-5.0 + - VERSION: python-3.8 + FRAMEWORK: django-5.0 + - VERSION: python-3.9 + FRAMEWORK: django-5.0 - VERSION: pypy-3 # current pypy-3 is compatible with Python 3.7 FRAMEWORK: celery-5-django-4 - VERSION: python-3.6 FRAMEWORK: celery-5-django-4 - VERSION: python-3.7 FRAMEWORK: celery-5-django-4 + - VERSION: python-3.6 + FRAMEWORK: celery-5-django-5 + - VERSION: python-3.7 + FRAMEWORK: celery-5-django-5 + - VERSION: python-3.8 + FRAMEWORK: celery-5-django-5 + - VERSION: python-3.9 + FRAMEWORK: celery-5-django-5 # Flask - VERSION: pypy-3 FRAMEWORK: flask-0.11 # see https://github.com/pallets/flask/commit/6e46d0cd, 0.11.2 was never released diff --git a/.ci/.matrix_framework.yml b/.ci/.matrix_framework.yml index 6bf64ab44..007827327 100644 --- a/.ci/.matrix_framework.yml +++ b/.ci/.matrix_framework.yml @@ -3,10 +3,10 @@ FRAMEWORK: - none - django-1.11 - - django-2.0 - - django-3.1 - django-3.2 - django-4.0 + - django-4.2 + - django-5.0 - flask-0.12 - flask-1.1 - flask-2.0 @@ -18,6 +18,7 @@ FRAMEWORK: - celery-4-django-2.0 - celery-5-flask-2 - celery-5-django-4 + - celery-5-django-5 - requests-newest - boto3-newest - pymongo-newest diff --git a/.ci/.matrix_framework_full.yml b/.ci/.matrix_framework_full.yml index 7b1ee213e..1cb11ae16 100644 --- a/.ci/.matrix_framework_full.yml +++ b/.ci/.matrix_framework_full.yml @@ -10,6 +10,8 @@ FRAMEWORK: - django-3.1 - django-3.2 - django-4.0 + - django-4.2 + - django-5.0 # - django-master - flask-0.10 - flask-0.11 @@ -25,6 +27,7 @@ FRAMEWORK: - celery-5-flask-2 - celery-5-django-3 - celery-5-django-4 + - celery-5-django-5 - opentelemetry-newest - opentracing-newest - opentracing-2.0 diff --git a/tests/contrib/django/django_tests.py b/tests/contrib/django/django_tests.py index 547d46b51..535729bcf 100644 --- a/tests/contrib/django/django_tests.py +++ b/tests/contrib/django/django_tests.py @@ -270,25 +270,7 @@ def test_user_info_with_custom_user_non_string_username(django_elasticapm_client assert user_info["username"] == "1" -@pytest.mark.skipif(django.VERSION > (1, 9), reason="MIDDLEWARE_CLASSES removed in Django 2.0") def test_user_info_with_non_django_auth(django_elasticapm_client, client): - with override_settings( - INSTALLED_APPS=[app for app in settings.INSTALLED_APPS if app != "django.contrib.auth"] - ) and override_settings( - MIDDLEWARE_CLASSES=[ - m for m in settings.MIDDLEWARE_CLASSES if m != "django.contrib.auth.middleware.AuthenticationMiddleware" - ] - ): - with pytest.raises(Exception): - resp = client.get(reverse("elasticapm-raise-exc")) - - assert len(django_elasticapm_client.events[ERROR]) == 1 - event = django_elasticapm_client.events[ERROR][0] - assert event["context"]["user"] == {} - - -@pytest.mark.skipif(django.VERSION < (1, 10), reason="MIDDLEWARE new in Django 1.10") -def test_user_info_with_non_django_auth_django_2(django_elasticapm_client, client): with override_settings( INSTALLED_APPS=[app for app in settings.INSTALLED_APPS if app != "django.contrib.auth"] ) and override_settings( @@ -303,22 +285,7 @@ def test_user_info_with_non_django_auth_django_2(django_elasticapm_client, clien assert event["context"]["user"] == {} -@pytest.mark.skipif(django.VERSION > (1, 9), reason="MIDDLEWARE_CLASSES removed in Django 2.0") def test_user_info_without_auth_middleware(django_elasticapm_client, client): - with override_settings( - MIDDLEWARE_CLASSES=[ - m for m in settings.MIDDLEWARE_CLASSES if m != "django.contrib.auth.middleware.AuthenticationMiddleware" - ] - ): - with pytest.raises(Exception): - client.get(reverse("elasticapm-raise-exc")) - assert len(django_elasticapm_client.events[ERROR]) == 1 - event = django_elasticapm_client.events[ERROR][0] - assert event["context"]["user"] == {} - - -@pytest.mark.skipif(django.VERSION < (1, 10), reason="MIDDLEWARE new in Django 1.10") -def test_user_info_without_auth_middleware_django_2(django_elasticapm_client, client): with override_settings( MIDDLEWARE_CLASSES=None, MIDDLEWARE=[m for m in settings.MIDDLEWARE if m != "django.contrib.auth.middleware.AuthenticationMiddleware"], @@ -614,8 +581,7 @@ def read(): assert_any_record_contains(caplog.records, "Can't capture request body: foobar") -@pytest.mark.skipif(django.VERSION < (1, 9), reason="get-raw-uri-not-available") -def test_disallowed_hosts_error_django_19(django_elasticapm_client): +def test_disallowed_hosts_error(django_elasticapm_client): request = WSGIRequest( environ={ "wsgi.input": io.BytesIO(), @@ -634,26 +600,6 @@ def test_disallowed_hosts_error_django_19(django_elasticapm_client): assert event["context"]["request"]["url"]["full"] == "http://testserver/" -@pytest.mark.skipif(django.VERSION >= (1, 9), reason="get-raw-uri-available") -def test_disallowed_hosts_error_django_18(django_elasticapm_client): - request = WSGIRequest( - environ={ - "wsgi.input": io.BytesIO(), - "wsgi.url_scheme": "http", - "REQUEST_METHOD": "POST", - "SERVER_NAME": "testserver", - "SERVER_PORT": "80", - "CONTENT_TYPE": "application/json", - "ACCEPT": "application/json", - } - ) - with override_settings(ALLOWED_HOSTS=["example.com"]): - # this should not raise a DisallowedHost exception - django_elasticapm_client.capture("Message", message="foo", request=request) - event = django_elasticapm_client.events[ERROR][0] - assert event["context"]["request"]["url"] == {"full": "DisallowedHost"} - - @pytest.mark.parametrize( "django_elasticapm_client", [{"capture_body": "errors"}, {"capture_body": "all"}, {"capture_body": "off"}], @@ -1196,16 +1142,6 @@ def test_stacktrace_filtered_for_elasticapm(client, django_elasticapm_client): assert spans[1]["stacktrace"][0]["module"].startswith("django.template"), spans[1]["stacktrace"][0]["function"] -@pytest.mark.skipif(django.VERSION > (1, 7), reason="argparse raises CommandError in this case") -@mock.patch("elasticapm.contrib.django.management.commands.elasticapm.Command._get_argv") -def test_subcommand_not_set(argv_mock): - stdout = io.StringIO() - argv_mock.return_value = ["manage.py", "elasticapm"] - call_command("elasticapm", stdout=stdout) - output = stdout.getvalue() - assert "No command specified" in output - - @mock.patch("elasticapm.contrib.django.management.commands.elasticapm.Command._get_argv") def test_subcommand_not_known(argv_mock): stdout = io.StringIO() @@ -1317,8 +1253,8 @@ def test_settings_server_url_with_credentials(): @pytest.mark.skipif( - not ((1, 10) <= django.VERSION < (2, 0)), - reason="only needed in 1.10 and 1.11 when both middleware settings are valid", + django.VERSION >= (2, 0), + reason="only needed in 1.11 when both middleware settings are valid", ) def test_django_1_10_uses_deprecated_MIDDLEWARE_CLASSES(): stdout = io.StringIO() diff --git a/tests/requirements/reqs-celery-5-django-5.txt b/tests/requirements/reqs-celery-5-django-5.txt new file mode 100644 index 000000000..b528dcb85 --- /dev/null +++ b/tests/requirements/reqs-celery-5-django-5.txt @@ -0,0 +1,2 @@ +-r reqs-celery-5.txt +-r reqs-django-5.0.txt diff --git a/tests/requirements/reqs-django-4.2.txt b/tests/requirements/reqs-django-4.2.txt new file mode 100644 index 000000000..6818ea895 --- /dev/null +++ b/tests/requirements/reqs-django-4.2.txt @@ -0,0 +1,3 @@ +Django>=4.2,<5.0 +jinja2<4 +-r reqs-base.txt diff --git a/tests/requirements/reqs-django-5.0.txt b/tests/requirements/reqs-django-5.0.txt new file mode 100644 index 000000000..dd2e1cea6 --- /dev/null +++ b/tests/requirements/reqs-django-5.0.txt @@ -0,0 +1,3 @@ +Django>=5.0,<5.1 +jinja2<4 +-r reqs-base.txt From da883f43081a6c4b4f0fc837e55e48f0fc7f2464 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 19 Mar 2024 17:49:40 +0100 Subject: [PATCH 049/409] update CHANGELOG and bump version to 6.21.4 (#2008) --- CHANGELOG.asciidoc | 8 ++++++++ elasticapm/version.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 57a6cb95f..588030c14 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -32,6 +32,14 @@ endif::[] [[release-notes-6.x]] === Python Agent version 6.x +[[release-notes-6.21.4]] +==== 6.21.4 - 2024-03-19 + +[float] +===== Bug fixes + +* Fix urllib3 2.0.1+ crash with many args {pull}2002[#2002] + [[release-notes-6.21.3]] ==== 6.21.3 - 2024-03-08 diff --git a/elasticapm/version.py b/elasticapm/version.py index 6da6c370d..d2ebe8c73 100644 --- a/elasticapm/version.py +++ b/elasticapm/version.py @@ -28,5 +28,5 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = (6, 21, 3) +__version__ = (6, 21, 4) VERSION = ".".join(map(str, __version__)) From ff57866a18eb4ac3993c598b6f497303ece2c6fb Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 21 Mar 2024 09:56:22 +0100 Subject: [PATCH 050/409] docs: Add Django 4.2 and 5.0 to supported (#2010) --- docs/supported-technologies.asciidoc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/supported-technologies.asciidoc b/docs/supported-technologies.asciidoc index 9a3b314d1..51358b0fd 100644 --- a/docs/supported-technologies.asciidoc +++ b/docs/supported-technologies.asciidoc @@ -42,6 +42,8 @@ We support these Django versions: * 3.1 * 3.2 * 4.0 + * 4.2 + * 5.0 For upcoming Django versions, we generally aim to ensure compatibility starting with the first Release Candidate. From f732b90e36ff5e831e112364d164b4b48fb8d717 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 21 Mar 2024 09:56:41 +0100 Subject: [PATCH 051/409] ci: add Flask 2.1+ and 3.0 to matrix (#2011) --- .ci/.matrix_exclude.yml | 12 ++++++++++++ .ci/.matrix_framework.yml | 4 ++-- .ci/.matrix_framework_full.yml | 4 ++++ .gitignore | 1 + docs/supported-technologies.asciidoc | 4 ++++ tests/requirements/reqs-flask-2.0.txt | 2 +- tests/requirements/reqs-flask-2.1.txt | 4 ++++ tests/requirements/reqs-flask-2.2.txt | 4 ++++ tests/requirements/reqs-flask-2.3.txt | 4 ++++ tests/requirements/reqs-flask-3.0.txt | 3 +++ 10 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 tests/requirements/reqs-flask-2.1.txt create mode 100644 tests/requirements/reqs-flask-2.2.txt create mode 100644 tests/requirements/reqs-flask-2.3.txt create mode 100644 tests/requirements/reqs-flask-3.0.txt diff --git a/.ci/.matrix_exclude.yml b/.ci/.matrix_exclude.yml index b3e735566..eb7d57779 100644 --- a/.ci/.matrix_exclude.yml +++ b/.ci/.matrix_exclude.yml @@ -40,6 +40,18 @@ exclude: # Flask - VERSION: pypy-3 FRAMEWORK: flask-0.11 # see https://github.com/pallets/flask/commit/6e46d0cd, 0.11.2 was never released + - VERSION: python-3.6 + FRAMEWORK: flask-2.1 + - VERSION: python-3.6 + FRAMEWORK: flask-2.2 + - VERSION: python-3.6 + FRAMEWORK: flask-2.3 + - VERSION: python-3.6 + FRAMEWORK: flask-3.0 + - VERSION: python-3.7 + FRAMEWORK: flask-2.3 + - VERSION: python-3.7 + FRAMEWORK: flask-3.0 # Python 3.10 removed a bunch of classes from collections, now in collections.abc - VERSION: python-3.10 FRAMEWORK: django-1.11 diff --git a/.ci/.matrix_framework.yml b/.ci/.matrix_framework.yml index 007827327..df04f639c 100644 --- a/.ci/.matrix_framework.yml +++ b/.ci/.matrix_framework.yml @@ -8,8 +8,8 @@ FRAMEWORK: - django-4.2 - django-5.0 - flask-0.12 - - flask-1.1 - - flask-2.0 + - flask-2.3 + - flask-3.0 - jinja2-3 - opentelemetry-newest - opentracing-newest diff --git a/.ci/.matrix_framework_full.yml b/.ci/.matrix_framework_full.yml index 1cb11ae16..cb4361711 100644 --- a/.ci/.matrix_framework_full.yml +++ b/.ci/.matrix_framework_full.yml @@ -19,6 +19,10 @@ FRAMEWORK: - flask-1.0 - flask-1.1 - flask-2.0 + - flask-2.1 + - flask-2.2 + - flask-2.3 + - flask-3.0 - jinja2-2 - jinja2-3 - celery-4-flask-1.0 diff --git a/.gitignore b/.gitignore index 88e0a400b..65dc23f5e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.egg *.db *.pid +*.swp .coverage* .DS_Store .idea diff --git a/docs/supported-technologies.asciidoc b/docs/supported-technologies.asciidoc index 51358b0fd..f170617a2 100644 --- a/docs/supported-technologies.asciidoc +++ b/docs/supported-technologies.asciidoc @@ -61,6 +61,10 @@ We support these Flask versions: * 1.0 * 1.1 * 2.0 + * 2.1 + * 2.2 + * 2.3 + * 3.0 [float] [[supported-aiohttp]] diff --git a/tests/requirements/reqs-flask-2.0.txt b/tests/requirements/reqs-flask-2.0.txt index d68be1afa..62397f9b5 100644 --- a/tests/requirements/reqs-flask-2.0.txt +++ b/tests/requirements/reqs-flask-2.0.txt @@ -1,4 +1,4 @@ -Flask>=2.0,<3 +Flask>=2.0,<2.1 blinker>=1.1 itsdangerous -r reqs-base.txt diff --git a/tests/requirements/reqs-flask-2.1.txt b/tests/requirements/reqs-flask-2.1.txt new file mode 100644 index 000000000..84d89b8b9 --- /dev/null +++ b/tests/requirements/reqs-flask-2.1.txt @@ -0,0 +1,4 @@ +Flask>=2.1,<2.2 +blinker>=1.1 +itsdangerous +-r reqs-base.txt diff --git a/tests/requirements/reqs-flask-2.2.txt b/tests/requirements/reqs-flask-2.2.txt new file mode 100644 index 000000000..0b244a851 --- /dev/null +++ b/tests/requirements/reqs-flask-2.2.txt @@ -0,0 +1,4 @@ +Flask>=2.2,<2.3 +blinker>=1.1 +itsdangerous +-r reqs-base.txt diff --git a/tests/requirements/reqs-flask-2.3.txt b/tests/requirements/reqs-flask-2.3.txt new file mode 100644 index 000000000..08434994e --- /dev/null +++ b/tests/requirements/reqs-flask-2.3.txt @@ -0,0 +1,4 @@ +Flask>=2.3,<3 +blinker>=1.1 +itsdangerous +-r reqs-base.txt diff --git a/tests/requirements/reqs-flask-3.0.txt b/tests/requirements/reqs-flask-3.0.txt new file mode 100644 index 000000000..92120aa14 --- /dev/null +++ b/tests/requirements/reqs-flask-3.0.txt @@ -0,0 +1,3 @@ +Flask>=3.0,<3.1 +itsdangerous +-r reqs-base.txt From db4029207038e42d89fd55279594c575fe633265 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 22 Mar 2024 19:37:56 +0100 Subject: [PATCH 052/409] CONTRIBUTING: add more hints on what is required to cut a release (#1989) * CONTRIBUTING: open PRs to the main branch * CONTRIBUTING: add more hints on what you need to setup for releasing Fixes #1991 --- CONTRIBUTING.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f31f6c3c9..014b4828d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -64,7 +64,7 @@ Once your changes are ready to submit for review: 1. Submit a pull request - Push your local changes to your forked copy of the repository and [submit a pull request](https://help.github.com/articles/using-pull-requests). + Push your local changes to your forked copy of the repository and [submit a pull request](https://help.github.com/articles/using-pull-requests) to the `main` branch. In the pull request, choose a title which sums up the changes that you have made, and in the body provide more details about what your changes do. @@ -174,6 +174,11 @@ should "Squash and merge". ### Releasing +Releases tags are signed so you need to have a PGP key set up, you can follow Github documentation on [creating a key](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key) and +on [telling git about it](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key). Alternatively you can sign with a SSH key, remember you have to upload your key +again even if you want to use the same key you are using for authorization. +Then make sure you have SSO figured out for the key you are using to push to github, see [Github documentation](https://docs.github.com/articles/authenticating-to-a-github-organization-with-saml-single-sign-on/). + If you have commit access, the process is as follows: 1. Update the version in `elasticapm/version.py` according to the scale of the change. (major, minor or patch) @@ -182,13 +187,14 @@ If you have commit access, the process is as follows: 1. For Majors: Add the new major version to `conf.yaml` in the [elastic/docs](https://github.com/elastic/docs) repo. 1. Commit changes with message `update CHANGELOG and bump version to X.Y.Z` where `X.Y.Z` is the version in `elasticapm/version.py` -1. Open a PR against `main` with these changes +1. Open a PR against `main` with these changes leaving the body empty 1. Once the PR is merged, fetch and checkout `upstream/main` 1. Tag the commit with `git tag -s vX.Y.Z`, for example `git tag -s v1.2.3`. Copy the changelog for the release to the tag message, removing any leading `#`. -1. Reset the current major branch (`1.x`, `2.x` etc) to point to the current main, e.g. `git branch -f 1.x main` 1. Push tag upstream with `git push upstream --tags` (and optionally to your own fork as well) -1. Update major branch, e.g. `1.x` on upstream with `git push upstream 1.x` +1. Open a PR from `main` to the major branch, e.g. `1.x` to update it. In order to keep history you may want to + merge with the `rebase` strategy. It is crucial that `main` and the major branch have the same content. 1. After tests pass, Github Actions will automatically build and push the new release to PyPI. 1. Edit and publish the [draft Github release](https://github.com/elastic/apm-agent-python/releases) - created by Github Actions. Copy the changelog into the body of the release. + created by Github Actions. Substitute the generated changelog with one hand written into the body of the + release and move the agent layer ARNs under a `
` block with a `summary`. From d43253f4d88683b5e4b719934d700e697d2c7607 Mon Sep 17 00:00:00 2001 From: Adrien Mannocci Date: Wed, 27 Mar 2024 14:05:24 +0100 Subject: [PATCH 053/409] fix: only set ELASTIC_CI_POST_VERSION when needed (Closes #2015) (#2016) Signed-off-by: Adrien Mannocci --- .github/workflows/packages.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 3f63467fe..4f04d78a4 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -16,10 +16,6 @@ on: permissions: contents: read -# Override the version if there is no tag release. -env: - ELASTIC_CI_POST_VERSION: ${{ startsWith(github.ref, 'refs/tags') && '' || github.run_id }} - jobs: build: runs-on: ubuntu-latest @@ -28,6 +24,11 @@ jobs: - uses: actions/setup-python@v5 with: python-version: "3.10" + - name: Override the version if there is no tag release. + run: | + if [[ "${GITHUB_REF}" != refs/tags/* ]]; then + echo "ELASTIC_CI_POST_VERSION=${{ github.run_id }}" >> "${GITHUB_ENV}" + fi - name: Install wheel run: pip install --user wheel - name: Building universal wheel @@ -41,4 +42,3 @@ jobs: path: | dist/*.whl dist/*tar.gz - From b78d465fc69f61a48a1d9649d6af373ca51dcfce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Mar 2024 12:05:14 +0100 Subject: [PATCH 054/409] build(deps): bump the github-actions group with 1 update (#2013) Bumps the github-actions group with 1 update: [geekyeggo/delete-artifact](https://github.com/geekyeggo/delete-artifact). Updates `geekyeggo/delete-artifact` from 4.1.0 to 5.0.0 - [Release notes](https://github.com/geekyeggo/delete-artifact/releases) - [Changelog](https://github.com/GeekyEggo/delete-artifact/blob/main/CHANGELOG.md) - [Commits](https://github.com/geekyeggo/delete-artifact/compare/65041433121f7239077fa20be14c0690f70569de...24928e75e6e6590170563b8ddae9fac674508aa1) --- updated-dependencies: - dependency-name: geekyeggo/delete-artifact dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cae9a846e..381c317b0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -215,6 +215,6 @@ jobs: with: name: html-coverage-report path: htmlcov - - uses: geekyeggo/delete-artifact@65041433121f7239077fa20be14c0690f70569de + - uses: geekyeggo/delete-artifact@24928e75e6e6590170563b8ddae9fac674508aa1 with: name: coverage-reports From 36883408722cc33dbc390740a5f6a38ce31fa79a Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 29 Mar 2024 21:55:35 +0100 Subject: [PATCH 055/409] Add ability to override default serialization (#2018) * utils: add simplejson based JSON handling Add an alternative json serialization and deserialization implementation using simplejson configured to be JSON compliant by converting floats like nan, +inf and -inf to null . The idea is to permit users to use a different serialization if they need a different behaviour. Refs #1886 * client: fix overriding of transport json serializer We get a string from config so need to import it. --- elasticapm/base.py | 3 +- elasticapm/utils/json_encoder.py | 6 +- elasticapm/utils/simplejson_encoder.py | 58 +++++++++++++++ tests/client/client_tests.py | 13 ++++ tests/requirements/reqs-base.txt | 1 + tests/utils/json_utils/tests.py | 7 ++ tests/utils/json_utils/tests_simplejson.py | 86 ++++++++++++++++++++++ 7 files changed, 168 insertions(+), 6 deletions(-) create mode 100644 elasticapm/utils/simplejson_encoder.py create mode 100644 tests/utils/json_utils/tests_simplejson.py diff --git a/elasticapm/base.py b/elasticapm/base.py index 5f50dc79d..ff2fd6e25 100644 --- a/elasticapm/base.py +++ b/elasticapm/base.py @@ -155,7 +155,8 @@ def __init__(self, config=None, **inline) -> None: "processors": self.load_processors(), } if config.transport_json_serializer: - transport_kwargs["json_serializer"] = config.transport_json_serializer + json_serializer_func = import_string(config.transport_json_serializer) + transport_kwargs["json_serializer"] = json_serializer_func self._api_endpoint_url = urllib.parse.urljoin( self.config.server_url if self.config.server_url.endswith("/") else self.config.server_url + "/", diff --git a/elasticapm/utils/json_encoder.py b/elasticapm/utils/json_encoder.py index c40e0accd..3918bb233 100644 --- a/elasticapm/utils/json_encoder.py +++ b/elasticapm/utils/json_encoder.py @@ -31,13 +31,9 @@ import datetime import decimal +import json import uuid -try: - import json -except ImportError: - import simplejson as json - class BetterJSONEncoder(json.JSONEncoder): ENCODERS = { diff --git a/elasticapm/utils/simplejson_encoder.py b/elasticapm/utils/simplejson_encoder.py new file mode 100644 index 000000000..f538ffdac --- /dev/null +++ b/elasticapm/utils/simplejson_encoder.py @@ -0,0 +1,58 @@ +# BSD 3-Clause License +# +# Copyright (c) 2012, the Sentry Team, see AUTHORS for more details +# Copyright (c) 2019, Elasticsearch BV +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + + +import simplejson as json + +from elasticapm.utils.json_encoder import BetterJSONEncoder + + +class BetterSimpleJSONEncoder(json.JSONEncoder): + ENCODERS = BetterJSONEncoder.ENCODERS + + def default(self, obj): + if type(obj) in self.ENCODERS: + return self.ENCODERS[type(obj)](obj) + try: + return super(BetterSimpleJSONEncoder, self).default(obj) + except TypeError: + return str(obj) + + +def better_decoder(data): + return data + + +def dumps(value, **kwargs): + return json.dumps(value, cls=BetterSimpleJSONEncoder, ignore_nan=True, **kwargs) + + +def loads(value, **kwargs): + return json.loads(value, object_hook=better_decoder) diff --git a/tests/client/client_tests.py b/tests/client/client_tests.py index af266b710..84e473fc8 100644 --- a/tests/client/client_tests.py +++ b/tests/client/client_tests.py @@ -48,6 +48,11 @@ import elasticapm from elasticapm.base import Client from elasticapm.conf.constants import ERROR + +try: + from elasticapm.utils.simplejson_encoder import dumps as simplejson_dumps +except ImportError: + simplejson_dumps = None from tests.fixtures import DummyTransport, TempStoreClient from tests.utils import assert_any_record_contains @@ -228,6 +233,14 @@ def test_custom_transport(elasticapm_client): assert isinstance(elasticapm_client._transport, DummyTransport) +@pytest.mark.skipIf(simplejson_dumps is None) +@pytest.mark.parametrize( + "elasticapm_client", [{"transport_json_serializer": "elasticapm.utils.simplejson_encoder.dumps"}], indirect=True +) +def test_custom_transport_json_serializer(elasticapm_client): + assert elasticapm_client._transport._json_serializer == simplejson_dumps + + @pytest.mark.parametrize("elasticapm_client", [{"processors": []}], indirect=True) def test_empty_processor_list(elasticapm_client): assert elasticapm_client.processors == [] diff --git a/tests/requirements/reqs-base.txt b/tests/requirements/reqs-base.txt index 42bac1bb8..747e42631 100644 --- a/tests/requirements/reqs-base.txt +++ b/tests/requirements/reqs-base.txt @@ -30,6 +30,7 @@ pytz ecs_logging structlog wrapt>=1.14.1,<1.15.0 +simplejson pytest-asyncio==0.21.0 ; python_version >= '3.7' asynctest==0.13.0 ; python_version >= '3.7' diff --git a/tests/utils/json_utils/tests.py b/tests/utils/json_utils/tests.py index 7cbef4b36..28791e79d 100644 --- a/tests/utils/json_utils/tests.py +++ b/tests/utils/json_utils/tests.py @@ -36,6 +36,8 @@ import decimal import uuid +import pytest + from elasticapm.utils import json_encoder as json @@ -69,6 +71,11 @@ def test_decimal(): assert json.dumps(res) == "1.0" +@pytest.mark.parametrize("res", [float("nan"), float("+inf"), float("-inf")]) +def test_float_invalid_json(res): + assert json.dumps(res) != "null" + + def test_unsupported(): res = object() assert json.dumps(res).startswith('" Date: Tue, 2 Apr 2024 10:00:41 +0200 Subject: [PATCH 056/409] tests/client: fix pytest mark skipif syntax (#2019) For some reason tests passed with this but now are failing because pytest test does not recognize skipIf. --- tests/client/client_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/client/client_tests.py b/tests/client/client_tests.py index 84e473fc8..a61248c85 100644 --- a/tests/client/client_tests.py +++ b/tests/client/client_tests.py @@ -233,7 +233,7 @@ def test_custom_transport(elasticapm_client): assert isinstance(elasticapm_client._transport, DummyTransport) -@pytest.mark.skipIf(simplejson_dumps is None) +@pytest.mark.skipif(simplejson_dumps is None, reason="no test without simplejson") @pytest.mark.parametrize( "elasticapm_client", [{"transport_json_serializer": "elasticapm.utils.simplejson_encoder.dumps"}], indirect=True ) From 309337624d5435587b179a39cbddbfc6b7008ae7 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 3 Apr 2024 09:20:37 +0200 Subject: [PATCH 057/409] tests: require Werkzeug < 3 for flask 2.0 tests (#2020) Otherwise it'll skip tests becasue flask import does not work. --- tests/requirements/reqs-flask-2.0.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/requirements/reqs-flask-2.0.txt b/tests/requirements/reqs-flask-2.0.txt index 62397f9b5..dc3cb6572 100644 --- a/tests/requirements/reqs-flask-2.0.txt +++ b/tests/requirements/reqs-flask-2.0.txt @@ -1,4 +1,5 @@ Flask>=2.0,<2.1 +Werkzeug<3 blinker>=1.1 itsdangerous -r reqs-base.txt From a79d8b2c67c772a0451da1536b360a0cb404a504 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 3 Apr 2024 09:20:59 +0200 Subject: [PATCH 058/409] Use docker compose subcommand instead of docker-compose (#2022) --- CONTRIBUTING.md | 4 ++-- tests/scripts/docker/run_tests.sh | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 014b4828d..21180e9f1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -108,7 +108,7 @@ that is a fixture which is defined #### Adding new instrumentations to the matrix build For tests that require external dependencies like databases, or for testing different versions of the same library, -we use a matrix build that leverages Docker and docker-compose. +we use a matrix build that leverages Docker. The setup requires a little bit of boilerplate to get started. In this example, we will create an instrumentation for the "foo" database, by instrumenting its Python driver, `foodriver`. @@ -153,7 +153,7 @@ In this example, we will create an instrumentation for the "foo" database, by in image: foobase:latest You'll also have to add a `DOCKER_DEPS` environment variable to `tests/scripts/envs/foo.sh` which tells the matrix - to spin up the given docker-compose service before running your tests. + to spin up the given Docker compose service before running your tests. You may also need to add things like hostname configuration here. DOCKER_DEPS="foo" diff --git a/tests/scripts/docker/run_tests.sh b/tests/scripts/docker/run_tests.sh index 9a89f93be..de518dc32 100755 --- a/tests/scripts/docker/run_tests.sh +++ b/tests/scripts/docker/run_tests.sh @@ -2,7 +2,7 @@ set -ex function cleanup { - PYTHON_VERSION=${1} docker-compose down -v + PYTHON_VERSION=${1} docker compose down -v if [[ $CODECOV_TOKEN ]]; then cd .. @@ -42,7 +42,7 @@ echo "Running tests for ${1}/${2}" if [[ -n $DOCKER_DEPS ]] then - PYTHON_VERSION=${1} docker-compose up -d ${DOCKER_DEPS} + PYTHON_VERSION=${1} docker compose up -d ${DOCKER_DEPS} fi # CASS_DRIVER_NO_EXTENSIONS is set so we don't build the Cassandra C-extensions, @@ -57,7 +57,7 @@ if ! ${CI}; then . fi -PYTHON_VERSION=${1} docker-compose run \ +PYTHON_VERSION=${1} docker compose run \ -e PYTHON_FULL_VERSION=${1} \ -e LOCAL_USER_ID=$LOCAL_USER_ID \ -e LOCAL_GROUP_ID=$LOCAL_GROUP_ID \ From 2e2ea47d7d143f74d3217e9206f98242e780b3c1 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 3 Apr 2024 09:38:55 +0200 Subject: [PATCH 059/409] Remove vendored wrapt references (#2021) --- .flake8 | 1 - .gitignore | 1 - pyproject.toml | 1 - tests/scripts/license_headers_check.sh | 2 +- 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.flake8 b/.flake8 index f629a9d29..7be07fbeb 100644 --- a/.flake8 +++ b/.flake8 @@ -1,6 +1,5 @@ [flake8] exclude= - elasticapm/utils/wrapt/**, build/**, src/**, tests/**, diff --git a/.gitignore b/.gitignore index 65dc23f5e..12eae962b 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,6 @@ pip-log.txt /docs/doctrees /example_project/*.db tests/.schemacache -elasticapm/utils/wrapt/_wrappers*.so coverage .tox .eggs diff --git a/pyproject.toml b/pyproject.toml index 167532517..019a7b666 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,6 @@ exclude = ''' | _build | build | dist - | elasticapm/utils/wrapt # The following are specific to Black, you probably don't want those. | blib2to3 diff --git a/tests/scripts/license_headers_check.sh b/tests/scripts/license_headers_check.sh index dc239df96..9ba9655e0 100755 --- a/tests/scripts/license_headers_check.sh +++ b/tests/scripts/license_headers_check.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash if [[ $# -eq 0 ]] then - FILES=$(find . -iname "*.py" -not -path "./elasticapm/utils/wrapt/*" -not -path "./dist/*" -not -path "./build/*" -not -path "./tests/utils/stacks/linenos.py") + FILES=$(find . -iname "*.py" -not -path "./dist/*" -not -path "./build/*" -not -path "./tests/utils/stacks/linenos.py") else FILES=$@ fi From 711788ba62f9162bcbd18575f1b792865e202c6b Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 3 Apr 2024 17:04:30 +0200 Subject: [PATCH 060/409] update CHANGELOG and bump version to 6.22.0 (#2023) --- CHANGELOG.asciidoc | 8 ++++++++ elasticapm/version.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 588030c14..03f4e048f 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -32,6 +32,14 @@ endif::[] [[release-notes-6.x]] === Python Agent version 6.x +[[release-notes-6.22.0]] +==== 6.22.0 - 2024-04-03 + +[float] +===== Features + +* Add ability to override default JSON serialization {pull}2018[#2018] + [[release-notes-6.21.4]] ==== 6.21.4 - 2024-03-19 diff --git a/elasticapm/version.py b/elasticapm/version.py index d2ebe8c73..c8799817e 100644 --- a/elasticapm/version.py +++ b/elasticapm/version.py @@ -28,5 +28,5 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = (6, 21, 4) +__version__ = (6, 22, 0) VERSION = ".".join(map(str, __version__)) From f364133bf25ef1c2e7219cbd5d93a42c5c8f83f8 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 4 Apr 2024 17:58:57 +0200 Subject: [PATCH 061/409] CONTRIBUTING: describe how to create the pr to the vX.x branch (#2026) Create a PR from a branch already rebased so that the commit list in the github UI is clean. --- CONTRIBUTING.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 21180e9f1..c58fbb7c6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -192,7 +192,8 @@ If you have commit access, the process is as follows: 1. Tag the commit with `git tag -s vX.Y.Z`, for example `git tag -s v1.2.3`. Copy the changelog for the release to the tag message, removing any leading `#`. 1. Push tag upstream with `git push upstream --tags` (and optionally to your own fork as well) -1. Open a PR from `main` to the major branch, e.g. `1.x` to update it. In order to keep history you may want to +1. Open a PR from `main` to the major branch, e.g. `1.x` to update it. In order to keep history create a + branch from the `main` branch, rebase it on top of the major branch to drop duplicated commits and then merge with the `rebase` strategy. It is crucial that `main` and the major branch have the same content. 1. After tests pass, Github Actions will automatically build and push the new release to PyPI. 1. Edit and publish the [draft Github release](https://github.com/elastic/apm-agent-python/releases) From 085ed0db01f38d6b7d8c56c19952480b2e7e88aa Mon Sep 17 00:00:00 2001 From: apmmachine <58790750+apmmachine@users.noreply.github.com> Date: Wed, 10 Apr 2024 10:30:38 -0400 Subject: [PATCH 062/409] chore: APM agent json server schema a76e999543efb3ba803c9a57dd13a4f6b... (#2028) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ... 3ffa7e1 Made with ❤️️ by updatecli Co-authored-by: apmmachine --- tests/upstream/json-specs/metadata.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/upstream/json-specs/metadata.json b/tests/upstream/json-specs/metadata.json index 7103bbeb5..1122ed68c 100644 --- a/tests/upstream/json-specs/metadata.json +++ b/tests/upstream/json-specs/metadata.json @@ -441,6 +441,14 @@ ], "maxLength": 1024 }, + "host_id": { + "description": "The OpenTelemetry semantic conventions compliant \"host.id\" attribute, if available.", + "type": [ + "null", + "string" + ], + "maxLength": 1024 + }, "hostname": { "description": "Deprecated: Use ConfiguredHostname and DetectedHostname instead. DeprecatedHostname is the host name of the system the service is running on. It does not distinguish between configured and detected hostname and therefore is deprecated and only used if no other hostname information is available.", "type": [ From 71bd1998d39288c998a52731898133565050c07c Mon Sep 17 00:00:00 2001 From: timostrunk Date: Thu, 18 Apr 2024 13:40:23 +0200 Subject: [PATCH 063/409] Relaxed wrapt-dependency to only blacklist 1.15.0 (#2005) --- setup.cfg | 2 +- tests/requirements/reqs-base.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index ce33450a6..2dca4283e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,7 +39,7 @@ zip_safe = false install_requires = urllib3!=2.0.0,<3.0.0 certifi - wrapt>=1.14.1,<1.15.0 # https://github.com/elastic/apm-agent-python/issues/1894 + wrapt>=1.14.1,!=1.15.0 # https://github.com/elastic/apm-agent-python/issues/1894 ecs_logging test_suite=tests diff --git a/tests/requirements/reqs-base.txt b/tests/requirements/reqs-base.txt index 747e42631..849469a79 100644 --- a/tests/requirements/reqs-base.txt +++ b/tests/requirements/reqs-base.txt @@ -29,7 +29,7 @@ mock pytz ecs_logging structlog -wrapt>=1.14.1,<1.15.0 +wrapt>=1.14.1,!=1.15.0 simplejson pytest-asyncio==0.21.0 ; python_version >= '3.7' From 186996ff7d3120dd4baab413f68b80f8c39800d5 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Tue, 23 Apr 2024 18:18:58 +0200 Subject: [PATCH 064/409] github-action: enable provenance (#2014) --- .github/actions/build-distribution/action.yml | 21 +++++++ .github/actions/packages/action.yml | 31 ++++++++++ .github/dependabot.yml | 27 ++++++++ .github/workflows/build-distribution.yml | 23 ------- .github/workflows/packages.yml | 25 ++------ .github/workflows/release.yml | 62 +++++++++++++------ .github/workflows/test.yml | 6 +- dev-utils/make-packages.sh | 16 +++++ 8 files changed, 147 insertions(+), 64 deletions(-) create mode 100644 .github/actions/build-distribution/action.yml create mode 100644 .github/actions/packages/action.yml delete mode 100644 .github/workflows/build-distribution.yml create mode 100755 dev-utils/make-packages.sh diff --git a/.github/actions/build-distribution/action.yml b/.github/actions/build-distribution/action.yml new file mode 100644 index 000000000..bc0d55c29 --- /dev/null +++ b/.github/actions/build-distribution/action.yml @@ -0,0 +1,21 @@ +--- + +name: common build distribution tasks +description: Run the build distribution + +runs: + using: "composite" + steps: + - uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Build lambda layer zip + run: ./dev-utils/make-distribution.sh + shell: bash + + - uses: actions/upload-artifact@v4 + with: + name: build-distribution + path: ./build/ + if-no-files-found: error diff --git a/.github/actions/packages/action.yml b/.github/actions/packages/action.yml new file mode 100644 index 000000000..3ffda1d8c --- /dev/null +++ b/.github/actions/packages/action.yml @@ -0,0 +1,31 @@ +--- + +name: common package tasks +description: Run the packages + +runs: + using: "composite" + steps: + - uses: actions/setup-python@v5 + with: + python-version: "3.10" + - name: Override the version if there is no tag release. + run: | + if [[ "${GITHUB_REF}" != refs/tags/* ]]; then + echo "ELASTIC_CI_POST_VERSION=${{ github.run_id }}" >> "${GITHUB_ENV}" + fi + shell: bash + - name: Build packages + run: ./dev-utils/make-packages.sh + shell: bash + - name: Upload Packages + uses: actions/upload-artifact@v4 + with: + name: packages + path: | + dist/*.whl + dist/*tar.gz + - name: generate build provenance + uses: github-early-access/generate-build-provenance@main + with: + subject-path: "${{ github.workspace }}/dist/*" \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml index eb1cff95b..afb941790 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -29,3 +29,30 @@ updates: github-actions: patterns: - "*" + + # GitHub composite actions + - package-ecosystem: "github-actions" + directory: "/.github/actions/packages" + reviewers: + - "elastic/observablt-ci" + schedule: + interval: "weekly" + day: "sunday" + time: "22:00" + groups: + github-actions: + patterns: + - "*" + + - package-ecosystem: "github-actions" + directory: "/.github/actions/build-distribution" + reviewers: + - "elastic/observablt-ci" + schedule: + interval: "weekly" + day: "sunday" + time: "22:00" + groups: + github-actions: + patterns: + - "*" diff --git a/.github/workflows/build-distribution.yml b/.github/workflows/build-distribution.yml deleted file mode 100644 index ba8497e0c..000000000 --- a/.github/workflows/build-distribution.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: build-distribution - -on: - workflow_call: ~ - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - name: Build lambda layer zip - run: ./dev-utils/make-distribution.sh - - uses: actions/upload-artifact@v3 - with: - name: build-distribution - path: ./build/ - if-no-files-found: error diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 4f04d78a4..7e11b4a6c 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -18,27 +18,10 @@ permissions: jobs: build: + permissions: + id-token: write + contents: write runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: "3.10" - - name: Override the version if there is no tag release. - run: | - if [[ "${GITHUB_REF}" != refs/tags/* ]]; then - echo "ELASTIC_CI_POST_VERSION=${{ github.run_id }}" >> "${GITHUB_ENV}" - fi - - name: Install wheel - run: pip install --user wheel - - name: Building universal wheel - run: python setup.py bdist_wheel - - name: Building source distribution - run: python setup.py sdist - - name: Upload Packages - uses: actions/upload-artifact@v4 - with: - name: packages - path: | - dist/*.whl - dist/*tar.gz + - uses: ./.github/actions/packages diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5f91014b4..616cd9d4d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,13 @@ jobs: enabled: ${{ startsWith(github.ref, 'refs/tags') }} packages: - uses: ./.github/workflows/packages.yml + permissions: + id-token: write + contents: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/packages publish-pypi: needs: @@ -46,7 +52,17 @@ jobs: repository-url: https://test.pypi.org/legacy/ build-distribution: - uses: ./.github/workflows/build-distribution.yml + permissions: + id-token: write + contents: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/build-distribution + - name: generate build provenance + uses: github-early-access/generate-build-provenance@main + with: + subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" publish-lambda-layers: needs: @@ -63,7 +79,7 @@ jobs: secrets: | secret/observability-team/ci/service-account/apm-agent-python access_key_id | AWS_ACCESS_KEY_ID ; secret/observability-team/ci/service-account/apm-agent-python secret_access_key | AWS_SECRET_ACCESS_KEY - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: build-distribution path: ./build @@ -86,6 +102,9 @@ jobs: needs: - build-distribution runs-on: ubuntu-latest + permissions: + id-token: write + contents: write env: DOCKER_IMAGE_NAME: docker.elastic.co/observability/apm-agent-python steps: @@ -97,7 +116,7 @@ jobs: url: ${{ secrets.VAULT_ADDR }} roleId: ${{ secrets.VAULT_ROLE_ID }} secretId: ${{ secrets.VAULT_SECRET_ID }} - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: build-distribution path: ./build @@ -107,25 +126,30 @@ jobs: if [ "${{ startsWith(github.ref, 'refs/tags') }}" == "false" ] ; then # for testing purposes echo "tag=test" >> "${GITHUB_OUTPUT}" + echo "latest=test-latest" >> "${GITHUB_OUTPUT}" else # version without v prefix (e.g. 1.2.3) echo "tag=${GITHUB_REF_NAME/v/}" >> "${GITHUB_OUTPUT}" + echo "latest=latest" >> "${GITHUB_OUTPUT}" fi - - name: Docker build - run: >- - docker build - -t ${{ env.DOCKER_IMAGE_NAME }}:${{ steps.setup-docker.outputs.tag }} - --build-arg AGENT_DIR=./build/dist/package/python - . - - name: Docker retag - run: >- - docker tag - ${{ env.DOCKER_IMAGE_NAME }}:${{ steps.setup-docker.outputs.tag }} - ${{ env.DOCKER_IMAGE_NAME }}:latest - - name: Docker push - if: startsWith(github.ref, 'refs/tags') - run: |- - docker push --all-tags ${{ env.DOCKER_IMAGE_NAME }} + - name: Build and push image + id: push + uses: docker/build-push-action@v5.3.0 + with: + context: . + push: true + tags: | + ${{ env.DOCKER_IMAGE_NAME }}:${{ steps.setup-docker.outputs.tag }} + ${{ env.DOCKER_IMAGE_NAME }}:${{ steps.setup-docker.outputs.latest }} + build-args: | + AGENT_DIR=./build/dist/package/python + + - name: Attest image + uses: github-early-access/generate-build-provenance@main + with: + subject-name: "${{ env.DOCKER_IMAGE_NAME }}:${{ steps.setup-docker.outputs.tag }}" + subject-digest: ${{ steps.push.outputs.digest }} + push-to-registry: false github-draft: permissions: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 381c317b0..967378697 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,7 +35,11 @@ permissions: jobs: build-distribution: - uses: ./.github/workflows/build-distribution.yml + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/build-distribution + create-matrix: runs-on: ubuntu-latest diff --git a/dev-utils/make-packages.sh b/dev-utils/make-packages.sh new file mode 100755 index 000000000..91b2a7bd1 --- /dev/null +++ b/dev-utils/make-packages.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# +# Make a Python APM agent distribution +# + +echo "::group::Install wheel" +pip install --user wheel +echo "::endgroup::" + +echo "::group::Building universal wheel" +python setup.py bdist_wheel +echo "::endgroup::" + +echo "::group::Building source distribution" +python setup.py sdist +echo "::endgroup::" From ffc08616a2426f48147e149f7dbdf977b93a3154 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Wed, 24 Apr 2024 10:49:33 +0200 Subject: [PATCH 065/409] github-action: use container image for provenance (#2030) --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 616cd9d4d..96b8e279a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -144,10 +144,10 @@ jobs: build-args: | AGENT_DIR=./build/dist/package/python - - name: Attest image + - name: generate build provenance (containers) uses: github-early-access/generate-build-provenance@main with: - subject-name: "${{ env.DOCKER_IMAGE_NAME }}:${{ steps.setup-docker.outputs.tag }}" + subject-name: "${{ env.DOCKER_IMAGE_NAME }}" subject-digest: ${{ steps.push.outputs.digest }} push-to-registry: false From 52e9c1b7ea261d81048f1e633f1fd7cfee6f84c7 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Fri, 26 Apr 2024 13:57:52 +0200 Subject: [PATCH 066/409] github-action: add attestations scope (#2032) --- .github/actions/packages/action.yml | 4 ---- .github/workflows/packages.yml | 3 --- .github/workflows/release.yml | 7 +++++++ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/actions/packages/action.yml b/.github/actions/packages/action.yml index 3ffda1d8c..871f49c32 100644 --- a/.github/actions/packages/action.yml +++ b/.github/actions/packages/action.yml @@ -25,7 +25,3 @@ runs: path: | dist/*.whl dist/*tar.gz - - name: generate build provenance - uses: github-early-access/generate-build-provenance@main - with: - subject-path: "${{ github.workspace }}/dist/*" \ No newline at end of file diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 7e11b4a6c..496107508 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -18,9 +18,6 @@ permissions: jobs: build: - permissions: - id-token: write - contents: write runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 96b8e279a..31932eb2e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,12 +19,17 @@ jobs: packages: permissions: + attestations: write id-token: write contents: write runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: ./.github/actions/packages + - name: generate build provenance + uses: github-early-access/generate-build-provenance@main + with: + subject-path: "${{ github.workspace }}/dist/*" publish-pypi: needs: @@ -53,6 +58,7 @@ jobs: build-distribution: permissions: + attestations: write id-token: write contents: write runs-on: ubuntu-latest @@ -103,6 +109,7 @@ jobs: - build-distribution runs-on: ubuntu-latest permissions: + attestations: write id-token: write contents: write env: From 880c5945aac6e3c54a4ebe63bfc67a980b39d2f1 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Tue, 7 May 2024 15:41:59 +0200 Subject: [PATCH 067/409] ci: use docker/metadata-action to gather tags/labels (#2033) --- .github/workflows/release.yml | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 31932eb2e..841057ac2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -123,31 +123,29 @@ jobs: url: ${{ secrets.VAULT_ADDR }} roleId: ${{ secrets.VAULT_ROLE_ID }} secretId: ${{ secrets.VAULT_SECRET_ID }} + - uses: actions/download-artifact@v4 with: name: build-distribution path: ./build - - id: setup-docker - name: Set up docker variables - run: |- - if [ "${{ startsWith(github.ref, 'refs/tags') }}" == "false" ] ; then - # for testing purposes - echo "tag=test" >> "${GITHUB_OUTPUT}" - echo "latest=test-latest" >> "${GITHUB_OUTPUT}" - else - # version without v prefix (e.g. 1.2.3) - echo "tag=${GITHUB_REF_NAME/v/}" >> "${GITHUB_OUTPUT}" - echo "latest=latest" >> "${GITHUB_OUTPUT}" - fi + + - name: Extract metadata (tags, labels) + id: docker-meta + uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1 + with: + images: ${{ env.DOCKER_IMAGE_NAME }} + tags: | + type=raw,value=latest,prefix=test-,enable={{is_default_branch}} + type=semver,pattern={{version}} + - name: Build and push image id: push - uses: docker/build-push-action@v5.3.0 + uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 with: context: . push: true - tags: | - ${{ env.DOCKER_IMAGE_NAME }}:${{ steps.setup-docker.outputs.tag }} - ${{ env.DOCKER_IMAGE_NAME }}:${{ steps.setup-docker.outputs.latest }} + tags: ${{ steps.docker-meta.outputs.tags }} + labels: ${{ steps.docker-meta.outputs.labels }} build-args: | AGENT_DIR=./build/dist/package/python @@ -156,7 +154,7 @@ jobs: with: subject-name: "${{ env.DOCKER_IMAGE_NAME }}" subject-digest: ${{ steps.push.outputs.digest }} - push-to-registry: false + push-to-registry: true github-draft: permissions: From e4488ede6dffd475d8c9ac6d82aa7a31303b349d Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 8 May 2024 09:13:44 +0200 Subject: [PATCH 068/409] tests: pick mysql image tagged as 8.0 (#2035) As for some reason latest 8.4.0 is not working. --- tests/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index 48b43cda2..62a05c83f 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -130,7 +130,7 @@ services: - pymssqldata:/var/opt/mssql mysql: - image: mysql + image: mysql:8.0 command: --default-authentication-plugin=mysql_native_password --log_error_verbosity=3 environment: - MYSQL_DATABASE=eapm_tests From c6fa2f70711cb598b583bdc331b1a988337e6bc0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 May 2024 17:23:40 +0200 Subject: [PATCH 069/409] build(deps): bump jinja2 from 3.0.3 to 3.1.4 in /tests/requirements (#2034) * build(deps): bump jinja2 from 3.0.3 to 3.1.4 in /tests/requirements Bumps [jinja2](https://github.com/pallets/jinja) from 3.0.3 to 3.1.4. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/3.0.3...3.1.4) --- updated-dependencies: - dependency-name: jinja2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Update tests/requirements/reqs-jinja2-2.txt --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Riccardo Magliocchetti --- tests/requirements/reqs-asgi-2.txt | 2 +- tests/requirements/reqs-base.txt | 2 +- tests/requirements/reqs-flask-1.0.txt | 2 +- tests/requirements/reqs-flask-1.1.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/requirements/reqs-asgi-2.txt b/tests/requirements/reqs-asgi-2.txt index eecc89d9a..97ff3022d 100644 --- a/tests/requirements/reqs-asgi-2.txt +++ b/tests/requirements/reqs-asgi-2.txt @@ -1,6 +1,6 @@ quart==0.6.13 MarkupSafe<2.1 -jinja2==3.0.3 +jinja2==3.1.4 async-asgi-testclient asgiref -r reqs-base.txt diff --git a/tests/requirements/reqs-base.txt b/tests/requirements/reqs-base.txt index 849469a79..4f79a5929 100644 --- a/tests/requirements/reqs-base.txt +++ b/tests/requirements/reqs-base.txt @@ -8,7 +8,7 @@ coverage[toml]==6.3 ; python_version == '3.7' coverage==7.3.1 ; python_version > '3.7' pytest-cov==4.0.0 ; python_version < '3.8' pytest-cov==4.1.0 ; python_version > '3.7' -jinja2==3.1.2 ; python_version == '3.7' +jinja2==3.1.4 ; python_version == '3.7' pytest-localserver==0.5.0 pytest-mock==3.6.1 ; python_version == '3.6' pytest-mock==3.10.0 ; python_version > '3.6' diff --git a/tests/requirements/reqs-flask-1.0.txt b/tests/requirements/reqs-flask-1.0.txt index 029a82cdf..b81a74b02 100644 --- a/tests/requirements/reqs-flask-1.0.txt +++ b/tests/requirements/reqs-flask-1.0.txt @@ -1,4 +1,4 @@ -jinja2<3.1.0 +jinja2<3.2.0 Werkzeug<2.1.0 Flask>=1.0,<1.1 MarkupSafe<2.1 diff --git a/tests/requirements/reqs-flask-1.1.txt b/tests/requirements/reqs-flask-1.1.txt index 107d375d5..cd32a4696 100644 --- a/tests/requirements/reqs-flask-1.1.txt +++ b/tests/requirements/reqs-flask-1.1.txt @@ -1,4 +1,4 @@ -jinja2<3.1.0 +jinja2<3.2.0 Werkzeug<2.1.0 Flask>=1.1,<1.2 MarkupSafe<2.1 From c15a7bdf4d862d1b3f4b010d5da7d43efc52ea4a Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 10 May 2024 09:43:27 +0200 Subject: [PATCH 070/409] tests: revert bumping jinja2 in flask 1.0 tests (#2037) Unfortunately flask 1.0 does not work with jinja 3.1.x: venv/lib/python3.10/site-packages/flask/__init__.py:19: in from jinja2 import Markup, escape E ImportError: cannot import name 'Markup' from 'jinja2' --- tests/requirements/reqs-flask-1.0.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/requirements/reqs-flask-1.0.txt b/tests/requirements/reqs-flask-1.0.txt index b81a74b02..029a82cdf 100644 --- a/tests/requirements/reqs-flask-1.0.txt +++ b/tests/requirements/reqs-flask-1.0.txt @@ -1,4 +1,4 @@ -jinja2<3.2.0 +jinja2<3.1.0 Werkzeug<2.1.0 Flask>=1.0,<1.1 MarkupSafe<2.1 From 73ac28047f7c8ff359d665a817516aa9f1275948 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 14 May 2024 09:31:05 +0200 Subject: [PATCH 071/409] Assume os.getppid is always available (#2038) It's always available since 3.2 and tests are already assuming that. --- elasticapm/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elasticapm/base.py b/elasticapm/base.py index ff2fd6e25..2c82f0d88 100644 --- a/elasticapm/base.py +++ b/elasticapm/base.py @@ -374,7 +374,7 @@ def get_service_info(self): def get_process_info(self): result = { "pid": os.getpid(), - "ppid": os.getppid() if hasattr(os, "getppid") else None, + "ppid": os.getppid(), "title": None, # Note: if we implement this, the value needs to be wrapped with keyword_field } if self.config.include_process_args: From 4196b3440f3fc3502d8a891f225089320f5aa1a1 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Tue, 14 May 2024 17:47:44 +0200 Subject: [PATCH 072/409] github-action: delete opentelemetry workflow (#2039) --- .github/workflows/opentelemetry.yml | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 .github/workflows/opentelemetry.yml diff --git a/.github/workflows/opentelemetry.yml b/.github/workflows/opentelemetry.yml deleted file mode 100644 index 84a6209ff..000000000 --- a/.github/workflows/opentelemetry.yml +++ /dev/null @@ -1,22 +0,0 @@ ---- -# Look up results at https://ela.st/oblt-ci-cd-stats. -# There will be one service per GitHub repository, including the org name, and one Transaction per Workflow. -name: OpenTelemetry Export Trace - -on: - workflow_run: - workflows: [ "*" ] - types: [completed] - -permissions: - contents: read - -jobs: - otel-export-trace: - runs-on: ubuntu-latest - steps: - - uses: elastic/apm-pipeline-library/.github/actions/opentelemetry@current - with: - vaultUrl: ${{ secrets.VAULT_ADDR }} - vaultRoleId: ${{ secrets.VAULT_ROLE_ID }} - vaultSecretId: ${{ secrets.VAULT_SECRET_ID }} From ecac14284bd33160cb019d30af19130afc3994d0 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Tue, 14 May 2024 21:32:12 +0200 Subject: [PATCH 073/409] ci: build and push Docker image based on Chainguard base image (#2036) --- .github/workflows/release.yml | 7 +++++++ Dockerfile.wolfi | 3 +++ 2 files changed, 10 insertions(+) create mode 100644 Dockerfile.wolfi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 841057ac2..854ee4231 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -112,6 +112,10 @@ jobs: attestations: write id-token: write contents: write + strategy: + fail-fast: false + matrix: + dockerfile: [ 'Dockerfile', 'Dockerfile.wolfi' ] env: DOCKER_IMAGE_NAME: docker.elastic.co/observability/apm-agent-python steps: @@ -137,6 +141,8 @@ jobs: tags: | type=raw,value=latest,prefix=test-,enable={{is_default_branch}} type=semver,pattern={{version}} + flavor: | + suffix=${{ contains(matrix.dockerfile, 'wolfi') && '-wolfi' || '' }} - name: Build and push image id: push @@ -144,6 +150,7 @@ jobs: with: context: . push: true + file: ${{ matrix.dockerfile }} tags: ${{ steps.docker-meta.outputs.tags }} labels: ${{ steps.docker-meta.outputs.labels }} build-args: | diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi new file mode 100644 index 000000000..1ed923ce5 --- /dev/null +++ b/Dockerfile.wolfi @@ -0,0 +1,3 @@ +FROM docker.elastic.co/wolfi/chainguard-base@sha256:9f940409f96296ef56140bcc4665c204dd499af4c32c96cc00e792558097c3f1 +ARG AGENT_DIR +COPY ${AGENT_DIR} /opt/python \ No newline at end of file From ce6eb5d4605a90b5721144ea865822d5b5bf9cba Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Wed, 15 May 2024 09:04:42 +0200 Subject: [PATCH 074/409] github-action: use actions/attest-build-provenance (#2040) --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 854ee4231..cc0de40a2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/packages - name: generate build provenance - uses: github-early-access/generate-build-provenance@main + uses: actions/attest-build-provenance@951c0c5f8e375ad4efad33405ab77f7ded2358e4 # v1.1.1 with: subject-path: "${{ github.workspace }}/dist/*" @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/build-distribution - name: generate build provenance - uses: github-early-access/generate-build-provenance@main + uses: actions/attest-build-provenance@951c0c5f8e375ad4efad33405ab77f7ded2358e4 # v1.1.1 with: subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" @@ -157,7 +157,7 @@ jobs: AGENT_DIR=./build/dist/package/python - name: generate build provenance (containers) - uses: github-early-access/generate-build-provenance@main + uses: actions/attest-build-provenance@951c0c5f8e375ad4efad33405ab77f7ded2358e4 # v1.1.1 with: subject-name: "${{ env.DOCKER_IMAGE_NAME }}" subject-digest: ${{ steps.push.outputs.digest }} From 011c6b4d53188b6cda8479daca19a1493655c7c5 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 16 May 2024 10:57:45 +0200 Subject: [PATCH 075/409] contrib/serverless: remove aws reference from azure implementation (#2042) --- elasticapm/contrib/serverless/azure.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/elasticapm/contrib/serverless/azure.py b/elasticapm/contrib/serverless/azure.py index ed2444d60..c5df4882a 100644 --- a/elasticapm/contrib/serverless/azure.py +++ b/elasticapm/contrib/serverless/azure.py @@ -43,8 +43,6 @@ from elasticapm.utils.disttracing import TraceParent from elasticapm.utils.logging import get_logger -SERVERLESS_HTTP_REQUEST = ("api", "elb") - logger = get_logger("elasticapm.serverless") _AnnotatedFunctionT = TypeVar("_AnnotatedFunctionT") From 62448d38c25736e898fb37caeae2a6613d05e15b Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 17 May 2024 15:40:27 +0200 Subject: [PATCH 076/409] update CHANGELOG and bump version to 6.22.1 (#2044) --- CHANGELOG.asciidoc | 8 ++++++++ elasticapm/version.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 03f4e048f..cf6a99a7c 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -32,6 +32,14 @@ endif::[] [[release-notes-6.x]] === Python Agent version 6.x +[[release-notes-6.22.1]] +==== 6.22.1 - 2024-05-17 + +[float] +===== Features + +* Relax wrapt dependency to only exclude 1.15.0 {pull}2005[#2005] + [[release-notes-6.22.0]] ==== 6.22.0 - 2024-04-03 diff --git a/elasticapm/version.py b/elasticapm/version.py index c8799817e..e778fc5b2 100644 --- a/elasticapm/version.py +++ b/elasticapm/version.py @@ -28,5 +28,5 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = (6, 22, 0) +__version__ = (6, 22, 1) VERSION = ".".join(map(str, __version__)) From a69528b50dfebaee040aae6ae96c062f47b2f688 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 17 May 2024 16:48:54 +0200 Subject: [PATCH 077/409] ci: use v3 artifact APIs for build-distribution (#2046) Hopefully fix: The lambda layer can be published as follows for dev work: aws lambda --output json publish-layer-version --layer-name 'runner-dev-elastic-apm-python' --description 'runner dev Elastic APM Python agent lambda layer' --zip-file 'fileb://build/dist/elastic-apm-python-lambda-layer.zip' Run actions/upload-artifact@v4 with: name: build-distribution path: ./build/ if-no-files-found: error compression-level: 6 overwrite: false env: ... With the provided path, there will be 636 files uploaded Artifact name is valid! Root directory input is valid! Error: Failed to CreateArtifact: Received non-retryable error: Failed request: (409) Conflict: an artifact with this name already exists on the workflow run --- .github/actions/build-distribution/action.yml | 2 +- .github/workflows/release.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/build-distribution/action.yml b/.github/actions/build-distribution/action.yml index bc0d55c29..05c32eeb8 100644 --- a/.github/actions/build-distribution/action.yml +++ b/.github/actions/build-distribution/action.yml @@ -14,7 +14,7 @@ runs: run: ./dev-utils/make-distribution.sh shell: bash - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v3 with: name: build-distribution path: ./build/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cc0de40a2..c00975d7c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -85,7 +85,7 @@ jobs: secrets: | secret/observability-team/ci/service-account/apm-agent-python access_key_id | AWS_ACCESS_KEY_ID ; secret/observability-team/ci/service-account/apm-agent-python secret_access_key | AWS_SECRET_ACCESS_KEY - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v3 with: name: build-distribution path: ./build @@ -128,7 +128,7 @@ jobs: roleId: ${{ secrets.VAULT_ROLE_ID }} secretId: ${{ secrets.VAULT_SECRET_ID }} - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v3 with: name: build-distribution path: ./build From 6056e241fd8fe7aa5e06450786f3843cf383f3a2 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 20 May 2024 10:25:02 +0200 Subject: [PATCH 078/409] update CHANGELOG and bump version to 6.22.2 (#2048) --- CHANGELOG.asciidoc | 8 ++++++++ elasticapm/version.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index cf6a99a7c..45934551a 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -32,6 +32,14 @@ endif::[] [[release-notes-6.x]] === Python Agent version 6.x +[[release-notes-6.22.2]] +==== 6.22.2 - 2024-05-20 + +[float] +===== Bug fixes + +* Fix CI release workflow {pull}2046[#2046] + [[release-notes-6.22.1]] ==== 6.22.1 - 2024-05-17 diff --git a/elasticapm/version.py b/elasticapm/version.py index e778fc5b2..9d26934f4 100644 --- a/elasticapm/version.py +++ b/elasticapm/version.py @@ -28,5 +28,5 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = (6, 22, 1) +__version__ = (6, 22, 2) VERSION = ".".join(map(str, __version__)) From 68c15d80a45822eedb25a4e69e50e1bd9e3e1b1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 May 2024 11:11:40 +0200 Subject: [PATCH 079/409] --- (#2050) updated-dependencies: - dependency-name: requests dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/requirements/reqs-starlette-0.13.txt | 2 +- tests/requirements/reqs-starlette-0.14.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/requirements/reqs-starlette-0.13.txt b/tests/requirements/reqs-starlette-0.13.txt index 3144bf464..51a5cd486 100644 --- a/tests/requirements/reqs-starlette-0.13.txt +++ b/tests/requirements/reqs-starlette-0.13.txt @@ -1,4 +1,4 @@ starlette>=0.13,<0.14 aiofiles==0.7.0 -requests==2.31.0 +requests==2.32.0 -r reqs-base.txt diff --git a/tests/requirements/reqs-starlette-0.14.txt b/tests/requirements/reqs-starlette-0.14.txt index e1952d09b..a075a72f3 100644 --- a/tests/requirements/reqs-starlette-0.14.txt +++ b/tests/requirements/reqs-starlette-0.14.txt @@ -1,4 +1,4 @@ starlette>=0.14,<0.15 -requests==2.31.0 +requests==2.32.0 aiofiles -r reqs-base.txt From 5b6075fee64eabf186fb23088ca2b9ba7d3939c8 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 22 May 2024 16:24:01 +0200 Subject: [PATCH 080/409] Fix starlette requirements (#2051) * tests: fix ancient starlette requirements Newer requests dropped support for Pyrhon < 3.8 so keep an old version for these requirements. Hopefully it'll also be useful as reminder next time dependabot will bump a release. * ci: run starlette-0.14 tests before merging To avoid regressions with dependabot bumps --- .ci/.matrix_framework.yml | 2 ++ tests/requirements/reqs-starlette-0.13.txt | 3 ++- tests/requirements/reqs-starlette-0.14.txt | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.ci/.matrix_framework.yml b/.ci/.matrix_framework.yml index df04f639c..6eff578d8 100644 --- a/.ci/.matrix_framework.yml +++ b/.ci/.matrix_framework.yml @@ -45,6 +45,8 @@ FRAMEWORK: - aiopg-newest - asyncpg-newest - tornado-newest + # this has a dependency on requests, run it to catch update issues before merging. Drop after baseline > 0.21.0 + - starlette-0.14 - starlette-newest - pymemcache-newest - graphene-2 diff --git a/tests/requirements/reqs-starlette-0.13.txt b/tests/requirements/reqs-starlette-0.13.txt index 51a5cd486..43d814c60 100644 --- a/tests/requirements/reqs-starlette-0.13.txt +++ b/tests/requirements/reqs-starlette-0.13.txt @@ -1,4 +1,5 @@ starlette>=0.13,<0.14 aiofiles==0.7.0 -requests==2.32.0 +requests==2.32.1; python_version >= '3.8' +requests==2.31.0; python_version < '3.8' -r reqs-base.txt diff --git a/tests/requirements/reqs-starlette-0.14.txt b/tests/requirements/reqs-starlette-0.14.txt index a075a72f3..52ea93114 100644 --- a/tests/requirements/reqs-starlette-0.14.txt +++ b/tests/requirements/reqs-starlette-0.14.txt @@ -1,4 +1,5 @@ starlette>=0.14,<0.15 -requests==2.32.0 +requests==2.32.1; python_version >= '3.8' +requests==2.31.0; python_version < '3.8' aiofiles -r reqs-base.txt From cbaa31e08aaf7b9ee5b2b2c9afa964747fb53c21 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 May 2024 11:37:59 +0200 Subject: [PATCH 081/409] build(deps): bump actions/attest-build-provenance (#2052) Bumps the github-actions group with 1 update: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance). Updates `actions/attest-build-provenance` from 1.1.1 to 1.1.2 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/951c0c5f8e375ad4efad33405ab77f7ded2358e4...173725a1209d09b31f9d30a3890cf2757ebbff0d) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c00975d7c..b4fff9d53 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/packages - name: generate build provenance - uses: actions/attest-build-provenance@951c0c5f8e375ad4efad33405ab77f7ded2358e4 # v1.1.1 + uses: actions/attest-build-provenance@173725a1209d09b31f9d30a3890cf2757ebbff0d # v1.1.2 with: subject-path: "${{ github.workspace }}/dist/*" @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/build-distribution - name: generate build provenance - uses: actions/attest-build-provenance@951c0c5f8e375ad4efad33405ab77f7ded2358e4 # v1.1.1 + uses: actions/attest-build-provenance@173725a1209d09b31f9d30a3890cf2757ebbff0d # v1.1.2 with: subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" @@ -157,7 +157,7 @@ jobs: AGENT_DIR=./build/dist/package/python - name: generate build provenance (containers) - uses: actions/attest-build-provenance@951c0c5f8e375ad4efad33405ab77f7ded2358e4 # v1.1.1 + uses: actions/attest-build-provenance@173725a1209d09b31f9d30a3890cf2757ebbff0d # v1.1.2 with: subject-name: "${{ env.DOCKER_IMAGE_NAME }}" subject-digest: ${{ steps.push.outputs.digest }} From d37cb201f959541f8173d705761c04c7315570be Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Fri, 31 May 2024 10:40:39 +0200 Subject: [PATCH 082/409] ci: use updatecli with GitHub secrets (#2053) --- .../updatecli.d/update-gherkin-specs.yml | 10 ++++----- .../updatecli.d/update-json-specs.yml | 10 ++++----- .ci/updatecli/updatecli.d/update-specs.yml | 10 ++++----- .github/workflows/updatecli.yml | 21 +++++++++---------- 4 files changed, 22 insertions(+), 29 deletions(-) diff --git a/.ci/updatecli/updatecli.d/update-gherkin-specs.yml b/.ci/updatecli/updatecli.d/update-gherkin-specs.yml index f12ece861..82f986fae 100644 --- a/.ci/updatecli/updatecli.d/update-gherkin-specs.yml +++ b/.ci/updatecli/updatecli.d/update-gherkin-specs.yml @@ -5,22 +5,20 @@ scms: default: kind: github spec: - user: '{{ requiredEnv "GIT_USER" }}' - email: '{{ requiredEnv "GIT_EMAIL" }}' + user: '{{ requiredEnv "GITHUB_ACTOR" }}' owner: "{{ .github.owner }}" repository: "{{ .github.repository }}" token: '{{ requiredEnv "GITHUB_TOKEN" }}' - username: '{{ requiredEnv "GIT_USER" }}' + username: '{{ requiredEnv "GITHUB_ACTOR" }}' branch: "{{ .github.branch }}" apm: kind: github spec: - user: '{{ requiredEnv "GIT_USER" }}' - email: '{{ requiredEnv "GIT_EMAIL" }}' + user: '{{ requiredEnv "GITHUB_ACTOR" }}' owner: "{{ .github.owner }}" repository: "{{ .github.apm_repository }}" token: '{{ requiredEnv "GITHUB_TOKEN" }}' - username: '{{ requiredEnv "GIT_USER" }}' + username: '{{ requiredEnv "GITHUB_ACTOR" }}' branch: "{{ .github.branch }}" sources: diff --git a/.ci/updatecli/updatecli.d/update-json-specs.yml b/.ci/updatecli/updatecli.d/update-json-specs.yml index e05aaecdb..7b86367a7 100644 --- a/.ci/updatecli/updatecli.d/update-json-specs.yml +++ b/.ci/updatecli/updatecli.d/update-json-specs.yml @@ -5,22 +5,20 @@ scms: default: kind: github spec: - user: '{{ requiredEnv "GIT_USER" }}' - email: '{{ requiredEnv "GIT_EMAIL" }}' + user: '{{ requiredEnv "GITHUB_ACTOR" }}' owner: "{{ .github.owner }}" repository: "{{ .github.repository }}" token: '{{ requiredEnv "GITHUB_TOKEN" }}' - username: '{{ requiredEnv "GIT_USER" }}' + username: '{{ requiredEnv "GITHUB_ACTOR" }}' branch: "{{ .github.branch }}" apm: kind: github spec: - user: '{{ requiredEnv "GIT_USER" }}' - email: '{{ requiredEnv "GIT_EMAIL" }}' + user: '{{ requiredEnv "GITHUB_ACTOR" }}' owner: "{{ .github.owner }}" repository: "{{ .github.apm_repository }}" token: '{{ requiredEnv "GITHUB_TOKEN" }}' - username: '{{ requiredEnv "GIT_USER" }}' + username: '{{ requiredEnv "GITHUB_ACTOR" }}' branch: "{{ .github.branch }}" sources: diff --git a/.ci/updatecli/updatecli.d/update-specs.yml b/.ci/updatecli/updatecli.d/update-specs.yml index 554140da2..ab5589f7e 100644 --- a/.ci/updatecli/updatecli.d/update-specs.yml +++ b/.ci/updatecli/updatecli.d/update-specs.yml @@ -5,23 +5,21 @@ scms: default: kind: github spec: - user: '{{ requiredEnv "GIT_USER" }}' - email: '{{ requiredEnv "GIT_EMAIL" }}' + user: '{{ requiredEnv "GITHUB_ACTOR" }}' owner: "{{ .github.owner }}" repository: "{{ .github.repository }}" token: '{{ requiredEnv "GITHUB_TOKEN" }}' - username: '{{ requiredEnv "GIT_USER" }}' + username: '{{ requiredEnv "GITHUB_ACTOR" }}' branch: "{{ .github.branch }}" apm-data: kind: github spec: - user: '{{ requiredEnv "GIT_USER" }}' - email: '{{ requiredEnv "GIT_EMAIL" }}' + user: '{{ requiredEnv "GITHUB_ACTOR" }}' owner: "{{ .github.owner }}" repository: "{{ .github.apm_data_repository }}" token: '{{ requiredEnv "GITHUB_TOKEN" }}' - username: '{{ requiredEnv "GIT_USER" }}' + username: '{{ requiredEnv "GITHUB_ACTOR" }}' branch: "{{ .github.branch }}" sources: diff --git a/.github/workflows/updatecli.yml b/.github/workflows/updatecli.yml index 4fc00bd71..8190487ad 100644 --- a/.github/workflows/updatecli.yml +++ b/.github/workflows/updatecli.yml @@ -13,17 +13,16 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: elastic/apm-pipeline-library/.github/actions/updatecli@current + + - uses: elastic/oblt-actions/updatecli/run@v1 with: - vaultUrl: ${{ secrets.VAULT_ADDR }} - vaultRoleId: ${{ secrets.VAULT_ROLE_ID }} - vaultSecretId: ${{ secrets.VAULT_SECRET_ID }} - pipeline: .ci/updatecli/updatecli.d - values: .ci/updatecli/values.yml + command: "apply --config .ci/updatecli/updatecli.d --values .ci/updatecli/values.yml" + env: + GITHUB_TOKEN: ${{ secrets.UPDATECLI_GH_TOKEN }} + - if: failure() - uses: elastic/apm-pipeline-library/.github/actions/notify-build-status@current + uses: elastic/oblt-actions/slack/send@v1 with: - vaultUrl: ${{ secrets.VAULT_ADDR }} - vaultRoleId: ${{ secrets.VAULT_ROLE_ID }} - vaultSecretId: ${{ secrets.VAULT_SECRET_ID }} - slackChannel: "#apm-agent-python" + bot-token: ${{ secrets.SLACK_BOT_TOKEN }} + channel-id: "#apm-agent-python" + message: ":traffic_cone: updatecli failed for `${{ github.repository }}@${{ github.ref_name }}`, @robots-ci please look what's going on " From 8ac4864e6d6d027b186653c8bd96ad1e382ffdfe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 09:33:07 +0200 Subject: [PATCH 083/409] build(deps): bump certifi from 2024.2.2 to 2024.6.2 in /dev-utils (#2055) Bumps [certifi](https://github.com/certifi/python-certifi) from 2024.2.2 to 2024.6.2. - [Commits](https://github.com/certifi/python-certifi/compare/2024.02.02...2024.06.02) --- updated-dependencies: - dependency-name: certifi dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dev-utils/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-utils/requirements.txt b/dev-utils/requirements.txt index 59008afc2..acb4cdcaa 100644 --- a/dev-utils/requirements.txt +++ b/dev-utils/requirements.txt @@ -1,4 +1,4 @@ # These are the pinned requirements for the lambda layer/docker image -certifi==2024.2.2 +certifi==2024.6.2 urllib3==1.26.18 wrapt==1.14.1 From 1704ee550b8cc6988f1e8e2770e247ff0c4e9a3f Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 3 Jun 2024 10:21:18 +0200 Subject: [PATCH 084/409] github: use docker github secrets (#2054) --- .github/workflows/release.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b4fff9d53..2523499f8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -120,13 +120,13 @@ jobs: DOCKER_IMAGE_NAME: docker.elastic.co/observability/apm-agent-python steps: - uses: actions/checkout@v4 - - uses: elastic/apm-pipeline-library/.github/actions/docker-login@current + + - name: Log in to the Elastic Container registry + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 with: - registry: docker.elastic.co - secret: secret/observability-team/ci/docker-registry/prod - url: ${{ secrets.VAULT_ADDR }} - roleId: ${{ secrets.VAULT_ROLE_ID }} - secretId: ${{ secrets.VAULT_SECRET_ID }} + registry: ${{ secrets.ELASTIC_DOCKER_REGISTRY }} + username: ${{ secrets.ELASTIC_DOCKER_USERNAME }} + password: ${{ secrets.ELASTIC_DOCKER_PASSWORD }} - uses: actions/download-artifact@v3 with: From fb9b40dd48d950ac66de73ad863fe10bb854c58f Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Tue, 4 Jun 2024 18:39:17 +0200 Subject: [PATCH 085/409] github-actions: remove snapshoty (#2059) --- .ci/snapshoty.yml | 36 --------------------------------- .github/workflows/README.md | 2 +- .github/workflows/snapshoty.yml | 35 -------------------------------- 3 files changed, 1 insertion(+), 72 deletions(-) delete mode 100644 .ci/snapshoty.yml delete mode 100644 .github/workflows/snapshoty.yml diff --git a/.ci/snapshoty.yml b/.ci/snapshoty.yml deleted file mode 100644 index ccebc3426..000000000 --- a/.ci/snapshoty.yml +++ /dev/null @@ -1,36 +0,0 @@ ---- - -# Version of configuration to use -version: '1.0' - -# You can define a Google Cloud Account to use -account: - # Project id of the service account - project: '${GCS_PROJECT}' - # Private key id of the service account - private_key_id: '${GCS_PRIVATE_KEY_ID}' - # Private key of the service account - private_key: '${GCS_PRIVATE_KEY}' - # Email of the service account - client_email: '${GCS_CLIENT_EMAIL}' - # URI token - token_uri: 'https://oauth2.googleapis.com/token' - -# List of artifacts -artifacts: - # Path to use for artifacts discovery - - path: './dist' - # Files pattern to match - files_pattern: 'elastic_apm-(?P\d+\.\d+\.\d+)-(.*)\.whl' - # File layout on GCS bucket - output_pattern: '{project}/{github_branch_name}/elastic-apm-python-{app_version}-{github_sha_short}.whl' - # List of metadata processors to use. - metadata: - # Define static custom metadata - - name: 'custom' - data: - project: 'apm-agent-python' - # Add git metadata - - name: 'git' - # Add github_actions metadata - - name: 'github_actions' diff --git a/.github/workflows/README.md b/.github/workflows/README.md index c224d62b8..5e2641541 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -52,4 +52,4 @@ The tag release follows the naming convention: `v...`, wher ### OpenTelemetry -There is a GitHub workflow in charge to populate what the workflow run in terms of jobs and steps. Those details can be seen in [here](https://ela.st/oblt-ci-cd-stats) (**NOTE**: only available for Elasticians). +Every workflow and its logs are exported to OpenTelemetry traces/logs/metrics. Those details can be seen [here](https://ela.st/oblt-ci-cd-stats) (**NOTE**: only available for Elasticians). diff --git a/.github/workflows/snapshoty.yml b/.github/workflows/snapshoty.yml deleted file mode 100644 index 49d1b3423..000000000 --- a/.github/workflows/snapshoty.yml +++ /dev/null @@ -1,35 +0,0 @@ ---- -# Publish a snapshot. A "snapshot" is a packaging of the latest *unreleased* APM agent, -# published to a known GCS bucket for use in edge demo/test environments. -name: snapshoty - -on: - workflow_run: - workflows: - - test - types: - - completed - branches: - - main - -jobs: - packages: - if: ${{ github.event.workflow_run.conclusion == 'success' }} - uses: ./.github/workflows/packages.yml - upload: - needs: - - packages - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 - with: - name: packages - path: dist - - name: Publish snaphosts - uses: elastic/apm-pipeline-library/.github/actions/snapshoty-simple@current - with: - config: '.ci/snapshoty.yml' - vaultUrl: ${{ secrets.VAULT_ADDR }} - vaultRoleId: ${{ secrets.VAULT_ROLE_ID }} - vaultSecretId: ${{ secrets.VAULT_SECRET_ID }} From cab134b7957151bec789f1ba625b5fbe566813af Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Wed, 5 Jun 2024 09:19:59 +0200 Subject: [PATCH 086/409] github-action: run buildkite action with GH secrets (#2057) --- .github/workflows/microbenchmark.yml | 30 +++++++--------------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/.github/workflows/microbenchmark.yml b/.github/workflows/microbenchmark.yml index 9af88a6b1..2230d7e41 100644 --- a/.github/workflows/microbenchmark.yml +++ b/.github/workflows/microbenchmark.yml @@ -16,32 +16,16 @@ permissions: jobs: microbenchmark: runs-on: ubuntu-latest - # wait up to 1 hour - timeout-minutes: 60 + timeout-minutes: 5 steps: - - id: buildkite - name: Run buildkite pipeline - uses: elastic/apm-pipeline-library/.github/actions/buildkite@current + - name: Run microbenchmark + uses: elastic/oblt-actions/buildkite/run@v1.5.0 with: - vaultUrl: ${{ secrets.VAULT_ADDR }} - vaultRoleId: ${{ secrets.VAULT_ROLE_ID }} - vaultSecretId: ${{ secrets.VAULT_SECRET_ID }} - pipeline: apm-agent-microbenchmark - waitFor: true - printBuildLogs: true - buildEnvVars: | + pipeline: "apm-agent-microbenchmark" + token: ${{ secrets.BUILDKITE_TOKEN }} + wait-for: false + env-vars: | script=.ci/bench.sh repo=apm-agent-python sha=${{ github.sha }} BRANCH_NAME=${{ github.ref_name }} - - - if: ${{ failure() }} - uses: elastic/apm-pipeline-library/.github/actions/slack-message@current - with: - url: ${{ secrets.VAULT_ADDR }} - roleId: ${{ secrets.VAULT_ROLE_ID }} - secretId: ${{ secrets.VAULT_SECRET_ID }} - channel: "#apm-agent-python" - message: | - :ghost: [${{ github.repository }}] microbenchmark *${{ github.ref_name }}* failed to run in Buildkite. - Build: (<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|here>) From 6a375386f74efbfac507fab787c15614c2adea92 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Wed, 5 Jun 2024 17:53:03 +0200 Subject: [PATCH 087/409] feat(slack-bot): use github secrets (#2058) --- .github/workflows/release.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2523499f8..9b5f43faf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -198,11 +198,9 @@ jobs: uses: elastic/apm-pipeline-library/.github/actions/check-dependent-jobs@current with: needs: ${{ toJSON(needs) }} - - uses: elastic/apm-pipeline-library/.github/actions/notify-build-status@current - if: startsWith(github.ref, 'refs/tags') + - if: startsWith(github.ref, 'refs/tags') + uses: elastic/oblt-actions/slack/notify-result@v1.7.0 with: + bot-token: ${{ secrets.SLACK_BOT_TOKEN }} + channel-id: "#apm-agent-python" status: ${{ steps.check.outputs.status }} - vaultUrl: ${{ secrets.VAULT_ADDR }} - vaultRoleId: ${{ secrets.VAULT_ROLE_ID }} - vaultSecretId: ${{ secrets.VAULT_SECRET_ID }} - slackChannel: "#apm-agent-python" From b6b673cb560d6f64b9a78096e2737206947bb603 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 09:01:29 +0200 Subject: [PATCH 088/409] build(deps): bump the github-actions group with 2 updates (#2061) Bumps the github-actions group with 2 updates: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) and [docker/login-action](https://github.com/docker/login-action). Updates `actions/attest-build-provenance` from 1.1.2 to 1.2.0 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/173725a1209d09b31f9d30a3890cf2757ebbff0d...49df96e17e918a15956db358890b08e61c704919) Updates `docker/login-action` from 3.1.0 to 3.2.0 - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/e92390c5fb421da1463c202d546fed0ec5c39f20...0d4c9c5ea7693da7b068278f7b52bda2a190a446) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9b5f43faf..d75b1627c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/packages - name: generate build provenance - uses: actions/attest-build-provenance@173725a1209d09b31f9d30a3890cf2757ebbff0d # v1.1.2 + uses: actions/attest-build-provenance@49df96e17e918a15956db358890b08e61c704919 # v1.2.0 with: subject-path: "${{ github.workspace }}/dist/*" @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/build-distribution - name: generate build provenance - uses: actions/attest-build-provenance@173725a1209d09b31f9d30a3890cf2757ebbff0d # v1.1.2 + uses: actions/attest-build-provenance@49df96e17e918a15956db358890b08e61c704919 # v1.2.0 with: subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" @@ -122,7 +122,7 @@ jobs: - uses: actions/checkout@v4 - name: Log in to the Elastic Container registry - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 with: registry: ${{ secrets.ELASTIC_DOCKER_REGISTRY }} username: ${{ secrets.ELASTIC_DOCKER_USERNAME }} @@ -157,7 +157,7 @@ jobs: AGENT_DIR=./build/dist/package/python - name: generate build provenance (containers) - uses: actions/attest-build-provenance@173725a1209d09b31f9d30a3890cf2757ebbff0d # v1.1.2 + uses: actions/attest-build-provenance@49df96e17e918a15956db358890b08e61c704919 # v1.2.0 with: subject-name: "${{ env.DOCKER_IMAGE_NAME }}" subject-digest: ${{ steps.push.outputs.digest }} From 30e474d32e668d7231abd65e23b1490b6493e685 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 10 Jun 2024 09:02:35 +0200 Subject: [PATCH 089/409] contrib/starlette: fix outcome for 500 responses without exception (#2060) * contrib/starlette: fix outcome for 500 responses without exception Set the outcome based on the status code of the http.response.start ASGI message instead of relying only on the raise of an exception. * contrib/asgi: fix outcome for 500 responses without exception Set the outcome based on the status code of the http.response.start ASGI message instead of relying only on the raise of an exception. --- elasticapm/contrib/asgi.py | 1 + elasticapm/contrib/starlette/__init__.py | 1 + tests/contrib/asgi/app.py | 5 +++++ tests/contrib/asgi/asgi_tests.py | 17 ++++++++++++++++ tests/contrib/asyncio/starlette_tests.py | 25 ++++++++++++++++++++++++ 5 files changed, 49 insertions(+) diff --git a/elasticapm/contrib/asgi.py b/elasticapm/contrib/asgi.py index 096fed36a..92ee6c193 100644 --- a/elasticapm/contrib/asgi.py +++ b/elasticapm/contrib/asgi.py @@ -50,6 +50,7 @@ async def wrapped_send(message) -> None: await set_context(lambda: middleware.get_data_from_response(message, constants.TRANSACTION), "response") result = "HTTP {}xx".format(message["status"] // 100) elasticapm.set_transaction_result(result, override=False) + elasticapm.set_transaction_outcome(http_status_code=message["status"], override=False) await send(message) return wrapped_send diff --git a/elasticapm/contrib/starlette/__init__.py b/elasticapm/contrib/starlette/__init__.py index ad26d7a0a..3dfb225c9 100644 --- a/elasticapm/contrib/starlette/__init__.py +++ b/elasticapm/contrib/starlette/__init__.py @@ -147,6 +147,7 @@ async def wrapped_send(message) -> None: ) result = "HTTP {}xx".format(message["status"] // 100) elasticapm.set_transaction_result(result, override=False) + elasticapm.set_transaction_outcome(http_status_code=message["status"], override=False) await send(message) _mocked_receive = None diff --git a/tests/contrib/asgi/app.py b/tests/contrib/asgi/app.py index a919b2cef..352720135 100644 --- a/tests/contrib/asgi/app.py +++ b/tests/contrib/asgi/app.py @@ -59,3 +59,8 @@ async def boom() -> None: @app.route("/body") async def json(): return jsonify({"hello": "world"}) + + +@app.route("/500", methods=["GET"]) +async def error(): + return "KO", 500 diff --git a/tests/contrib/asgi/asgi_tests.py b/tests/contrib/asgi/asgi_tests.py index 824a23b68..f2a096dcc 100644 --- a/tests/contrib/asgi/asgi_tests.py +++ b/tests/contrib/asgi/asgi_tests.py @@ -66,6 +66,23 @@ async def test_transaction_span(instrumented_app, elasticapm_client): assert span["sync"] == False +@pytest.mark.asyncio +async def test_transaction_span(instrumented_app, elasticapm_client): + async with async_asgi_testclient.TestClient(instrumented_app) as client: + resp = await client.get("/500") + assert resp.status_code == 500 + assert resp.text == "KO" + + assert len(elasticapm_client.events[constants.TRANSACTION]) == 1 + assert len(elasticapm_client.events[constants.SPAN]) == 0 + transaction = elasticapm_client.events[constants.TRANSACTION][0] + assert transaction["name"] == "GET unknown route" + assert transaction["result"] == "HTTP 5xx" + assert transaction["outcome"] == "failure" + assert transaction["context"]["request"]["url"]["full"] == "/500" + assert transaction["context"]["response"]["status_code"] == 500 + + @pytest.mark.asyncio async def test_transaction_ignore_url(instrumented_app, elasticapm_client): elasticapm_client.config.update("1", transaction_ignore_urls="/foo*") diff --git a/tests/contrib/asyncio/starlette_tests.py b/tests/contrib/asyncio/starlette_tests.py index fcd7d0dee..e3c4f4a16 100644 --- a/tests/contrib/asyncio/starlette_tests.py +++ b/tests/contrib/asyncio/starlette_tests.py @@ -110,6 +110,10 @@ async def with_slash(request): async def without_slash(request): return PlainTextResponse("Hi {}".format(request.path_params["name"])) + @app.route("/500/", methods=["GET"]) + async def with_500_status_code(request): + return PlainTextResponse("Oops", status_code=500) + @sub.route("/hi") async def hi_from_sub(request): return PlainTextResponse("sub") @@ -236,6 +240,27 @@ def test_exception(app, elasticapm_client): assert error["context"]["request"] == transaction["context"]["request"] +def test_failure_outcome_with_500_status_code(app, elasticapm_client): + client = TestClient(app) + + client.get("/500/") + + assert len(elasticapm_client.events[constants.TRANSACTION]) == 1 + transaction = elasticapm_client.events[constants.TRANSACTION][0] + spans = elasticapm_client.spans_for_transaction(transaction) + assert len(spans) == 0 + + assert transaction["name"] == "GET /500/" + assert transaction["result"] == "HTTP 5xx" + assert transaction["outcome"] == "failure" + assert transaction["type"] == "request" + request = transaction["context"]["request"] + assert request["method"] == "GET" + assert transaction["context"]["response"]["status_code"] == 500 + + assert len(elasticapm_client.events[constants.ERROR]) == 0 + + @pytest.mark.parametrize("header_name", [constants.TRACEPARENT_HEADER_NAME, constants.TRACEPARENT_LEGACY_HEADER_NAME]) def test_traceparent_handling(app, elasticapm_client, header_name): client = TestClient(app) From a0d334f06f3e0cb6e762ddc4be52ec94247736f4 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 10 Jun 2024 10:24:30 +0200 Subject: [PATCH 090/409] update CHANGELOG and bump version to 6.22.3 (#2062) --- CHANGELOG.asciidoc | 8 ++++++++ elasticapm/version.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 45934551a..b8df53ab6 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -32,6 +32,14 @@ endif::[] [[release-notes-6.x]] === Python Agent version 6.x +[[release-notes-6.22.3]] +==== 6.22.3 - 2024-06-10 + +[float] +===== Bug fixes + +* Fix outcome in ASGI and Starlette apps on error status codes without an exception {pull}2060[#2060] + [[release-notes-6.22.2]] ==== 6.22.2 - 2024-05-20 diff --git a/elasticapm/version.py b/elasticapm/version.py index 9d26934f4..82aa446ad 100644 --- a/elasticapm/version.py +++ b/elasticapm/version.py @@ -28,5 +28,5 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = (6, 22, 2) +__version__ = (6, 22, 3) VERSION = ".".join(map(str, __version__)) From b1e2e05398dc5928ba16b591e5eea3d95a8cc4bb Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Thu, 13 Jun 2024 10:26:41 +0200 Subject: [PATCH 091/409] github-actions: use slack and aws gh secrets (#2065) --- .github/workflows/release.yml | 13 ++++--------- .github/workflows/test.yml | 8 +++----- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d75b1627c..ea3dd63d9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -74,17 +74,12 @@ jobs: needs: - build-distribution runs-on: ubuntu-latest + env: + # TODO: use keyless + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} steps: - uses: actions/checkout@v4 - - uses: hashicorp/vault-action@v3.0.0 - with: - url: ${{ secrets.VAULT_ADDR }} - method: approle - roleId: ${{ secrets.VAULT_ROLE_ID }} - secretId: ${{ secrets.VAULT_SECRET_ID }} - secrets: | - secret/observability-team/ci/service-account/apm-agent-python access_key_id | AWS_ACCESS_KEY_ID ; - secret/observability-team/ci/service-account/apm-agent-python secret_access_key | AWS_SECRET_ACCESS_KEY - uses: actions/download-artifact@v3 with: name: build-distribution diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 967378697..391d67f67 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -174,13 +174,11 @@ jobs: needs: ${{ toJSON(needs) }} - run: ${{ steps.check.outputs.isSuccess }} - if: failure() && (github.event_name == 'schedule' || github.event_name == 'push') - uses: elastic/apm-pipeline-library/.github/actions/notify-build-status@current + uses: elastic/oblt-actions/slack/notify-result@v1 with: + bot-token: ${{ secrets.SLACK_BOT_TOKEN }} status: ${{ steps.check.outputs.status }} - vaultUrl: ${{ secrets.VAULT_ADDR }} - vaultRoleId: ${{ secrets.VAULT_ROLE_ID }} - vaultSecretId: ${{ secrets.VAULT_SECRET_ID }} - slackChannel: "#apm-agent-python" + channel-id: "#apm-agent-python" coverage: name: Combine & check coverage. From 11bfd82e31a2286ae6526161b6a697c8aae16731 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 12:04:32 +0200 Subject: [PATCH 092/409] build(deps): bump the github-actions group with 3 updates (#2066) Bumps the github-actions group with 3 updates: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance), [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) and [docker/build-push-action](https://github.com/docker/build-push-action). Updates `actions/attest-build-provenance` from 1.2.0 to 1.3.1 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/49df96e17e918a15956db358890b08e61c704919...534b352d658f90498fd148d231fdbf88f3886a3a) Updates `pypa/gh-action-pypi-publish` from 1.8.14 to 1.9.0 - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/81e9d935c883d0b210363ab89cf05f3894778450...ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0) Updates `docker/build-push-action` from 5.3.0 to 5.4.0 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/2cdde995de11925a030ce8070c3d77a52ffcf1c0...ca052bb54ab0790a636c9b5f226502c73d547a25) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ea3dd63d9..b8fbe1686 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/packages - name: generate build provenance - uses: actions/attest-build-provenance@49df96e17e918a15956db358890b08e61c704919 # v1.2.0 + uses: actions/attest-build-provenance@534b352d658f90498fd148d231fdbf88f3886a3a # v1.3.1 with: subject-path: "${{ github.workspace }}/dist/*" @@ -47,12 +47,12 @@ jobs: path: dist - name: Upload pypi.org if: startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 + uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 with: repository-url: https://upload.pypi.org/legacy/ - name: Upload test.pypi.org if: ${{ ! startsWith(github.ref, 'refs/tags') }} - uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 + uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 with: repository-url: https://test.pypi.org/legacy/ @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/build-distribution - name: generate build provenance - uses: actions/attest-build-provenance@49df96e17e918a15956db358890b08e61c704919 # v1.2.0 + uses: actions/attest-build-provenance@534b352d658f90498fd148d231fdbf88f3886a3a # v1.3.1 with: subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" @@ -141,7 +141,7 @@ jobs: - name: Build and push image id: push - uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 + uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0 with: context: . push: true @@ -152,7 +152,7 @@ jobs: AGENT_DIR=./build/dist/package/python - name: generate build provenance (containers) - uses: actions/attest-build-provenance@49df96e17e918a15956db358890b08e61c704919 # v1.2.0 + uses: actions/attest-build-provenance@534b352d658f90498fd148d231fdbf88f3886a3a # v1.3.1 with: subject-name: "${{ env.DOCKER_IMAGE_NAME }}" subject-digest: ${{ steps.push.outputs.digest }} From 6799806e00cbbc4462e409f626e7f171dcf08806 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 09:06:24 +0200 Subject: [PATCH 093/409] build(deps): bump urllib3 from 1.26.18 to 1.26.19 in /dev-utils (#2067) Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.18 to 1.26.19. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/1.26.19/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.26.18...1.26.19) --- updated-dependencies: - dependency-name: urllib3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dev-utils/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-utils/requirements.txt b/dev-utils/requirements.txt index acb4cdcaa..87ab0cf28 100644 --- a/dev-utils/requirements.txt +++ b/dev-utils/requirements.txt @@ -1,4 +1,4 @@ # These are the pinned requirements for the lambda layer/docker image certifi==2024.6.2 -urllib3==1.26.18 +urllib3==1.26.19 wrapt==1.14.1 From 5c13f5601531148091c88f3f7398a5274a542634 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 24 Jun 2024 10:25:23 +0200 Subject: [PATCH 094/409] ci: remove celery 4 testing (#2068) With latest pip 24.1 Celery 4 is not installable anymore because the latest released version in 2020 contains invalid metadata: WARNING: Ignoring version 4.4.7 of celery since it has invalid metadata: Requested celery<5,>4.0 from celery-4.4.7-py2.py3-none-any.whl (from -r tests/requirements/reqs-celery-4.txt (line 1)) has invalid metadata: Expected matching RIGHT_PARENTHESIS for LEFT_PARENTHESIS, after version specifier pytz (>dev) ~^ Please use pip<24.1 if you need to use this version. --- .ci/.matrix_exclude.yml | 18 ------------------ .ci/.matrix_framework.yml | 2 -- .ci/.matrix_framework_full.yml | 3 --- docs/supported-technologies.asciidoc | 4 ++-- .../requirements/reqs-celery-4-django-1.11.txt | 2 -- .../requirements/reqs-celery-4-django-2.0.txt | 2 -- tests/requirements/reqs-celery-4-flask-1.0.txt | 2 -- tests/requirements/reqs-celery-4.txt | 4 ---- 8 files changed, 2 insertions(+), 35 deletions(-) delete mode 100644 tests/requirements/reqs-celery-4-django-1.11.txt delete mode 100644 tests/requirements/reqs-celery-4-django-2.0.txt delete mode 100644 tests/requirements/reqs-celery-4-flask-1.0.txt delete mode 100644 tests/requirements/reqs-celery-4.txt diff --git a/.ci/.matrix_exclude.yml b/.ci/.matrix_exclude.yml index eb7d57779..8df06914d 100644 --- a/.ci/.matrix_exclude.yml +++ b/.ci/.matrix_exclude.yml @@ -57,20 +57,12 @@ exclude: FRAMEWORK: django-1.11 - VERSION: python-3.10 FRAMEWORK: django-2.0 - - VERSION: python-3.10 - FRAMEWORK: celery-4-django-2.0 - - VERSION: python-3.10 - FRAMEWORK: celery-4-django-1.11 - - VERSION: python-3.11 # cannot import name 'formatargspec' from 'inspect' - FRAMEWORK: celery-4-flask-1.0 - VERSION: python-3.11 # https://github.com/celery/billiard/issues/377 FRAMEWORK: celery-5-flask-2 - VERSION: python-3.11 # https://github.com/celery/billiard/issues/377 FRAMEWORK: celery-5-django-3 - VERSION: python-3.11 # https://github.com/celery/billiard/issues/377 FRAMEWORK: celery-5-django-4 - - VERSION: python-3.12 # cannot import name 'formatargspec' from 'inspect' - FRAMEWORK: celery-4-flask-1.0 - VERSION: python-3.12 # https://github.com/celery/billiard/issues/377 FRAMEWORK: celery-5-flask-2 - VERSION: python-3.12 # https://github.com/celery/billiard/issues/377 @@ -93,10 +85,6 @@ exclude: FRAMEWORK: django-2.0 - VERSION: python-3.11 FRAMEWORK: django-2.1 - - VERSION: python-3.11 - FRAMEWORK: celery-4-django-2.0 - - VERSION: python-3.11 - FRAMEWORK: celery-4-django-1.11 - VERSION: python-3.11 FRAMEWORK: graphene-2 - VERSION: python-3.11 @@ -113,10 +101,6 @@ exclude: FRAMEWORK: django-2.0 - VERSION: python-3.12 FRAMEWORK: django-2.1 - - VERSION: python-3.12 - FRAMEWORK: celery-4-django-2.0 - - VERSION: python-3.12 - FRAMEWORK: celery-4-django-1.11 - VERSION: python-3.12 FRAMEWORK: graphene-2 - VERSION: python-3.12 @@ -294,8 +278,6 @@ exclude: FRAMEWORK: flask-1.1 - VERSION: python-3.7 FRAMEWORK: jinja2-2 - - VERSION: python-3.7 - FRAMEWORK: celery-4-flask-1.0 # TODO py3.12 - VERSION: python-3.12 FRAMEWORK: sanic-20.12 # no wheels available yet diff --git a/.ci/.matrix_framework.yml b/.ci/.matrix_framework.yml index 6eff578d8..679064a72 100644 --- a/.ci/.matrix_framework.yml +++ b/.ci/.matrix_framework.yml @@ -14,8 +14,6 @@ FRAMEWORK: - opentelemetry-newest - opentracing-newest - twisted-newest - - celery-4-flask-1.0 - - celery-4-django-2.0 - celery-5-flask-2 - celery-5-django-4 - celery-5-django-5 diff --git a/.ci/.matrix_framework_full.yml b/.ci/.matrix_framework_full.yml index cb4361711..d2482d9ff 100644 --- a/.ci/.matrix_framework_full.yml +++ b/.ci/.matrix_framework_full.yml @@ -25,9 +25,6 @@ FRAMEWORK: - flask-3.0 - jinja2-2 - jinja2-3 - - celery-4-flask-1.0 - - celery-4-django-1.11 - - celery-4-django-2.0 - celery-5-flask-2 - celery-5-django-3 - celery-5-django-4 diff --git a/docs/supported-technologies.asciidoc b/docs/supported-technologies.asciidoc index f170617a2..50198a102 100644 --- a/docs/supported-technologies.asciidoc +++ b/docs/supported-technologies.asciidoc @@ -128,8 +128,8 @@ The Python APM agent comes with automatic instrumentation of various 3rd party m We support these Celery versions: -* 3.x -* 4.x +* 4.x (deprecated) +* 5.x Celery tasks will be recorded automatically with Django and Flask only. diff --git a/tests/requirements/reqs-celery-4-django-1.11.txt b/tests/requirements/reqs-celery-4-django-1.11.txt deleted file mode 100644 index 4440bb70f..000000000 --- a/tests/requirements/reqs-celery-4-django-1.11.txt +++ /dev/null @@ -1,2 +0,0 @@ --r reqs-celery-4.txt --r reqs-django-1.11.txt diff --git a/tests/requirements/reqs-celery-4-django-2.0.txt b/tests/requirements/reqs-celery-4-django-2.0.txt deleted file mode 100644 index 72e805f38..000000000 --- a/tests/requirements/reqs-celery-4-django-2.0.txt +++ /dev/null @@ -1,2 +0,0 @@ --r reqs-celery-4.txt --r reqs-django-2.0.txt diff --git a/tests/requirements/reqs-celery-4-flask-1.0.txt b/tests/requirements/reqs-celery-4-flask-1.0.txt deleted file mode 100644 index e357a036f..000000000 --- a/tests/requirements/reqs-celery-4-flask-1.0.txt +++ /dev/null @@ -1,2 +0,0 @@ --r reqs-celery-4.txt --r reqs-flask-1.0.txt diff --git a/tests/requirements/reqs-celery-4.txt b/tests/requirements/reqs-celery-4.txt deleted file mode 100644 index 57ba4c638..000000000 --- a/tests/requirements/reqs-celery-4.txt +++ /dev/null @@ -1,4 +0,0 @@ -celery>4.0,<5 -# including future as it was missing in celery 4.4.4, see https://github.com/celery/celery/issues/6145 -future>=0.18.0 -importlib-metadata<5.0; python_version<"3.8" From 300f927d9d64081406187f0dd4cf74c0ca30cbe0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 15:04:29 +0200 Subject: [PATCH 095/409] build(deps): bump the github-actions group with 2 updates (#2069) Bumps the github-actions group with 2 updates: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) and [docker/build-push-action](https://github.com/docker/build-push-action). Updates `actions/attest-build-provenance` from 1.3.1 to 1.3.2 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/534b352d658f90498fd148d231fdbf88f3886a3a...bdd51370e0416ac948727f861e03c2f05d32d78e) Updates `docker/build-push-action` from 5.4.0 to 6.1.0 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/ca052bb54ab0790a636c9b5f226502c73d547a25...31159d49c0d4756269a0940a750801a1ea5d7003) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b8fbe1686..36c909f4e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/packages - name: generate build provenance - uses: actions/attest-build-provenance@534b352d658f90498fd148d231fdbf88f3886a3a # v1.3.1 + uses: actions/attest-build-provenance@bdd51370e0416ac948727f861e03c2f05d32d78e # v1.3.2 with: subject-path: "${{ github.workspace }}/dist/*" @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/build-distribution - name: generate build provenance - uses: actions/attest-build-provenance@534b352d658f90498fd148d231fdbf88f3886a3a # v1.3.1 + uses: actions/attest-build-provenance@bdd51370e0416ac948727f861e03c2f05d32d78e # v1.3.2 with: subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" @@ -141,7 +141,7 @@ jobs: - name: Build and push image id: push - uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5.4.0 + uses: docker/build-push-action@31159d49c0d4756269a0940a750801a1ea5d7003 # v6.1.0 with: context: . push: true @@ -152,7 +152,7 @@ jobs: AGENT_DIR=./build/dist/package/python - name: generate build provenance (containers) - uses: actions/attest-build-provenance@534b352d658f90498fd148d231fdbf88f3886a3a # v1.3.1 + uses: actions/attest-build-provenance@bdd51370e0416ac948727f861e03c2f05d32d78e # v1.3.2 with: subject-name: "${{ env.DOCKER_IMAGE_NAME }}" subject-digest: ${{ steps.push.outputs.digest }} From 8646535b7db11906366efdfa13c780ad1f9a916f Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Tue, 25 Jun 2024 15:40:55 +0200 Subject: [PATCH 096/409] Use elastic/oblt-actions/aws/auth action to authenticate with AWS (#2070) --- .github/workflows/release.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 36c909f4e..d1fd242f5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -74,16 +74,15 @@ jobs: needs: - build-distribution runs-on: ubuntu-latest - env: - # TODO: use keyless - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v3 with: name: build-distribution path: ./build + - uses: elastic/oblt-actions/aws/auth@v1 + with: + aws-account-id: "267093732750" - name: Publish lambda layers to AWS if: startsWith(github.ref, 'refs/tags') run: | From 4ba7a0b1cefac43202ccff497a65babea50b9e9e Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Tue, 25 Jun 2024 16:43:41 +0200 Subject: [PATCH 097/409] Automate collapsing ARN table in release notes (#2071) * Automate collapsing ARN table in release notes * Apply suggestions from code review --------- Co-authored-by: Riccardo Magliocchetti --- .ci/create-arn-table.sh | 9 +++++++-- CONTRIBUTING.md | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.ci/create-arn-table.sh b/.ci/create-arn-table.sh index 3105822ea..a03ead4c6 100755 --- a/.ci/create-arn-table.sh +++ b/.ci/create-arn-table.sh @@ -10,7 +10,8 @@ AWS_FOLDER=${AWS_FOLDER?:No aws folder provided} ARN_FILE=".arn-file.md" { - echo "### Elastic APM Python agent layer ARNs" + echo "
" + echo "Elastic APM Python agent layer ARNs" echo '' echo '|Region|ARN|' echo '|------|---|' @@ -22,4 +23,8 @@ for f in $(ls "${AWS_FOLDER}"); do echo "|${f}|${LAYER_VERSION_ARN}|" >> "${ARN_FILE}" done -echo '' >> "${ARN_FILE}" +{ + echo '' + echo '
' + echo '' +} >> "${ARN_FILE}" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c58fbb7c6..687ac5efb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -198,4 +198,4 @@ If you have commit access, the process is as follows: 1. After tests pass, Github Actions will automatically build and push the new release to PyPI. 1. Edit and publish the [draft Github release](https://github.com/elastic/apm-agent-python/releases) created by Github Actions. Substitute the generated changelog with one hand written into the body of the - release and move the agent layer ARNs under a `
` block with a `summary`. + release. From dea50dd92a2cef299cbff08e339b4459a72ad453 Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Tue, 25 Jun 2024 20:53:03 +0200 Subject: [PATCH 098/409] Add necessary permissions to use aws/auth action (#2072) --- .github/workflows/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d1fd242f5..6759af036 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -71,6 +71,9 @@ jobs: subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" publish-lambda-layers: + permissions: + contents: read + id-token: write needs: - build-distribution runs-on: ubuntu-latest From 3e5ad6c19aec1fba1cf86873f65a7e5b05da007a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 09:34:51 +0200 Subject: [PATCH 099/409] build(deps): bump docker/build-push-action in the github-actions group (#2074) Bumps the github-actions group with 1 update: [docker/build-push-action](https://github.com/docker/build-push-action). Updates `docker/build-push-action` from 6.1.0 to 6.2.0 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/31159d49c0d4756269a0940a750801a1ea5d7003...15560696de535e4014efeff63c48f16952e52dd1) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6759af036..8c6af2473 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -143,7 +143,7 @@ jobs: - name: Build and push image id: push - uses: docker/build-push-action@31159d49c0d4756269a0940a750801a1ea5d7003 # v6.1.0 + uses: docker/build-push-action@15560696de535e4014efeff63c48f16952e52dd1 # v6.2.0 with: context: . push: true From 97cef7dc34ee9927c315851ba6a719df7208cf56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 08:42:44 +0200 Subject: [PATCH 100/409] build(deps): bump docker/build-push-action in the github-actions group (#2079) Bumps the github-actions group with 1 update: [docker/build-push-action](https://github.com/docker/build-push-action). Updates `docker/build-push-action` from 6.2.0 to 6.3.0 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/15560696de535e4014efeff63c48f16952e52dd1...1a162644f9a7e87d8f4b053101d1d9a712edc18c) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8c6af2473..33a54e798 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -143,7 +143,7 @@ jobs: - name: Build and push image id: push - uses: docker/build-push-action@15560696de535e4014efeff63c48f16952e52dd1 # v6.2.0 + uses: docker/build-push-action@1a162644f9a7e87d8f4b053101d1d9a712edc18c # v6.3.0 with: context: . push: true From 34b0ba58a0e99fa43c5d6bbe5dd9e2c277930771 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 08:43:26 +0200 Subject: [PATCH 101/409] build(deps): bump certifi from 2024.6.2 to 2024.7.4 in /dev-utils (#2078) Bumps [certifi](https://github.com/certifi/python-certifi) from 2024.6.2 to 2024.7.4. - [Commits](https://github.com/certifi/python-certifi/compare/2024.06.02...2024.07.04) --- updated-dependencies: - dependency-name: certifi dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dev-utils/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-utils/requirements.txt b/dev-utils/requirements.txt index 87ab0cf28..ccc3d9baf 100644 --- a/dev-utils/requirements.txt +++ b/dev-utils/requirements.txt @@ -1,4 +1,4 @@ # These are the pinned requirements for the lambda layer/docker image -certifi==2024.6.2 +certifi==2024.7.4 urllib3==1.26.19 wrapt==1.14.1 From c5b6e157a6d127b45e7b6d821462f209427996c5 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 9 Jul 2024 10:00:39 +0200 Subject: [PATCH 102/409] instrumentation/kafka: fix handling consumer iteration if transaction not sampled (#2075) Handle the case where if the transaction is not sampled capture_span will return None instead of span. While at it fix handling of checking for KAFKA_HOST in tests. Fix #2073 --- elasticapm/instrumentation/packages/kafka.py | 3 ++- tests/instrumentation/kafka_tests.py | 24 +++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/elasticapm/instrumentation/packages/kafka.py b/elasticapm/instrumentation/packages/kafka.py index c3bc2d64d..ab9ebd1a4 100644 --- a/elasticapm/instrumentation/packages/kafka.py +++ b/elasticapm/instrumentation/packages/kafka.py @@ -143,7 +143,8 @@ def call(self, module, method, wrapped, instance, args, kwargs): try: result = wrapped(*args, **kwargs) except StopIteration: - span.cancel() + if span: + span.cancel() raise if span and not isinstance(span, DroppedSpan): topic = result[0] diff --git a/tests/instrumentation/kafka_tests.py b/tests/instrumentation/kafka_tests.py index 71416c130..0bfc5c496 100644 --- a/tests/instrumentation/kafka_tests.py +++ b/tests/instrumentation/kafka_tests.py @@ -45,11 +45,10 @@ pytestmark = [pytest.mark.kafka] -if "KAFKA_HOST" not in os.environ: +KAFKA_HOST = os.environ.get("KAFKA_HOST") +if not KAFKA_HOST: pytestmark.append(pytest.mark.skip("Skipping kafka tests, no KAFKA_HOST environment variable set")) -KAFKA_HOST = os.environ["KAFKA_HOST"] - @pytest.fixture(scope="function") def topics(): @@ -233,3 +232,22 @@ def test_kafka_poll_unsampled_transaction(instrument, elasticapm_client, consume elasticapm_client.end_transaction("foo") spans = elasticapm_client.events[SPAN] assert len(spans) == 0 + + +def test_kafka_consumer_unsampled_transaction_handles_stop_iteration( + instrument, elasticapm_client, producer, consumer, topics +): + def delayed_send(): + time.sleep(0.2) + producer.send("test", key=b"foo", value=b"bar") + + thread = threading.Thread(target=delayed_send) + thread.start() + transaction = elasticapm_client.begin_transaction("foo") + transaction.is_sampled = False + for item in consumer: + pass + thread.join() + elasticapm_client.end_transaction("foo") + spans = elasticapm_client.events[SPAN] + assert len(spans) == 0 From ded855b89f73d94e7d851d0f2cdb45978b66a020 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 9 Jul 2024 10:01:05 +0200 Subject: [PATCH 103/409] Promote some PendingDeprecationWarning to DeprecationWarning (#2076) Promote the removal of opentracing integration and the removal of logging handling to DeprecationWarnings. Refs #2029 --- elasticapm/contrib/django/handlers.py | 4 ++-- elasticapm/contrib/flask/__init__.py | 2 +- elasticapm/contrib/opentracing/__init__.py | 4 ++-- elasticapm/handlers/logging.py | 11 ++++------- tests/handlers/logging/logging_tests.py | 2 +- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/elasticapm/contrib/django/handlers.py b/elasticapm/contrib/django/handlers.py index 0c97e888d..c980acc4f 100644 --- a/elasticapm/contrib/django/handlers.py +++ b/elasticapm/contrib/django/handlers.py @@ -47,11 +47,11 @@ class LoggingHandler(BaseLoggingHandler): def __init__(self, level=logging.NOTSET) -> None: warnings.warn( - "The LoggingHandler will be deprecated in v7.0 of the agent. " + "The LoggingHandler is deprecated and will be removed in v7.0 of the agent. " "Please use `log_ecs_reformatting` and ship the logs with Elastic " "Agent or Filebeat instead. " "https://www.elastic.co/guide/en/apm/agent/python/current/logs.html", - PendingDeprecationWarning, + DeprecationWarning, ) # skip initialization of BaseLoggingHandler logging.Handler.__init__(self, level=level) diff --git a/elasticapm/contrib/flask/__init__.py b/elasticapm/contrib/flask/__init__.py index e9ec3323e..fdb6906dd 100644 --- a/elasticapm/contrib/flask/__init__.py +++ b/elasticapm/contrib/flask/__init__.py @@ -87,7 +87,7 @@ def __init__(self, app=None, client=None, client_cls=Client, logging=False, **de if self.logging: warnings.warn( "Flask log shipping is deprecated. See the Flask docs for more info and alternatives.", - PendingDeprecationWarning, + DeprecationWarning, ) self.client = client or get_client() self.client_cls = client_cls diff --git a/elasticapm/contrib/opentracing/__init__.py b/elasticapm/contrib/opentracing/__init__.py index 8fbc99b19..71619ea20 100644 --- a/elasticapm/contrib/opentracing/__init__.py +++ b/elasticapm/contrib/opentracing/__init__.py @@ -36,8 +36,8 @@ warnings.warn( ( - "The OpenTracing bridge will be deprecated in the next major release. " + "The OpenTracing bridge is deprecated and will be removed in the next major release. " "Please migrate to the OpenTelemetry bridge." ), - PendingDeprecationWarning, + DeprecationWarning, ) diff --git a/elasticapm/handlers/logging.py b/elasticapm/handlers/logging.py index ed4db87ac..96718d2db 100644 --- a/elasticapm/handlers/logging.py +++ b/elasticapm/handlers/logging.py @@ -51,7 +51,7 @@ def __init__(self, *args, **kwargs) -> None: "the agent. Please use `log_ecs_reformatting` and ship the logs " "with Elastic Agent or Filebeat instead. " "https://www.elastic.co/guide/en/apm/agent/python/current/logs.html", - PendingDeprecationWarning, + DeprecationWarning, ) self.client = None if "client" in kwargs: @@ -66,12 +66,9 @@ def __init__(self, *args, **kwargs) -> None: if client_cls: self.client = client_cls(*args, **kwargs) else: - # In 6.0, this should raise a ValueError warnings.warn( - "LoggingHandler requires a Client instance. No Client was " - "received. This will result in an error starting in v6.0 " - "of the agent", - PendingDeprecationWarning, + "LoggingHandler requires a Client instance. No Client was received.", + DeprecationWarning, ) self.client = Client(*args, **kwargs) logging.Handler.__init__(self, level=kwargs.get("level", logging.NOTSET)) @@ -201,7 +198,7 @@ def __init__(self, name=""): "the agent. On Python 3.2+, by default we add a LogRecordFactory to " "your root logger automatically" "https://www.elastic.co/guide/en/apm/agent/python/current/logs.html", - PendingDeprecationWarning, + DeprecationWarning, ) def filter(self, record): diff --git a/tests/handlers/logging/logging_tests.py b/tests/handlers/logging/logging_tests.py index 8e23a0b69..8cc8fc4f1 100644 --- a/tests/handlers/logging/logging_tests.py +++ b/tests/handlers/logging/logging_tests.py @@ -380,7 +380,7 @@ def test_logging_handler_no_client(recwarn): while True: # If we never find our desired warning this will eventually throw an # AssertionError - w = recwarn.pop(PendingDeprecationWarning) + w = recwarn.pop(DeprecationWarning) if "LoggingHandler requires a Client instance" in w.message.args[0]: return True From 82df0919b3f3ebb52290aa0f2b7d215867b020cd Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Tue, 9 Jul 2024 10:02:31 +0200 Subject: [PATCH 104/409] updatecli: use shared policy (#2077) --- .../updatecli.d/update-gherkin-specs.yml | 82 ------------------ .../updatecli.d/update-json-specs.yml | 82 ------------------ .ci/updatecli/updatecli.d/update-specs.yml | 84 ------------------- .ci/updatecli/values.d/apm-data-spec.yml | 1 + .ci/updatecli/values.d/apm-gherkin.yml | 1 + .ci/updatecli/values.d/apm-json-specs.yml | 1 + .ci/updatecli/values.d/scm.yml | 7 ++ .ci/updatecli/values.yml | 14 ---- .github/workflows/updatecli.yml | 19 ++++- update-compose.yaml | 18 ++++ 10 files changed, 45 insertions(+), 264 deletions(-) delete mode 100644 .ci/updatecli/updatecli.d/update-gherkin-specs.yml delete mode 100644 .ci/updatecli/updatecli.d/update-json-specs.yml delete mode 100644 .ci/updatecli/updatecli.d/update-specs.yml create mode 100644 .ci/updatecli/values.d/apm-data-spec.yml create mode 100644 .ci/updatecli/values.d/apm-gherkin.yml create mode 100644 .ci/updatecli/values.d/apm-json-specs.yml create mode 100644 .ci/updatecli/values.d/scm.yml delete mode 100644 .ci/updatecli/values.yml create mode 100644 update-compose.yaml diff --git a/.ci/updatecli/updatecli.d/update-gherkin-specs.yml b/.ci/updatecli/updatecli.d/update-gherkin-specs.yml deleted file mode 100644 index 82f986fae..000000000 --- a/.ci/updatecli/updatecli.d/update-gherkin-specs.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: update-gherkin-specs -pipelineid: update-gherkin-specs - -scms: - default: - kind: github - spec: - user: '{{ requiredEnv "GITHUB_ACTOR" }}' - owner: "{{ .github.owner }}" - repository: "{{ .github.repository }}" - token: '{{ requiredEnv "GITHUB_TOKEN" }}' - username: '{{ requiredEnv "GITHUB_ACTOR" }}' - branch: "{{ .github.branch }}" - apm: - kind: github - spec: - user: '{{ requiredEnv "GITHUB_ACTOR" }}' - owner: "{{ .github.owner }}" - repository: "{{ .github.apm_repository }}" - token: '{{ requiredEnv "GITHUB_TOKEN" }}' - username: '{{ requiredEnv "GITHUB_ACTOR" }}' - branch: "{{ .github.branch }}" - -sources: - sha: - kind: file - spec: - file: 'https://github.com/{{ .github.owner }}/{{ .github.apm_repository }}/commit/{{ .github.branch }}.patch' - matchpattern: "^From\\s([0-9a-f]{40})\\s" - transformers: - - findsubmatch: - pattern: "[0-9a-f]{40}" - pull_request: - kind: shell - dependson: - - sha - spec: - command: gh api /repos/{{ .github.owner }}/{{ .github.apm_repository }}/commits/{{ source "sha" }}/pulls --jq '.[].html_url' - environments: - - name: GITHUB_TOKEN - - name: PATH - agents-gherkin-specs-tarball: - kind: shell - scmid: apm - dependson: - - sha - spec: - command: tar cvzf {{ requiredEnv "GITHUB_WORKSPACE" }}/gherkin-specs.tgz . - environments: - - name: PATH - workdir: "{{ .specs.apm_gherkin_path }}" - -actions: - pr: - kind: "github/pullrequest" - scmid: default - spec: - automerge: false - draft: false - labels: - - "automation" - description: |- - ### What - APM agent Gherkin specs automatic sync - - ### Why - *Changeset* - * {{ source "pull_request" }} - * https://github.com/elastic/apm/commit/{{ source "sha" }} - title: '[Automation] Update Gherkin specs' - -targets: - agent-gherkin-specs: - name: APM agent gherkin specs {{ source "sha" }} - scmid: default - disablesourceinput: true - kind: shell - spec: - # git diff helps to print what it changed, If it is empty, then updatecli report a success with no changes applied. - # See https://www.updatecli.io/docs/plugins/resource/shell/#_shell_target - command: 'tar -xzf {{ requiredEnv "GITHUB_WORKSPACE" }}/gherkin-specs.tgz && git --no-pager diff' - workdir: "{{ .apm_agent.gherkin_specs_path }}" diff --git a/.ci/updatecli/updatecli.d/update-json-specs.yml b/.ci/updatecli/updatecli.d/update-json-specs.yml deleted file mode 100644 index 7b86367a7..000000000 --- a/.ci/updatecli/updatecli.d/update-json-specs.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: update-json-specs -pipelineid: update-json-specs - -scms: - default: - kind: github - spec: - user: '{{ requiredEnv "GITHUB_ACTOR" }}' - owner: "{{ .github.owner }}" - repository: "{{ .github.repository }}" - token: '{{ requiredEnv "GITHUB_TOKEN" }}' - username: '{{ requiredEnv "GITHUB_ACTOR" }}' - branch: "{{ .github.branch }}" - apm: - kind: github - spec: - user: '{{ requiredEnv "GITHUB_ACTOR" }}' - owner: "{{ .github.owner }}" - repository: "{{ .github.apm_repository }}" - token: '{{ requiredEnv "GITHUB_TOKEN" }}' - username: '{{ requiredEnv "GITHUB_ACTOR" }}' - branch: "{{ .github.branch }}" - -sources: - sha: - kind: file - spec: - file: 'https://github.com/{{ .github.owner }}/{{ .github.apm_repository }}/commit/{{ .github.branch }}.patch' - matchpattern: "^From\\s([0-9a-f]{40})\\s" - transformers: - - findsubmatch: - pattern: "[0-9a-f]{40}" - pull_request: - kind: shell - dependson: - - sha - spec: - command: gh api /repos/{{ .github.owner }}/{{ .github.apm_repository }}/commits/{{ source "sha" }}/pulls --jq '.[].html_url' - environments: - - name: GITHUB_TOKEN - - name: PATH - agents-json-specs-tarball: - kind: shell - scmid: apm - dependson: - - sha - spec: - command: tar cvzf {{ requiredEnv "GITHUB_WORKSPACE" }}/json-specs.tgz . - environments: - - name: PATH - workdir: "{{ .specs.apm_json_path }}" - -actions: - pr: - kind: "github/pullrequest" - scmid: default - spec: - automerge: false - draft: false - labels: - - "automation" - description: |- - ### What - APM agent specs automatic sync - - ### Why - *Changeset* - * {{ source "pull_request" }} - * https://github.com/{{ .github.owner }}/{{ .github.apm_repository }}/commit/{{ source "sha" }} - title: '[Automation] Update JSON specs' - -targets: - agent-json-specs: - name: APM agent json specs {{ source "sha" }} - scmid: default - disablesourceinput: true - kind: shell - spec: - # git diff helps to print what it changed, If it is empty, then updatecli report a success with no changes applied. - # See https://www.updatecli.io/docs/plugins/resource/shell/#_shell_target - command: 'tar -xzf {{ requiredEnv "GITHUB_WORKSPACE" }}/json-specs.tgz && git --no-pager diff' - workdir: "{{ .apm_agent.json_specs_path }}" diff --git a/.ci/updatecli/updatecli.d/update-specs.yml b/.ci/updatecli/updatecli.d/update-specs.yml deleted file mode 100644 index ab5589f7e..000000000 --- a/.ci/updatecli/updatecli.d/update-specs.yml +++ /dev/null @@ -1,84 +0,0 @@ -name: update-specs -pipelineid: update-schema-specs - -scms: - default: - kind: github - spec: - user: '{{ requiredEnv "GITHUB_ACTOR" }}' - owner: "{{ .github.owner }}" - repository: "{{ .github.repository }}" - token: '{{ requiredEnv "GITHUB_TOKEN" }}' - username: '{{ requiredEnv "GITHUB_ACTOR" }}' - branch: "{{ .github.branch }}" - - apm-data: - kind: github - spec: - user: '{{ requiredEnv "GITHUB_ACTOR" }}' - owner: "{{ .github.owner }}" - repository: "{{ .github.apm_data_repository }}" - token: '{{ requiredEnv "GITHUB_TOKEN" }}' - username: '{{ requiredEnv "GITHUB_ACTOR" }}' - branch: "{{ .github.branch }}" - -sources: - sha: - kind: file - spec: - file: 'https://github.com/{{ .github.owner }}/{{ .github.apm_data_repository }}/commit/{{ .github.branch }}.patch' - matchpattern: "^From\\s([0-9a-f]{40})\\s" - transformers: - - findsubmatch: - pattern: "[0-9a-f]{40}" - pull_request: - kind: shell - dependson: - - sha - spec: - command: gh api /repos/{{ .github.owner }}/{{ .github.apm_data_repository }}/commits/{{ source "sha" }}/pulls --jq '.[].html_url' - environments: - - name: GITHUB_TOKEN - - name: PATH - agent-specs-tarball: - kind: shell - scmid: apm-data - dependson: - - sha - spec: - command: tar cvzf {{ requiredEnv "GITHUB_WORKSPACE" }}/json-schema.tgz . - environments: - - name: PATH - workdir: "{{ .specs.apm_data_path }}" - -actions: - pr: - kind: "github/pullrequest" - scmid: default - sourceid: sha - spec: - automerge: false - draft: false - labels: - - "automation" - description: |- - ### What - APM agent json server schema automatic sync - - ### Why - *Changeset* - * {{ source "pull_request" }} - * https://github.com/{{ .github.owner }}/{{ .github.apm_data_repository }}/commit/{{ source "sha" }} - title: '[Automation] Update JSON server schema specs' - -targets: - agent-json-schema: - name: APM agent json server schema {{ source "sha" }} - scmid: default - disablesourceinput: true - kind: shell - spec: - # git diff helps to print what it changed, If it is empty, then updatecli report a success with no changes applied. - # See https://www.updatecli.io/docs/plugins/resource/shell/#_shell_target - command: 'tar -xzf {{ requiredEnv "GITHUB_WORKSPACE" }}/json-schema.tgz && git --no-pager diff' - workdir: "{{ .apm_agent.server_schema_specs_path }}" diff --git a/.ci/updatecli/values.d/apm-data-spec.yml b/.ci/updatecli/values.d/apm-data-spec.yml new file mode 100644 index 000000000..4bf89f633 --- /dev/null +++ b/.ci/updatecli/values.d/apm-data-spec.yml @@ -0,0 +1 @@ +apm_schema_specs_path: tests/upstream/json-specs diff --git a/.ci/updatecli/values.d/apm-gherkin.yml b/.ci/updatecli/values.d/apm-gherkin.yml new file mode 100644 index 000000000..7234fe8c8 --- /dev/null +++ b/.ci/updatecli/values.d/apm-gherkin.yml @@ -0,0 +1 @@ +apm_gherkin_specs_path: tests/bdd/features \ No newline at end of file diff --git a/.ci/updatecli/values.d/apm-json-specs.yml b/.ci/updatecli/values.d/apm-json-specs.yml new file mode 100644 index 000000000..c527210e4 --- /dev/null +++ b/.ci/updatecli/values.d/apm-json-specs.yml @@ -0,0 +1 @@ +apm_json_specs_path: tests/upstream/json-specs diff --git a/.ci/updatecli/values.d/scm.yml b/.ci/updatecli/values.d/scm.yml new file mode 100644 index 000000000..78f9e4bc0 --- /dev/null +++ b/.ci/updatecli/values.d/scm.yml @@ -0,0 +1,7 @@ +scm: + enabled: true + owner: elastic + repository: apm-agent-python + branch: main + +signedcommit: true \ No newline at end of file diff --git a/.ci/updatecli/values.yml b/.ci/updatecli/values.yml deleted file mode 100644 index b0b58d73e..000000000 --- a/.ci/updatecli/values.yml +++ /dev/null @@ -1,14 +0,0 @@ -github: - owner: "elastic" - repository: "apm-agent-python" - apm_repository: "apm" - apm_data_repository: "apm-data" - branch: "main" -specs: - apm_data_path: "input/elasticapm/docs/spec/v2" - apm_json_path: "tests/agents/json-specs" - apm_gherkin_path: "tests/agents/gherkin-specs" -apm_agent: - gherkin_specs_path: "tests/bdd/features" - json_specs_path: "tests/upstream/json-specs" - server_schema_specs_path: "tests/upstream/json-specs" \ No newline at end of file diff --git a/.github/workflows/updatecli.yml b/.github/workflows/updatecli.yml index 8190487ad..b2d1e7a8a 100644 --- a/.github/workflows/updatecli.yml +++ b/.github/workflows/updatecli.yml @@ -9,14 +9,29 @@ permissions: contents: read jobs: - bump: + compose: runs-on: ubuntu-latest + permissions: + contents: read + packages: read steps: - uses: actions/checkout@v4 + - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - uses: elastic/oblt-actions/updatecli/run@v1 + with: + command: --experimental compose diff + env: + GITHUB_TOKEN: ${{ secrets.UPDATECLI_GH_TOKEN }} + - uses: elastic/oblt-actions/updatecli/run@v1 with: - command: "apply --config .ci/updatecli/updatecli.d --values .ci/updatecli/values.yml" + command: --experimental compose apply env: GITHUB_TOKEN: ${{ secrets.UPDATECLI_GH_TOKEN }} diff --git a/update-compose.yaml b/update-compose.yaml new file mode 100644 index 000000000..d40020933 --- /dev/null +++ b/update-compose.yaml @@ -0,0 +1,18 @@ +policies: + - name: Handle apm-data server specs + policy: ghcr.io/elastic/oblt-updatecli-policies/apm/apm-data-spec:0.2.0@sha256:7069c0773d44a74c4c8103b4d9957b468f66081ee9d677238072fe11c4d2197c + values: + - .ci/updatecli/values.d/scm.yml + - .ci/updatecli/values.d/apm-data-spec.yml + + - name: Handle apm gherkin specs + policy: ghcr.io/elastic/oblt-updatecli-policies/apm/apm-gherkin:0.2.0@sha256:26a30ad2b98a6e4cb17fb88a28fa3277ced8ca862d6388943afaafbf8ee96e7d + values: + - .ci/updatecli/values.d/scm.yml + - .ci/updatecli/values.d/apm-gherkin.yml + + - name: Handle apm json specs + policy: ghcr.io/elastic/oblt-updatecli-policies/apm/apm-json-specs:0.2.0@sha256:969a6d21eabd6ebea66cb29b35294a273d6dbc0f7da78589c416aedf08728e78 + values: + - .ci/updatecli/values.d/scm.yml + - .ci/updatecli/values.d/apm-json-specs.yml From b85245cb71124e542486572cac150e5f5b592aad Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Wed, 10 Jul 2024 10:23:01 +0200 Subject: [PATCH 105/409] feat: make published Docker images multi-platform, add linux/arm64 plat (#2080) --- .github/workflows/release.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 33a54e798..ea2ab7b78 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -118,6 +118,9 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 + - name: Log in to the Elastic Container registry uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 with: @@ -142,10 +145,11 @@ jobs: suffix=${{ contains(matrix.dockerfile, 'wolfi') && '-wolfi' || '' }} - name: Build and push image - id: push + id: docker-push uses: docker/build-push-action@1a162644f9a7e87d8f4b053101d1d9a712edc18c # v6.3.0 with: context: . + platforms: linux/amd64,linux/arm64 push: true file: ${{ matrix.dockerfile }} tags: ${{ steps.docker-meta.outputs.tags }} @@ -157,7 +161,7 @@ jobs: uses: actions/attest-build-provenance@bdd51370e0416ac948727f861e03c2f05d32d78e # v1.3.2 with: subject-name: "${{ env.DOCKER_IMAGE_NAME }}" - subject-digest: ${{ steps.push.outputs.digest }} + subject-digest: ${{ steps.docker-push.outputs.digest }} push-to-registry: true github-draft: @@ -196,7 +200,7 @@ jobs: with: needs: ${{ toJSON(needs) }} - if: startsWith(github.ref, 'refs/tags') - uses: elastic/oblt-actions/slack/notify-result@v1.7.0 + uses: elastic/oblt-actions/slack/notify-result@v1 with: bot-token: ${{ secrets.SLACK_BOT_TOKEN }} channel-id: "#apm-agent-python" From 22cc055a6cfc3628ae96a26d32370dd03224858c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 09:22:17 +0200 Subject: [PATCH 106/409] build(deps): bump the github-actions group with 2 updates (#2081) Bumps the github-actions group with 2 updates: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) and [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action). Updates `actions/attest-build-provenance` from 1.3.2 to 1.3.3 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/bdd51370e0416ac948727f861e03c2f05d32d78e...5e9cb68e95676991667494a6a4e59b8a2f13e1d0) Updates `docker/setup-buildx-action` from 3.3.0 to 3.4.0 - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/d70bba72b1f3fd22344832f00baa16ece964efeb...4fd812986e6c8c2a69e18311145f9371337f27d4) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ea2ab7b78..08457b92e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/packages - name: generate build provenance - uses: actions/attest-build-provenance@bdd51370e0416ac948727f861e03c2f05d32d78e # v1.3.2 + uses: actions/attest-build-provenance@5e9cb68e95676991667494a6a4e59b8a2f13e1d0 # v1.3.3 with: subject-path: "${{ github.workspace }}/dist/*" @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/build-distribution - name: generate build provenance - uses: actions/attest-build-provenance@bdd51370e0416ac948727f861e03c2f05d32d78e # v1.3.2 + uses: actions/attest-build-provenance@5e9cb68e95676991667494a6a4e59b8a2f13e1d0 # v1.3.3 with: subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" @@ -119,7 +119,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 + uses: docker/setup-buildx-action@4fd812986e6c8c2a69e18311145f9371337f27d4 # v3.4.0 - name: Log in to the Elastic Container registry uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 @@ -158,7 +158,7 @@ jobs: AGENT_DIR=./build/dist/package/python - name: generate build provenance (containers) - uses: actions/attest-build-provenance@bdd51370e0416ac948727f861e03c2f05d32d78e # v1.3.2 + uses: actions/attest-build-provenance@5e9cb68e95676991667494a6a4e59b8a2f13e1d0 # v1.3.3 with: subject-name: "${{ env.DOCKER_IMAGE_NAME }}" subject-digest: ${{ steps.docker-push.outputs.digest }} From 8163b0cabaaf2d88627962e0f052cb4dbaac1713 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 17 Jul 2024 17:38:41 +0200 Subject: [PATCH 107/409] contrib/serverless: capture_serverless is already called by the APM layer (#2083) Add a small note in capture_serverless docstring that with the APM layer it is already been called and adding it manually will result in instrumenting the handler twice. --- elasticapm/contrib/serverless/aws.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/elasticapm/contrib/serverless/aws.py b/elasticapm/contrib/serverless/aws.py index 9f5f7b133..1717d57cc 100644 --- a/elasticapm/contrib/serverless/aws.py +++ b/elasticapm/contrib/serverless/aws.py @@ -71,6 +71,9 @@ def capture_serverless(func: Optional[callable] = None, **kwargs) -> callable: @capture_serverless def handler(event, context): return {"statusCode": r.status_code, "body": "Success!"} + + Please note that when using the APM Layer and setting AWS_LAMBDA_EXEC_WRAPPER this is not required and + the handler would be instrumented automatically. """ if not func: # This allows for `@capture_serverless()` in addition to From b219e9168fe18181fee1bebb6a7b748c4c45b083 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 10:03:04 +0200 Subject: [PATCH 108/409] build(deps): bump docker/build-push-action in the github-actions group (#2086) Bumps the github-actions group with 1 update: [docker/build-push-action](https://github.com/docker/build-push-action). Updates `docker/build-push-action` from 6.3.0 to 6.4.1 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/1a162644f9a7e87d8f4b053101d1d9a712edc18c...1ca370b3a9802c92e886402e0dd88098a2533b12) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 08457b92e..8148d1548 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -146,7 +146,7 @@ jobs: - name: Build and push image id: docker-push - uses: docker/build-push-action@1a162644f9a7e87d8f4b053101d1d9a712edc18c # v6.3.0 + uses: docker/build-push-action@1ca370b3a9802c92e886402e0dd88098a2533b12 # v6.4.1 with: context: . platforms: linux/amd64,linux/arm64 From ae65416ec5c9ca9323ede70605ed837698f1e5f7 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 26 Jul 2024 09:13:24 +0200 Subject: [PATCH 109/409] elasticapm/transport: specific shutdown handling for http transport (#2085) We have a race condition at http transport shutdown where our atexit handler is racing against urllib3 ConnectionPool weakref finalizer. Having the urllib3 finalizer called before atexit would lead to have our thread hang waiting for send any eventual queued data via urllib3 pools that are closed. Force the creation of a new PoolManager so that we are always able to flush. --- elasticapm/transport/base.py | 3 +- elasticapm/transport/http.py | 18 +++++++ tests/transports/test_urllib3.py | 88 ++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) diff --git a/elasticapm/transport/base.py b/elasticapm/transport/base.py index 487e13a93..24911c395 100644 --- a/elasticapm/transport/base.py +++ b/elasticapm/transport/base.py @@ -292,7 +292,8 @@ def close(self) -> None: if not self._flushed.wait(timeout=self._max_flush_time_seconds): logger.error("Closing the transport connection timed out.") - stop_thread = close + def stop_thread(self) -> None: + self.close() def flush(self): """ diff --git a/elasticapm/transport/http.py b/elasticapm/transport/http.py index 17ae50ba3..ed132068c 100644 --- a/elasticapm/transport/http.py +++ b/elasticapm/transport/http.py @@ -32,6 +32,7 @@ import hashlib import json +import os import re import ssl import urllib.parse @@ -250,6 +251,23 @@ def ca_certs(self): return self._server_ca_cert_file return certifi.where() if (certifi and self.client.config.use_certifi) else None + def close(self): + """ + Take care of being able to shutdown cleanly + :return: + """ + if self._closed or (not self._thread or self._thread.pid != os.getpid()): + return + + self._closed = True + # we are racing against urllib3 ConnectionPool weakref finalizer that would lead to having them closed + # and we hanging waiting for send any eventual queued data + # Force the creation of a new PoolManager so that we are always able to flush + self._http = None + self.queue("close", None) + if not self._flushed.wait(timeout=self._max_flush_time_seconds): + logger.error("Closing the transport connection timed out.") + def version_string_to_tuple(version): if version: diff --git a/tests/transports/test_urllib3.py b/tests/transports/test_urllib3.py index b24408e54..32a5b7384 100644 --- a/tests/transports/test_urllib3.py +++ b/tests/transports/test_urllib3.py @@ -30,6 +30,7 @@ import os +import time import certifi import mock @@ -517,3 +518,90 @@ def test_fetch_server_info_flat_string(waiting_httpserver, caplog, elasticapm_cl transport.fetch_server_info() assert elasticapm_client.server_version is None assert_any_record_contains(caplog.records, "No version key found in server response") + + +def test_close(waiting_httpserver, elasticapm_client): + elasticapm_client.server_version = (8, 0, 0) # avoid making server_info request + waiting_httpserver.serve_content(code=202, content="", headers={"Location": "http://example.com/foo"}) + transport = Transport( + waiting_httpserver.url, client=elasticapm_client, headers=elasticapm_client._transport._headers + ) + transport.start_thread() + + transport.close() + + assert transport._closed is True + assert transport._flushed.is_set() is True + + +def test_close_does_nothing_if_called_from_another_pid(waiting_httpserver, caplog, elasticapm_client): + elasticapm_client.server_version = (8, 0, 0) # avoid making server_info request + waiting_httpserver.serve_content(code=202, content="", headers={"Location": "http://example.com/foo"}) + transport = Transport( + waiting_httpserver.url, client=elasticapm_client, headers=elasticapm_client._transport._headers + ) + transport.start_thread() + + with mock.patch("os.getpid") as getpid_mock: + getpid_mock.return_value = 0 + transport.close() + + assert transport._closed is False + + transport.close() + + +def test_close_can_be_called_multiple_times(waiting_httpserver, caplog, elasticapm_client): + elasticapm_client.server_version = (8, 0, 0) # avoid making server_info request + waiting_httpserver.serve_content(code=202, content="", headers={"Location": "http://example.com/foo"}) + transport = Transport( + waiting_httpserver.url, client=elasticapm_client, headers=elasticapm_client._transport._headers + ) + transport.start_thread() + + with caplog.at_level("INFO", logger="elasticapm.transport.http"): + transport.close() + + assert transport._closed is True + + transport.close() + + +def test_close_timeout_error_without_flushing(waiting_httpserver, caplog, elasticapm_client): + elasticapm_client.server_version = (8, 0, 0) # avoid making server_info request + waiting_httpserver.serve_content(code=202, content="", headers={"Location": "http://example.com/foo"}) + + with caplog.at_level("INFO", logger="elasticapm.transport.http"): + with mock.patch.object(Transport, "_max_flush_time_seconds", 0): + with mock.patch.object(Transport, "_flush") as flush_mock: + # sleep more that the timeout + flush_mock.side_effect = lambda x: time.sleep(0.1) + transport = Transport( + waiting_httpserver.url, client=elasticapm_client, headers=elasticapm_client._transport._headers + ) + transport.start_thread() + # need to write something to the buffer to have _flush() called + transport.queue("error", {"an": "error"}) + transport.close() + + assert transport._flushed.is_set() is False + assert transport._closed is True + record = caplog.records[-1] + assert "Closing the transport connection timed out." in record.msg + + +def test_http_pool_manager_is_recycled_at_stop_thread(waiting_httpserver, caplog, elasticapm_client): + elasticapm_client.server_version = (8, 0, 0) # avoid making server_info request + waiting_httpserver.serve_content(code=202, content="", headers={"Location": "http://example.com/foo"}) + transport = Transport( + waiting_httpserver.url, client=elasticapm_client, headers=elasticapm_client._transport._headers + ) + transport.start_thread() + pool_manager = transport.http + + with caplog.at_level("INFO", logger="elasticapm.transport.http"): + transport.stop_thread() + + assert transport._flushed.is_set() is True + assert pool_manager != transport._http + assert not caplog.records From c7f83055a8149ae7246c725807fe848955c78aaa Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 29 Jul 2024 09:53:46 +0200 Subject: [PATCH 110/409] tests: bump setuptools to latest (#2087) For some reason twisted tests are now failing with not so recent setuptools versions: 56.0 fails but 59.6.0 works. Twisted fails to install with: File "/usr/local/lib/python3.8/site-packages/pkg_resources/__init__.py", line 2456, in resolve module = __import__(self.module_name, fromlist=['__name__'], level=0) ModuleNotFoundError: No module named 'setuptools.command.build' --- tests/scripts/run_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/scripts/run_tests.sh b/tests/scripts/run_tests.sh index fc248949f..7fcc85010 100755 --- a/tests/scripts/run_tests.sh +++ b/tests/scripts/run_tests.sh @@ -3,7 +3,7 @@ set -e export PATH=${HOME}/.local/bin:${PATH} -python -m pip install --user -U pip --cache-dir "${PIP_CACHE}" +python -m pip install --user -U pip setuptools --cache-dir "${PIP_CACHE}" python -m pip install --user -r "tests/requirements/reqs-${FRAMEWORK}.txt" --cache-dir "${PIP_CACHE}" export PYTHON_VERSION=$(python -c "import platform; pv=platform.python_version_tuple(); print('pypy' + ('' if pv[0] == 2 else str(pv[0])) if platform.python_implementation() == 'PyPy' else '.'.join(map(str, platform.python_version_tuple()[:2])))") From 75efcca9e0c9c7d8da2ece508c91f89961861f80 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jul 2024 10:42:10 +0200 Subject: [PATCH 111/409] build(deps): bump the github-actions group with 3 updates (#2089) Bumps the github-actions group with 3 updates: [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action), [docker/login-action](https://github.com/docker/login-action) and [docker/build-push-action](https://github.com/docker/build-push-action). Updates `docker/setup-buildx-action` from 3.4.0 to 3.5.0 - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/4fd812986e6c8c2a69e18311145f9371337f27d4...aa33708b10e362ff993539393ff100fa93ed6a27) Updates `docker/login-action` from 3.2.0 to 3.3.0 - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/0d4c9c5ea7693da7b068278f7b52bda2a190a446...9780b0c442fbb1117ed29e0efdff1e18412f7567) Updates `docker/build-push-action` from 6.4.1 to 6.5.0 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/1ca370b3a9802c92e886402e0dd88098a2533b12...5176d81f87c23d6fc96624dfdbcd9f3830bbe445) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 6 +++--- .github/workflows/updatecli.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8148d1548..a68e508c3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -119,10 +119,10 @@ jobs: - uses: actions/checkout@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@4fd812986e6c8c2a69e18311145f9371337f27d4 # v3.4.0 + uses: docker/setup-buildx-action@aa33708b10e362ff993539393ff100fa93ed6a27 # v3.5.0 - name: Log in to the Elastic Container registry - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ${{ secrets.ELASTIC_DOCKER_REGISTRY }} username: ${{ secrets.ELASTIC_DOCKER_USERNAME }} @@ -146,7 +146,7 @@ jobs: - name: Build and push image id: docker-push - uses: docker/build-push-action@1ca370b3a9802c92e886402e0dd88098a2533b12 # v6.4.1 + uses: docker/build-push-action@5176d81f87c23d6fc96624dfdbcd9f3830bbe445 # v6.5.0 with: context: . platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/updatecli.yml b/.github/workflows/updatecli.yml index b2d1e7a8a..c56645f0e 100644 --- a/.github/workflows/updatecli.yml +++ b/.github/workflows/updatecli.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ghcr.io username: ${{ github.actor }} From c2b6d376dc61d8f137f4e45e89a2629571e50517 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 30 Jul 2024 09:47:22 +0200 Subject: [PATCH 112/409] Drop `setup.py test` (#2090) * scripts/run-tests: remove python2 relics * setup.py: drop test For compatibility with setuptools>=72 that remove the test command. See https://github.com/pypa/setuptools/issues/931 * setup.py: remove unused imports --- scripts/run-tests.bat | 6 ++---- setup.py | 38 ++------------------------------------ 2 files changed, 4 insertions(+), 40 deletions(-) diff --git a/scripts/run-tests.bat b/scripts/run-tests.bat index 0fce63000..f17e57d9c 100644 --- a/scripts/run-tests.bat +++ b/scripts/run-tests.bat @@ -9,14 +9,12 @@ set VENV_PYTHON=%cd%\venv\Scripts\ set COVERAGE_FILE=.coverage.windows.%VERSION%.%FRAMEWORK%.%ASYNCIO% -set IGNORE_PYTHON3_WITH_PYTHON2= -if "%VERSION%" == "2.7" set IGNORE_PYTHON3_WITH_PYTHON2=--ignore-glob="*\py3_*.py" set PYTEST_JUNIT="--junitxml=.\tests\windows-%VERSION%-%FRAMEWORK%-%ASYNCIO%-python-agent-junit.xml" if "%ASYNCIO%" == "true" ( - %VENV_PYTHON%\python.exe -m pytest %PYTEST_JUNIT% %IGNORE_PYTHON3_WITH_PYTHON2% --cov --cov-context=test --cov-branch --cov-config=setup.cfg -m "not integrationtest" || exit /b 1 + %VENV_PYTHON%\python.exe -m pytest %PYTEST_JUNIT% --cov --cov-context=test --cov-branch --cov-config=setup.cfg -m "not integrationtest" || exit /b 1 ) if "%ASYNCIO%" == "false" ( - %VENV_PYTHON%\python.exe -m pytest %PYTEST_JUNIT% --ignore-glob="*\asyncio*\*" %IGNORE_PYTHON3_WITH_PYTHON2% --cov --cov-context=test --cov-branch --cov-config=setup.cfg -m "not integrationtest" || exit /b 1 + %VENV_PYTHON%\python.exe -m pytest %PYTEST_JUNIT% --ignore-glob="*\asyncio*\*" --cov --cov-context=test --cov-branch --cov-config=setup.cfg -m "not integrationtest" || exit /b 1 ) call %VENV_PYTHON%\python.exe setup.py bdist_wheel diff --git a/setup.py b/setup.py index a88cdeb5b..5dbb8f643 100644 --- a/setup.py +++ b/setup.py @@ -40,50 +40,16 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# Hack to prevent stupid "TypeError: 'NoneType' object is not callable" error -# in multiprocessing/util.py _exit_function when running `python -# setup.py test` (see -# http://www.eby-sarna.com/pipermail/peak/2010-May/003357.html) -for m in ("multiprocessing", "billiard"): - try: - __import__(m) - except ImportError: - pass - import ast import codecs import os -import sys -from distutils.command.build_ext import build_ext -from distutils.errors import CCompilerError, DistutilsExecError, DistutilsPlatformError import pkg_resources -from setuptools import Extension, setup -from setuptools.command.test import test as TestCommand +from setuptools import setup pkg_resources.require("setuptools>=39.2") -class PyTest(TestCommand): - user_options = [("pytest-args=", "a", "Arguments to pass to py.test")] - - def initialize_options(self) -> None: - TestCommand.initialize_options(self) - self.pytest_args = [] - - def finalize_options(self) -> None: - TestCommand.finalize_options(self) - self.test_args = [] - self.test_suite = True - - def run_tests(self) -> None: - # import here, cause outside the eggs aren't loaded - import pytest - - errno = pytest.main(self.pytest_args) - sys.exit(errno) - - def get_version(): """ Get version without importing from elasticapm. This avoids any side effects @@ -108,4 +74,4 @@ def get_version(): return "unknown" -setup(cmdclass={"test": PyTest}, version=get_version()) +setup(version=get_version()) From 3289d7001829864ce81716c555b36b1df52c5ab4 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 30 Jul 2024 17:11:42 +0200 Subject: [PATCH 113/409] update CHANGELOG and bump version to 6.23.0 (#2088) --- CHANGELOG.asciidoc | 24 ++++++++++++++++++++++++ elasticapm/version.py | 2 +- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index b8df53ab6..7550c42d6 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -32,6 +32,30 @@ endif::[] [[release-notes-6.x]] === Python Agent version 6.x +[[release-notes-6.23.0]] +==== 6.23.0 - 2024-07-30 + +[float] +===== Features + +* Make published Docker images multi-platform with the addition of linux/arm64 {pull}2080[#2080] + +[float] +===== Bug fixes + +* Fix handling consumer iteration if transaction not sampled in kafka instrumentation {pull}2075[#2075] +* Fix race condition with urllib3 at shutdown {pull}2085[#2085] +* Fix compatibility with setuptools>=72 that removed test command {pull}2090[#2090] + +===== Deprecations + +* Python 3.6 support will be removed in version 7.0.0 of the agent +* The log shipping LoggingHandler will be removed in version 7.0.0 of the agent. +* The log shipping feature in the Flask instrumentation will be removed in version 7.0.0 of the agent. +* The log shipping feature in the Django instrumentation will be removed in version 7.0.0 of the agent. +* The OpenTracing bridge will be removed in version 7.0.0 of the agent. +* Celery 4.0 support is deprecated because it's not installable anymore with a modern pip + [[release-notes-6.22.3]] ==== 6.22.3 - 2024-06-10 diff --git a/elasticapm/version.py b/elasticapm/version.py index 82aa446ad..e9eff8543 100644 --- a/elasticapm/version.py +++ b/elasticapm/version.py @@ -28,5 +28,5 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = (6, 22, 3) +__version__ = (6, 23, 0) VERSION = ".".join(map(str, __version__)) From e53c37c23c49099fa250b01c2ea310cebc492769 Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Fri, 2 Aug 2024 14:35:27 +0200 Subject: [PATCH 114/409] updatecli: rename update-compose.yaml to updatecli-compose.yaml (#2095) --- update-compose.yaml => updatecli-compose.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename update-compose.yaml => updatecli-compose.yaml (100%) diff --git a/update-compose.yaml b/updatecli-compose.yaml similarity index 100% rename from update-compose.yaml rename to updatecli-compose.yaml From 7cf3fb5924281eb3beca0c881d0456a2cec4de75 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 12:32:48 +0200 Subject: [PATCH 115/409] build(deps): bump the github-actions group with 2 updates (#2096) Bumps the github-actions group with 2 updates: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) and [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action). Updates `actions/attest-build-provenance` from 1.3.3 to 1.4.0 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/5e9cb68e95676991667494a6a4e59b8a2f13e1d0...210c1913531870065f03ce1f9440dd87bc0938cd) Updates `docker/setup-buildx-action` from 3.5.0 to 3.6.1 - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/aa33708b10e362ff993539393ff100fa93ed6a27...988b5a0280414f521da01fcc63a27aeeb4b104db) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a68e508c3..8abce8e86 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/packages - name: generate build provenance - uses: actions/attest-build-provenance@5e9cb68e95676991667494a6a4e59b8a2f13e1d0 # v1.3.3 + uses: actions/attest-build-provenance@210c1913531870065f03ce1f9440dd87bc0938cd # v1.4.0 with: subject-path: "${{ github.workspace }}/dist/*" @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/build-distribution - name: generate build provenance - uses: actions/attest-build-provenance@5e9cb68e95676991667494a6a4e59b8a2f13e1d0 # v1.3.3 + uses: actions/attest-build-provenance@210c1913531870065f03ce1f9440dd87bc0938cd # v1.4.0 with: subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" @@ -119,7 +119,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@aa33708b10e362ff993539393ff100fa93ed6a27 # v3.5.0 + uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1 - name: Log in to the Elastic Container registry uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 @@ -158,7 +158,7 @@ jobs: AGENT_DIR=./build/dist/package/python - name: generate build provenance (containers) - uses: actions/attest-build-provenance@5e9cb68e95676991667494a6a4e59b8a2f13e1d0 # v1.3.3 + uses: actions/attest-build-provenance@210c1913531870065f03ce1f9440dd87bc0938cd # v1.4.0 with: subject-name: "${{ env.DOCKER_IMAGE_NAME }}" subject-digest: ${{ steps.docker-push.outputs.digest }} From 0290c7fc9a0a7b5f9cc4e5f6a0cc1394b9d44430 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 15:43:09 +0200 Subject: [PATCH 116/409] chore: Configure Renovate (#2082) * Add renovate.json * Disable non-wolfi package updates --------- Co-authored-by: elastic-renovate-prod[bot] Co-authored-by: Ellie <4158750+esenmarti@users.noreply.github.com> Co-authored-by: Riccardo Magliocchetti --- renovate.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 000000000..6da18794f --- /dev/null +++ b/renovate.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "local>elastic/renovate-config", + "github>elastic/renovate-config:only-chainguard" + ] +} From 7216a7499d8c172d93bcc84f3d8f2372c4566658 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Mon, 5 Aug 2024 17:41:44 +0200 Subject: [PATCH 117/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to 19764e8 (#2098) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 1ed923ce5..a898dfa9f 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base@sha256:9f940409f96296ef56140bcc4665c204dd499af4c32c96cc00e792558097c3f1 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:19764e89441be1f36544f715a738abc1a1898f35ed729486d33172eb54e8d84a ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 9c900eed7aea5d8b4f37828ec672ae5d814b3130 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 19 Aug 2024 09:34:30 +0200 Subject: [PATCH 118/409] renovate: enable only for chainguard (#2103) As far as I know, we use Dependabot for all the dependencies and Renovate only for the chainguard. --- renovate.json | 1 - 1 file changed, 1 deletion(-) diff --git a/renovate.json b/renovate.json index 6da18794f..10a37617c 100644 --- a/renovate.json +++ b/renovate.json @@ -1,7 +1,6 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ - "local>elastic/renovate-config", "github>elastic/renovate-config:only-chainguard" ] } From aba55f3a706ec657a0de2023fe8a400bd7b29547 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 09:35:31 +0200 Subject: [PATCH 119/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to e11c691 (#2100) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index a898dfa9f..ad1599917 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:19764e89441be1f36544f715a738abc1a1898f35ed729486d33172eb54e8d84a +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:e11c6912723c5a0ceeaeb2353329606270292e20c280a0a28d25e8d35474475f ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 837fd09f4a8ea69d792c8f7b80552b10b467fc8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Aug 2024 09:36:17 +0200 Subject: [PATCH 120/409] build(deps): bump the github-actions group across 1 directory with 2 updates (#2104) Bumps the github-actions group with 2 updates in the / directory: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) and [docker/build-push-action](https://github.com/docker/build-push-action). Updates `actions/attest-build-provenance` from 1.4.0 to 1.4.1 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/210c1913531870065f03ce1f9440dd87bc0938cd...310b0a4a3b0b78ef57ecda988ee04b132db73ef8) Updates `docker/build-push-action` from 6.5.0 to 6.7.0 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/5176d81f87c23d6fc96624dfdbcd9f3830bbe445...5cd11c3a4ced054e52742c5fd54dca954e0edd85) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8abce8e86..23f13d9de 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/packages - name: generate build provenance - uses: actions/attest-build-provenance@210c1913531870065f03ce1f9440dd87bc0938cd # v1.4.0 + uses: actions/attest-build-provenance@310b0a4a3b0b78ef57ecda988ee04b132db73ef8 # v1.4.1 with: subject-path: "${{ github.workspace }}/dist/*" @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/build-distribution - name: generate build provenance - uses: actions/attest-build-provenance@210c1913531870065f03ce1f9440dd87bc0938cd # v1.4.0 + uses: actions/attest-build-provenance@310b0a4a3b0b78ef57ecda988ee04b132db73ef8 # v1.4.1 with: subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" @@ -146,7 +146,7 @@ jobs: - name: Build and push image id: docker-push - uses: docker/build-push-action@5176d81f87c23d6fc96624dfdbcd9f3830bbe445 # v6.5.0 + uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0 with: context: . platforms: linux/amd64,linux/arm64 @@ -158,7 +158,7 @@ jobs: AGENT_DIR=./build/dist/package/python - name: generate build provenance (containers) - uses: actions/attest-build-provenance@210c1913531870065f03ce1f9440dd87bc0938cd # v1.4.0 + uses: actions/attest-build-provenance@310b0a4a3b0b78ef57ecda988ee04b132db73ef8 # v1.4.1 with: subject-name: "${{ env.DOCKER_IMAGE_NAME }}" subject-digest: ${{ steps.docker-push.outputs.digest }} From 73680e012c1ab2b5a88ea539ec1db47b50f1d700 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 09:03:46 +0200 Subject: [PATCH 121/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to c16d3ad (#2106) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index ad1599917..360c2392f 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:e11c6912723c5a0ceeaeb2353329606270292e20c280a0a28d25e8d35474475f +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:c16d3ad6cebf387e8dd2ad769f54320c4819fbbaa21e729fad087c7ae223b4d0 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From ecccdba1399188211ffd6734e6c39c0e8855d7e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 09:55:53 +0200 Subject: [PATCH 122/409] build(deps): bump actions/attest-build-provenance (#2107) Bumps the github-actions group with 1 update: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance). Updates `actions/attest-build-provenance` from 1.4.1 to 1.4.2 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/310b0a4a3b0b78ef57ecda988ee04b132db73ef8...6149ea5740be74af77f260b9db67e633f6b0a9a1) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 23f13d9de..42da7452d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/packages - name: generate build provenance - uses: actions/attest-build-provenance@310b0a4a3b0b78ef57ecda988ee04b132db73ef8 # v1.4.1 + uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2 with: subject-path: "${{ github.workspace }}/dist/*" @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/build-distribution - name: generate build provenance - uses: actions/attest-build-provenance@310b0a4a3b0b78ef57ecda988ee04b132db73ef8 # v1.4.1 + uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2 with: subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" @@ -158,7 +158,7 @@ jobs: AGENT_DIR=./build/dist/package/python - name: generate build provenance (containers) - uses: actions/attest-build-provenance@310b0a4a3b0b78ef57ecda988ee04b132db73ef8 # v1.4.1 + uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2 with: subject-name: "${{ env.DOCKER_IMAGE_NAME }}" subject-digest: ${{ steps.docker-push.outputs.digest }} From c78c38bbd1dde2bb393343a450fe67ad00d9c5fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 09:58:58 +0200 Subject: [PATCH 123/409] build(deps): bump the github-actions group with 2 updates (#2111) Bumps the github-actions group with 2 updates: [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) and [geekyeggo/delete-artifact](https://github.com/geekyeggo/delete-artifact). Updates `pypa/gh-action-pypi-publish` from 1.9.0 to 1.10.0 - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0...8a08d616893759ef8e1aa1f2785787c0b97e20d6) Updates `geekyeggo/delete-artifact` from 5.0.0 to 5.1.0 - [Release notes](https://github.com/geekyeggo/delete-artifact/releases) - [Changelog](https://github.com/GeekyEggo/delete-artifact/blob/main/CHANGELOG.md) - [Commits](https://github.com/geekyeggo/delete-artifact/compare/24928e75e6e6590170563b8ddae9fac674508aa1...f275313e70c08f6120db482d7a6b98377786765b) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: geekyeggo/delete-artifact dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 4 ++-- .github/workflows/test.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 42da7452d..a5c2a3687 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,12 +47,12 @@ jobs: path: dist - name: Upload pypi.org if: startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 + uses: pypa/gh-action-pypi-publish@8a08d616893759ef8e1aa1f2785787c0b97e20d6 with: repository-url: https://upload.pypi.org/legacy/ - name: Upload test.pypi.org if: ${{ ! startsWith(github.ref, 'refs/tags') }} - uses: pypa/gh-action-pypi-publish@ec4db0b4ddc65acdf4bff5fa45ac92d78b56bdf0 + uses: pypa/gh-action-pypi-publish@8a08d616893759ef8e1aa1f2785787c0b97e20d6 with: repository-url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 391d67f67..99b195670 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -217,6 +217,6 @@ jobs: with: name: html-coverage-report path: htmlcov - - uses: geekyeggo/delete-artifact@24928e75e6e6590170563b8ddae9fac674508aa1 + - uses: geekyeggo/delete-artifact@f275313e70c08f6120db482d7a6b98377786765b with: name: coverage-reports From 69eb1449ca85f1b2ebe13b6a8ebc5450bc9bdcaa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 08:41:10 +0000 Subject: [PATCH 124/409] build(deps): bump certifi from 2024.7.4 to 2024.8.30 in /dev-utils (#2113) Bumps [certifi](https://github.com/certifi/python-certifi) from 2024.7.4 to 2024.8.30. - [Commits](https://github.com/certifi/python-certifi/compare/2024.07.04...2024.08.30) --- updated-dependencies: - dependency-name: certifi dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Riccardo Magliocchetti --- dev-utils/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-utils/requirements.txt b/dev-utils/requirements.txt index ccc3d9baf..5496601f6 100644 --- a/dev-utils/requirements.txt +++ b/dev-utils/requirements.txt @@ -1,4 +1,4 @@ # These are the pinned requirements for the lambda layer/docker image -certifi==2024.7.4 +certifi==2024.8.30 urllib3==1.26.19 wrapt==1.14.1 From 60d98ea21946661a4067703d2b7aea0f923eaecd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 09:02:46 +0000 Subject: [PATCH 125/409] build(deps): bump urllib3 from 1.26.19 to 1.26.20 in /dev-utils (#2112) Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.19 to 1.26.20. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.26.19...1.26.20) --- updated-dependencies: - dependency-name: urllib3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dev-utils/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-utils/requirements.txt b/dev-utils/requirements.txt index 5496601f6..1b81ff8d3 100644 --- a/dev-utils/requirements.txt +++ b/dev-utils/requirements.txt @@ -1,4 +1,4 @@ # These are the pinned requirements for the lambda layer/docker image certifi==2024.8.30 -urllib3==1.26.19 +urllib3==1.26.20 wrapt==1.14.1 From fc8ef22e3427f23d77d26cbb647eceafd9ec3f19 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 4 Sep 2024 11:30:32 +0200 Subject: [PATCH 126/409] ci: fix upload of codecov data (#2116) From latest release upload artifact will ignore hidden files by default, configure to don't ignore them when uploading converage data. --- .github/workflows/run-matrix.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/run-matrix.yml b/.github/workflows/run-matrix.yml index d5db311d6..053d557a0 100644 --- a/.github/workflows/run-matrix.yml +++ b/.github/workflows/run-matrix.yml @@ -38,3 +38,4 @@ jobs: with: name: coverage-reports path: "**/.coverage*" + include-hidden-files: true From 5f248a371161d3e98ebbb80830e2de90ae849fff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 09:34:48 +0200 Subject: [PATCH 127/409] build(deps): bump the github-actions group with 2 updates (#2121) Bumps the github-actions group with 2 updates: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) and [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish). Updates `actions/attest-build-provenance` from 1.4.2 to 1.4.3 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/6149ea5740be74af77f260b9db67e633f6b0a9a1...1c608d11d69870c2092266b3f9a6f3abbf17002c) Updates `pypa/gh-action-pypi-publish` from 1.10.0 to 1.10.1 - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/8a08d616893759ef8e1aa1f2785787c0b97e20d6...0ab0b79471669eb3a4d647e625009c62f9f3b241) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a5c2a3687..3fef1933b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/packages - name: generate build provenance - uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2 + uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 with: subject-path: "${{ github.workspace }}/dist/*" @@ -47,12 +47,12 @@ jobs: path: dist - name: Upload pypi.org if: startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@8a08d616893759ef8e1aa1f2785787c0b97e20d6 + uses: pypa/gh-action-pypi-publish@0ab0b79471669eb3a4d647e625009c62f9f3b241 with: repository-url: https://upload.pypi.org/legacy/ - name: Upload test.pypi.org if: ${{ ! startsWith(github.ref, 'refs/tags') }} - uses: pypa/gh-action-pypi-publish@8a08d616893759ef8e1aa1f2785787c0b97e20d6 + uses: pypa/gh-action-pypi-publish@0ab0b79471669eb3a4d647e625009c62f9f3b241 with: repository-url: https://test.pypi.org/legacy/ @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/build-distribution - name: generate build provenance - uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2 + uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 with: subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" @@ -158,7 +158,7 @@ jobs: AGENT_DIR=./build/dist/package/python - name: generate build provenance (containers) - uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2 + uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 with: subject-name: "${{ env.DOCKER_IMAGE_NAME }}" subject-digest: ${{ steps.docker-push.outputs.digest }} From bf84dca07a64cfc001ef8c6749db3f3112272dd5 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 9 Sep 2024 15:11:59 +0200 Subject: [PATCH 128/409] github-action: use ephemeral tokens with the required permissions (#2122) --- .github/workflows/updatecli.yml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/updatecli.yml b/.github/workflows/updatecli.yml index c56645f0e..3b38bde40 100644 --- a/.github/workflows/updatecli.yml +++ b/.github/workflows/updatecli.yml @@ -17,6 +17,18 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Get token + id: get_token + uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0 + with: + app_id: ${{ secrets.OBS_AUTOMATION_APP_ID }} + private_key: ${{ secrets.OBS_AUTOMATION_APP_PEM }} + permissions: >- + { + "contents": "write", + "pull_requests": "write" + } + - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ghcr.io @@ -27,13 +39,13 @@ jobs: with: command: --experimental compose diff env: - GITHUB_TOKEN: ${{ secrets.UPDATECLI_GH_TOKEN }} + GITHUB_TOKEN: ${{ steps.get_token.outputs.token }} - uses: elastic/oblt-actions/updatecli/run@v1 with: command: --experimental compose apply env: - GITHUB_TOKEN: ${{ secrets.UPDATECLI_GH_TOKEN }} + GITHUB_TOKEN: ${{ steps.get_token.outputs.token }} - if: failure() uses: elastic/oblt-actions/slack/send@v1 From f8ba59d26bcfe8fc95f3750332978558d1d594a8 Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Tue, 10 Sep 2024 13:57:30 +0200 Subject: [PATCH 129/409] Migrate to artifact-upload and artifact-download v4 (#2124) * Migrate to artifact-upload and artifact-download v4 * Fix artifact names * Fix usage of geekyeggo/delete-artifac --- .github/actions/build-distribution/action.yml | 2 +- .github/workflows/release.yml | 4 ++-- .github/workflows/run-matrix.yml | 8 ++++---- .github/workflows/test-docs.yml | 4 ++-- .github/workflows/test.yml | 19 +++++++++++-------- 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/.github/actions/build-distribution/action.yml b/.github/actions/build-distribution/action.yml index 05c32eeb8..bc0d55c29 100644 --- a/.github/actions/build-distribution/action.yml +++ b/.github/actions/build-distribution/action.yml @@ -14,7 +14,7 @@ runs: run: ./dev-utils/make-distribution.sh shell: bash - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: build-distribution path: ./build/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3fef1933b..8fcd8e775 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -79,7 +79,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: build-distribution path: ./build @@ -128,7 +128,7 @@ jobs: username: ${{ secrets.ELASTIC_DOCKER_USERNAME }} password: ${{ secrets.ELASTIC_DOCKER_PASSWORD }} - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: build-distribution path: ./build diff --git a/.github/workflows/run-matrix.yml b/.github/workflows/run-matrix.yml index 053d557a0..0b31f4318 100644 --- a/.github/workflows/run-matrix.yml +++ b/.github/workflows/run-matrix.yml @@ -28,14 +28,14 @@ jobs: LOCALSTACK_VOLUME_DIR: localstack_data - if: success() || failure() name: Upload JUnit Test Results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: test-results + name: test-results-${{ matrix.framework }}-${{ matrix.version }} path: "**/*-python-agent-junit.xml" - if: success() || failure() name: Upload Coverage Reports - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: coverage-reports + name: coverage-reports-${{ matrix.framework }}-${{ matrix.version }} path: "**/.coverage*" include-hidden-files: true diff --git a/.github/workflows/test-docs.yml b/.github/workflows/test-docs.yml index 86b24cc0c..e1c4c4ae4 100644 --- a/.github/workflows/test-docs.yml +++ b/.github/workflows/test-docs.yml @@ -36,7 +36,7 @@ jobs: ENDOFFILE - if: success() || failure() name: Upload JUnit Test Results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: test-results + name: test-results-docs path: "docs-python-agent-junit.xml" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 99b195670..62a157118 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -145,16 +145,18 @@ jobs: run: .\scripts\run-tests.bat - if: success() || failure() name: Upload JUnit Test Results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: test-results + name: test-results-${{ matrix.framework }}-${{ matrix.version }}-asyncio-${{ matrix.asyncio }} path: "**/*-python-agent-junit.xml" + retention-days: 1 - if: success() || failure() name: Upload Coverage Reports - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: coverage-reports + name: coverage-reports-${{ matrix.framework }}-${{ matrix.version }}-asyncio-${{ matrix.asyncio }} path: "**/.coverage*" + retention-days: 1 # This job is here to have a single status check that can be set as required. # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds # If a run contains a series of jobs that need each other, a failure applies to all jobs in the dependency chain from the point of failure onwards. @@ -197,9 +199,10 @@ jobs: - run: python -Im pip install --upgrade coverage[toml] - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: - name: coverage-reports + pattern: coverage-reports-* + merge-multiple: true - name: Combine coverage & fail if it's <84%. run: | @@ -217,6 +220,6 @@ jobs: with: name: html-coverage-report path: htmlcov - - uses: geekyeggo/delete-artifact@f275313e70c08f6120db482d7a6b98377786765b + - uses: geekyeggo/delete-artifact@f275313e70c08f6120db482d7a6b98377786765b # 5.1.0 with: - name: coverage-reports + name: coverage-reports-* From 0962d144c1eaa513962120331f5ea27684a4dc41 Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Wed, 11 Sep 2024 16:44:39 +0200 Subject: [PATCH 130/409] Update test-reporter workflow (#2125) --- .github/workflows/test-reporter.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-reporter.yml b/.github/workflows/test-reporter.yml index 1060771c5..ffb1206a6 100644 --- a/.github/workflows/test-reporter.yml +++ b/.github/workflows/test-reporter.yml @@ -17,9 +17,9 @@ jobs: report: runs-on: ubuntu-latest steps: - - uses: elastic/apm-pipeline-library/.github/actions/test-report@current + - uses: elastic/oblt-actions/test-report@v1 with: - artifact: test-results - name: JUnit Tests + artifact: /test-results(.*)/ + name: 'Test Report $1' path: "**/*-python-agent-junit.xml" reporter: java-junit From 8f7127d383730ec1e161a2b73fb218211f75a220 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Wed, 11 Sep 2024 15:06:55 +0000 Subject: [PATCH 131/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to aad4cd4 (#2126) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 360c2392f..5b8396398 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:c16d3ad6cebf387e8dd2ad769f54320c4819fbbaa21e729fad087c7ae223b4d0 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:aad4cd4e5f6d849691748c6933761889db1a20a57231613b98bbff61fa7723ab ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 02669e461776eec0f82ea14ee61bfe6be4f71a84 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 11:04:15 +0200 Subject: [PATCH 132/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to d4def25 (#2127) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 5b8396398..7c7990600 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:aad4cd4e5f6d849691748c6933761889db1a20a57231613b98bbff61fa7723ab +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:d4def25f2fd3b0ff9bc68091cd1d89524e41b7d3fc0d3b3a665720eb92145f3b ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 906fce3c0c20028ed8fc42135299ddc95a327899 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Fri, 20 Sep 2024 10:30:50 +0200 Subject: [PATCH 133/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to 6fbf078 (#2128) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 7c7990600..67acba114 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:d4def25f2fd3b0ff9bc68091cd1d89524e41b7d3fc0d3b3a665720eb92145f3b +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:6fbf07849a440c8dca9aa7e9cb56ed3ecaa9eb40f8a4f36b39393d7b32d78ecc ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 98c4c93b777f98038cf58594787b5dc55f84d997 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 09:38:44 +0200 Subject: [PATCH 134/409] build(deps): bump pypa/gh-action-pypi-publish (#2129) Bumps the github-actions group with 1 update: [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish). Updates `pypa/gh-action-pypi-publish` from 1.10.1 to 1.10.2 - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/0ab0b79471669eb3a4d647e625009c62f9f3b241...897895f1e160c830e369f9779632ebc134688e1b) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8fcd8e775..eb28b0248 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,12 +47,12 @@ jobs: path: dist - name: Upload pypi.org if: startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@0ab0b79471669eb3a4d647e625009c62f9f3b241 + uses: pypa/gh-action-pypi-publish@897895f1e160c830e369f9779632ebc134688e1b with: repository-url: https://upload.pypi.org/legacy/ - name: Upload test.pypi.org if: ${{ ! startsWith(github.ref, 'refs/tags') }} - uses: pypa/gh-action-pypi-publish@0ab0b79471669eb3a4d647e625009c62f9f3b241 + uses: pypa/gh-action-pypi-publish@897895f1e160c830e369f9779632ebc134688e1b with: repository-url: https://test.pypi.org/legacy/ From d7b81032d78382c020e511b8ef713748525e4ac9 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Thu, 26 Sep 2024 15:05:39 +0200 Subject: [PATCH 135/409] github-action: use elastic/oblt-actions/github/is-member-of (#2130) --- .github/workflows/labeler.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index a14b036c0..61db99ad0 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -20,10 +20,11 @@ jobs: with: labels: agent-python - id: is_elastic_member - uses: elastic/apm-pipeline-library/.github/actions/is-member-elastic-org@current + uses: elastic/oblt-actions/github/is-member-of@v1 with: - username: ${{ github.actor }} - token: ${{ secrets.APM_TECH_USER_TOKEN }} + github-org: "elastic" + github-user: ${{ github.actor }} + github-token: ${{ secrets.APM_TECH_USER_TOKEN }} - name: Add community and triage labels if: contains(steps.is_elastic_member.outputs.result, 'false') && github.actor != 'dependabot[bot]' && github.actor != 'apmmachine' uses: actions-ecosystem/action-add-labels@v1 From 5ccf9f97692181977d92a6672b34c7a7876ecffa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:26:51 +0200 Subject: [PATCH 136/409] build(deps): bump docker/build-push-action in the github-actions group (#2132) Bumps the github-actions group with 1 update: [docker/build-push-action](https://github.com/docker/build-push-action). Updates `docker/build-push-action` from 6.7.0 to 6.8.0 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/5cd11c3a4ced054e52742c5fd54dca954e0edd85...32945a339266b759abcbdc89316275140b0fc960) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eb28b0248..12e1728d9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -146,7 +146,7 @@ jobs: - name: Build and push image id: docker-push - uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0 + uses: docker/build-push-action@32945a339266b759abcbdc89316275140b0fc960 # v6.8.0 with: context: . platforms: linux/amd64,linux/arm64 From c55578695f46c62bac745b68789e449331fed3f3 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Fri, 4 Oct 2024 10:50:55 +0200 Subject: [PATCH 137/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to 90888b1 (#2131) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 67acba114..9ec524dc4 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:6fbf07849a440c8dca9aa7e9cb56ed3ecaa9eb40f8a4f36b39393d7b32d78ecc +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:90888b190da54062f67f3fef1372eb0ae7d81ea55f5a1f56d748b13e4853d984 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 3c352ccff833e1574d4d1e1199ce6028bdddeb6e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 09:26:38 +0200 Subject: [PATCH 138/409] build(deps): bump the github-actions group with 3 updates (#2135) * build(deps): bump the github-actions group with 3 updates Bumps the github-actions group with 3 updates: [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish), [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) and [docker/build-push-action](https://github.com/docker/build-push-action). Updates `pypa/gh-action-pypi-publish` from 1.10.2 to 1.10.3 - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/897895f1e160c830e369f9779632ebc134688e1b...f7600683efdcb7656dec5b29656edb7bc586e597) Updates `docker/setup-buildx-action` from 3.6.1 to 3.7.1 - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/988b5a0280414f521da01fcc63a27aeeb4b104db...c47758b77c9736f4b2ef4073d4d51994fabfe349) Updates `docker/build-push-action` from 6.8.0 to 6.9.0 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/32945a339266b759abcbdc89316275140b0fc960...4f58ea79222b3b9dc2c8bbdd6debcef730109a75) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] * Add version as comment --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jan Calanog --- .github/workflows/release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 12e1728d9..6e854e4c2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,12 +47,12 @@ jobs: path: dist - name: Upload pypi.org if: startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@897895f1e160c830e369f9779632ebc134688e1b + uses: pypa/gh-action-pypi-publish@f7600683efdcb7656dec5b29656edb7bc586e597 # v1.10.3 with: repository-url: https://upload.pypi.org/legacy/ - name: Upload test.pypi.org if: ${{ ! startsWith(github.ref, 'refs/tags') }} - uses: pypa/gh-action-pypi-publish@897895f1e160c830e369f9779632ebc134688e1b + uses: pypa/gh-action-pypi-publish@f7600683efdcb7656dec5b29656edb7bc586e597 # v1.10.3 with: repository-url: https://test.pypi.org/legacy/ @@ -119,7 +119,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1 + uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 - name: Log in to the Elastic Container registry uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 @@ -146,7 +146,7 @@ jobs: - name: Build and push image id: docker-push - uses: docker/build-push-action@32945a339266b759abcbdc89316275140b0fc960 # v6.8.0 + uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 with: context: . platforms: linux/amd64,linux/arm64 From 97ddf02c1a88d75971a51ca5d2632983466bf040 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Tue, 8 Oct 2024 16:36:55 +0200 Subject: [PATCH 139/409] github-actions: use ephemeral tokens (#2136) --- .github/workflows/labeler.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 61db99ad0..26e551bc3 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -15,6 +15,16 @@ jobs: triage: runs-on: ubuntu-latest steps: + - name: Get token + id: get_token + uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0 + with: + app_id: ${{ secrets.OBS_AUTOMATION_APP_ID }} + private_key: ${{ secrets.OBS_AUTOMATION_APP_PEM }} + permissions: >- + { + "members": "read" + } - name: Add agent-python label uses: actions-ecosystem/action-add-labels@v1 with: @@ -24,7 +34,7 @@ jobs: with: github-org: "elastic" github-user: ${{ github.actor }} - github-token: ${{ secrets.APM_TECH_USER_TOKEN }} + github-token: ${{ steps.get_token.outputs.token }} - name: Add community and triage labels if: contains(steps.is_elastic_member.outputs.result, 'false') && github.actor != 'dependabot[bot]' && github.actor != 'apmmachine' uses: actions-ecosystem/action-add-labels@v1 From 5208b40b4103fb2622d5277167e35ec8e2852208 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Thu, 17 Oct 2024 14:04:25 +0200 Subject: [PATCH 140/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to d56fa50 (#2140) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 9ec524dc4..cfd438514 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:90888b190da54062f67f3fef1372eb0ae7d81ea55f5a1f56d748b13e4853d984 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:d56fa50e5427f566f0a9ba8b1cad553bfca706de9223776d658e5cd608edd58b ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From ea46d1b9f3b5588e9cda3d93ce6e03ca57a0a537 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:12:43 +0200 Subject: [PATCH 141/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to 5bc7518 (#2142) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index cfd438514..ef3308ff6 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:d56fa50e5427f566f0a9ba8b1cad553bfca706de9223776d658e5cd608edd58b +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:5bc7518045103e085b07842c4362ca5366e117abfdcd3010030f1c072a5607ac ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 8ca7422214dd4f6a22db3e52ec1aaee557100a21 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Fri, 18 Oct 2024 15:22:31 +0200 Subject: [PATCH 142/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to bf163e1 (#2143) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index ef3308ff6..2cffbe6df 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:5bc7518045103e085b07842c4362ca5366e117abfdcd3010030f1c072a5607ac +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:bf163e1977002301f7b9fd28fe6837a8cb2dd5c83e4cd45fb67fb28d15d5d40f ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From e656ef8b4df8b32c939590742362cd7743d5f72f Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:17:02 +0200 Subject: [PATCH 143/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to 2e3da56 (#2144) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 2cffbe6df..fe1dcb8b6 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:bf163e1977002301f7b9fd28fe6837a8cb2dd5c83e4cd45fb67fb28d15d5d40f +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:2e3da56229f5673b149191a5451bb4c6ead117a307b0cc98c7a0651ca6f4523e ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From f570e8c2b68a8714628acac815aebcc3518b44c7 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Mon, 28 Oct 2024 10:44:05 +0100 Subject: [PATCH 144/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to de4d5b0 (#2145) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index fe1dcb8b6..222b774a1 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:2e3da56229f5673b149191a5451bb4c6ead117a307b0cc98c7a0651ca6f4523e +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:de4d5b06ee2074eb716f29e72b170346fd4715e5f083fc83a378603ce5bd9ced ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 2a721ceac1ce60be4e6128f60d599d6cfbd2ace8 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Tue, 29 Oct 2024 15:39:24 +0100 Subject: [PATCH 145/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to 1815394 (#2147) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 222b774a1..d3aefe45c 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:de4d5b06ee2074eb716f29e72b170346fd4715e5f083fc83a378603ce5bd9ced +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:18153942f0d6e97bc6131cd557c7ed3be6e892846a5df0760896eb8d15b1b236 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 1d6e9a7f68a94ab097c57ade7b016262d015595a Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:10:44 +0100 Subject: [PATCH 146/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to 8cff240 (#2148) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index d3aefe45c..116afcc7a 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:18153942f0d6e97bc6131cd557c7ed3be6e892846a5df0760896eb8d15b1b236 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:8cff240b81057968575dd28dab0c3609657cb7e0e60ff017261e5b721fad9e1b ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 4ec5ec653622c071a311a1014711d8ab38c92185 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 08:48:03 +0100 Subject: [PATCH 147/409] build(deps): bump pypa/gh-action-pypi-publish (#2152) Bumps the github-actions group with 1 update: [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish). Updates `pypa/gh-action-pypi-publish` from 1.10.3 to 1.11.0 - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/f7600683efdcb7656dec5b29656edb7bc586e597...fb13cb306901256ace3dab689990e13a5550ffaa) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6e854e4c2..0f3790632 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,12 +47,12 @@ jobs: path: dist - name: Upload pypi.org if: startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@f7600683efdcb7656dec5b29656edb7bc586e597 # v1.10.3 + uses: pypa/gh-action-pypi-publish@fb13cb306901256ace3dab689990e13a5550ffaa # v1.11.0 with: repository-url: https://upload.pypi.org/legacy/ - name: Upload test.pypi.org if: ${{ ! startsWith(github.ref, 'refs/tags') }} - uses: pypa/gh-action-pypi-publish@f7600683efdcb7656dec5b29656edb7bc586e597 # v1.10.3 + uses: pypa/gh-action-pypi-publish@fb13cb306901256ace3dab689990e13a5550ffaa # v1.11.0 with: repository-url: https://test.pypi.org/legacy/ From bad5ffd58164557317bb8ab46ad74ae65071aa92 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 4 Nov 2024 09:17:38 +0100 Subject: [PATCH 148/409] github-action: use elastic/oblt-actions/check-dependent-jobs (#2150) --- .github/workflows/release.yml | 4 ++-- .github/workflows/test.yml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0f3790632..76154d935 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -196,9 +196,9 @@ jobs: - github-draft steps: - id: check - uses: elastic/apm-pipeline-library/.github/actions/check-dependent-jobs@current + uses: elastic/oblt-actions/check-dependent-jobs@v1 with: - needs: ${{ toJSON(needs) }} + jobs: ${{ toJSON(needs) }} - if: startsWith(github.ref, 'refs/tags') uses: elastic/oblt-actions/slack/notify-result@v1 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 62a157118..016de58b7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -171,10 +171,10 @@ jobs: - windows steps: - id: check - uses: elastic/apm-pipeline-library/.github/actions/check-dependent-jobs@current + uses: elastic/oblt-actions/check-dependent-jobs@v1 with: - needs: ${{ toJSON(needs) }} - - run: ${{ steps.check.outputs.isSuccess }} + jobs: ${{ toJSON(needs) }} + - run: ${{ steps.check.outputs.is-success }} - if: failure() && (github.event_name == 'schedule' || github.event_name == 'push') uses: elastic/oblt-actions/slack/notify-result@v1 with: From 708caf3b171abed6c786292887be0347e7293a2f Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 4 Nov 2024 09:54:29 +0100 Subject: [PATCH 149/409] github-action: use elastic/oblt-actions/version-framework (#2151) --- .github/workflows/test.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 016de58b7..36294b1f4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,13 +52,13 @@ jobs: with: ref: ${{ inputs.ref || github.ref }} - id: generate - uses: elastic/apm-pipeline-library/.github/actions/version-framework@current + uses: elastic/oblt-actions/version-framework@v1 with: # Use .ci/.matrix_python_full.yml if it's a scheduled workflow, otherwise use .ci/.matrix_python.yml - versionsFile: .ci/.matrix_python${{ (github.event_name == 'schedule' || github.event_name == 'push' || inputs.full-matrix) && '_full' || '' }}.yml + versions-file: .ci/.matrix_python${{ (github.event_name == 'schedule' || github.event_name == 'push' || inputs.full-matrix) && '_full' || '' }}.yml # Use .ci/.matrix_framework_full.yml if it's a scheduled workflow, otherwise use .ci/.matrix_framework.yml - frameworksFile: .ci/.matrix_framework${{ (github.event_name == 'schedule' || github.event_name == 'push' || inputs.full-matrix) && '_full' || '' }}.yml - excludedFile: .ci/.matrix_exclude.yml + frameworks-file: .ci/.matrix_framework${{ (github.event_name == 'schedule' || github.event_name == 'push' || inputs.full-matrix) && '_full' || '' }}.yml + excluded-file: .ci/.matrix_exclude.yml - name: Split matrix shell: python id: split From 57513e1e6894feea595cd74a89ad2e5c5d8ec54d Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 12:38:05 +0100 Subject: [PATCH 150/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to 74385d2 (#2149) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 116afcc7a..67747d5d5 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:8cff240b81057968575dd28dab0c3609657cb7e0e60ff017261e5b721fad9e1b +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:74385d2e67fb0df0df261e57ea174bb2df6e858110ecd1e8928d06f53c9efa4b ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From d7ea86ea300cee80ea45cd6b21b1cdab0284b946 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 10:30:38 +0100 Subject: [PATCH 151/409] build(deps): bump the github-actions group with 2 updates (#2155) Bumps the github-actions group with 2 updates: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) and [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish). Updates `actions/attest-build-provenance` from 1.4.3 to 1.4.4 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/1c608d11d69870c2092266b3f9a6f3abbf17002c...ef244123eb79f2f7a7e75d99086184180e6d0018) Updates `pypa/gh-action-pypi-publish` from 1.11.0 to 1.12.2 - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/fb13cb306901256ace3dab689990e13a5550ffaa...15c56dba361d8335944d31a2ecd17d700fc7bcbc) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 76154d935..adae6823d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/packages - name: generate build provenance - uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 + uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4 with: subject-path: "${{ github.workspace }}/dist/*" @@ -47,12 +47,12 @@ jobs: path: dist - name: Upload pypi.org if: startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@fb13cb306901256ace3dab689990e13a5550ffaa # v1.11.0 + uses: pypa/gh-action-pypi-publish@15c56dba361d8335944d31a2ecd17d700fc7bcbc # v1.12.2 with: repository-url: https://upload.pypi.org/legacy/ - name: Upload test.pypi.org if: ${{ ! startsWith(github.ref, 'refs/tags') }} - uses: pypa/gh-action-pypi-publish@fb13cb306901256ace3dab689990e13a5550ffaa # v1.11.0 + uses: pypa/gh-action-pypi-publish@15c56dba361d8335944d31a2ecd17d700fc7bcbc # v1.12.2 with: repository-url: https://test.pypi.org/legacy/ @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/build-distribution - name: generate build provenance - uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 + uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4 with: subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" @@ -158,7 +158,7 @@ jobs: AGENT_DIR=./build/dist/package/python - name: generate build provenance (containers) - uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 + uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4 with: subject-name: "${{ env.DOCKER_IMAGE_NAME }}" subject-digest: ${{ steps.docker-push.outputs.digest }} From 362b093064b71eaa5cab18beb27ae803bbe60119 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Thu, 14 Nov 2024 19:10:52 +0100 Subject: [PATCH 152/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to 32099b9 (#2154) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 67747d5d5..a0b6386f8 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:74385d2e67fb0df0df261e57ea174bb2df6e858110ecd1e8928d06f53c9efa4b +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:32099b99697d9da842c1ccacdbef1beee05a68cddb817e858d7656df45ed4c93 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From ab5c4b853b7553af3f8110bd806836fc9d8bde91 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Fri, 22 Nov 2024 10:06:48 +0100 Subject: [PATCH 153/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to 55b297d (#2156) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index a0b6386f8..6f499f406 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:32099b99697d9da842c1ccacdbef1beee05a68cddb817e858d7656df45ed4c93 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:55b297da5151d2a2997e8ab9729fe1304e4869389d7090ab7031cc29530f69f8 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 43414adc21e222c75a4dd039e2dff8f5da793b3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 11:50:00 +0100 Subject: [PATCH 154/409] build(deps): bump docker/metadata-action in the github-actions group (#2157) Bumps the github-actions group with 1 update: [docker/metadata-action](https://github.com/docker/metadata-action). Updates `docker/metadata-action` from 5.5.1 to 5.6.1 - [Release notes](https://github.com/docker/metadata-action/releases) - [Commits](https://github.com/docker/metadata-action/compare/8e5442c4ef9f78752691e2d8f8d19755c6f78e81...369eb591f429131d6889c46b94e711f089e6ca96) --- updated-dependencies: - dependency-name: docker/metadata-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index adae6823d..5ef5e9650 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -135,7 +135,7 @@ jobs: - name: Extract metadata (tags, labels) id: docker-meta - uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1 + uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1 with: images: ${{ env.DOCKER_IMAGE_NAME }} tags: | From 76b31439d821f0299e3d2b245a65130e67d693ed Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 26 Nov 2024 11:29:59 +0100 Subject: [PATCH 155/409] Use build to create distributions (#2160) Instead of calling setup.py. This fixes the name of built distributions to match pep625, i.e. elastic_apm instead of elastic-apm --- dev-utils/make-packages.sh | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/dev-utils/make-packages.sh b/dev-utils/make-packages.sh index 91b2a7bd1..27e63fcac 100755 --- a/dev-utils/make-packages.sh +++ b/dev-utils/make-packages.sh @@ -3,14 +3,10 @@ # Make a Python APM agent distribution # -echo "::group::Install wheel" -pip install --user wheel +echo "::group::Install build" +pip install --user build echo "::endgroup::" -echo "::group::Building universal wheel" -python setup.py bdist_wheel -echo "::endgroup::" - -echo "::group::Building source distribution" -python setup.py sdist +echo "::group::Building packages" +python -m build echo "::endgroup::" From aaaa6ab231ec3ab0f60b0b08e6ac884cd24b2d75 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 26 Nov 2024 21:07:28 +0100 Subject: [PATCH 156/409] Fix failures in docker tests with latest Pymssql and older python versions (#2162) * tests: limit latest pymssql for python < 3.9 Limit to the latest release that ships wheels for older Pythons. * tests: remove python >= 3.6 requirements markers --- tests/requirements/reqs-elasticsearch-7.txt | 2 +- tests/requirements/reqs-elasticsearch-8.txt | 2 +- tests/requirements/reqs-pymssql-newest.txt | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/requirements/reqs-elasticsearch-7.txt b/tests/requirements/reqs-elasticsearch-7.txt index 5fa4e0a25..c1ee19a1a 100644 --- a/tests/requirements/reqs-elasticsearch-7.txt +++ b/tests/requirements/reqs-elasticsearch-7.txt @@ -1,3 +1,3 @@ elasticsearch>=7.0,<8.0 -aiohttp ; python_version >= '3.6' +aiohttp -r reqs-base.txt diff --git a/tests/requirements/reqs-elasticsearch-8.txt b/tests/requirements/reqs-elasticsearch-8.txt index c2b0c8d8c..2cbb658f1 100644 --- a/tests/requirements/reqs-elasticsearch-8.txt +++ b/tests/requirements/reqs-elasticsearch-8.txt @@ -1,3 +1,3 @@ elasticsearch>=8.0,<9.0 -aiohttp ; python_version >= '3.6' +aiohttp -r reqs-base.txt diff --git a/tests/requirements/reqs-pymssql-newest.txt b/tests/requirements/reqs-pymssql-newest.txt index 9a4c379dc..3b3553ac6 100644 --- a/tests/requirements/reqs-pymssql-newest.txt +++ b/tests/requirements/reqs-pymssql-newest.txt @@ -1,3 +1,4 @@ -cython ; python_version >= '3.6' -pymssql +cython +pymssql ; python_version >= '3.9' +pymssql==2.3.1 ; python_version < '3.9' -r reqs-base.txt From ae9b6e30b1ff94cbd9c19a601f72d1727fe839b8 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Tue, 26 Nov 2024 21:08:14 +0100 Subject: [PATCH 157/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to 32f06b1 (#2161) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 6f499f406..1a3ca750f 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:55b297da5151d2a2997e8ab9729fe1304e4869389d7090ab7031cc29530f69f8 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:32f06b169bb4b0f257fbb10e8c8379f06d3ee1355c89b3327cb623781a29590e ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From b6d2fcad949b582887c1cf03b78413cb0bdcedd9 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Thu, 28 Nov 2024 10:33:48 +0100 Subject: [PATCH 158/409] github-actions: use v1 for the oblt-actions (#2165) --- .github/workflows/microbenchmark.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/microbenchmark.yml b/.github/workflows/microbenchmark.yml index 2230d7e41..e3f0a41d6 100644 --- a/.github/workflows/microbenchmark.yml +++ b/.github/workflows/microbenchmark.yml @@ -19,7 +19,7 @@ jobs: timeout-minutes: 5 steps: - name: Run microbenchmark - uses: elastic/oblt-actions/buildkite/run@v1.5.0 + uses: elastic/oblt-actions/buildkite/run@v1 with: pipeline: "apm-agent-microbenchmark" token: ${{ secrets.BUILDKITE_TOKEN }} From 9b67c7c344a3e94cd8c344226f576e7b2d34d449 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 09:56:41 +0100 Subject: [PATCH 159/409] build(deps): bump docker/build-push-action in the github-actions group (#2167) Bumps the github-actions group with 1 update: [docker/build-push-action](https://github.com/docker/build-push-action). Updates `docker/build-push-action` from 6.9.0 to 6.10.0 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/4f58ea79222b3b9dc2c8bbdd6debcef730109a75...48aba3b46d1b1fec4febb7c5d0c644b249a11355) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5ef5e9650..daf3f677b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -146,7 +146,7 @@ jobs: - name: Build and push image id: docker-push - uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 + uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 with: context: . platforms: linux/amd64,linux/arm64 From 7babc3b35eb4c750d80fbde50ac18aa73db4088a Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 10:02:05 +0100 Subject: [PATCH 160/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to ad2e15a (#2166) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 1a3ca750f..4415f98fa 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:32f06b169bb4b0f257fbb10e8c8379f06d3ee1355c89b3327cb623781a29590e +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:ad2e15a6b7fbd893990fd9bd39fb0f367282a9ba65e350655540e470858ef382 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 6b32a8980cfc5aa23a403a5ad96ad1b19fd347f0 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 2 Dec 2024 12:50:22 +0100 Subject: [PATCH 161/409] ci(updatecli): add policies autodiscovery, bump updatecli version and specs/jsons policies (#2168) --- .ci/updatecli/values.d/scm.yml | 7 +++++-- .ci/updatecli/values.d/update-compose.yml | 3 +++ .github/workflows/updatecli.yml | 6 ++++++ updatecli-compose.yaml | 15 ++++++++++----- 4 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 .ci/updatecli/values.d/update-compose.yml diff --git a/.ci/updatecli/values.d/scm.yml b/.ci/updatecli/values.d/scm.yml index 78f9e4bc0..ac8be9843 100644 --- a/.ci/updatecli/values.d/scm.yml +++ b/.ci/updatecli/values.d/scm.yml @@ -3,5 +3,8 @@ scm: owner: elastic repository: apm-agent-python branch: main - -signedcommit: true \ No newline at end of file + commitusingapi: true + # begin update-compose policy values + user: obltmachine + email: obltmachine@users.noreply.github.com + # end update-compose policy values \ No newline at end of file diff --git a/.ci/updatecli/values.d/update-compose.yml b/.ci/updatecli/values.d/update-compose.yml new file mode 100644 index 000000000..02df609f2 --- /dev/null +++ b/.ci/updatecli/values.d/update-compose.yml @@ -0,0 +1,3 @@ +spec: + files: + - "updatecli-compose.yaml" \ No newline at end of file diff --git a/.github/workflows/updatecli.yml b/.github/workflows/updatecli.yml index 3b38bde40..7e6c92e08 100644 --- a/.github/workflows/updatecli.yml +++ b/.github/workflows/updatecli.yml @@ -38,12 +38,18 @@ jobs: - uses: elastic/oblt-actions/updatecli/run@v1 with: command: --experimental compose diff + # TODO: update to the latest version so the policies can work as expected. + # latest changes in the policies require to use the dependson feature. + version: "v0.88.0" env: GITHUB_TOKEN: ${{ steps.get_token.outputs.token }} - uses: elastic/oblt-actions/updatecli/run@v1 with: command: --experimental compose apply + # TODO: update to the latest version so the policies can work as expected. + # latest changes in the policies require to use the dependson feature. + version: "v0.88.0" env: GITHUB_TOKEN: ${{ steps.get_token.outputs.token }} diff --git a/updatecli-compose.yaml b/updatecli-compose.yaml index d40020933..270e1805a 100644 --- a/updatecli-compose.yaml +++ b/updatecli-compose.yaml @@ -1,18 +1,23 @@ +# Config file for `updatecli compose ...`. +# https://www.updatecli.io/docs/core/compose/ policies: - name: Handle apm-data server specs - policy: ghcr.io/elastic/oblt-updatecli-policies/apm/apm-data-spec:0.2.0@sha256:7069c0773d44a74c4c8103b4d9957b468f66081ee9d677238072fe11c4d2197c + policy: ghcr.io/elastic/oblt-updatecli-policies/apm/apm-data-spec:0.6.0@sha256:c0bbdec23541bed38df1342c95aeb601530a113db1ff11715c1c7616ed5e9e8b values: - .ci/updatecli/values.d/scm.yml - .ci/updatecli/values.d/apm-data-spec.yml - - name: Handle apm gherkin specs - policy: ghcr.io/elastic/oblt-updatecli-policies/apm/apm-gherkin:0.2.0@sha256:26a30ad2b98a6e4cb17fb88a28fa3277ced8ca862d6388943afaafbf8ee96e7d + policy: ghcr.io/elastic/oblt-updatecli-policies/apm/apm-gherkin:0.6.0@sha256:dbaf4d855c5c212c3b5a8d2cc98c243a2b769ac347198ae8814393a1a0576587 values: - .ci/updatecli/values.d/scm.yml - .ci/updatecli/values.d/apm-gherkin.yml - - name: Handle apm json specs - policy: ghcr.io/elastic/oblt-updatecli-policies/apm/apm-json-specs:0.2.0@sha256:969a6d21eabd6ebea66cb29b35294a273d6dbc0f7da78589c416aedf08728e78 + policy: ghcr.io/elastic/oblt-updatecli-policies/apm/apm-json-specs:0.6.0@sha256:e5a74c159ceed02fd20515ea76fa25ff81e3ccf977e74e636f9973db86aa52a5 values: - .ci/updatecli/values.d/scm.yml - .ci/updatecli/values.d/apm-json-specs.yml + - name: Update Updatecli policies + policy: ghcr.io/updatecli/policies/autodiscovery/updatecli:0.8.0@sha256:99e9e61b501575c2c176c39f2275998d198b590a3f6b1fe829f7315f8d457e7f + values: + - .ci/updatecli/values.d/scm.yml + - .ci/updatecli/values.d/update-compose.yml \ No newline at end of file From 2b38796b481134f43565051d7e9e18b0afaa463c Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Wed, 4 Dec 2024 10:02:40 +0100 Subject: [PATCH 162/409] ci: pin updatecli version using .tool-versions and autobump (#2170) --- .github/workflows/updatecli.yml | 8 ++------ .tool-versions | 1 + updatecli-compose.yaml | 6 +++++- 3 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 .tool-versions diff --git a/.github/workflows/updatecli.yml b/.github/workflows/updatecli.yml index 7e6c92e08..d6d1ed4c6 100644 --- a/.github/workflows/updatecli.yml +++ b/.github/workflows/updatecli.yml @@ -38,18 +38,14 @@ jobs: - uses: elastic/oblt-actions/updatecli/run@v1 with: command: --experimental compose diff - # TODO: update to the latest version so the policies can work as expected. - # latest changes in the policies require to use the dependson feature. - version: "v0.88.0" + version-file: .tool-versions env: GITHUB_TOKEN: ${{ steps.get_token.outputs.token }} - uses: elastic/oblt-actions/updatecli/run@v1 with: command: --experimental compose apply - # TODO: update to the latest version so the policies can work as expected. - # latest changes in the policies require to use the dependson feature. - version: "v0.88.0" + version-file: .tool-versions env: GITHUB_TOKEN: ${{ steps.get_token.outputs.token }} diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 000000000..3d067142f --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +updatecli v0.88.0 \ No newline at end of file diff --git a/updatecli-compose.yaml b/updatecli-compose.yaml index 270e1805a..92655b637 100644 --- a/updatecli-compose.yaml +++ b/updatecli-compose.yaml @@ -20,4 +20,8 @@ policies: policy: ghcr.io/updatecli/policies/autodiscovery/updatecli:0.8.0@sha256:99e9e61b501575c2c176c39f2275998d198b590a3f6b1fe829f7315f8d457e7f values: - .ci/updatecli/values.d/scm.yml - - .ci/updatecli/values.d/update-compose.yml \ No newline at end of file + - .ci/updatecli/values.d/update-compose.yml + - name: Update Updatecli version + policy: ghcr.io/elastic/oblt-updatecli-policies/updatecli/version:0.2.0@sha256:013a37ddcdb627c46e7cba6fb9d1d7bc144584fa9063843ae7ee0f6ef26b4bea + values: + - .ci/updatecli/values.d/scm.yml \ No newline at end of file From 9f31bc9222cfaa54f9c0c81cffd26a351bab3e09 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Thu, 5 Dec 2024 09:18:03 +0100 Subject: [PATCH 163/409] chore: deps(updatecli): Bump updatecli version to v0.88.1 (#2171) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 3d067142f..058189984 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.88.0 \ No newline at end of file +updatecli v0.88.1 \ No newline at end of file From fc547c579b91ae42ed5d8accd44d59bf2d0f98d3 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 9 Dec 2024 09:52:21 +0100 Subject: [PATCH 164/409] github-actions: exclude bot from the triage (#2172) --- .github/workflows/labeler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 26e551bc3..fcab871c7 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -36,7 +36,7 @@ jobs: github-user: ${{ github.actor }} github-token: ${{ steps.get_token.outputs.token }} - name: Add community and triage labels - if: contains(steps.is_elastic_member.outputs.result, 'false') && github.actor != 'dependabot[bot]' && github.actor != 'apmmachine' + if: contains(steps.is_elastic_member.outputs.result, 'false') && github.actor != 'dependabot[bot]' && github.actor != 'elastic-observability-automation[bot]' uses: actions-ecosystem/action-add-labels@v1 with: labels: | From e4e68297ac2fdd1a4beb5d812ab59ad76b74ec20 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:00:00 +0100 Subject: [PATCH 165/409] build(deps): bump actions/attest-build-provenance (#2173) Bumps the github-actions group with 1 update: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance). Updates `actions/attest-build-provenance` from 1.4.4 to 2.0.1 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/ef244123eb79f2f7a7e75d99086184180e6d0018...c4fbc648846ca6f503a13a2281a5e7b98aa57202) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index daf3f677b..206f04195 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/packages - name: generate build provenance - uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4 + uses: actions/attest-build-provenance@c4fbc648846ca6f503a13a2281a5e7b98aa57202 # v2.0.1 with: subject-path: "${{ github.workspace }}/dist/*" @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/build-distribution - name: generate build provenance - uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4 + uses: actions/attest-build-provenance@c4fbc648846ca6f503a13a2281a5e7b98aa57202 # v2.0.1 with: subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" @@ -158,7 +158,7 @@ jobs: AGENT_DIR=./build/dist/package/python - name: generate build provenance (containers) - uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4 + uses: actions/attest-build-provenance@c4fbc648846ca6f503a13a2281a5e7b98aa57202 # v2.0.1 with: subject-name: "${{ env.DOCKER_IMAGE_NAME }}" subject-digest: ${{ steps.docker-push.outputs.digest }} From 97d8ba59c61e7c9c51e295760ff4813f9bde915f Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 09:09:54 +0100 Subject: [PATCH 166/409] chore: deps(updatecli): Bump updatecli version to v0.89.0 (#2174) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 058189984..433f827ca 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.88.1 \ No newline at end of file +updatecli v0.89.0 \ No newline at end of file From 7fd6311ae2ea5aefdc845c74855f95f000331584 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 09:50:42 +0100 Subject: [PATCH 167/409] build(deps): bump certifi from 2024.8.30 to 2024.12.14 in /dev-utils (#2177) Bumps [certifi](https://github.com/certifi/python-certifi) from 2024.8.30 to 2024.12.14. - [Commits](https://github.com/certifi/python-certifi/compare/2024.08.30...2024.12.14) --- updated-dependencies: - dependency-name: certifi dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dev-utils/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-utils/requirements.txt b/dev-utils/requirements.txt index 1b81ff8d3..7ab878dff 100644 --- a/dev-utils/requirements.txt +++ b/dev-utils/requirements.txt @@ -1,4 +1,4 @@ # These are the pinned requirements for the lambda layer/docker image -certifi==2024.8.30 +certifi==2024.12.14 urllib3==1.26.20 wrapt==1.14.1 From e8f75d4c12fc233e7d50e05713e57f12946f7f30 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 10:02:05 +0100 Subject: [PATCH 168/409] build(deps): bump the github-actions group with 2 updates (#2178) Bumps the github-actions group with 2 updates: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) and [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish). Updates `actions/attest-build-provenance` from 2.0.1 to 2.1.0 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/c4fbc648846ca6f503a13a2281a5e7b98aa57202...7668571508540a607bdfd90a87a560489fe372eb) Updates `pypa/gh-action-pypi-publish` from 1.12.2 to 1.12.3 - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/15c56dba361d8335944d31a2ecd17d700fc7bcbc...67339c736fd9354cd4f8cb0b744f2b82a74b5c70) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 206f04195..aa1c7acc9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/packages - name: generate build provenance - uses: actions/attest-build-provenance@c4fbc648846ca6f503a13a2281a5e7b98aa57202 # v2.0.1 + uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0 with: subject-path: "${{ github.workspace }}/dist/*" @@ -47,12 +47,12 @@ jobs: path: dist - name: Upload pypi.org if: startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@15c56dba361d8335944d31a2ecd17d700fc7bcbc # v1.12.2 + uses: pypa/gh-action-pypi-publish@67339c736fd9354cd4f8cb0b744f2b82a74b5c70 # v1.12.3 with: repository-url: https://upload.pypi.org/legacy/ - name: Upload test.pypi.org if: ${{ ! startsWith(github.ref, 'refs/tags') }} - uses: pypa/gh-action-pypi-publish@15c56dba361d8335944d31a2ecd17d700fc7bcbc # v1.12.2 + uses: pypa/gh-action-pypi-publish@67339c736fd9354cd4f8cb0b744f2b82a74b5c70 # v1.12.3 with: repository-url: https://test.pypi.org/legacy/ @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/build-distribution - name: generate build provenance - uses: actions/attest-build-provenance@c4fbc648846ca6f503a13a2281a5e7b98aa57202 # v2.0.1 + uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0 with: subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" @@ -158,7 +158,7 @@ jobs: AGENT_DIR=./build/dist/package/python - name: generate build provenance (containers) - uses: actions/attest-build-provenance@c4fbc648846ca6f503a13a2281a5e7b98aa57202 # v2.0.1 + uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0 with: subject-name: "${{ env.DOCKER_IMAGE_NAME }}" subject-digest: ${{ steps.docker-push.outputs.digest }} From ea7571d537d44629abec3b6f5abb3936d4d04a8d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 08:22:46 +0100 Subject: [PATCH 169/409] build(deps): bump docker/setup-buildx-action in the github-actions group (#2182) Bumps the github-actions group with 1 update: [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action). Updates `docker/setup-buildx-action` from 3.7.1 to 3.8.0 - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/c47758b77c9736f4b2ef4073d4d51994fabfe349...6524bf65af31da8d45b59e8c27de4bd072b392f5) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aa1c7acc9..99bacfff8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -119,7 +119,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 - name: Log in to the Elastic Container registry uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 From e4bd9b3cc270b7ab91e535b2f9d717179d638792 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 09:48:33 +0000 Subject: [PATCH 170/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to 3a6e913 (#2176) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Co-authored-by: Riccardo Magliocchetti --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 4415f98fa..bf624e961 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:ad2e15a6b7fbd893990fd9bd39fb0f367282a9ba65e350655540e470858ef382 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:3a6e9134cf3142da74153a522822c8fa56d09376e294627e51db8aa28f5d20d3 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 4a22dad22c9f865551d92ba94a4e9f2e9c495164 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 13:47:25 +0100 Subject: [PATCH 171/409] chore: deps(updatecli): Bump updatecli version to v0.90.0 (#2181) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 433f827ca..91ee6d148 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.89.0 \ No newline at end of file +updatecli v0.90.0 \ No newline at end of file From a14eea021cfc65b3fdd3cf10cf23c84496f2a071 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Mon, 23 Dec 2024 12:54:10 +0000 Subject: [PATCH 172/409] chore: APM agent json server schema 560cc4b604ede25ec70afd09f213d892f... (#2180) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ... 30ceedc Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Co-authored-by: Riccardo Magliocchetti --- tests/upstream/json-specs/span.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/upstream/json-specs/span.json b/tests/upstream/json-specs/span.json index e86da9a69..14eea1b15 100644 --- a/tests/upstream/json-specs/span.json +++ b/tests/upstream/json-specs/span.json @@ -188,6 +188,9 @@ "object" ], "properties": { + "body": { + "description": "The http request body usually as a string, but may be a dictionary for multipart/form-data content" + }, "id": { "description": "ID holds the unique identifier for the http request.", "type": [ From 77ae5a39fe0e669dfca378452382022df65d180f Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 24 Dec 2024 11:52:19 +0100 Subject: [PATCH 173/409] tests/requirements: bump jinja2 to 3.1.5 (#2185) --- tests/requirements/reqs-asgi-2.txt | 2 +- tests/requirements/reqs-base.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/requirements/reqs-asgi-2.txt b/tests/requirements/reqs-asgi-2.txt index 97ff3022d..ca2f94b02 100644 --- a/tests/requirements/reqs-asgi-2.txt +++ b/tests/requirements/reqs-asgi-2.txt @@ -1,6 +1,6 @@ quart==0.6.13 MarkupSafe<2.1 -jinja2==3.1.4 +jinja2==3.1.5 async-asgi-testclient asgiref -r reqs-base.txt diff --git a/tests/requirements/reqs-base.txt b/tests/requirements/reqs-base.txt index 4f79a5929..f59cbc088 100644 --- a/tests/requirements/reqs-base.txt +++ b/tests/requirements/reqs-base.txt @@ -8,7 +8,7 @@ coverage[toml]==6.3 ; python_version == '3.7' coverage==7.3.1 ; python_version > '3.7' pytest-cov==4.0.0 ; python_version < '3.8' pytest-cov==4.1.0 ; python_version > '3.7' -jinja2==3.1.4 ; python_version == '3.7' +jinja2==3.1.5 ; python_version == '3.7' pytest-localserver==0.5.0 pytest-mock==3.6.1 ; python_version == '3.6' pytest-mock==3.10.0 ; python_version > '3.6' From 094652a9aefbf391acedd619215e99643f04aad4 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 24 Dec 2024 14:47:42 +0100 Subject: [PATCH 174/409] tests: add more tests for validators (#2186) --- tests/config/tests.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/config/tests.py b/tests/config/tests.py index b69a0fe1d..c0d6820c4 100644 --- a/tests/config/tests.py +++ b/tests/config/tests.py @@ -43,9 +43,11 @@ Config, ConfigurationError, EnumerationValidator, + ExcludeRangeValidator, FileIsReadableValidator, PrecisionValidator, RegexValidator, + UnitValidator, VersionedConfig, _BoolConfigValue, _ConfigBase, @@ -450,3 +452,38 @@ def test_config_all_upper_case(): if not isinstance(config_value, _ConfigValue): continue assert config_value.env_key == config_value.env_key.upper() + + +def test_regex_validator_without_match(): + validator = RegexValidator("\d") + with pytest.raises(ConfigurationError) as e: + validator("foo", "field") + assert "does not match pattern" in e.value.args[0] + + +def test_unit_validator_without_match(): + validator = RegexValidator("ms") + with pytest.raises(ConfigurationError) as e: + validator("s", "field") + assert "does not match pattern" in e.value.args[0] + + +def test_unit_validator_with_unsupported_unit(): + validator = UnitValidator("(\d+)(s)", "secs", {}) + with pytest.raises(ConfigurationError) as e: + validator("10s", "field") + assert "is not a supported unit" in e.value.args[0] + + +def test_precision_validator_not_a_float(): + validator = PrecisionValidator() + with pytest.raises(ConfigurationError) as e: + validator("notafloat", "field") + assert "is not a float" in e.value.args[0] + + +def test_exclude_range_validator_not_in_range(): + validator = ExcludeRangeValidator(1, 100, "desc") + with pytest.raises(ConfigurationError) as e: + validator(10, "field") + assert "cannot be in range" in e.value.args[0] From 778797b4466b344063f82f6fd49a4e4d65f002a7 Mon Sep 17 00:00:00 2001 From: Radu Potop Date: Tue, 24 Dec 2024 14:17:53 +0000 Subject: [PATCH 175/409] Add field_name to ConfigurationError messages on init (#2133) * Add field_name to ConfigurationError messages * Format with black --------- Co-authored-by: Riccardo Magliocchetti --- elasticapm/conf/__init__.py | 41 ++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/elasticapm/conf/__init__.py b/elasticapm/conf/__init__.py index 5318d64b5..5b851c9a5 100644 --- a/elasticapm/conf/__init__.py +++ b/elasticapm/conf/__init__.py @@ -275,7 +275,14 @@ def __call__(self, value, field_name): match = re.match(self.regex, value) if match: return value - raise ConfigurationError("{} does not match pattern {}".format(value, self.verbose_pattern), field_name) + raise ConfigurationError( + "{}={} does not match pattern {}".format( + field_name, + value, + self.verbose_pattern, + ), + field_name, + ) class UnitValidator(object): @@ -288,12 +295,19 @@ def __call__(self, value, field_name): value = str(value) match = re.match(self.regex, value, re.IGNORECASE) if not match: - raise ConfigurationError("{} does not match pattern {}".format(value, self.verbose_pattern), field_name) + raise ConfigurationError( + "{}={} does not match pattern {}".format( + field_name, + value, + self.verbose_pattern, + ), + field_name, + ) val, unit = match.groups() try: val = int(val) * self.unit_multipliers[unit] except KeyError: - raise ConfigurationError("{} is not a supported unit".format(unit), field_name) + raise ConfigurationError("{}={} is not a supported unit".format(field_name, unit), field_name) return val @@ -315,7 +329,7 @@ def __call__(self, value, field_name): try: value = float(value) except ValueError: - raise ConfigurationError("{} is not a float".format(value), field_name) + raise ConfigurationError("{}={} is not a float".format(field_name, value), field_name) multiplier = 10**self.precision rounded = math.floor(value * multiplier + 0.5) / multiplier if rounded == 0 and self.minimum and value != 0: @@ -337,8 +351,10 @@ def __init__(self, range_start, range_end, range_desc) -> None: def __call__(self, value, field_name): if self.range_start <= value <= self.range_end: raise ConfigurationError( - "{} cannot be in range: {}".format( - value, self.range_desc.format(**{"range_start": self.range_start, "range_end": self.range_end}) + "{}={} cannot be in range: {}".format( + field_name, + value, + self.range_desc.format(**{"range_start": self.range_start, "range_end": self.range_end}), ), field_name, ) @@ -349,11 +365,11 @@ class FileIsReadableValidator(object): def __call__(self, value, field_name): value = os.path.normpath(value) if not os.path.exists(value): - raise ConfigurationError("{} does not exist".format(value), field_name) + raise ConfigurationError("{}={} does not exist".format(field_name, value), field_name) elif not os.path.isfile(value): - raise ConfigurationError("{} is not a file".format(value), field_name) + raise ConfigurationError("{}={} is not a file".format(field_name, value), field_name) elif not os.access(value, os.R_OK): - raise ConfigurationError("{} is not readable".format(value), field_name) + raise ConfigurationError("{}={} is not readable".format(field_name, value), field_name) return value @@ -384,7 +400,12 @@ def __call__(self, value, field_name): ret = self.valid_values.get(value.lower()) if ret is None: raise ConfigurationError( - "{} is not in the list of valid values: {}".format(value, list(self.valid_values.values())), field_name + "{}={} is not in the list of valid values: {}".format( + field_name, + value, + list(self.valid_values.values()), + ), + field_name, ) return ret From b7294d9c24c4ec0b30cc6be53c0d64be06f777fc Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Mon, 30 Dec 2024 10:05:53 +0100 Subject: [PATCH 176/409] chore: deps(updatecli): Bump updatecli version to v0.91.0 (#2188) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 91ee6d148..b4283d6fe 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.90.0 \ No newline at end of file +updatecli v0.91.0 \ No newline at end of file From 80d167f54b6bf1db8b6e7ee52e2ac6803bc64f54 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 13 Jan 2025 10:32:22 +0100 Subject: [PATCH 177/409] Fix tests with latest starlette and sanic (#2190) * contrib/sanic: handle 24.12 CookieJar.items() removal * tests: update starlette tests after TestClient allow_redirects removal --- elasticapm/contrib/sanic/utils.py | 10 +++++++++- tests/contrib/asyncio/starlette_tests.py | 3 ++- tests/contrib/sanic/fixtures.py | 6 ++++++ tests/contrib/sanic/sanic_tests.py | 10 ++++++++++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/elasticapm/contrib/sanic/utils.py b/elasticapm/contrib/sanic/utils.py index e4e987274..91cb986e3 100644 --- a/elasticapm/contrib/sanic/utils.py +++ b/elasticapm/contrib/sanic/utils.py @@ -148,4 +148,12 @@ def make_client(client_cls=Client, **defaults) -> Client: def _transform_response_cookie(cookies: CookieJar) -> Dict[str, str]: """Transform the Sanic's CookieJar instance into a Normal dictionary to build the context""" - return {k: {"value": v.value, "path": v["path"]} for k, v in cookies.items()} + # old sanic versions used to have an items() method + if hasattr(cookies, "items"): + return {k: {"value": v.value, "path": v["path"]} for k, v in cookies.items()} + + try: + return {cookie.key: {"value": cookie.value, "path": cookie.path} for cookie in cookies.cookies} + except KeyError: + # cookies.cookies assumes Set-Cookie header will be there + return {} diff --git a/tests/contrib/asyncio/starlette_tests.py b/tests/contrib/asyncio/starlette_tests.py index e3c4f4a16..38c51fa08 100644 --- a/tests/contrib/asyncio/starlette_tests.py +++ b/tests/contrib/asyncio/starlette_tests.py @@ -348,7 +348,8 @@ def test_transaction_name_is_route(app, elasticapm_client): ) def test_trailing_slash_redirect_detection(app, elasticapm_client, url, expected): client = TestClient(app) - response = client.get(url, allow_redirects=False) + kwargs = {"allow_redirects": False} if starlette_version_tuple < (0, 43) else {"follow_redirects": False} + response = client.get(url, **kwargs) assert response.status_code == 307 assert len(elasticapm_client.events[constants.TRANSACTION]) == 1 for transaction in elasticapm_client.events[constants.TRANSACTION]: diff --git a/tests/contrib/sanic/fixtures.py b/tests/contrib/sanic/fixtures.py index e4d8d4158..0f231243c 100644 --- a/tests/contrib/sanic/fixtures.py +++ b/tests/contrib/sanic/fixtures.py @@ -155,6 +155,12 @@ async def raise_value_error(request): async def custom_headers(request): return json({"data": "message"}, headers={"sessionid": 1234555}) + @app.get("/add-cookies") + async def add_cookies(request): + response = json({"data": "message"}, headers={"sessionid": 1234555}) + response.add_cookie("some", "cookie") + return response + try: yield app, apm finally: diff --git a/tests/contrib/sanic/sanic_tests.py b/tests/contrib/sanic/sanic_tests.py index ec97fb02f..291ceae7c 100644 --- a/tests/contrib/sanic/sanic_tests.py +++ b/tests/contrib/sanic/sanic_tests.py @@ -194,6 +194,16 @@ def test_header_field_sanitization(sanic_elastic_app, elasticapm_client): assert transaction["context"]["request"]["headers"]["api_key"] == "[REDACTED]" +def test_cookies_normalization(sanic_elastic_app, elasticapm_client): + sanic_app, apm = next(sanic_elastic_app(elastic_client=elasticapm_client)) + _, resp = sanic_app.test_client.get( + "/add-cookies", + ) + assert len(apm._client.events[constants.TRANSACTION]) == 1 + transaction = apm._client.events[constants.TRANSACTION][0] + assert transaction["context"]["response"]["cookies"] == {"some": {"value": "cookie", "path": "/"}} + + def test_custom_callback_handlers(sanic_elastic_app, elasticapm_client): def _custom_transaction_callback(request): return "my-custom-name" From bf89eccdd882d6b0a0f7fdbb3ed51077f7c861df Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 15 Jan 2025 10:03:36 +0100 Subject: [PATCH 178/409] Fix sanic cookies headers serialization (#2194) * contrib/sanic: fix the format of set-cookie header stored in context We are getting a Cookie instance from Sanic that by default get translate to a dict that is not a proper type for an header. Instead convert it to its string represantation. * tests/sanic: use a different way of setting cookies on older versions * Skip sanic newest on python 3.8 Because it fails with: /lib/python3.8/site-packages/sanic/helpers.py:21: in STATUS_CODES: dict[int, bytes] = { E TypeError: 'type' object is not subscriptabl --- .ci/.matrix_exclude.yml | 2 ++ elasticapm/contrib/sanic/utils.py | 11 +++++++++-- tests/contrib/sanic/fixtures.py | 5 ++++- tests/contrib/sanic/sanic_tests.py | 1 + 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.ci/.matrix_exclude.yml b/.ci/.matrix_exclude.yml index 8df06914d..0349959a1 100644 --- a/.ci/.matrix_exclude.yml +++ b/.ci/.matrix_exclude.yml @@ -217,6 +217,8 @@ exclude: FRAMEWORK: sanic-20.12 - VERSION: python-3.6 FRAMEWORK: sanic-newest + - VERSION: python-3.8 + FRAMEWORK: sanic-newest - VERSION: pypy-3 # aioredis FRAMEWORK: aioredis-newest diff --git a/elasticapm/contrib/sanic/utils.py b/elasticapm/contrib/sanic/utils.py index 91cb986e3..9744bf89b 100644 --- a/elasticapm/contrib/sanic/utils.py +++ b/elasticapm/contrib/sanic/utils.py @@ -33,7 +33,7 @@ from sanic import Sanic from sanic import __version__ as version -from sanic.cookies import CookieJar +from sanic.cookies import Cookie, CookieJar from sanic.request import Request from sanic.response import HTTPResponse @@ -120,7 +120,14 @@ async def get_response_info(config: Config, response: HTTPResponse, event_type: result["status_code"] = response.status if config.capture_headers: - result["headers"] = dict(response.headers) + + def normalize(v): + # we are getting entries for Set-Cookie headers as Cookie instances + if isinstance(v, Cookie): + return str(v) + return v + + result["headers"] = {k: normalize(v) for k, v in response.headers.items()} if config.capture_body in ("all", event_type) and "octet-stream" not in response.content_type: result["body"] = response.body.decode("utf-8") diff --git a/tests/contrib/sanic/fixtures.py b/tests/contrib/sanic/fixtures.py index 0f231243c..e21bb00b9 100644 --- a/tests/contrib/sanic/fixtures.py +++ b/tests/contrib/sanic/fixtures.py @@ -158,7 +158,10 @@ async def custom_headers(request): @app.get("/add-cookies") async def add_cookies(request): response = json({"data": "message"}, headers={"sessionid": 1234555}) - response.add_cookie("some", "cookie") + if hasattr(response, "add_cookie"): + response.add_cookie("some", "cookie") + else: + response.cookies["some"] = "cookie" return response try: diff --git a/tests/contrib/sanic/sanic_tests.py b/tests/contrib/sanic/sanic_tests.py index 291ceae7c..a59d508a1 100644 --- a/tests/contrib/sanic/sanic_tests.py +++ b/tests/contrib/sanic/sanic_tests.py @@ -199,6 +199,7 @@ def test_cookies_normalization(sanic_elastic_app, elasticapm_client): _, resp = sanic_app.test_client.get( "/add-cookies", ) + assert resp.status_code == 200 assert len(apm._client.events[constants.TRANSACTION]) == 1 transaction = apm._client.events[constants.TRANSACTION][0] assert transaction["context"]["response"]["cookies"] == {"some": {"value": "cookie", "path": "/"}} From 41f65b40a9327f88f8869fb904bef98d6b8184f0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 09:26:01 +0000 Subject: [PATCH 179/409] build(deps): bump docker/build-push-action in the github-actions group (#2192) Bumps the github-actions group with 1 update: [docker/build-push-action](https://github.com/docker/build-push-action). Updates `docker/build-push-action` from 6.10.0 to 6.11.0 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/48aba3b46d1b1fec4febb7c5d0c644b249a11355...b32b51a8eda65d6793cd0494a773d4f6bcef32dc) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Riccardo Magliocchetti --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 99bacfff8..237581934 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -146,7 +146,7 @@ jobs: - name: Build and push image id: docker-push - uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355 # v6.10.0 + uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 with: context: . platforms: linux/amd64,linux/arm64 From 28b2a6ea09e4dfdd1d65e2b4e711af74a0525e6f Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:47:44 +0100 Subject: [PATCH 180/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to dd66bee (#2189) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index bf624e961..d2f9e1802 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:3a6e9134cf3142da74153a522822c8fa56d09376e294627e51db8aa28f5d20d3 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:dd66beec64a7f9b19c6c35a1195153b2b630a55e16ec71949ed5187c5947eea1 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From c82dbcf7639b136d56c49912b0f8eb8fbb1708dc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 09:57:34 +0100 Subject: [PATCH 181/409] build(deps): bump docker/build-push-action in the github-actions group (#2199) Bumps the github-actions group with 1 update: [docker/build-push-action](https://github.com/docker/build-push-action). Updates `docker/build-push-action` from 6.11.0 to 6.12.0 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/b32b51a8eda65d6793cd0494a773d4f6bcef32dc...67a2d409c0a876cbe6b11854e3e25193efe4e62d) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 237581934..25e4ec7d8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -146,7 +146,7 @@ jobs: - name: Build and push image id: docker-push - uses: docker/build-push-action@b32b51a8eda65d6793cd0494a773d4f6bcef32dc # v6.11.0 + uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0 with: context: . platforms: linux/amd64,linux/arm64 From 06da3047fa4f39d9a082e6755b602b045594683c Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Mon, 20 Jan 2025 10:06:52 +0100 Subject: [PATCH 182/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to ea157dd (#2198) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index d2f9e1802..ab56b7edb 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:dd66beec64a7f9b19c6c35a1195153b2b630a55e16ec71949ed5187c5947eea1 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:ea157dd3d70787c6b6dc9e14dda1ff103c781d4c3f9a544393ff4583dd80c9d0 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From b0922bb74025770de1ccc1080f3713c3172db878 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Wed, 22 Jan 2025 09:43:34 +0100 Subject: [PATCH 183/409] chore: deps(updatecli): Bump updatecli version to v0.92.0 (#2200) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index b4283d6fe..e9de826ba 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.91.0 \ No newline at end of file +updatecli v0.92.0 \ No newline at end of file From 0cdb276303793076d6b8c7a4db33007b628df35e Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Thu, 23 Jan 2025 15:48:09 +0100 Subject: [PATCH 184/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to e777226 (#2201) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index ab56b7edb..bc65389f1 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:ea157dd3d70787c6b6dc9e14dda1ff103c781d4c3f9a544393ff4583dd80c9d0 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:e777226b7f8f6e90aed8255b56e763ee73a0f8885a4e4ddc4159d318d90fd1b6 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 1f90be228010c2c59bac5be89cbdb75d8ca1a13c Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:56:34 +0100 Subject: [PATCH 185/409] chore: deps(updatecli): Bump updatecli version to v0.93.0 (#2203) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index e9de826ba..9846e7746 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.92.0 \ No newline at end of file +updatecli v0.93.0 \ No newline at end of file From d8203a2c37c305fe698f60a59b7c8166014ca41d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:58:45 +0100 Subject: [PATCH 186/409] build(deps): bump the github-actions group with 3 updates (#2204) Bumps the github-actions group with 3 updates: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance), [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) and [docker/build-push-action](https://github.com/docker/build-push-action). Updates `actions/attest-build-provenance` from 2.1.0 to 2.2.0 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/7668571508540a607bdfd90a87a560489fe372eb...520d128f165991a6c774bcb264f323e3d70747f4) Updates `pypa/gh-action-pypi-publish` from 1.12.3 to 1.12.4 - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/67339c736fd9354cd4f8cb0b744f2b82a74b5c70...76f52bc884231f62b9a034ebfe128415bbaabdfc) Updates `docker/build-push-action` from 6.12.0 to 6.13.0 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/67a2d409c0a876cbe6b11854e3e25193efe4e62d...ca877d9245402d1537745e0e356eab47c3520991) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 25e4ec7d8..27c4cb769 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/packages - name: generate build provenance - uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0 + uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0 with: subject-path: "${{ github.workspace }}/dist/*" @@ -47,12 +47,12 @@ jobs: path: dist - name: Upload pypi.org if: startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@67339c736fd9354cd4f8cb0b744f2b82a74b5c70 # v1.12.3 + uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 with: repository-url: https://upload.pypi.org/legacy/ - name: Upload test.pypi.org if: ${{ ! startsWith(github.ref, 'refs/tags') }} - uses: pypa/gh-action-pypi-publish@67339c736fd9354cd4f8cb0b744f2b82a74b5c70 # v1.12.3 + uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 with: repository-url: https://test.pypi.org/legacy/ @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/build-distribution - name: generate build provenance - uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0 + uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0 with: subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" @@ -146,7 +146,7 @@ jobs: - name: Build and push image id: docker-push - uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d # v6.12.0 + uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0 with: context: . platforms: linux/amd64,linux/arm64 @@ -158,7 +158,7 @@ jobs: AGENT_DIR=./build/dist/package/python - name: generate build provenance (containers) - uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0 + uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0 with: subject-name: "${{ env.DOCKER_IMAGE_NAME }}" subject-digest: ${{ steps.docker-push.outputs.digest }} From 09dd0f1c6f38ea2bdb7cc6db39b8cd7973c196b9 Mon Sep 17 00:00:00 2001 From: "elastic-renovate-prod[bot]" <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 11:00:27 +0100 Subject: [PATCH 187/409] chore(deps): update docker.elastic.co/wolfi/chainguard-base:latest docker digest to bd40170 (#2202) Co-authored-by: elastic-renovate-prod[bot] <174716857+elastic-renovate-prod[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index bc65389f1..356dfb6fa 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:e777226b7f8f6e90aed8255b56e763ee73a0f8885a4e4ddc4159d318d90fd1b6 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:bd401704a162a7937cd1015f755ca9da9aba0fdf967fc6bf90bf8d3f6b2eb557 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From e4a05bacdf96f3ae4b6275d100db9a177e7b53ff Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 5 Feb 2025 17:11:07 +0100 Subject: [PATCH 188/409] ci: test pymongo against mongodb 4.0 (#2211) And add 3.6 to matrix. --- .ci/.matrix_framework_full.yml | 1 + tests/docker-compose.yml | 9 +++++++++ tests/scripts/envs/pymongo-3.6.sh | 3 +++ tests/scripts/envs/pymongo-newest.sh | 4 ++-- 4 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 tests/scripts/envs/pymongo-3.6.sh diff --git a/.ci/.matrix_framework_full.yml b/.ci/.matrix_framework_full.yml index d2482d9ff..8cd63d48d 100644 --- a/.ci/.matrix_framework_full.yml +++ b/.ci/.matrix_framework_full.yml @@ -46,6 +46,7 @@ FRAMEWORK: - pymongo-3.3 - pymongo-3.4 - pymongo-3.5 + - pymongo-3.6 - pymongo-newest - redis-3 - redis-2 diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index 62a05c83f..c6598a969 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -46,6 +46,13 @@ services: volumes: - pymongodata36:/data/db + mongodb40: + image: mongo:4.0 + ports: + - "27017:27017" + volumes: + - pymongodata40:/data/db + memcached: image: memcached @@ -198,6 +205,8 @@ volumes: driver: local pymongodata36: driver: local + pymongodata40: + driver: local pyesdata7: driver: local pyesdata8: diff --git a/tests/scripts/envs/pymongo-3.6.sh b/tests/scripts/envs/pymongo-3.6.sh new file mode 100644 index 000000000..4454a674f --- /dev/null +++ b/tests/scripts/envs/pymongo-3.6.sh @@ -0,0 +1,3 @@ +export PYTEST_MARKER="-m mongodb" +export DOCKER_DEPS="mongodb36" +export MONGODB_HOST="mongodb36" diff --git a/tests/scripts/envs/pymongo-newest.sh b/tests/scripts/envs/pymongo-newest.sh index 4454a674f..d1766b69f 100644 --- a/tests/scripts/envs/pymongo-newest.sh +++ b/tests/scripts/envs/pymongo-newest.sh @@ -1,3 +1,3 @@ export PYTEST_MARKER="-m mongodb" -export DOCKER_DEPS="mongodb36" -export MONGODB_HOST="mongodb36" +export DOCKER_DEPS="mongodb40" +export MONGODB_HOST="mongodb40" From 6f521ec7c2dc0db1316ffa3a0344d33cd3ef5b89 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 5 Feb 2025 17:43:51 +0100 Subject: [PATCH 189/409] tests: fix pymongo requirements (#2212) --- tests/requirements/reqs-pymongo-3.6.txt | 2 ++ tests/requirements/reqs-pymongo-newest.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 tests/requirements/reqs-pymongo-3.6.txt diff --git a/tests/requirements/reqs-pymongo-3.6.txt b/tests/requirements/reqs-pymongo-3.6.txt new file mode 100644 index 000000000..763bd98bc --- /dev/null +++ b/tests/requirements/reqs-pymongo-3.6.txt @@ -0,0 +1,2 @@ +pymongo>=3.6,<4.0 +-r reqs-base.txt diff --git a/tests/requirements/reqs-pymongo-newest.txt b/tests/requirements/reqs-pymongo-newest.txt index 330140ad6..7e5174d70 100644 --- a/tests/requirements/reqs-pymongo-newest.txt +++ b/tests/requirements/reqs-pymongo-newest.txt @@ -1,2 +1,2 @@ -pymongo>=3.6 +pymongo>=4.0 -r reqs-base.txt From 197746d8fc00b9affb524b27878724fe6e9cb0a1 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 5 Feb 2025 20:25:40 +0100 Subject: [PATCH 190/409] Add testing against fips docker image (#2209) * Add testing against fips docker image Schedule a weekly run for running a portion of the test suite inside a fips enabled container image. Co-authored-by: Trent Mick --------- Co-authored-by: Trent Mick --- .ci/.matrix_framework_fips.yml | 23 +++++++++++ .ci/.matrix_python_fips.yml | 2 + .github/workflows/test-fips.yml | 69 +++++++++++++++++++++++++++++++++ tests/config/tests.py | 5 ++- 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 .ci/.matrix_framework_fips.yml create mode 100644 .ci/.matrix_python_fips.yml create mode 100644 .github/workflows/test-fips.yml diff --git a/.ci/.matrix_framework_fips.yml b/.ci/.matrix_framework_fips.yml new file mode 100644 index 000000000..6bbc9cd3e --- /dev/null +++ b/.ci/.matrix_framework_fips.yml @@ -0,0 +1,23 @@ +# this is a limited list of matrix builds to be used for PRs +# see .matrix_framework_full.yml for a full list +FRAMEWORK: + - none + - django-5.0 + - flask-3.0 + - jinja2-3 + - opentelemetry-newest + - opentracing-newest + - twisted-newest + - celery-5-flask-2 + - celery-5-django-5 + - requests-newest + - psutil-newest + - gevent-newest + - aiohttp-newest + - tornado-newest + - starlette-newest + - graphene-2 + - httpx-newest + - httplib2-newest + - prometheus_client-newest + - sanic-newest diff --git a/.ci/.matrix_python_fips.yml b/.ci/.matrix_python_fips.yml new file mode 100644 index 000000000..01cf811ac --- /dev/null +++ b/.ci/.matrix_python_fips.yml @@ -0,0 +1,2 @@ +VERSION: + - python-3.12 diff --git a/.github/workflows/test-fips.yml b/.github/workflows/test-fips.yml new file mode 100644 index 000000000..3712f00d0 --- /dev/null +++ b/.github/workflows/test-fips.yml @@ -0,0 +1,69 @@ + +# run test suite inside a FIPS 140 container +name: test-fips + +on: + workflow_dispatch: + schedule: + - cron: '0 4 * * 1' + +permissions: + contents: read + +jobs: + create-matrix: + runs-on: ubuntu-24.04 + outputs: + matrix: ${{ steps.generate.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + - id: generate + uses: elastic/oblt-actions/version-framework@v1 + with: + versions-file: .ci/.matrix_python_fips.yml + frameworks-file: .ci/.matrix_framework_fips.yml + + test-fips: + needs: create-matrix + runs-on: ubuntu-24.04 + # https://docs.github.com/en/actions/writing-workflows/choosing-where-your-workflow-runs/running-jobs-in-a-container + # docker run -it --rm --name fipsy docker.elastic.co/wolfi/python-fips:3.12 + container: + image: docker.elastic.co/wolfi/python-fips:3.12-dev + options: --user root + credentials: + username: ${{ secrets.ELASTIC_DOCKER_USERNAME }} + password: ${{ secrets.ELASTIC_DOCKER_PASSWORD }} + timeout-minutes: 30 + strategy: + fail-fast: false + max-parallel: 10 + matrix: ${{ fromJSON(needs.create-matrix.outputs.matrix) }} + steps: + - uses: actions/checkout@v4 + - name: check that python has fips mode enabled + run: | + python3 -c 'import _hashlib; assert _hashlib.get_fips_mode() == 1' + - name: install run_tests.sh requirements + run: apk add netcat-openbsd tzdata + - name: Run tests + run: ./tests/scripts/run_tests.sh + env: + FRAMEWORK: ${{ matrix.framework }} + + notify-on-failure: + if: always() + runs-on: ubuntu-24.04 + needs: test-fips + steps: + - id: check + uses: elastic/oblt-actions/check-dependent-jobs@v1 + with: + jobs: ${{ toJSON(needs) }} + - name: Notify in Slack + if: steps.check.outputs.status == 'failure' + uses: elastic/oblt-actions/slack/notify-result@v1 + with: + bot-token: ${{ secrets.SLACK_BOT_TOKEN }} + status: ${{ steps.check.outputs.status }} + channel-id: "#apm-agent-python" diff --git a/tests/config/tests.py b/tests/config/tests.py index c0d6820c4..5fb9848be 100644 --- a/tests/config/tests.py +++ b/tests/config/tests.py @@ -278,7 +278,10 @@ def test_file_is_readable_validator_not_a_file(tmpdir): assert "is not a file" in e.value.args[0] -@pytest.mark.skipif(platform.system() == "Windows", reason="os.access() doesn't seem to work as we expect on Windows") +@pytest.mark.skipif( + platform.system() == "Windows" or os.getuid() == 0, + reason="os.access() doesn't seem to work as we expect on Windows and test will fail as root user", +) def test_file_is_readable_validator_not_readable(tmpdir): p = tmpdir.join("nonreadable") p.write("") From 1c6d8ec4925a83569cc5d896a4ec807c23fd313f Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Fri, 7 Feb 2025 15:24:40 +0100 Subject: [PATCH 191/409] github-action: Add AsciiDoc freeze warning (#2205) * github-action: Add AsciiDoc freeze warning * github-action: Add AsciiDoc freeze warning --------- Co-authored-by: Riccardo Magliocchetti --- .../workflows/comment-on-asciidoc-changes.yml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/comment-on-asciidoc-changes.yml diff --git a/.github/workflows/comment-on-asciidoc-changes.yml b/.github/workflows/comment-on-asciidoc-changes.yml new file mode 100644 index 000000000..8e5f836b1 --- /dev/null +++ b/.github/workflows/comment-on-asciidoc-changes.yml @@ -0,0 +1,21 @@ +--- +name: Comment on PR for .asciidoc changes + +on: + # We need to use pull_request_target to be able to comment on PRs from forks + pull_request_target: + types: + - synchronize + - opened + - reopened + branches: + - main + - master + - "9.0" + +jobs: + comment-on-asciidoc-change: + permissions: + contents: read + pull-requests: write + uses: elastic/docs-builder/.github/workflows/comment-on-asciidoc-changes.yml@main From 357907ba0366060356b68df87647d44ff2cc94a1 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Fri, 7 Feb 2025 15:30:00 +0100 Subject: [PATCH 192/409] chore: deps(updatecli): Bump updatecli version to v0.93.1 (#2215) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 9846e7746..0cf1b341d 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.93.0 \ No newline at end of file +updatecli v0.93.1 \ No newline at end of file From da01dc3fb5a6f72689e6b5452b65ee2468459830 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 11:32:01 +0100 Subject: [PATCH 193/409] build(deps): bump docker/setup-buildx-action in the github-actions group (#2217) Bumps the github-actions group with 1 update: [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action). Updates `docker/setup-buildx-action` from 3.8.0 to 3.9.0 - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/6524bf65af31da8d45b59e8c27de4bd072b392f5...f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 27c4cb769..29d991147 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -119,7 +119,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 + uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0 - name: Log in to the Elastic Container registry uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 From 6ae3aa1630acc8792ac487fd489354e111ca720f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Feb 2025 15:01:17 +0100 Subject: [PATCH 194/409] build(deps): bump docker/build-push-action in the github-actions group (#2219) Bumps the github-actions group with 1 update: [docker/build-push-action](https://github.com/docker/build-push-action). Updates `docker/build-push-action` from 6.13.0 to 6.14.0 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/ca877d9245402d1537745e0e356eab47c3520991...0adf9959216b96bec444f325f1e493d4aa344497) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 29d991147..af21733bf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -146,7 +146,7 @@ jobs: - name: Build and push image id: docker-push - uses: docker/build-push-action@ca877d9245402d1537745e0e356eab47c3520991 # v6.13.0 + uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.0 with: context: . platforms: linux/amd64,linux/arm64 From 205ff4b91d4920d23e771e88a82c88d86926aa2c Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Wed, 26 Feb 2025 13:43:18 -0600 Subject: [PATCH 195/409] add the new ci checks (#2220) --- .../workflows/comment-on-asciidoc-changes.yml | 21 ------------------- .github/workflows/docs-build.yml | 19 +++++++++++++++++ .github/workflows/docs-cleanup.yml | 14 +++++++++++++ 3 files changed, 33 insertions(+), 21 deletions(-) delete mode 100644 .github/workflows/comment-on-asciidoc-changes.yml create mode 100644 .github/workflows/docs-build.yml create mode 100644 .github/workflows/docs-cleanup.yml diff --git a/.github/workflows/comment-on-asciidoc-changes.yml b/.github/workflows/comment-on-asciidoc-changes.yml deleted file mode 100644 index 8e5f836b1..000000000 --- a/.github/workflows/comment-on-asciidoc-changes.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: Comment on PR for .asciidoc changes - -on: - # We need to use pull_request_target to be able to comment on PRs from forks - pull_request_target: - types: - - synchronize - - opened - - reopened - branches: - - main - - master - - "9.0" - -jobs: - comment-on-asciidoc-change: - permissions: - contents: read - pull-requests: write - uses: elastic/docs-builder/.github/workflows/comment-on-asciidoc-changes.yml@main diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml new file mode 100644 index 000000000..bb466166d --- /dev/null +++ b/.github/workflows/docs-build.yml @@ -0,0 +1,19 @@ +name: docs-build + +on: + push: + branches: + - main + pull_request_target: ~ + merge_group: ~ + +jobs: + docs-preview: + uses: elastic/docs-builder/.github/workflows/preview-build.yml@main + with: + path-pattern: docs/** + permissions: + deployments: write + id-token: write + contents: read + pull-requests: read diff --git a/.github/workflows/docs-cleanup.yml b/.github/workflows/docs-cleanup.yml new file mode 100644 index 000000000..f83e017b5 --- /dev/null +++ b/.github/workflows/docs-cleanup.yml @@ -0,0 +1,14 @@ +name: docs-cleanup + +on: + pull_request_target: + types: + - closed + +jobs: + docs-preview: + uses: elastic/docs-builder/.github/workflows/preview-cleanup.yml@main + permissions: + contents: none + id-token: write + deployments: write From 52fd97915cae55bd4b537124e40a9b644ba16273 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Fri, 28 Feb 2025 10:06:59 +0100 Subject: [PATCH 196/409] chore: deps(updatecli): Bump updatecli version to v0.94.1 (#2222) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 0cf1b341d..c245dc531 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.93.1 \ No newline at end of file +updatecli v0.94.1 \ No newline at end of file From f4040b8cacd6410f0a83398708ddc8121813b0fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Mar 2025 12:22:52 +0100 Subject: [PATCH 197/409] build(deps): bump the github-actions group with 4 updates (#2226) Bumps the github-actions group with 4 updates: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance), [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action), [docker/metadata-action](https://github.com/docker/metadata-action) and [docker/build-push-action](https://github.com/docker/build-push-action). Updates `actions/attest-build-provenance` from 2.2.0 to 2.2.2 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/520d128f165991a6c774bcb264f323e3d70747f4...bd77c077858b8d561b7a36cbe48ef4cc642ca39d) Updates `docker/setup-buildx-action` from 3.9.0 to 3.10.0 - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca...b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2) Updates `docker/metadata-action` from 5.6.1 to 5.7.0 - [Release notes](https://github.com/docker/metadata-action/releases) - [Commits](https://github.com/docker/metadata-action/compare/369eb591f429131d6889c46b94e711f089e6ca96...902fa8ec7d6ecbf8d84d538b9b233a880e428804) Updates `docker/build-push-action` from 6.14.0 to 6.15.0 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/0adf9959216b96bec444f325f1e493d4aa344497...471d1dc4e07e5cdedd4c2171150001c434f0b7a4) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: docker/metadata-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index af21733bf..a8c0d9bbc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/packages - name: generate build provenance - uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0 + uses: actions/attest-build-provenance@bd77c077858b8d561b7a36cbe48ef4cc642ca39d # v2.2.2 with: subject-path: "${{ github.workspace }}/dist/*" @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/build-distribution - name: generate build provenance - uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0 + uses: actions/attest-build-provenance@bd77c077858b8d561b7a36cbe48ef4cc642ca39d # v2.2.2 with: subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" @@ -119,7 +119,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@f7ce87c1d6bead3e36075b2ce75da1f6cc28aaca # v3.9.0 + uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 - name: Log in to the Elastic Container registry uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 @@ -135,7 +135,7 @@ jobs: - name: Extract metadata (tags, labels) id: docker-meta - uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96 # v5.6.1 + uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 with: images: ${{ env.DOCKER_IMAGE_NAME }} tags: | @@ -146,7 +146,7 @@ jobs: - name: Build and push image id: docker-push - uses: docker/build-push-action@0adf9959216b96bec444f325f1e493d4aa344497 # v6.14.0 + uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 with: context: . platforms: linux/amd64,linux/arm64 @@ -158,7 +158,7 @@ jobs: AGENT_DIR=./build/dist/package/python - name: generate build provenance (containers) - uses: actions/attest-build-provenance@520d128f165991a6c774bcb264f323e3d70747f4 # v2.2.0 + uses: actions/attest-build-provenance@bd77c077858b8d561b7a36cbe48ef4cc642ca39d # v2.2.2 with: subject-name: "${{ env.DOCKER_IMAGE_NAME }}" subject-digest: ${{ steps.docker-push.outputs.digest }} From 6004832a33dd9c93061e982ec475836ab1f653f2 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 10 Mar 2025 10:02:03 +0100 Subject: [PATCH 198/409] Make server certificate verification mandatory in fips mode (#2227) * elasticapm/conf: block disabling of verify cert server in fips mode This requires to add validation support to _BoolConfigValue * Send regexp in conf tests as raw strings --- elasticapm/conf/__init__.py | 33 ++++++++++++++++++++++++++++++- tests/config/tests.py | 39 +++++++++++++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/elasticapm/conf/__init__.py b/elasticapm/conf/__init__.py index 5b851c9a5..6d19eb96c 100644 --- a/elasticapm/conf/__init__.py +++ b/elasticapm/conf/__init__.py @@ -37,6 +37,8 @@ import threading from datetime import timedelta +import _hashlib + from elasticapm.conf.constants import BASE_SANITIZE_FIELD_NAMES, TRACE_CONTINUATION_STRATEGY from elasticapm.utils import compat, starmatch_to_regex from elasticapm.utils.logging import get_logger @@ -220,6 +222,8 @@ class _BoolConfigValue(_ConfigValue): def __init__(self, dict_key, true_string="true", false_string="false", **kwargs) -> None: self.true_string = true_string self.false_string = false_string + # this is necessary to have the bool type preserved in _validate + kwargs["type"] = bool super(_BoolConfigValue, self).__init__(dict_key, **kwargs) def __set__(self, instance, value) -> None: @@ -228,6 +232,7 @@ def __set__(self, instance, value) -> None: value = True elif value.lower() == self.false_string: value = False + value = self._validate(instance, value) self._callback_if_changed(instance, value) instance._values[self.dict_key] = bool(value) @@ -373,6 +378,30 @@ def __call__(self, value, field_name): return value +def _in_fips_mode(): + try: + return _hashlib.get_fips_mode() == 1 + except AttributeError: + # versions older of Python3.9 do not have the helper + return False + + +class SupportedValueInFipsModeValidator(object): + """If FIPS mode is enabled only supported_value is accepted""" + + def __init__(self, supported_value) -> None: + self.supported_value = supported_value + + def __call__(self, value, field_name): + if _in_fips_mode(): + if value != self.supported_value: + raise ConfigurationError( + "{}={} must be set to {} if FIPS mode is enabled".format(field_name, value, self.supported_value), + field_name, + ) + return value + + class EnumerationValidator(object): """ Validator which ensures that a given config value is chosen from a list @@ -579,7 +608,9 @@ class Config(_ConfigBase): server_url = _ConfigValue("SERVER_URL", default="http://127.0.0.1:8200", required=True) server_cert = _ConfigValue("SERVER_CERT", validators=[FileIsReadableValidator()]) server_ca_cert_file = _ConfigValue("SERVER_CA_CERT_FILE", validators=[FileIsReadableValidator()]) - verify_server_cert = _BoolConfigValue("VERIFY_SERVER_CERT", default=True) + verify_server_cert = _BoolConfigValue( + "VERIFY_SERVER_CERT", default=True, validators=[SupportedValueInFipsModeValidator(supported_value=True)] + ) use_certifi = _BoolConfigValue("USE_CERTIFI", default=True) include_paths = _ListConfigValue("INCLUDE_PATHS") exclude_paths = _ListConfigValue("EXCLUDE_PATHS", default=compat.get_default_library_patters()) diff --git a/tests/config/tests.py b/tests/config/tests.py index 5fb9848be..284f5694a 100644 --- a/tests/config/tests.py +++ b/tests/config/tests.py @@ -39,6 +39,7 @@ import mock import pytest +import elasticapm.conf from elasticapm.conf import ( Config, ConfigurationError, @@ -47,6 +48,7 @@ FileIsReadableValidator, PrecisionValidator, RegexValidator, + SupportedValueInFipsModeValidator, UnitValidator, VersionedConfig, _BoolConfigValue, @@ -458,7 +460,7 @@ def test_config_all_upper_case(): def test_regex_validator_without_match(): - validator = RegexValidator("\d") + validator = RegexValidator(r"\d") with pytest.raises(ConfigurationError) as e: validator("foo", "field") assert "does not match pattern" in e.value.args[0] @@ -472,7 +474,7 @@ def test_unit_validator_without_match(): def test_unit_validator_with_unsupported_unit(): - validator = UnitValidator("(\d+)(s)", "secs", {}) + validator = UnitValidator(r"(\d+)(s)", "secs", {}) with pytest.raises(ConfigurationError) as e: validator("10s", "field") assert "is not a supported unit" in e.value.args[0] @@ -490,3 +492,36 @@ def test_exclude_range_validator_not_in_range(): with pytest.raises(ConfigurationError) as e: validator(10, "field") assert "cannot be in range" in e.value.args[0] + + +def test_supported_value_in_fips_mode_validator_in_fips_mode_with_invalid_value(monkeypatch): + monkeypatch.setattr(elasticapm.conf, "_in_fips_mode", lambda: True) + exception_message = "VERIFY_SERVER_CERT=False must be set to True if FIPS mode is enabled" + validator = SupportedValueInFipsModeValidator(supported_value=True) + with pytest.raises(ConfigurationError) as e: + validator(False, "VERIFY_SERVER_CERT") + assert exception_message == e.value.args[0] + + config = Config({"VERIFY_SERVER_CERT": False}) + assert config.errors["VERIFY_SERVER_CERT"] == exception_message + + +def test_supported_value_in_fips_mode_validator_in_fips_mode_with_valid_value(monkeypatch): + monkeypatch.setattr(elasticapm.conf, "_in_fips_mode", lambda: True) + validator = SupportedValueInFipsModeValidator(supported_value=True) + assert validator(True, "VERIFY_SERVER_CERT") == True + config = Config({"VERIFY_SERVER_CERT": True}) + assert config.verify_server_cert == True + assert "VERIFY_SERVER_CERT" not in config.errors + + +def test_supported_value_in_fips_mode_validator_not_in_fips_mode(monkeypatch): + monkeypatch.setattr(elasticapm.conf, "_in_fips_mode", lambda: False) + validator = SupportedValueInFipsModeValidator(supported_value=True) + assert validator(True, "field") == True + assert validator(False, "field") == False + + config = Config({"VERIFY_SERVER_CERT": False}) + assert not config.errors + config = Config({"VERIFY_SERVER_CERT": True}) + assert not config.errors From 752e6aadc73dbe597442bac13a9515ebeae6acff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Mar 2025 10:21:29 +0100 Subject: [PATCH 199/409] build(deps): bump actions/attest-build-provenance (#2230) Bumps the github-actions group with 1 update: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance). Updates `actions/attest-build-provenance` from 2.2.2 to 2.2.3 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/bd77c077858b8d561b7a36cbe48ef4cc642ca39d...c074443f1aee8d4aeeae555aebba3282517141b2) --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a8c0d9bbc..b4b105cc1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/packages - name: generate build provenance - uses: actions/attest-build-provenance@bd77c077858b8d561b7a36cbe48ef4cc642ca39d # v2.2.2 + uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3 with: subject-path: "${{ github.workspace }}/dist/*" @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/build-distribution - name: generate build provenance - uses: actions/attest-build-provenance@bd77c077858b8d561b7a36cbe48ef4cc642ca39d # v2.2.2 + uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3 with: subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" @@ -158,7 +158,7 @@ jobs: AGENT_DIR=./build/dist/package/python - name: generate build provenance (containers) - uses: actions/attest-build-provenance@bd77c077858b8d561b7a36cbe48ef4cc642ca39d # v2.2.2 + uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3 with: subject-name: "${{ env.DOCKER_IMAGE_NAME }}" subject-digest: ${{ steps.docker-push.outputs.digest }} From d1bdc7b8810c643cd687a145df5bae12c97c1e98 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Wed, 12 Mar 2025 09:46:04 +0100 Subject: [PATCH 200/409] chore: deps(updatecli): Bump updatecli version to v0.95.0 (#2231) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index c245dc531..b2b4d9ed9 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.94.1 \ No newline at end of file +updatecli v0.95.0 \ No newline at end of file From 3a0708edb4e262ad8304f48b39d9db8c74fba224 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Thu, 13 Mar 2025 10:14:49 +0100 Subject: [PATCH 201/409] chore: deps(updatecli): Bump updatecli version to v0.95.1 (#2232) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index b2b4d9ed9..7133ace51 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.95.0 \ No newline at end of file +updatecli v0.95.1 \ No newline at end of file From a15ec54f72ebae5c5f31c3feb8979130cb6901ae Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Tue, 18 Mar 2025 11:46:37 -0500 Subject: [PATCH 202/409] [docs] Migrate docs from AsciiDoc to Markdown (#2221) * delete asciidoc files * add migrated files * clean up cross-repo links * apply suggestions from code review Co-authored-by: Riccardo Magliocchetti * fix external links that should contain .html * update lambda support page * add code annotation * rename files that contain a period * clean up --------- Co-authored-by: Riccardo Magliocchetti --- docs/advanced-topics.asciidoc | 13 - docs/aiohttp-server.asciidoc | 124 -- docs/api.asciidoc | 551 ------- docs/asgi-middleware.asciidoc | 61 - docs/configuration.asciidoc | 1387 ----------------- docs/custom-instrumentation.asciidoc | 143 -- docs/django.asciidoc | 375 ----- docs/docset.yml | 494 ++++++ docs/flask.asciidoc | 245 --- docs/getting-started.asciidoc | 32 - docs/grpc.asciidoc | 65 - docs/how-the-agent-works.asciidoc | 72 - docs/images/choose-a-layer.png | Bin 0 -> 135763 bytes docs/images/config-layer.png | Bin 0 -> 61275 bytes docs/images/python-lambda-env-vars.png | Bin 0 -> 54573 bytes docs/index.asciidoc | 40 - docs/lambda/configure-lambda-widget.asciidoc | 118 -- docs/lambda/configure-lambda.asciidoc | 113 -- docs/lambda/python-arn-replacement.asciidoc | 9 - docs/logging.asciidoc | 175 --- docs/metrics.asciidoc | 215 --- docs/opentelemetry.asciidoc | 76 - docs/redirects.asciidoc | 14 - docs/reference/advanced-topics.md | 16 + docs/reference/aiohttp-server-support.md | 112 ++ docs/reference/api-reference.md | 463 ++++++ docs/reference/asgi-middleware.md | 66 + docs/reference/azure-functions-support.md | 53 + docs/reference/configuration.md | 1067 +++++++++++++ docs/reference/django-support.md | 327 ++++ docs/reference/flask-support.md | 215 +++ docs/reference/how-agent-works.md | 52 + docs/reference/index.md | 28 + docs/reference/instrumenting-custom-code.md | 118 ++ docs/reference/lambda-support.md | 263 ++++ docs/reference/logs.md | 141 ++ docs/reference/metrics.md | 185 +++ docs/reference/opentelemetry-api-bridge.md | 63 + docs/reference/performance-tuning.md | 86 + docs/reference/run-tests-locally.md | 72 + docs/reference/sanic-support.md | 140 ++ .../sanitizing-data.md} | 48 +- docs/reference/set-up-apm-python-agent.md | 32 + docs/reference/starlette-support.md | 127 ++ docs/reference/supported-technologies.md | 631 ++++++++ docs/reference/toc.yml | 33 + docs/reference/tornado-support.md | 108 ++ docs/reference/upgrading-4-x.md | 29 + docs/reference/upgrading-5-x.md | 19 + docs/reference/upgrading-6-x.md | 22 + docs/reference/upgrading.md | 19 + .../wrapper-support.md} | 60 +- docs/release-notes.asciidoc | 15 - docs/release-notes/breaking-changes.md | 28 + docs/release-notes/deprecations.md | 38 + docs/release-notes/index.md | 545 +++++++ docs/release-notes/known-issues.md | 19 + docs/release-notes/toc.yml | 5 + docs/run-tests-locally.asciidoc | 78 - docs/sanic.asciidoc | 179 --- docs/serverless-azure-functions.asciidoc | 61 - docs/serverless-lambda.asciidoc | 53 - docs/set-up.asciidoc | 37 - docs/starlette.asciidoc | 152 -- docs/supported-technologies.asciidoc | 683 -------- docs/tornado.asciidoc | 125 -- docs/troubleshooting.asciidoc | 172 -- docs/tuning.asciidoc | 115 -- docs/upgrading.asciidoc | 81 - 69 files changed, 5670 insertions(+), 5633 deletions(-) delete mode 100644 docs/advanced-topics.asciidoc delete mode 100644 docs/aiohttp-server.asciidoc delete mode 100644 docs/api.asciidoc delete mode 100644 docs/asgi-middleware.asciidoc delete mode 100644 docs/configuration.asciidoc delete mode 100644 docs/custom-instrumentation.asciidoc delete mode 100644 docs/django.asciidoc create mode 100644 docs/docset.yml delete mode 100644 docs/flask.asciidoc delete mode 100644 docs/getting-started.asciidoc delete mode 100644 docs/grpc.asciidoc delete mode 100644 docs/how-the-agent-works.asciidoc create mode 100644 docs/images/choose-a-layer.png create mode 100644 docs/images/config-layer.png create mode 100644 docs/images/python-lambda-env-vars.png delete mode 100644 docs/index.asciidoc delete mode 100644 docs/lambda/configure-lambda-widget.asciidoc delete mode 100644 docs/lambda/configure-lambda.asciidoc delete mode 100644 docs/lambda/python-arn-replacement.asciidoc delete mode 100644 docs/logging.asciidoc delete mode 100644 docs/metrics.asciidoc delete mode 100644 docs/opentelemetry.asciidoc delete mode 100644 docs/redirects.asciidoc create mode 100644 docs/reference/advanced-topics.md create mode 100644 docs/reference/aiohttp-server-support.md create mode 100644 docs/reference/api-reference.md create mode 100644 docs/reference/asgi-middleware.md create mode 100644 docs/reference/azure-functions-support.md create mode 100644 docs/reference/configuration.md create mode 100644 docs/reference/django-support.md create mode 100644 docs/reference/flask-support.md create mode 100644 docs/reference/how-agent-works.md create mode 100644 docs/reference/index.md create mode 100644 docs/reference/instrumenting-custom-code.md create mode 100644 docs/reference/lambda-support.md create mode 100644 docs/reference/logs.md create mode 100644 docs/reference/metrics.md create mode 100644 docs/reference/opentelemetry-api-bridge.md create mode 100644 docs/reference/performance-tuning.md create mode 100644 docs/reference/run-tests-locally.md create mode 100644 docs/reference/sanic-support.md rename docs/{sanitizing-data.asciidoc => reference/sanitizing-data.md} (72%) create mode 100644 docs/reference/set-up-apm-python-agent.md create mode 100644 docs/reference/starlette-support.md create mode 100644 docs/reference/supported-technologies.md create mode 100644 docs/reference/toc.yml create mode 100644 docs/reference/tornado-support.md create mode 100644 docs/reference/upgrading-4-x.md create mode 100644 docs/reference/upgrading-5-x.md create mode 100644 docs/reference/upgrading-6-x.md create mode 100644 docs/reference/upgrading.md rename docs/{wrapper.asciidoc => reference/wrapper-support.md} (50%) delete mode 100644 docs/release-notes.asciidoc create mode 100644 docs/release-notes/breaking-changes.md create mode 100644 docs/release-notes/deprecations.md create mode 100644 docs/release-notes/index.md create mode 100644 docs/release-notes/known-issues.md create mode 100644 docs/release-notes/toc.yml delete mode 100644 docs/run-tests-locally.asciidoc delete mode 100644 docs/sanic.asciidoc delete mode 100644 docs/serverless-azure-functions.asciidoc delete mode 100644 docs/serverless-lambda.asciidoc delete mode 100644 docs/set-up.asciidoc delete mode 100644 docs/starlette.asciidoc delete mode 100644 docs/supported-technologies.asciidoc delete mode 100644 docs/tornado.asciidoc delete mode 100644 docs/troubleshooting.asciidoc delete mode 100644 docs/tuning.asciidoc delete mode 100644 docs/upgrading.asciidoc diff --git a/docs/advanced-topics.asciidoc b/docs/advanced-topics.asciidoc deleted file mode 100644 index f2aa3c0d3..000000000 --- a/docs/advanced-topics.asciidoc +++ /dev/null @@ -1,13 +0,0 @@ -[[advanced-topics]] -== Advanced Topics - -* <> -* <> -* <> -* <> - -include::./custom-instrumentation.asciidoc[Custom Instrumentation] -include::./sanitizing-data.asciidoc[Sanitizing Data] -include::./how-the-agent-works.asciidoc[How the Agent works] -include::./run-tests-locally.asciidoc[Run Tests Locally] - diff --git a/docs/aiohttp-server.asciidoc b/docs/aiohttp-server.asciidoc deleted file mode 100644 index 357aa79b3..000000000 --- a/docs/aiohttp-server.asciidoc +++ /dev/null @@ -1,124 +0,0 @@ -[[aiohttp-server-support]] -=== Aiohttp Server support - -Getting Elastic APM set up for your Aiohttp Server project is easy, -and there are various ways you can tweak it to fit to your needs. - -[float] -[[aiohttp-server-installation]] -==== Installation - -Install the Elastic APM agent using pip: - -[source,bash] ----- -$ pip install elastic-apm ----- - -or add `elastic-apm` to your project's `requirements.txt` file. - - -[float] -[[aiohttp-server-setup]] -==== Setup - -To set up the agent, you need to initialize it with appropriate settings. - -The settings are configured either via environment variables, -the application's settings, or as initialization arguments. - -You can find a list of all available settings in the <> page. - -To initialize the agent for your application using environment variables: - -[source,python] ----- -from aiohttp import web - -from elasticapm.contrib.aiohttp import ElasticAPM - -app = web.Application() - -apm = ElasticAPM(app) ----- - -To configure the agent using `ELASTIC_APM` in your application's settings: - -[source,python] ----- -from aiohttp import web - -from elasticapm.contrib.aiohttp import ElasticAPM - -app = web.Application() - -app['ELASTIC_APM'] = { - 'SERVICE_NAME': '', - 'SECRET_TOKEN': '', -} -apm = ElasticAPM(app) ----- - -[float] -[[aiohttp-server-usage]] -==== Usage - -Once you have configured the agent, -it will automatically track transactions and capture uncaught exceptions within aiohttp. - -Capture an arbitrary exception by calling <>: - -[source,python] ----- -try: - 1 / 0 -except ZeroDivisionError: - apm.client.capture_exception() ----- - -Log a generic message with <>: - -[source,python] ----- -apm.client.capture_message('hello, world!') ----- - -[float] -[[aiohttp-server-performance-metrics]] -==== Performance metrics - -If you've followed the instructions above, the agent has already installed our middleware. -This will measure response times, as well as detailed performance data for all supported technologies. - -NOTE: due to the fact that `asyncio` drivers are usually separate from their synchronous counterparts, -specific instrumentation is needed for all drivers. -The support for asynchronous drivers is currently quite limited. - -[float] -[[aiohttp-server-ignoring-specific-views]] -===== Ignoring specific routes - -You can use the <> configuration option to ignore specific routes. -The list given should be a list of regular expressions which are matched against the transaction name: - -[source,python] ----- -app['ELASTIC_APM'] = { - # ... - 'TRANSACTIONS_IGNORE_PATTERNS': ['^OPTIONS ', '/api/'] - # ... -} ----- - -This would ignore any requests using the `OPTIONS` method -and any requests containing `/api/`. - - - -[float] -[[supported-aiohttp-and-python-versions]] -==== Supported aiohttp and Python versions - -A list of supported <> and <> versions can be found on our <> page. - -NOTE: Elastic APM only supports `asyncio` when using Python 3.7+ diff --git a/docs/api.asciidoc b/docs/api.asciidoc deleted file mode 100644 index 327ddee71..000000000 --- a/docs/api.asciidoc +++ /dev/null @@ -1,551 +0,0 @@ -[[api]] -== API reference - -The Elastic APM Python agent has several public APIs. -Most of the public API functionality is not needed when using one of our <>, -but they allow customized usage. - -[float] -[[client-api]] -=== Client API - -The public Client API consists of several methods on the `Client` class. -This API can be used to track exceptions and log messages, -as well as to mark the beginning and end of transactions. - -[float] -[[client-api-init]] -==== Instantiation - -[small]#Added in v1.0.0.# - -To create a `Client` instance, import it and call its constructor: - -[source,python] ----- -from elasticapm import Client - -client = Client({'SERVICE_NAME': 'example'}, **defaults) ----- - - * `config`: A dictionary, with key/value configuration. For the possible configuration keys, see <>. - * `**defaults`: default values for configuration. These can be omitted in most cases, and take the least precedence. - -NOTE: framework integrations like <> and <> -instantiate the client automatically. - -[float] -[[api-get-client]] -===== `elasticapm.get_client()` - -[small]#Added in v6.1.0. - -Retrieves the `Client` singleton. This is useful for many framework integrations, -where the client is instantiated automatically. - -[source,python] ----- -client = elasticapm.get_client() -client.capture_message('foo') ----- - -[float] -[[error-api]] -==== Errors - -[float] -[[client-api-capture-exception]] -===== `Client.capture_exception()` - -[small]#Added in v1.0.0. `handled` added in v2.0.0.# - -Captures an exception object: - -[source,python] ----- -try: - x = int("five") -except ValueError: - client.capture_exception() ----- - - * `exc_info`: A `(type, value, traceback)` tuple as returned by https://docs.python.org/3/library/sys.html#sys.exc_info[`sys.exc_info()`]. If not provided, it will be captured automatically. - * `date`: A `datetime.datetime` object representing the occurrence time of the error. If left empty, it defaults to `datetime.datetime.utcnow()`. - * `context`: A dictionary with contextual information. This dictionary must follow the - {apm-guide-ref}/api-error.html[Context] schema definition. - * `custom`: A dictionary of custom data you want to attach to the event. - * `handled`: A boolean to indicate if this exception was handled or not. - -Returns the id of the error as a string. - -[float] -[[client-api-capture-message]] -===== `Client.capture_message()` - -[small]#Added in v1.0.0.# - -Captures a message with optional added contextual data. Example: - -[source,python] ----- -client.capture_message('Billing process succeeded.') ----- - - * `message`: The message as a string. - * `param_message`: Alternatively, a parameterized message as a dictionary. - The dictionary contains two values: `message`, and `params`. - This allows the APM Server to group messages together that share the same - parameterized message. Example: -+ -[source,python] ----- -client.capture_message(param_message={ - 'message': 'Billing process for %s succeeded. Amount: %s', - 'params': (customer.id, order.total_amount), -}) ----- -+ - * `stack`: If set to `True` (the default), a stacktrace from the call site will be captured. - * `exc_info`: A `(type, value, traceback)` tuple as returned by - https://docs.python.org/3/library/sys.html#sys.exc_info[`sys.exc_info()`]. - If not provided, it will be captured automatically, if `capture_message()` was called in an `except` block. - * `date`: A `datetime.datetime` object representing the occurrence time of the error. - If left empty, it defaults to `datetime.datetime.utcnow()`. - * `context`: A dictionary with contextual information. This dictionary must follow the - {apm-guide-ref}/api-error.html[Context] schema definition. - * `custom`: A dictionary of custom data you want to attach to the event. - -Returns the id of the message as a string. - -NOTE: Either the `message` or the `param_message` argument is required. - -[float] -[[transaction-api]] -==== Transactions - -[float] -[[client-api-begin-transaction]] -===== `Client.begin_transaction()` - -[small]#Added in v1.0.0. `trace_parent` support added in v5.6.0.# - -Begin tracking a transaction. -Should be called e.g. at the beginning of a request or when starting a background task. Example: - -[source,python] ----- -client.begin_transaction('processors') ----- - - * `transaction_type`: (*required*) A string describing the type of the transaction, e.g. `'request'` or `'celery'`. - * `trace_parent`: (*optional*) A `TraceParent` object. See <>. - * `links`: (*optional*) A list of `TraceParent` objects to which this transaction is causally linked. - -[float] -[[client-api-end-transaction]] -===== `Client.end_transaction()` - -[small]#Added in v1.0.0.# - -End tracking the transaction. -Should be called e.g. at the end of a request or when ending a background task. Example: - -[source,python] ----- -client.end_transaction('myapp.billing_process', processor.status) ----- - - * `name`: (*optional*) A string describing the name of the transaction, e.g. `process_order`. - This is typically the name of the view/controller that handles the request, or the route name. - * `result`: (*optional*) A string describing the result of the transaction. - This is typically the HTTP status code, or e.g. `'success'` for a background task. - -NOTE: if `name` and `result` are not set in the `end_transaction()` call, -they have to be set beforehand by calling <> and <> during the transaction. - -[float] -[[traceparent-api]] -==== `TraceParent` - -Transactions can be started with a `TraceParent` object. This creates a -transaction that is a child of the `TraceParent`, which is essential for -distributed tracing. - -[float] -[[api-traceparent-from-string]] -===== `elasticapm.trace_parent_from_string()` - -[small]#Added in v5.6.0.# - -Create a `TraceParent` object from the string representation generated by -`TraceParent.to_string()`: - -[source,python] ----- -parent = elasticapm.trace_parent_from_string('00-03d67dcdd62b7c0f7a675424347eee3a-5f0e87be26015733-01') -client.begin_transaction('processors', trace_parent=parent) ----- - - * `traceparent_string`: (*required*) A string representation of a `TraceParent` object. - - -[float] -[[api-traceparent-from-headers]] -===== `elasticapm.trace_parent_from_headers()` - -[small]#Added in v5.6.0.# - -Create a `TraceParent` object from HTTP headers (usually generated by another -Elastic APM agent): - -[source,python] ----- -parent = elasticapm.trace_parent_from_headers(headers_dict) -client.begin_transaction('processors', trace_parent=parent) ----- - - * `headers`: (*required*) HTTP headers formed as a dictionary. - -[float] -[[api-traceparent-get-header]] -===== `elasticapm.get_trace_parent_header()` - -[small]#Added in v5.10.0.# - -Return the string representation of the current transaction `TraceParent` object: - -[source,python] ----- -elasticapm.get_trace_parent_header() ----- - -[float] -[[api-other]] -=== Other APIs - -[float] -[[api-elasticapm-instrument]] -==== `elasticapm.instrument()` - -[small]#Added in v1.0.0.# - -Instruments libraries automatically. -This includes a wide range of standard library and 3rd party modules. -A list of instrumented modules can be found in `elasticapm.instrumentation.register`. -This function should be called as early as possibly in the startup of your application. -For <>, this is called automatically. Example: - -[source,python] ----- -import elasticapm - -elasticapm.instrument() ----- - -[float] -[[api-set-transaction-name]] -==== `elasticapm.set_transaction_name()` - -[small]#Added in v1.0.0.# - -Set the name of the current transaction. -For supported frameworks, the transaction name is determined automatically, -and can be overridden using this function. Example: - -[source,python] ----- -import elasticapm - -elasticapm.set_transaction_name('myapp.billing_process') ----- - - * `name`: (*required*) A string describing name of the transaction - * `override`: if `True` (the default), overrides any previously set transaction name. - If `False`, only sets the name if the transaction name hasn't already been set. - -[float] -[[api-set-transaction-result]] -==== `elasticapm.set_transaction_result()` - -[small]#Added in v2.2.0.# - -Set the result of the current transaction. -For supported frameworks, the transaction result is determined automatically, -and can be overridden using this function. Example: - -[source,python] ----- -import elasticapm - -elasticapm.set_transaction_result('SUCCESS') ----- - - * `result`: (*required*) A string describing the result of the transaction, e.g. `HTTP 2xx` or `SUCCESS` - * `override`: if `True` (the default), overrides any previously set result. - If `False`, only sets the result if the result hasn't already been set. - -[float] -[[api-set-transaction-outcome]] -==== `elasticapm.set_transaction_outcome()` - -[small]#Added in v5.9.0.# - -Sets the outcome of the transaction. The value can either be `"success"`, `"failure"` or `"unknown"`. -This should only be called at the end of a transaction after the outcome is determined. - -The `outcome` is used for error rate calculations. -`success` denotes that a transaction has concluded successful, while `failure` indicates that the transaction failed -to finish successfully. -If the `outcome` is set to `unknown`, the transaction will not be included in error rate calculations. - -For supported web frameworks, the transaction outcome is set automatically if it has not been set yet, based on the -HTTP status code. -A status code below `500` is considered a `success`, while any value of `500` or higher is counted as a `failure`. - -If your transaction results in an HTTP response, you can alternatively provide the HTTP status code. - -NOTE: While the `outcome` and `result` field look very similar, they serve different purposes. - Other than the `result` field, which canhold an arbitrary string value, - `outcome` is limited to three different values, - `"success"`, `"failure"` and `"unknown"`. - This allows the APM app to perform error rate calculations on these values. - -Example: - -[source,python] ----- -import elasticapm - -elasticapm.set_transaction_outcome("success") - -# Using an HTTP status code -elasticapm.set_transaction_outcome(http_status_code=200) - -# Using predefined constants: - -from elasticapm.conf.constants import OUTCOME - -elasticapm.set_transaction_outcome(OUTCOME.SUCCESS) -elasticapm.set_transaction_outcome(OUTCOME.FAILURE) -elasticapm.set_transaction_outcome(OUTCOME.UNKNOWN) ----- - - * `outcome`: One of `"success"`, `"failure"` or `"unknown"`. Can be omitted if `http_status_code` is provided. - * `http_status_code`: if the transaction represents an HTTP response, its status code can be provided to determine the `outcome` automatically. - * `override`: if `True` (the default), any previously set `outcome` will be overriden. - If `False`, the outcome will only be set if it was not set before. - - -[float] -[[api-get-transaction-id]] -==== `elasticapm.get_transaction_id()` - -[small]#Added in v5.2.0.# - -Get the id of the current transaction. Example: - -[source,python] ----- -import elasticapm - -transaction_id = elasticapm.get_transaction_id() ----- - - -[float] -[[api-get-trace-id]] -==== `elasticapm.get_trace_id()` - -[small]#Added in v5.2.0.# - -Get the `trace_id` of the current transaction's trace. Example: - -[source,python] ----- -import elasticapm - -trace_id = elasticapm.get_trace_id() ----- - - -[float] -[[api-get-span-id]] -==== `elasticapm.get_span_id()` - -[small]#Added in v5.2.0.# - -Get the id of the current span. Example: - -[source,python] ----- -import elasticapm - -span_id = elasticapm.get_span_id() ----- - - -[float] -[[api-set-custom-context]] -==== `elasticapm.set_custom_context()` - -[small]#Added in v2.0.0.# - -Attach custom contextual data to the current transaction and errors. -Supported frameworks will automatically attach information about the HTTP request and the logged in user. -You can attach further data using this function. - -TIP: Before using custom context, ensure you understand the different types of -{apm-guide-ref}/data-model-metadata.html[metadata] that are available. - -Example: - -[source,python] ----- -import elasticapm - -elasticapm.set_custom_context({'billing_amount': product.price * item_count}) ----- - - * `data`: (*required*) A dictionary with the data to be attached. This should be a flat key/value `dict` object. - -NOTE: `.`, `*`, and `"` are invalid characters for key names and will be replaced with `_`. - - -Errors that happen after this call will also have the custom context attached to them. -You can call this function multiple times, new context data will be merged with existing data, -following the `update()` semantics of Python dictionaries. - -[float] -[[api-set-user-context]] -==== `elasticapm.set_user_context()` - -[small]#Added in v2.0.0.# - -Attach information about the currently logged in user to the current transaction and errors. -Example: - -[source,python] ----- -import elasticapm - -elasticapm.set_user_context(username=user.username, email=user.email, user_id=user.id) ----- - - * `username`: The username of the logged in user - * `email`: The email of the logged in user - * `user_id`: The unique identifier of the logged in user, e.g. the primary key value - -Errors that happen after this call will also have the user context attached to them. -You can call this function multiple times, new user data will be merged with existing data, -following the `update()` semantics of Python dictionaries. - - -[float] -[[api-capture-span]] -==== `elasticapm.capture_span` - -[small]#Added in v4.1.0.# - -Capture a custom span. -This can be used either as a function decorator or as a context manager (in a `with` statement). -When used as a decorator, the name of the span will be set to the name of the function. -When used as a context manager, a name has to be provided. - -[source,python] ----- -import elasticapm - -@elasticapm.capture_span() -def coffee_maker(strength): - fetch_water() - - with elasticapm.capture_span('near-to-machine', labels={"type": "arabica"}): - insert_filter() - for i in range(strength): - pour_coffee() - - start_drip() - - fresh_pots() ----- - - * `name`: The name of the span. Defaults to the function name if used as a decorator. - * `span_type`: (*optional*) The type of the span, usually in a dot-separated hierarchy of `type`, `subtype`, and `action`, e.g. `db.mysql.query`. Alternatively, type, subtype and action can be provided as three separate arguments, see `span_subtype` and `span_action`. - * `skip_frames`: (*optional*) The number of stack frames to skip when collecting stack traces. Defaults to `0`. - * `leaf`: (*optional*) if `True`, all spans nested bellow this span will be ignored. Defaults to `False`. - * `labels`: (*optional*) a dictionary of labels. Keys must be strings, values can be strings, booleans, or numerical (`int`, `float`, `decimal.Decimal`). Defaults to `None`. - * `span_subtype`: (*optional*) subtype of the span, e.g. name of the database. Defaults to `None`. - * `span_action`: (*optional*) action of the span, e.g. `query`. Defaults to `None`. - * `links`: (*optional*) A list of `TraceParent` objects to which this span is causally linked. - - -[float] -[[api-async-capture-span]] -==== `elasticapm.async_capture_span` - -[small]#Added in v5.4.0.# - -Capture a custom async-aware span. -This can be used either as a function decorator or as a context manager (in an `async with` statement). -When used as a decorator, the name of the span will be set to the name of the function. -When used as a context manager, a name has to be provided. - -[source,python] ----- -import elasticapm - -@elasticapm.async_capture_span() -async def coffee_maker(strength): - await fetch_water() - - async with elasticapm.async_capture_span('near-to-machine', labels={"type": "arabica"}): - await insert_filter() - async for i in range(strength): - await pour_coffee() - - start_drip() - - fresh_pots() ----- - - * `name`: The name of the span. Defaults to the function name if used as a decorator. - * `span_type`: (*optional*) The type of the span, usually in a dot-separated hierarchy of `type`, `subtype`, and `action`, e.g. `db.mysql.query`. Alternatively, type, subtype and action can be provided as three separate arguments, see `span_subtype` and `span_action`. - * `skip_frames`: (*optional*) The number of stack frames to skip when collecting stack traces. Defaults to `0`. - * `leaf`: (*optional*) if `True`, all spans nested bellow this span will be ignored. Defaults to `False`. - * `labels`: (*optional*) a dictionary of labels. Keys must be strings, values can be strings, booleans, or numerical (`int`, `float`, `decimal.Decimal`). Defaults to `None`. - * `span_subtype`: (*optional*) subtype of the span, e.g. name of the database. Defaults to `None`. - * `span_action`: (*optional*) action of the span, e.g. `query`. Defaults to `None`. - * `links`: (*optional*) A list of `TraceParent` objects to which this span is causally linked. - -NOTE: `asyncio` is only supported for Python 3.7+. - -[float] -[[api-label]] -==== `elasticapm.label()` - -[small]#Added in v5.0.0.# - -Attach labels to the the current transaction and errors. - -TIP: Before using custom labels, ensure you understand the different types of -{apm-guide-ref}/data-model-metadata.html[metadata] that are available. - -Example: - -[source,python] ----- -import elasticapm - -elasticapm.label(ecommerce=True, dollar_value=47.12) ----- - -Errors that happen after this call will also have the labels attached to them. -You can call this function multiple times, new labels will be merged with existing labels, -following the `update()` semantics of Python dictionaries. - -Keys must be strings, values can be strings, booleans, or numerical (`int`, `float`, `decimal.Decimal`) -`.`, `*`, and `"` are invalid characters for label names and will be replaced with `_`. - -WARNING: Avoid defining too many user-specified labels. -Defining too many unique fields in an index is a condition that can lead to a -{ref}/mapping.html#mapping-limit-settings[mapping explosion]. diff --git a/docs/asgi-middleware.asciidoc b/docs/asgi-middleware.asciidoc deleted file mode 100644 index 75607d8bc..000000000 --- a/docs/asgi-middleware.asciidoc +++ /dev/null @@ -1,61 +0,0 @@ -[[asgi-middleware]] -=== ASGI Middleware - -experimental::[] - -Incorporating Elastic APM into your ASGI-based project only requires a few easy -steps. - -NOTE: Several ASGI frameworks are supported natively. -Please check <> for more information - -[float] -[[asgi-installation]] -==== Installation - -Install the Elastic APM agent using pip: - -[source,bash] ----- -$ pip install elastic-apm ----- - -or add `elastic-apm` to your project's `requirements.txt` file. - - -[float] -[[asgi-setup]] -==== Setup - -To set up the agent, you need to initialize it with appropriate settings. - -The settings are configured either via environment variables, or as -initialization arguments. - -You can find a list of all available settings in the -<> page. - -To set up the APM agent, wrap your ASGI app with the `ASGITracingMiddleware`: - -[source,python] ----- -from elasticapm.contrib.asgi import ASGITracingMiddleware - -app = MyGenericASGIApp() # depending on framework - -app = ASGITracingMiddleware(app) - ----- - -Make sure to call <> with an appropriate transaction name in all your routes. - -NOTE: Currently, the agent doesn't support automatic capturing of exceptions. -You can follow progress on this issue on https://github.com/elastic/apm-agent-python/issues/1548[Github]. - -[float] -[[supported-python-versions]] -==== Supported Python versions - -A list of supported <> versions can be found on our <> page. - -NOTE: Elastic APM only supports `asyncio` when using Python 3.7+ diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc deleted file mode 100644 index 112d4ca3e..000000000 --- a/docs/configuration.asciidoc +++ /dev/null @@ -1,1387 +0,0 @@ -[[configuration]] -== Configuration - -To adapt the Elastic APM agent to your needs, configure it using environment variables or framework-specific -configuration. - -You can either configure the agent by setting environment variables: -[source,bash] ----- -ELASTIC_APM_SERVICE_NAME=foo python manage.py runserver ----- - -or with inline configuration: - -[source,python] ----- -apm_client = Client(service_name="foo") ----- - -or by using framework specific configuration e.g. in your Django `settings.py` file: - -[source,python] ----- -ELASTIC_APM = { - "SERVICE_NAME": "foo", -} ----- - -The precedence is as follows: - - * <> -(supported options are marked with <>) - * Environment variables - * Inline configuration - * Framework-specific configuration - * Default value - -[float] -[[dynamic-configuration]] -=== Dynamic configuration - -Configuration options marked with the image:./images/dynamic-config.svg[] badge can be changed at runtime -when set from a supported source. - -The Python Agent supports {apm-app-ref}/agent-configuration.html[Central configuration], -which allows you to fine-tune certain configurations from in the APM app. -This feature is enabled in the Agent by default with <>. - -[float] -[[django-configuration]] -=== Django - -To configure Django, add an `ELASTIC_APM` dictionary to your `settings.py`: - -[source,python] ----- -ELASTIC_APM = { - 'SERVICE_NAME': 'my-app', - 'SECRET_TOKEN': 'changeme', -} ----- - -[float] -[[flask-configuration]] -=== Flask - -To configure Flask, add an `ELASTIC_APM` dictionary to your `app.config`: - -[source,python] ----- -app.config['ELASTIC_APM'] = { - 'SERVICE_NAME': 'my-app', - 'SECRET_TOKEN': 'changeme', -} - -apm = ElasticAPM(app) ----- - -[float] -[[core-options]] -=== Core options - -[float] -[[config-service-name]] -==== `service_name` - -[options="header"] -|============ -| Environment | Django/Flask | Default | Example -| `ELASTIC_APM_SERVICE_NAME` | `SERVICE_NAME` | `unknown-python-service` | `my-app` -|============ - - -The name of your service. -This is used to keep all the errors and transactions of your service together -and is the primary filter in the Elastic APM user interface. - -While a default is provided, it is essential that you override this default -with something more descriptive and unique across your infrastructure. - -NOTE: The service name must conform to this regular expression: `^[a-zA-Z0-9 _-]+$`. -In other words, the service name must only contain characters from the ASCII -alphabet, numbers, dashes, underscores, and spaces. It cannot be an empty string -or whitespace-only. - -[float] -[[config-server-url]] -==== `server_url` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_SERVER_URL` | `SERVER_URL` | `'http://127.0.0.1:8200'` -|============ - -The URL for your APM Server. -The URL must be fully qualified, including protocol (`http` or `https`) and port. -Note: Do not set this if you are using APM in an AWS lambda function. APM Agents are designed to proxy their calls to the APM Server through the lambda extension. Instead, set `ELASTIC_APM_LAMBDA_APM_SERVER`. For more info, see <>. - -[float] -[[config-enabled]] -=== `enabled` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_ENABLED` | `ENABLED` | `true` -|============ - -Enable or disable the agent. -When set to false, the agent will not collect any data or start any background threads. - - -[float] -[[config-recording]] -=== `recording` - -<> - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_RECORDING` | `RECORDING` | `true` -|============ - -Enable or disable recording of events. -If set to false, then the Python agent does not send any events to the Elastic APM server, -and instrumentation overhead is minimized. The agent will continue to poll the server for configuration changes. - - -[float] -[[logging-options]] -=== Logging Options - -[float] -[[config-log_level]] -==== `log_level` - -<> - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_LOG_LEVEL` | `LOG_LEVEL` | -|============ - -The `logging.logLevel` at which the `elasticapm` logger will log. The available -options are: - -* `"off"` (sets `logging.logLevel` to 1000) -* `"critical"` -* `"error"` -* `"warning"` -* `"info"` -* `"debug"` -* `"trace"` (sets `logging.log_level` to 5) - -Options are case-insensitive - -Note that this option doesn't do anything with logging handlers. In order -for any logs to be visible, you must either configure a handler -(https://docs.python.org/3/library/logging.html#logging.basicConfig[`logging.basicConfig`] -will do this for you) or set <>. This will also override -any log level your app has set for the `elasticapm` logger. - -[float] -[[config-log_file]] -==== `log_file` - -[options="header"] -|============ -| Environment | Django/Flask | Default | Example -| `ELASTIC_APM_LOG_FILE` | `LOG_FILE` | `""` | `"/var/log/elasticapm/log.txt"` -|============ - -This enables the agent to log to a file. This is disabled by default. The agent will log -at the `logging.logLevel` configured with <>. Use -<> to configure the maximum size of the log file. This log -file will automatically rotate. - -Note that setting <> is required for this setting to do -anything. - -If https://github.com/elastic/ecs-logging-python[`ecs_logging`] is installed, -the logs will automatically be formatted as ecs-compatible json. - -[float] -[[config-log_file_size]] -==== `log_file_size` - -[options="header"] -|============ -| Environment | Django/Flask | Default | Example -| `ELASTIC_APM_LOG_FILE_SIZE` | `LOG_FILE_SIZE` | `"50mb"` | `"100mb"` -|============ - -The size of the log file if <> is set. - -The agent always keeps one backup file when rotating, so the maximum space that -the log files will consume is twice the value of this setting. - -[float] -[[config-log_ecs_reformatting]] -==== `log_ecs_reformatting` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_LOG_ECS_REFORMATTING` | `LOG_ECS_REFORMATTING` | `"off"` -|============ - -experimental::[] - -Valid options: - -* `"off"` -* `"override"` - -If https://github.com/elastic/ecs-logging-python[`ecs_logging`] is installed, -setting this to `"override"` will cause the agent to automatically attempt to enable -ecs-formatted logging. - -For base `logging` from the standard library, the agent will get the root -logger, find any attached handlers, and for each, set the formatter to -`ecs_logging.StdlibFormatter()`. - -If `structlog` is installed, the agent will override any configured processors -with `ecs_logging.StructlogFormatter()`. - -Note that this is a very blunt instrument that could have unintended side effects. -If problems arise, please apply these formatters manually and leave this setting -as `"off"`. See the -https://www.elastic.co/guide/en/ecs-logging/python/current/installation.html[`ecs_logging` docs] -for more information about using these formatters. - -Also note that this setting does not facilitate shipping logs to Elasticsearch. -We recommend https://www.elastic.co/beats/filebeat[Filebeat] for that purpose. - -[float] -[[other-options]] -=== Other options - -[float] -[[config-transport-class]] -==== `transport_class` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_TRANSPORT_CLASS` | `TRANSPORT_CLASS` | `elasticapm.transport.http.Transport` -|============ - - -The transport class to use when sending events to the APM Server. - -[float] -[[config-service-node-name]] -==== `service_node_name` - -[options="header"] -|============ -| Environment | Django/Flask | Default | Example -| `ELASTIC_APM_SERVICE_NODE_NAME` | `SERVICE_NODE_NAME` | `None` | `"redis1"` -|============ - -The name of the given service node. This is optional and if omitted, the APM -Server will fall back on `system.container.id` if available, and -`host.name` if necessary. - -This option allows you to set the node name manually to ensure it is unique and meaningful. - -[float] -[[config-environment]] -==== `environment` - -[options="header"] -|============ -| Environment | Django/Flask | Default | Example -| `ELASTIC_APM_ENVIRONMENT` | `ENVIRONMENT` | `None` | `"production"` -|============ - -The name of the environment this service is deployed in, -e.g. "production" or "staging". - -Environments allow you to easily filter data on a global level in the APM app. -It's important to be consistent when naming environments across agents. -See {apm-app-ref}/filters.html#environment-selector[environment selector] in the APM app for more information. - -NOTE: This feature is fully supported in the APM app in Kibana versions >= 7.2. -You must use the query bar to filter for a specific environment in versions prior to 7.2. - -[float] -[[config-cloud-provider]] -==== `cloud_provider` - -[options="header"] -|============ -| Environment | Django/Flask | Default | Example -| `ELASTIC_APM_CLOUD_PROVIDER` | `CLOUD_PROVIDER` | `"auto"` | `"aws"` -|============ - -This config value allows you to specify which cloud provider should be assumed -for metadata collection. By default, the agent will attempt to detect the cloud -provider or, if that fails, will use trial and error to collect the metadata. - -Valid options are `"auto"`, `"aws"`, `"gcp"`, and `"azure"`. If this config value is set -to `"none"`, then no cloud metadata will be collected. - -[float] -[[config-secret-token]] -==== `secret_token` - -[options="header"] -|============ -| Environment | Django/Flask | Default | Example -| `ELASTIC_APM_SECRET_TOKEN` | `SECRET_TOKEN` | `None` | A random string -|============ - -This string is used to ensure that only your agents can send data to your APM Server. -Both the agents and the APM Server have to be configured with the same secret token. -An example to generate a secure secret token is: - -[source,bash] ----- -python -c "import secrets; print(secrets.token_urlsafe(32))" ----- - -WARNING: Secret tokens only provide any security if your APM Server uses TLS. - -[float] -[[config-api-key]] -==== `api_key` - -[options="header"] -|============ -| Environment | Django/Flask | Default | Example -| `ELASTIC_APM_API_KEY` | `API_KEY` | `None` | A base64-encoded string -|============ - -experimental::[] - -// TODO: add link to APM Server API Key docs once the docs are released - -This base64-encoded string is used to ensure that only your agents can send data to your APM Server. -The API key must be created using the {apm-guide-ref}/api-key.html[APM server command-line tool]. - -WARNING: API keys only provide any real security if your APM Server uses TLS. - -[float] -[[config-service-version]] -==== `service_version` -[options="header"] -|============ -| Environment | Django/Flask | Default | Example -| `ELASTIC_APM_SERVICE_VERSION` | `SERVICE_VERSION` | `None` | A string indicating the version of the deployed service -|============ - -A version string for the currently deployed version of the service. -If youre deploys are not versioned, the recommended value for this field is the commit identifier of the deployed revision, e.g. the output of `git rev-parse HEAD`. - -[float] -[[config-framework-name]] -==== `framework_name` -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_FRAMEWORK_NAME` | `FRAMEWORK_NAME` | Depending on framework -|============ - -The name of the used framework. -For Django and Flask, this defaults to `django` and `flask` respectively, -otherwise, the default is `None`. - - -[float] -[[config-framework-version]] -==== `framework_version` -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_FRAMEWORK_VERSION` | `FRAMEWORK_VERSION` | Depending on framework -|============ - -The version number of the used framework. -For Django and Flask, this defaults to the used version of the framework, -otherwise, the default is `None`. - -[float] -[[config-filter-exception-types]] -==== `filter_exception_types` -[options="header"] -|============ -| Environment | Django/Flask | Default | Example -| `ELASTIC_APM_FILTER_EXCEPTION_TYPES` | `FILTER_EXCEPTION_TYPES` | `[]` | `['OperationalError', 'mymodule.SomeoneElsesProblemError']` -| multiple values separated by commas, without spaces ||| -|============ - -A list of exception types to be filtered. -Exceptions of these types will not be sent to the APM Server. - - -[float] -[[config-transaction-ignore-urls]] -==== `transaction_ignore_urls` - -<> - -[options="header"] -|============ -| Environment | Django/Flask | Default | Example -| `ELASTIC_APM_TRANSACTION_IGNORE_URLS` | `TRANSACTION_IGNORE_URLS` | `[]` | `['/api/ping', '/static/*']` -| multiple values separated by commas, without spaces ||| -|============ - -A list of URLs for which the agent should not capture any transaction data. - -Optionally, `*` can be used to match multiple URLs at once. - -[float] -[[config-transactions-ignore-patterns]] -==== `transactions_ignore_patterns` -[options="header"] -|============ -| Environment | Django/Flask | Default | Example -| `ELASTIC_APM_TRANSACTIONS_IGNORE_PATTERNS` | `TRANSACTIONS_IGNORE_PATTERNS` | `[]` | `['^OPTIONS ', 'myviews.Healthcheck']` -| multiple values separated by commas, without spaces ||| -|============ - -A list of regular expressions. -Transactions with a name that matches any of the configured patterns will be ignored and not sent to the APM Server. - -NOTE: as the the name of the transaction can only be determined at the end of the transaction, -the agent might still cause overhead for transactions ignored through this setting. -If agent overhead is a concern, we recommend <> instead. - -[float] -[[config-server-timeout]] -==== `server_timeout` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_SERVER_TIMEOUT` | `SERVER_TIMEOUT` | `"5s"` -|============ - -A timeout for requests to the APM Server. -The setting has to be provided in *<>*. -If a request to the APM Server takes longer than the configured timeout, -the request is cancelled and the event (exception or transaction) is discarded. -Set to `None` to disable timeouts. - -WARNING: If timeouts are disabled or set to a high value, -your app could experience memory issues if the APM Server times out. - - -[float] -[[config-hostname]] -==== `hostname` - -[options="header"] -|============ -| Environment | Django/Flask | Default | Example -| `ELASTIC_APM_HOSTNAME` | `HOSTNAME` | `socket.gethostname()` | `app-server01.example.com` -|============ - -The host name to use when sending error and transaction data to the APM Server. - -[float] -[[config-auto-log-stacks]] -==== `auto_log_stacks` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_AUTO_LOG_STACKS` | `AUTO_LOG_STACKS` | `True` -| set to `"true"` / `"false"` ||| -|============ - -If set to `True` (the default), the agent will add a stack trace to each log event, -indicating where the log message has been issued. - -This setting can be overridden on an individual basis by setting the `extra`-key `stack`: - -[source,python] ----- -logger.info('something happened', extra={'stack': False}) ----- - -[float] -[[config-collect-local-variables]] -==== `collect_local_variables` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_COLLECT_LOCAL_VARIABLES` | `COLLECT_LOCAL_VARIABLES` | `errors` -|============ - -Possible values: `errors`, `transactions`, `all`, `off` - -The Elastic APM Python agent can collect local variables for stack frames. -By default, this is only done for errors. - -NOTE: Collecting local variables has a non-trivial overhead. -Collecting local variables for transactions in production environments -can have adverse effects for the performance of your service. - -[float] -[[config-local-var-max-length]] -==== `local_var_max_length` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_LOCAL_VAR_MAX_LENGTH` | `LOCAL_VAR_MAX_LENGTH` | `200` -|============ - -When collecting local variables, they will be converted to strings. -This setting allows you to limit the length of the resulting string. - - -[float] -[[config-local-list-var-max-length]] -==== `local_var_list_max_length` - -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_LOCAL_VAR_LIST_MAX_LENGTH` | `LOCAL_VAR_LIST_MAX_LENGTH` | `10` -|============ - -This setting allows you to limit the length of lists in local variables. - - -[float] -[[config-local-dict-var-max-length]] -==== `local_var_dict_max_length` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_LOCAL_VAR_DICT_MAX_LENGTH` | `LOCAL_VAR_DICT_MAX_LENGTH` | `10` -|============ - -This setting allows you to limit the length of dicts in local variables. - - -[float] -[[config-source-lines-error-app-frames]] -==== `source_lines_error_app_frames` -[float] -[[config-source-lines-error-library-frames]] -==== `source_lines_error_library_frames` -[float] -[[config-source-lines-span-app-frames]] -==== `source_lines_span_app_frames` -[float] -[[config-source-lines-span-library-frames]] -==== `source_lines_span_library_frames` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_SOURCE_LINES_ERROR_APP_FRAMES` | `SOURCE_LINES_ERROR_APP_FRAMES` | `5` -| `ELASTIC_APM_SOURCE_LINES_ERROR_LIBRARY_FRAMES` | `SOURCE_LINES_ERROR_LIBRARY_FRAMES` | `5` -| `ELASTIC_APM_SOURCE_LINES_SPAN_APP_FRAMES` | `SOURCE_LINES_SPAN_APP_FRAMES` | `0` -| `ELASTIC_APM_SOURCE_LINES_SPAN_LIBRARY_FRAMES` | `SOURCE_LINES_SPAN_LIBRARY_FRAMES` | `0` -|============ - -By default, the APM agent collects source code snippets for errors. -This setting allows you to modify the number of lines of source code that are being collected. - -We differ between errors and spans, as well as library frames and app frames. - -WARNING: Especially for spans, collecting source code can have a large impact on storage use in your Elasticsearch cluster. - -[float] -[[config-capture-body]] -==== `capture_body` - -<> - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_CAPTURE_BODY` | `CAPTURE_BODY` | `off` -|============ - -For transactions that are HTTP requests, -the Python agent can optionally capture the request body (e.g. `POST` variables). - -Possible values: `errors`, `transactions`, `all`, `off`. - -If the request has a body and this setting is disabled, the body will be shown as `[REDACTED]`. - -For requests with a content type of `multipart/form-data`, -any uploaded files will be referenced in a special `_files` key. -It contains the name of the field and the name of the uploaded file, if provided. - -WARNING: Request bodies often contain sensitive values like passwords and credit card numbers. -If your service handles data like this, we advise to only enable this feature with care. - -[float] -[[config-capture-headers]] -==== `capture_headers` - -<> - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_CAPTURE_HEADERS` | `CAPTURE_HEADERS` | `true` -|============ - -For transactions and errors that happen due to HTTP requests, -the Python agent can optionally capture the request and response headers. - -Possible values: `true`, `false` - -WARNING: Request headers often contain sensitive values like session IDs and cookies. -See <> for more information on how to filter out sensitive data. - -[float] -[[config-transaction-max-spans]] -==== `transaction_max_spans` - -<> - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_TRANSACTION_MAX_SPANS` | `TRANSACTION_MAX_SPANS` | `500` -|============ - -This limits the amount of spans that are recorded per transaction. -This is helpful in cases where a transaction creates a very high amount of spans (e.g. thousands of SQL queries). -Setting an upper limit will prevent edge cases from overloading the agent and the APM Server. - -[float] -[[config-stack-trace-limit]] -==== `stack_trace_limit` - -<> - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_STACK_TRACE_LIMIT` | `STACK_TRACE_LIMIT` | `50` -|============ - -This limits the number of frames captured for each stack trace. - -Setting the limit to `0` will disable stack trace collection, -while any positive integer value will be used as the maximum number of frames to collect. -To disable the limit and always capture all frames, set the value to `-1`. - - -[float] -[[config-span-stack-trace-min-duration]] -==== `span_stack_trace_min_duration` - -<> - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_SPAN_STACK_TRACE_MIN_DURATION` | `SPAN_STACK_TRACE_MIN_DURATION` | `"5ms"` -|============ - -By default, the APM agent collects a stack trace with every recorded span -that has a duration equal to or longer than this configured threshold. While -stack traces are very helpful to find the exact place in your code from which a -span originates, collecting this stack trace does have some overhead. Tune this -threshold to ensure that you only collect stack traces for spans that -could be problematic. - -To collect traces for all spans, regardless of their length, set the value to `0`. - -To disable stack trace collection for spans completely, set the value to `-1`. - -Except for the special values `-1` and `0`, -this setting should be provided in *<>*. - - -[float] -[[config-span-frames-min-duration]] -==== `span_frames_min_duration` - -<> - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_SPAN_FRAMES_MIN_DURATION` | `SPAN_FRAMES_MIN_DURATION` | `"5ms"` -|============ - -NOTE: This config value is being deprecated. Use -<> instead. - - -[float] -[[config-span-compression-enabled]] -==== `span_compression_enabled` - -<> - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_SPAN_COMPRESSION_ENABLED` | `SPAN_COMPRESSION_ENABLED` | `True` -|============ - -Enable/disable span compression. - -If enabled, the agent will compress very short, repeated spans into a single span, -which is beneficial for storage and processing requirements. -Some information is lost in this process, e.g. exact durations of each compressed span. - -[float] -[[config-span-compression-exact-match-max_duration]] -==== `span_compression_exact_match_max_duration` - -<> - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_SPAN_COMPRESSION_EXACT_MATCH_MAX_DURATION` | `SPAN_COMPRESSION_EXACT_MATCH_MAX_DURATION` | `"50ms"` -|============ - -Consecutive spans that are exact match and that are under this threshold will be compressed into a single composite span. -This reduces the collection, processing, and storage overhead, and removes clutter from the UI. -The tradeoff is that the DB statements of all the compressed spans will not be collected. - -Two spans are considered exact matches if the following attributes are identical: - * span name - * span type - * span subtype - * destination resource (e.g. the Database name) - -[float] -[[config-span-compression-same-kind-max-duration]] -==== `span_compression_same_kind_max_duration` - -<> - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_SPAN_COMPRESSION_SAME_KIND_MAX_DURATION` | `SPAN_COMPRESSION_SAME_KIND_MAX_DURATION` | `"0ms"` (disabled) -|============ - -Consecutive spans to the same destination that are under this threshold will be compressed into a single composite span. -This reduces the collection, processing, and storage overhead, and removes clutter from the UI. -The tradeoff is that metadata such as database statements of all the compressed spans will not be collected. - -Two spans are considered to be of the same kind if the following attributes are identical: - * span type - * span subtype - * destination resource (e.g. the Database name) - -[float] -[[config-exit-span-min-duration]] -==== `exit_span_min_duration` - -<> - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_EXIT_SPAN_MIN_DURATION` | `EXIT_SPAN_MIN_DURATION` | `"0ms"` -|============ - -Exit spans are spans that represent a call to an external service, like a database. -If such calls are very short, they are usually not relevant and can be ignored. - -This feature is disabled by default. - -NOTE: if a span propagates distributed tracing IDs, it will not be ignored, even if it is shorter than the configured threshold. -This is to ensure that no broken traces are recorded. - -[float] -[[config-api-request-size]] -==== `api_request_size` - -<> - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_API_REQUEST_SIZE` | `API_REQUEST_SIZE` | `"768kb"` -|============ - -The maximum queue length of the request buffer before sending the request to the APM Server. -A lower value will increase the load on your APM Server, -while a higher value can increase the memory pressure of your app. -A higher value also impacts the time until data is indexed and searchable in Elasticsearch. - -This setting is useful to limit memory consumption if you experience a sudden spike of traffic. -It has to be provided in *<>*. - -NOTE: Due to internal buffering of gzip, the actual request size can be a few kilobytes larger than the given limit. -By default, the APM Server limits request payload size to `1 MByte`. - -[float] -[[config-api-request-time]] -==== `api_request_time` - -<> - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_API_REQUEST_TIME` | `API_REQUEST_TIME` | `"10s"` -|============ - -The maximum queue time of the request buffer before sending the request to the APM Server. -A lower value will increase the load on your APM Server, -while a higher value can increase the memory pressure of your app. -A higher value also impacts the time until data is indexed and searchable in Elasticsearch. - -This setting is useful to limit memory consumption if you experience a sudden spike of traffic. -It has to be provided in *<>*. - -NOTE: The actual time will vary between 90-110% of the given value, -to avoid stampedes of instances that start at the same time. - -[float] -[[config-processors]] -==== `processors` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_PROCESSORS` | `PROCESSORS` | `['elasticapm.processors.sanitize_stacktrace_locals', - 'elasticapm.processors.sanitize_http_request_cookies', - 'elasticapm.processors.sanitize_http_headers', - 'elasticapm.processors.sanitize_http_wsgi_env', - 'elasticapm.processors.sanitize_http_request_body']` -|============ - -A list of processors to process transactions and errors. -For more information, see <>. - -WARNING: We recommend always including the default set of validators if you customize this setting. - -[float] -[[config-sanitize-field-names]] -==== `sanitize_field_names` - -<> - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_SANITIZE_FIELD_NAMES` | `SANITIZE_FIELD_NAMES` | `["password", - "passwd", - "pwd", - "secret", - "*key", - "*token*", - "*session*", - "*credit*", - "*card*", - "*auth*", - "*principal*", - "set-cookie"]` -|============ - -A list of glob-matched field names to match and mask when using processors. -For more information, see <>. - -WARNING: We recommend always including the default set of field name matches -if you customize this setting. - - -[float] -[[config-transaction-sample-rate]] -==== `transaction_sample_rate` - -<> - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_TRANSACTION_SAMPLE_RATE` | `TRANSACTION_SAMPLE_RATE` | `1.0` -|============ - -By default, the agent samples every transaction (e.g. request to your service). -To reduce overhead and storage requirements, set the sample rate to a value between `0.0` and `1.0`. -We still record overall time and the result for unsampled transactions, but no context information, labels, or spans. - -NOTE: This setting will be automatically rounded to 4 decimals of precision. - -[float] -[[config-include-paths]] -==== `include_paths` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_INCLUDE_PATHS` | `INCLUDE_PATHS` | `[]` -| multiple values separated by commas, without spaces ||| -|============ - -A set of paths, optionally using shell globs -(see https://docs.python.org/3/library/fnmatch.html[`fnmatch`] for a description of the syntax). -These are matched against the absolute filename of every frame, and if a pattern matches, the frame is considered -to be an "in-app frame". - -`include_paths` *takes precedence* over `exclude_paths`. - -[float] -[[config-exclude-paths]] -==== `exclude_paths` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_EXCLUDE_PATHS` | `EXCLUDE_PATHS` | Varies on Python version and implementation -| multiple values separated by commas, without spaces ||| -|============ - -A set of paths, optionally using shell globs -(see https://docs.python.org/3/library/fnmatch.html[`fnmatch`] for a description of the syntax). -These are matched against the absolute filename of every frame, and if a pattern matches, the frame is considered -to be a "library frame". - -`include_paths` *takes precedence* over `exclude_paths`. - -The default value varies based on your Python version and implementation, e.g.: - - * PyPy3: `['\*/lib-python/3/*', '\*/site-packages/*']` - * CPython 2.7: `['\*/lib/python2.7/*', '\*/lib64/python2.7/*']` - -[float] -[[config-debug]] -==== `debug` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_DEBUG` | `DEBUG` | `False` -|============ - -If your app is in debug mode (e.g. in Django with `settings.DEBUG = True` or in Flask with `app.debug = True`), -the agent won't send any data to the APM Server. You can override it by changing this setting to `True`. - - -[float] -[[config-disable-send]] -==== `disable_send` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_DISABLE_SEND` | `DISABLE_SEND` | `False` -|============ - -If set to `True`, the agent won't send any events to the APM Server, independent of any debug state. - - -[float] -[[config-instrument]] -==== `instrument` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_INSTRUMENT` | `INSTRUMENT` | `True` -|============ - -If set to `False`, the agent won't instrument any code. -This disables most of the tracing functionality, but can be useful to debug possible instrumentation issues. - - -[float] -[[config-verify-server-cert]] -==== `verify_server_cert` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_VERIFY_SERVER_CERT` | `VERIFY_SERVER_CERT` | `True` -|============ - -By default, the agent verifies the SSL certificate if an HTTPS connection to the APM Server is used. -Verification can be disabled by changing this setting to `False`. -This setting is ignored when <> is set. - -[float] -[[config-server-cert]] -==== `server_cert` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_SERVER_CERT` | `SERVER_CERT` | `None` -|============ - -If you have configured your APM Server with a self-signed TLS certificate, or you -just wish to pin the server certificate, you can specify the path to the PEM-encoded -certificate via the `ELASTIC_APM_SERVER_CERT` configuration. - -NOTE: If this option is set, the agent only verifies that the certificate provided by the APM Server is -identical to the one configured here. Validity of the certificate is not checked. - -[float] -[[config-server-ca-cert-file]] -==== `server_ca_cert_file` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_SERVER_CA_CERT_FILE` | `SERVER_CA_CERT_FILE` | `None` -|============ - -By default, the agent will validate the TLS/SSL certificate of the APM Server using the well-known CAs curated by Mozilla, -and provided by the https://pypi.org/project/certifi/[`certifi`] package. - -You can set this option to the path of a file containing a CA certificate that will be used instead. - -Specifying this option is required when using self-signed certificates, unless server certificate validation is disabled. - -[float] -[[config-use-certifi]] -==== `use_certifi` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_USE_CERTIFI` | `USE_CERTIFI` | `True` -|============ - -By default, the Python Agent uses the https://pypi.org/project/certifi/[`certifi`] certificate store. -To use Python's default mechanism for finding certificates, set this option to `False`. - -[float] -[[config-metrics_interval]] -==== `metrics_interval` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_METRICS_INTERVAL` | `METRICS_INTERVAL` | `30s` -|============ - - -The interval in which the agent collects metrics. A shorter interval increases the granularity of metrics, -but also increases the overhead of the agent, as well as storage requirements. - -It has to be provided in *<>*. - -[float] -[[config-disable_metrics]] -==== `disable_metrics` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_DISABLE_METRICS` | `DISABLE_METRICS` | `None` -|============ - - -A comma-separated list of dotted metrics names that should not be sent to the APM Server. -You can use `*` to match multiple metrics; for example, to disable all CPU-related metrics, -as well as the "total system memory" metric, set `disable_metrics` to: - -.... -"*.cpu.*,system.memory.total" -.... - -NOTE: This setting only disables the *sending* of the given metrics, not collection. - -[float] -[[config-breakdown_metrics]] -==== `breakdown_metrics` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_BREAKDOWN_METRICS` | `BREAKDOWN_METRICS` | `True` -|============ - -Enable or disable the tracking and collection of breakdown metrics. -Setting this to `False` disables the tracking of breakdown metrics, which can reduce the overhead of the agent. - -NOTE: This feature requires APM Server and Kibana >= 7.3. - -[float] -[[config-prometheus_metrics]] -==== `prometheus_metrics` (Beta) - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_PROMETHEUS_METRICS` | `PROMETHEUS_METRICS` | `False` -|============ - -Enable/disable the tracking and collection of metrics from `prometheus_client`. - -See <> for more information. - -NOTE: This feature is currently in beta status. - -[float] -[[config-prometheus_metrics_prefix]] -==== `prometheus_metrics_prefix` (Beta) - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_PROMETHEUS_METRICS_PREFIX` | `PROMETHEUS_METRICS_PREFIX` | `prometheus.metrics.` -|============ - -A prefix to prepend to Prometheus metrics names. - -See <> for more information. - -NOTE: This feature is currently in beta status. - -[float] -[[config-metrics_sets]] -==== `metrics_sets` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_METRICS_SETS` | `METRICS_SETS` | ["elasticapm.metrics.sets.cpu.CPUMetricSet"] -|============ - -List of import paths for the MetricSets that should be used to collect metrics. - -See <> for more information. - -[float] -[[config-central_config]] -==== `central_config` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_CENTRAL_CONFIG` | `CENTRAL_CONFIG` | `True` -|============ - -When enabled, the agent will make periodic requests to the APM Server to fetch updated configuration. - -See <> for more information. - -NOTE: This feature requires APM Server and Kibana >= 7.3. - -[float] -[[config-global_labels]] -==== `global_labels` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_GLOBAL_LABELS` | `GLOBAL_LABELS` | `None` -|============ - -Labels added to all events, with the format `key=value[,key=value[,...]]`. -Any labels set by application via the API will override global labels with the same keys. - -NOTE: This feature requires APM Server >= 7.2. - -[float] -[[config-generic-disable-log-record-factory]] -==== `disable_log_record_factory` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_DISABLE_LOG_RECORD_FACTORY` | `DISABLE_LOG_RECORD_FACTORY` | `False` -|============ - -By default in python 3, the agent installs a <> that -automatically adds tracing fields to your log records. Disable this -behavior by setting this to `True`. - -[float] -[[config-use-elastic-traceparent-header]] -==== `use_elastic_traceparent_header` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_USE_ELASTIC_TRACEPARENT_HEADER` | `USE_ELASTIC_TRACEPARENT_HEADER` | `True` -|============ - -To enable {apm-guide-ref}/apm-distributed-tracing.html[distributed tracing], -the agent sets a number of HTTP headers to outgoing requests made with <>. -These headers (`traceparent` and `tracestate`) are defined in the https://www.w3.org/TR/trace-context-1/[W3C Trace Context] specification. - -Additionally, when this setting is set to `True`, the agent will set `elasticapm-traceparent` for backwards compatibility. - -[float] -[[config-trace-continuation-strategy]] -==== `trace_continuation_strategy` - -<> - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_TRACE_CONTINUATION_STRATEGY` | `TRACE_CONTINUATION_STRATEGY` | `continue` -|============ - -This option allows some control on how the APM agent handles W3C trace-context headers on incoming requests. -By default, the `traceparent` and `tracestate` headers are used per W3C spec for distributed tracing. -However, in certain cases it can be helpful to *not* use the incoming `traceparent` header. -Some example use cases: - -- An Elastic-monitored service is receiving requests with `traceparent` headers from *unmonitored* services. -- An Elastic-monitored service is publicly exposed, and does not want tracing data (trace-ids, sampling decisions) to possibly be spoofed by user requests. - -Valid values are: - -- `'continue'`: The default behavior. An incoming `traceparent` value is used to continue the trace and determine the sampling decision. -- `'restart'`: Always ignores the `traceparent` header of incoming requests. - A new trace-id will be generated and the sampling decision will be made based on <>. - A *span link* will be made to the incoming traceparent. -- `'restart_external'`: If an incoming request includes the `es` vendor flag in `tracestate`, then any 'traceparent' will be considered internal and will be handled as described for `'continue'` above. - Otherwise, any `'traceparent'` is considered external and will be handled as described for `'restart'` above. - -Starting with Elastic Observability 8.2, span links will be visible in trace -views. - -[float] -[[config-use-elastic-excepthook]] -==== `use_elastic_excepthook` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_USE_ELASTIC_EXCEPTHOOK` | `USE_ELASTIC_EXCEPTHOOK` | `False` -|============ - -If set to `True`, the agent will intercept the default `sys.excepthook`, which -allows the agent to collect all uncaught exceptions. - -[float] -[[config-include-process-args]] -==== `include_process_args` - -[options="header"] -|============ -| Environment | Django/Flask | Default -| `ELASTIC_APM_INCLUDE_PROCESS_ARGS` | `INCLUDE_PROCESS_ARGS` | `False` -|============ - -Whether each transaction should have the process arguments attached. Disabled by default to save disk space. - -[float] -[[config-django-specific]] -=== Django-specific configuration - -[float] -[[config-django-transaction-name-from-route]] -==== `django_transaction_name_from_route` - -[options="header"] -|============ -| Environment | Django | Default -| `ELASTIC_APM_DJANGO_TRANSACTION_NAME_FROM_ROUTE` | `DJANGO_TRANSACTION_NAME_FROM_ROUTE` | `False` -|============ - - -By default, we use the function or class name of the view as the transaction name. -Starting with Django 2.2, Django makes the route (e.g. `users//`) available on the `request.resolver_match` object. -If you want to use the route instead of the view name as the transaction name, set this config option to `true`. - -NOTE: in versions previous to Django 2.2, changing this setting will have no effect. - -[float] -[[config-django-autoinsert-middleware]] -==== `django_autoinsert_middleware` - -[options="header"] -|============ -| Environment | Django | Default -| `ELASTIC_APM_DJANGO_AUTOINSERT_MIDDLEWARE` | `DJANGO_AUTOINSERT_MIDDLEWARE` | `True` -|============ - -To trace Django requests, the agent uses a middleware, `elasticapm.contrib.django.middleware.TracingMiddleware`. -By default, this middleware is inserted automatically as the first item in `settings.MIDDLEWARES`. -To disable the automatic insertion of the middleware, change this setting to `False`. - - -[float] -[[config-generic-environment]] -=== Generic Environment variables - -Some environment variables that are not specific to the APM agent can be used to configure the agent. - -[float] -[[config-generic-http-proxy]] -==== `HTTP_PROXY` and `HTTPS_PROXY` - -By using `HTTP_PROXY` and `HTTPS_PROXY`, the agent can be instructed to use a proxy to connect to the APM Server. -If both are set, `HTTPS_PROXY` takes precedence. - -NOTE: The environment variables are case-insensitive. - -[float] -[[config-generic-no-proxy]] -==== `NO_PROXY` - -To instruct the agent to *not* use a proxy, you can use the `NO_PROXY` environment variable. -You can either set it to a comma-separated list of hosts for which no proxy should be used (e.g. `localhost,example.com`) -or use `*` to match any host. - -This is useful if `HTTP_PROXY` / `HTTPS_PROXY` is set for other reasons than agent / APM Server communication. - - -[float] -[[config-ssl-cert-file]] -==== `SSL_CERT_FILE` and `SSL_CERT_DIR` - -To tell the agent to use a different SSL certificate, you can use these environment variables. -See also https://www.openssl.org/docs/manmaster/man7/openssl-env.html#SSL_CERT_DIR-SSL_CERT_FILE[OpenSSL docs]. - -Please note that these variables may apply to other SSL/TLS communication in your service, -not just related to the APM agent. - -NOTE: These environment variables only take effect if <> is set to `False`. - -[float] -[[config-formats]] -=== Configuration formats - -Some options require a unit, either duration or size. -These need to be provided in a specific format. - -[float] -[[config-format-duration]] -==== Duration format - -The _duration_ format is used for options like timeouts. -The unit is provided as a suffix directly after the number–without any separation by whitespace. - -*Example*: `5ms` - -*Supported units* - - * `us` (microseconds) - * `ms` (milliseconds) - * `s` (seconds) - * `m` (minutes) - -[float] -[[config-format-size]] -==== Size format - -The _size_ format is used for options like maximum buffer sizes. -The unit is provided as suffix directly after the number, without and separation by whitespace. - - -*Example*: `10kb` - -*Supported units*: - - * `b` (bytes) - * `kb` (kilobytes) - * `mb` (megabytes) - * `gb` (gigabytes) - -NOTE: We use the power-of-two sizing convention, e.g. `1 kilobyte == 1024 bytes` diff --git a/docs/custom-instrumentation.asciidoc b/docs/custom-instrumentation.asciidoc deleted file mode 100644 index 1db067f72..000000000 --- a/docs/custom-instrumentation.asciidoc +++ /dev/null @@ -1,143 +0,0 @@ -[[instrumenting-custom-code]] -=== Instrumenting custom code - -[float] -[[instrumenting-custom-code-spans]] -==== Creating Additional Spans in a Transaction - -Elastic APM instruments a variety of libraries out of the box, but sometimes you -need to know how long a specific function took or how often it gets -called. - -Assuming you're using one of our <>, you can -apply the `@elasticapm.capture_span()` decorator to achieve exactly that. If -you're not using a supported framework, see -<>. - -`elasticapm.capture_span` can be used either as a decorator or as a context -manager. The following example uses it both ways: - -[source,python] ----- -import elasticapm - -@elasticapm.capture_span() -def coffee_maker(strength): - fetch_water() - - with elasticapm.capture_span('near-to-machine'): - insert_filter() - for i in range(strength): - pour_coffee() - - start_drip() - - fresh_pots() ----- - -Similarly, you can use `elasticapm.async_capture_span` for instrumenting `async` workloads: - -[source,python] ----- -import elasticapm - -@elasticapm.async_capture_span() -async def coffee_maker(strength): - await fetch_water() - - async with elasticapm.async_capture_span('near-to-machine'): - await insert_filter() - async for i in range(strength): - await pour_coffee() - - start_drip() - - fresh_pots() ----- - -NOTE: `asyncio` support is only available in Python 3.7+. - -See <> for more information on `capture_span`. - -[float] -[[instrumenting-custom-code-transactions]] -==== Creating New Transactions - -It's important to note that `elasticapm.capture_span` only works if there is -an existing transaction. If you're not using one of our <>, you need to create a `Client` object and begin and end the -transactions yourself. You can even utilize the agent's -<>! - -To collect the spans generated by the supported libraries, you need -to invoke `elasticapm.instrument()` (just once, at the initialization stage of -your application) and create at least one transaction. It is up to you to -determine what you consider a transaction within your application -- it can -be the whole execution of the script or a part of it. - -The example below will consider the whole execution as a single transaction -with two HTTP request spans in it. The config for `elasticapm.Client` can be -passed in programmatically, and it will also utilize any config environment -variables available to it automatically. - -[source,python] ----- -import requests -import time -import elasticapm - -def main(): - sess = requests.Session() - for url in [ 'https://www.elastic.co', 'https://benchmarks.elastic.co' ]: - resp = sess.get(url) - time.sleep(1) - -if __name__ == '__main__': - client = elasticapm.Client(service_name="foo", server_url="https://example.com:8200") - elasticapm.instrument() # Only call this once, as early as possible. - client.begin_transaction(transaction_type="script") - main() - client.end_transaction(name=__name__, result="success") ----- - -Note that you don't need to do anything to send the data -- the `Client` object -will handle that before the script exits. Additionally, the `Client` object should -be treated as a singleton -- you should only create one instance and store/pass -around that instance for all transaction handling. - -[float] -[[instrumenting-custom-code-distributed-tracing]] -==== Distributed Tracing - -When instrumenting custom code across multiple services, you should propagate -the TraceParent where possible. This allows Elastic APM to bundle the various -transactions into a single distributed trace. The Python Agent will -automatically add TraceParent information to the headers of outgoing HTTP -requests, which can then be used on the receiving end to add that TraceParent -information to new manually-created transactions. - -Additionally, the Python Agent provides utilities for propagating the -TraceParent in string format. - -[source,python] ----- -import elasticapm - -client = elasticapm.Client(service_name="foo", server_url="https://example.com:8200") - -# Retrieve the current TraceParent as a string, requires active transaction -traceparent_string = elasticapm.get_trace_parent_header() - -# Create a TraceParent object from a string and use it for a new transaction -parent = elasticapm.trace_parent_from_string(traceparent_string) -client.begin_transaction(transaction_type="script", trace_parent=parent) -# Do some work -client.end_transaction(name=__name__, result="success") - -# Create a TraceParent object from a dictionary of headers, provided -# automatically by the sending service if it is using an Elastic APM Agent. -parent = elasticapm.trace_parent_from_headers(headers_dict) -client.begin_transaction(transaction_type="script", trace_parent=parent) -# Do some work -client.end_transaction(name=__name__, result="success") ----- diff --git a/docs/django.asciidoc b/docs/django.asciidoc deleted file mode 100644 index 1aa8396f6..000000000 --- a/docs/django.asciidoc +++ /dev/null @@ -1,375 +0,0 @@ -[[django-support]] -=== Django support - -Getting Elastic APM set up for your Django project is easy, and there are various ways you can tweak it to fit to your needs. - -[float] -[[django-installation]] -==== Installation - -Install the Elastic APM agent using pip: - -[source,bash] ----- -$ pip install elastic-apm ----- - -or add it to your project's `requirements.txt` file. - -NOTE: For apm-server 6.2+, make sure you use version 2.0 or higher of `elastic-apm`. - - -NOTE: If you use Django with uwsgi, make sure to -http://uwsgi-docs.readthedocs.org/en/latest/Options.html#enable-threads[enable -threads]. - -[float] -[[django-setup]] -==== Setup - -Set up the Elastic APM agent in Django with these two steps: - -1. Add `elasticapm.contrib.django` to `INSTALLED_APPS` in your settings: - -[source,python] ----- -INSTALLED_APPS = ( - # ... - 'elasticapm.contrib.django', -) ----- - -1. Choose a service name, and set the secret token if needed. - -[source,python] ----- -ELASTIC_APM = { - 'SERVICE_NAME': '', - 'SECRET_TOKEN': '', -} ----- - -or as environment variables: - -[source,shell] ----- -ELASTIC_APM_SERVICE_NAME= -ELASTIC_APM_SECRET_TOKEN= ----- - -You now have basic error logging set up, and everything resulting in a 500 HTTP status code will be reported to the APM Server. - -You can find a list of all available settings in the <> page. - -[NOTE] -==== -The agent only captures and sends data if you have `DEBUG = False` in your settings. -To force the agent to capture data in Django debug mode, set the <> configuration option, e.g.: - -[source,python] ----- -ELASTIC_APM = { - 'SERVICE_NAME': '', - 'DEBUG': True, -} ----- -==== - -[float] -[[django-performance-metrics]] -==== Performance metrics - -In order to collect performance metrics, -the agent automatically inserts a middleware at the top of your middleware list -(`settings.MIDDLEWARE` in current versions of Django, `settings.MIDDLEWARE_CLASSES` in some older versions). -To disable the automatic insertion of the middleware, -see <>. - -NOTE: For automatic insertion to work, -your list of middlewares (`settings.MIDDLEWARE` or `settings.MIDDLEWARE_CLASSES`) must be of type `list` or `tuple`. - -In addition to broad request metrics (what will appear in the APM app as transactions), -the agent also collects fine grained metrics on template rendering, -database queries, HTTP requests, etc. -You can find more information on what we instrument in the <> section. - -[float] -[[django-instrumenting-custom-python-code]] -===== Instrumenting custom Python code - -To gain further insights into the performance of your code, please see -<>. - -[float] -[[django-ignoring-specific-views]] -===== Ignoring specific views - -You can use the `TRANSACTIONS_IGNORE_PATTERNS` configuration option to ignore specific views. -The list given should be a list of regular expressions which are matched against the transaction name as seen in the Elastic APM user interface: - -[source,python] ----- -ELASTIC_APM['TRANSACTIONS_IGNORE_PATTERNS'] = ['^OPTIONS ', 'views.api.v2'] ----- - -This example ignores any requests using the `OPTIONS` method and any requests containing `views.api.v2`. - -[float] -[[django-transaction-name-route]] -===== Using the route as transaction name - -By default, we use the function or class name of the view as the transaction name. -Starting with Django 2.2, Django makes the route (e.g. `users//`) available on the `request.resolver_match` object. -If you want to use the route instead of the view name as the transaction name, you can set the <> config option to `true`. - -[source,python] ----- -ELASTIC_APM['DJANGO_TRANSACTION_NAME_FROM_ROUTE'] = True ----- - -NOTE: in versions previous to Django 2.2, changing this setting will have no effect. - -[float] -[[django-integrating-with-the-rum-agent]] -===== Integrating with the RUM Agent - -To correlate performance measurement in the browser with measurements in your Django app, -you can help the RUM (Real User Monitoring) agent by configuring it with the Trace ID and Span ID of the backend request. -We provide a handy template context processor which adds all the necessary bits into the context of your templates. - -To enable this feature, first add the `rum_tracing` context processor to your `TEMPLATES` setting. -You most likely already have a list of `context_processors`, in which case you can simply append ours to the list. - -[source,python] ----- -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'OPTIONS': { - 'context_processors': [ - # ... - 'elasticapm.contrib.django.context_processors.rum_tracing', - ], - }, - }, -] - ----- - -Then, update the call to initialize the RUM agent (which probably happens in your base template) like this: - -[source,javascript] ----- -elasticApm.init({ - serviceName: "my-frontend-service", - pageLoadTraceId: "{{ apm.trace_id }}", - pageLoadSpanId: "{{ apm.span_id }}", - pageLoadSampled: {{ apm.is_sampled_js }} -}) - ----- - -See the {apm-rum-ref}[JavaScript RUM agent documentation] for more information. - -[float] -[[django-enabling-and-disabling-the-agent]] -==== Enabling and disabling the agent - -The easiest way to disable the agent is to set Django’s `DEBUG` option to `True` in your development configuration. -No errors or metrics will be logged to Elastic APM. - -However, if during debugging you would like to force logging of errors to Elastic APM, then you can set `DEBUG` to `True` inside of the Elastic APM -configuration dictionary, like this: - -[source,python] ----- -ELASTIC_APM = { - # ... - 'DEBUG': True, -} ----- - -[float] -[[django-logging]] -==== Integrating with Python logging - -To easily send Python `logging` messages as "error" objects to Elasticsearch, -we provide a `LoggingHandler` which you can use in your logging setup. -The log messages will be enriched with a stack trace, data from the request, and more. - -NOTE: the intended use case for this handler is to send high priority log messages (e.g. log messages with level `ERROR`) -to Elasticsearch. For normal log shipping, we recommend using {filebeat-ref}[filebeat]. - -If you are new to how the `logging` module works together with Django, read more -https://docs.djangoproject.com/en/2.1/topics/logging/[in the Django documentation]. - -An example of how your `LOGGING` setting could look: - -[source,python] ----- -LOGGING = { - 'version': 1, - 'disable_existing_loggers': True, - 'formatters': { - 'verbose': { - 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' - }, - }, - 'handlers': { - 'elasticapm': { - 'level': 'WARNING', - 'class': 'elasticapm.contrib.django.handlers.LoggingHandler', - }, - 'console': { - 'level': 'DEBUG', - 'class': 'logging.StreamHandler', - 'formatter': 'verbose' - } - }, - 'loggers': { - 'django.db.backends': { - 'level': 'ERROR', - 'handlers': ['console'], - 'propagate': False, - }, - 'mysite': { - 'level': 'WARNING', - 'handlers': ['elasticapm'], - 'propagate': False, - }, - # Log errors from the Elastic APM module to the console (recommended) - 'elasticapm.errors': { - 'level': 'ERROR', - 'handlers': ['console'], - 'propagate': False, - }, - }, -} ----- - -With this configuration, logging can be done like this in any module in the `myapp` django app: - -You can now use the logger in any module in the `myapp` Django app, for instance `myapp/views.py`: - -[source,python] ----- -import logging -logger = logging.getLogger('mysite') - -try: - instance = MyModel.objects.get(pk=42) -except MyModel.DoesNotExist: - logger.error( - 'Could not find instance, doing something else', - exc_info=True - ) ----- - -Note that `exc_info=True` adds the exception information to the data that gets sent to Elastic APM. -Without it, only the message is sent. - -[float] -[[django-extra-data]] -===== Extra data - -If you want to send more data than what you get with the agent by default, logging can be done like so: - -[source,python] ----- -import logging -logger = logging.getLogger('mysite') - -try: - instance = MyModel.objects.get(pk=42) -except MyModel.DoesNotExist: - logger.error( - 'There was some crazy error', - exc_info=True, - extra={ - 'datetime': str(datetime.now()), - } - ) ----- - -[float] -[[django-celery-integration]] -==== Celery integration - -For a general guide on how to set up Django with Celery, head over to -Celery's http://celery.readthedocs.org/en/latest/django/first-steps-with-django.html#django-first-steps[Django -documentation]. - -Elastic APM will automatically log errors from your celery tasks, record performance data and keep the trace.id -when the task is launched from an already started Elastic transaction. - -[float] -[[django-logging-http-404-not-found-errors]] -==== Logging "HTTP 404 Not Found" errors - -By default, Elastic APM does not log HTTP 404 errors. If you wish to log -these errors, add -`'elasticapm.contrib.django.middleware.Catch404Middleware'` to -`MIDDLEWARE` in your settings: - -[source,python] ----- -MIDDLEWARE = ( - # ... - 'elasticapm.contrib.django.middleware.Catch404Middleware', - # ... -) ----- - -Note that this middleware respects Django's -https://docs.djangoproject.com/en/1.11/ref/settings/#ignorable-404-urls[`IGNORABLE_404_URLS`] -setting. - -[float] -[[django-disable-agent-during-tests]] -==== Disable the agent during tests - -To prevent the agent from sending any data to the APM Server during tests, set the `ELASTIC_APM_DISABLE_SEND` environment variable to `true`, e.g.: - -[source,python] ----- -ELASTIC_APM_DISABLE_SEND=true python manage.py test ----- - -[float] -[[django-troubleshooting]] -==== Troubleshooting - -Elastic APM comes with a Django command that helps troubleshooting your setup. To check your configuration, run - -[source,bash] ----- -python manage.py elasticapm check ----- - -To send a test exception using the current settings, run - -[source,bash] ----- -python manage.py elasticapm test ----- - -If the command succeeds in sending a test exception, it will print a success message: - -[source,bash] ----- -python manage.py elasticapm test - -Trying to send a test error using these settings: - -SERVICE_NAME: -SECRET_TOKEN: -SERVER: http://127.0.0.1:8200 - -Success! We tracked the error successfully! You should be able to see it in a few seconds. ----- - -[float] -[[supported-django-and-python-versions]] -==== Supported Django and Python versions - -A list of supported <> and <> versions can be found on our <> page. diff --git a/docs/docset.yml b/docs/docset.yml new file mode 100644 index 000000000..2c8aafee1 --- /dev/null +++ b/docs/docset.yml @@ -0,0 +1,494 @@ +project: 'APM Python agent docs' +cross_links: + - apm-agent-rum-js + - apm-aws-lambda + - beats + - docs-content + - ecs + - ecs-logging + - ecs-logging-python + - elasticsearch + - logstash +toc: + - toc: reference + - toc: release-notes +subs: + ref: "https://www.elastic.co/guide/en/elasticsearch/reference/current" + ref-bare: "https://www.elastic.co/guide/en/elasticsearch/reference" + ref-8x: "https://www.elastic.co/guide/en/elasticsearch/reference/8.1" + ref-80: "https://www.elastic.co/guide/en/elasticsearch/reference/8.0" + ref-7x: "https://www.elastic.co/guide/en/elasticsearch/reference/7.17" + ref-70: "https://www.elastic.co/guide/en/elasticsearch/reference/7.0" + ref-60: "https://www.elastic.co/guide/en/elasticsearch/reference/6.0" + ref-64: "https://www.elastic.co/guide/en/elasticsearch/reference/6.4" + xpack-ref: "https://www.elastic.co/guide/en/x-pack/6.2" + logstash-ref: "https://www.elastic.co/guide/en/logstash/current" + kibana-ref: "https://www.elastic.co/guide/en/kibana/current" + kibana-ref-all: "https://www.elastic.co/guide/en/kibana" + beats-ref-root: "https://www.elastic.co/guide/en/beats" + beats-ref: "https://www.elastic.co/guide/en/beats/libbeat/current" + beats-ref-60: "https://www.elastic.co/guide/en/beats/libbeat/6.0" + beats-ref-63: "https://www.elastic.co/guide/en/beats/libbeat/6.3" + beats-devguide: "https://www.elastic.co/guide/en/beats/devguide/current" + auditbeat-ref: "https://www.elastic.co/guide/en/beats/auditbeat/current" + packetbeat-ref: "https://www.elastic.co/guide/en/beats/packetbeat/current" + metricbeat-ref: "https://www.elastic.co/guide/en/beats/metricbeat/current" + filebeat-ref: "https://www.elastic.co/guide/en/beats/filebeat/current" + functionbeat-ref: "https://www.elastic.co/guide/en/beats/functionbeat/current" + winlogbeat-ref: "https://www.elastic.co/guide/en/beats/winlogbeat/current" + heartbeat-ref: "https://www.elastic.co/guide/en/beats/heartbeat/current" + journalbeat-ref: "https://www.elastic.co/guide/en/beats/journalbeat/current" + ingest-guide: "https://www.elastic.co/guide/en/ingest/current" + fleet-guide: "https://www.elastic.co/guide/en/fleet/current" + apm-guide-ref: "https://www.elastic.co/guide/en/apm/guide/current" + apm-guide-7x: "https://www.elastic.co/guide/en/apm/guide/7.17" + apm-app-ref: "https://www.elastic.co/guide/en/kibana/current" + apm-agents-ref: "https://www.elastic.co/guide/en/apm/agent" + apm-android-ref: "https://www.elastic.co/guide/en/apm/agent/android/current" + apm-py-ref: "https://www.elastic.co/guide/en/apm/agent/python/current" + apm-py-ref-3x: "https://www.elastic.co/guide/en/apm/agent/python/3.x" + apm-node-ref-index: "https://www.elastic.co/guide/en/apm/agent/nodejs" + apm-node-ref: "https://www.elastic.co/guide/en/apm/agent/nodejs/current" + apm-node-ref-1x: "https://www.elastic.co/guide/en/apm/agent/nodejs/1.x" + apm-rum-ref: "https://www.elastic.co/guide/en/apm/agent/rum-js/current" + apm-ruby-ref: "https://www.elastic.co/guide/en/apm/agent/ruby/current" + apm-java-ref: "https://www.elastic.co/guide/en/apm/agent/java/current" + apm-go-ref: "https://www.elastic.co/guide/en/apm/agent/go/current" + apm-dotnet-ref: "https://www.elastic.co/guide/en/apm/agent/dotnet/current" + apm-php-ref: "https://www.elastic.co/guide/en/apm/agent/php/current" + apm-ios-ref: "https://www.elastic.co/guide/en/apm/agent/swift/current" + apm-lambda-ref: "https://www.elastic.co/guide/en/apm/lambda/current" + apm-attacher-ref: "https://www.elastic.co/guide/en/apm/attacher/current" + docker-logging-ref: "https://www.elastic.co/guide/en/beats/loggingplugin/current" + esf-ref: "https://www.elastic.co/guide/en/esf/current" + kinesis-firehose-ref: "https://www.elastic.co/guide/en/kinesis/{{kinesis_version}}" + estc-welcome-current: "https://www.elastic.co/guide/en/starting-with-the-elasticsearch-platform-and-its-solutions/current" + estc-welcome: "https://www.elastic.co/guide/en/starting-with-the-elasticsearch-platform-and-its-solutions/current" + estc-welcome-all: "https://www.elastic.co/guide/en/starting-with-the-elasticsearch-platform-and-its-solutions" + hadoop-ref: "https://www.elastic.co/guide/en/elasticsearch/hadoop/current" + stack-ref: "https://www.elastic.co/guide/en/elastic-stack/current" + stack-ref-67: "https://www.elastic.co/guide/en/elastic-stack/6.7" + stack-ref-68: "https://www.elastic.co/guide/en/elastic-stack/6.8" + stack-ref-70: "https://www.elastic.co/guide/en/elastic-stack/7.0" + stack-ref-80: "https://www.elastic.co/guide/en/elastic-stack/8.0" + stack-ov: "https://www.elastic.co/guide/en/elastic-stack-overview/current" + stack-gs: "https://www.elastic.co/guide/en/elastic-stack-get-started/current" + stack-gs-current: "https://www.elastic.co/guide/en/elastic-stack-get-started/current" + javaclient: "https://www.elastic.co/guide/en/elasticsearch/client/java-api/current" + java-api-client: "https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current" + java-rest: "https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current" + jsclient: "https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current" + jsclient-current: "https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current" + es-ruby-client: "https://www.elastic.co/guide/en/elasticsearch/client/ruby-api/current" + es-dotnet-client: "https://www.elastic.co/guide/en/elasticsearch/client/net-api/current" + es-php-client: "https://www.elastic.co/guide/en/elasticsearch/client/php-api/current" + es-python-client: "https://www.elastic.co/guide/en/elasticsearch/client/python-api/current" + defguide: "https://www.elastic.co/guide/en/elasticsearch/guide/2.x" + painless: "https://www.elastic.co/guide/en/elasticsearch/painless/current" + plugins: "https://www.elastic.co/guide/en/elasticsearch/plugins/current" + plugins-8x: "https://www.elastic.co/guide/en/elasticsearch/plugins/8.1" + plugins-7x: "https://www.elastic.co/guide/en/elasticsearch/plugins/7.17" + plugins-6x: "https://www.elastic.co/guide/en/elasticsearch/plugins/6.8" + glossary: "https://www.elastic.co/guide/en/elastic-stack-glossary/current" + upgrade_guide: "https://www.elastic.co/products/upgrade_guide" + blog-ref: "https://www.elastic.co/blog/" + curator-ref: "https://www.elastic.co/guide/en/elasticsearch/client/curator/current" + curator-ref-current: "https://www.elastic.co/guide/en/elasticsearch/client/curator/current" + metrics-ref: "https://www.elastic.co/guide/en/metrics/current" + metrics-guide: "https://www.elastic.co/guide/en/metrics/guide/current" + logs-ref: "https://www.elastic.co/guide/en/logs/current" + logs-guide: "https://www.elastic.co/guide/en/logs/guide/current" + uptime-guide: "https://www.elastic.co/guide/en/uptime/current" + observability-guide: "https://www.elastic.co/guide/en/observability/current" + observability-guide-all: "https://www.elastic.co/guide/en/observability" + siem-guide: "https://www.elastic.co/guide/en/siem/guide/current" + security-guide: "https://www.elastic.co/guide/en/security/current" + security-guide-all: "https://www.elastic.co/guide/en/security" + endpoint-guide: "https://www.elastic.co/guide/en/endpoint/current" + sql-odbc: "https://www.elastic.co/guide/en/elasticsearch/sql-odbc/current" + ecs-ref: "https://www.elastic.co/guide/en/ecs/current" + ecs-logging-ref: "https://www.elastic.co/guide/en/ecs-logging/overview/current" + ecs-logging-go-logrus-ref: "https://www.elastic.co/guide/en/ecs-logging/go-logrus/current" + ecs-logging-go-zap-ref: "https://www.elastic.co/guide/en/ecs-logging/go-zap/current" + ecs-logging-go-zerolog-ref: "https://www.elastic.co/guide/en/ecs-logging/go-zap/current" + ecs-logging-java-ref: "https://www.elastic.co/guide/en/ecs-logging/java/current" + ecs-logging-dotnet-ref: "https://www.elastic.co/guide/en/ecs-logging/dotnet/current" + ecs-logging-nodejs-ref: "https://www.elastic.co/guide/en/ecs-logging/nodejs/current" + ecs-logging-php-ref: "https://www.elastic.co/guide/en/ecs-logging/php/current" + ecs-logging-python-ref: "https://www.elastic.co/guide/en/ecs-logging/python/current" + ecs-logging-ruby-ref: "https://www.elastic.co/guide/en/ecs-logging/ruby/current" + ml-docs: "https://www.elastic.co/guide/en/machine-learning/current" + eland-docs: "https://www.elastic.co/guide/en/elasticsearch/client/eland/current" + eql-ref: "https://eql.readthedocs.io/en/latest/query-guide" + extendtrial: "https://www.elastic.co/trialextension" + wikipedia: "https://en.wikipedia.org/wiki" + forum: "https://discuss.elastic.co/" + xpack-forum: "https://discuss.elastic.co/c/50-x-pack" + security-forum: "https://discuss.elastic.co/c/x-pack/shield" + watcher-forum: "https://discuss.elastic.co/c/x-pack/watcher" + monitoring-forum: "https://discuss.elastic.co/c/x-pack/marvel" + graph-forum: "https://discuss.elastic.co/c/x-pack/graph" + apm-forum: "https://discuss.elastic.co/c/apm" + enterprise-search-ref: "https://www.elastic.co/guide/en/enterprise-search/current" + app-search-ref: "https://www.elastic.co/guide/en/app-search/current" + workplace-search-ref: "https://www.elastic.co/guide/en/workplace-search/current" + enterprise-search-node-ref: "https://www.elastic.co/guide/en/enterprise-search-clients/enterprise-search-node/current" + enterprise-search-php-ref: "https://www.elastic.co/guide/en/enterprise-search-clients/php/current" + enterprise-search-python-ref: "https://www.elastic.co/guide/en/enterprise-search-clients/python/current" + enterprise-search-ruby-ref: "https://www.elastic.co/guide/en/enterprise-search-clients/ruby/current" + elastic-maps-service: "https://maps.elastic.co" + integrations-docs: "https://docs.elastic.co/en/integrations" + integrations-devguide: "https://www.elastic.co/guide/en/integrations-developer/current" + time-units: "https://www.elastic.co/guide/en/elasticsearch/reference/current/api-conventions.html#time-units" + byte-units: "https://www.elastic.co/guide/en/elasticsearch/reference/current/api-conventions.html#byte-units" + apm-py-ref-v: "https://www.elastic.co/guide/en/apm/agent/python/current" + apm-node-ref-v: "https://www.elastic.co/guide/en/apm/agent/nodejs/current" + apm-rum-ref-v: "https://www.elastic.co/guide/en/apm/agent/rum-js/current" + apm-ruby-ref-v: "https://www.elastic.co/guide/en/apm/agent/ruby/current" + apm-java-ref-v: "https://www.elastic.co/guide/en/apm/agent/java/current" + apm-go-ref-v: "https://www.elastic.co/guide/en/apm/agent/go/current" + apm-ios-ref-v: "https://www.elastic.co/guide/en/apm/agent/swift/current" + apm-dotnet-ref-v: "https://www.elastic.co/guide/en/apm/agent/dotnet/current" + apm-php-ref-v: "https://www.elastic.co/guide/en/apm/agent/php/current" + ecloud: "Elastic Cloud" + esf: "Elastic Serverless Forwarder" + ess: "Elasticsearch Service" + ece: "Elastic Cloud Enterprise" + eck: "Elastic Cloud on Kubernetes" + serverless-full: "Elastic Cloud Serverless" + serverless-short: "Serverless" + es-serverless: "Elasticsearch Serverless" + es3: "Elasticsearch Serverless" + obs-serverless: "Elastic Observability Serverless" + sec-serverless: "Elastic Security Serverless" + serverless-docs: "https://docs.elastic.co/serverless" + cloud: "https://www.elastic.co/guide/en/cloud/current" + ess-utm-params: "?page=docs&placement=docs-body" + ess-baymax: "?page=docs&placement=docs-body" + ess-trial: "https://cloud.elastic.co/registration?page=docs&placement=docs-body" + ess-product: "https://www.elastic.co/cloud/elasticsearch-service?page=docs&placement=docs-body" + ess-console: "https://cloud.elastic.co?page=docs&placement=docs-body" + ess-console-name: "Elasticsearch Service Console" + ess-deployments: "https://cloud.elastic.co/deployments?page=docs&placement=docs-body" + ece-ref: "https://www.elastic.co/guide/en/cloud-enterprise/current" + eck-ref: "https://www.elastic.co/guide/en/cloud-on-k8s/current" + ess-leadin: "You can run Elasticsearch on your own hardware or use our hosted Elasticsearch Service that is available on AWS, GCP, and Azure. https://cloud.elastic.co/registration{ess-utm-params}[Try the Elasticsearch Service for free]." + ess-leadin-short: "Our hosted Elasticsearch Service is available on AWS, GCP, and Azure, and you can https://cloud.elastic.co/registration{ess-utm-params}[try it for free]." + ess-icon: "image:https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg[link=\"https://cloud.elastic.co/registration{ess-utm-params}\", title=\"Supported on Elasticsearch Service\"]" + ece-icon: "image:https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud_ece.svg[link=\"https://cloud.elastic.co/registration{ess-utm-params}\", title=\"Supported on Elastic Cloud Enterprise\"]" + cloud-only: "This feature is designed for indirect use by https://cloud.elastic.co/registration{ess-utm-params}[Elasticsearch Service], https://www.elastic.co/guide/en/cloud-enterprise/{ece-version-link}[Elastic Cloud Enterprise], and https://www.elastic.co/guide/en/cloud-on-k8s/current[Elastic Cloud on Kubernetes]. Direct use is not supported." + ess-setting-change: "image:https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg[link=\"{ess-trial}\", title=\"Supported on {ess}\"] indicates a change to a supported https://www.elastic.co/guide/en/cloud/current/ec-add-user-settings.html[user setting] for Elasticsearch Service." + ess-skip-section: "If you use Elasticsearch Service, skip this section. Elasticsearch Service handles these changes for you." + api-cloud: "https://www.elastic.co/docs/api/doc/cloud" + api-ece: "https://www.elastic.co/docs/api/doc/cloud-enterprise" + api-kibana-serverless: "https://www.elastic.co/docs/api/doc/serverless" + es-feature-flag: "This feature is in development and not yet available for use. This documentation is provided for informational purposes only." + es-ref-dir: "'{{elasticsearch-root}}/docs/reference'" + apm-app: "APM app" + uptime-app: "Uptime app" + synthetics-app: "Synthetics app" + logs-app: "Logs app" + metrics-app: "Metrics app" + infrastructure-app: "Infrastructure app" + siem-app: "SIEM app" + security-app: "Elastic Security app" + ml-app: "Machine Learning" + dev-tools-app: "Dev Tools" + ingest-manager-app: "Ingest Manager" + stack-manage-app: "Stack Management" + stack-monitor-app: "Stack Monitoring" + alerts-ui: "Alerts and Actions" + rules-ui: "Rules" + rac-ui: "Rules and Connectors" + connectors-ui: "Connectors" + connectors-feature: "Actions and Connectors" + stack-rules-feature: "Stack Rules" + user-experience: "User Experience" + ems: "Elastic Maps Service" + ems-init: "EMS" + hosted-ems: "Elastic Maps Server" + ipm-app: "Index Pattern Management" + ingest-pipelines: "ingest pipelines" + ingest-pipelines-app: "Ingest Pipelines" + ingest-pipelines-cap: "Ingest pipelines" + ls-pipelines: "Logstash pipelines" + ls-pipelines-app: "Logstash Pipelines" + maint-windows: "maintenance windows" + maint-windows-app: "Maintenance Windows" + maint-windows-cap: "Maintenance windows" + custom-roles-app: "Custom Roles" + data-source: "data view" + data-sources: "data views" + data-source-caps: "Data View" + data-sources-caps: "Data Views" + data-source-cap: "Data view" + data-sources-cap: "Data views" + project-settings: "Project settings" + manage-app: "Management" + index-manage-app: "Index Management" + data-views-app: "Data Views" + rules-app: "Rules" + saved-objects-app: "Saved Objects" + tags-app: "Tags" + api-keys-app: "API keys" + transforms-app: "Transforms" + connectors-app: "Connectors" + files-app: "Files" + reports-app: "Reports" + maps-app: "Maps" + alerts-app: "Alerts" + crawler: "Enterprise Search web crawler" + ents: "Enterprise Search" + app-search-crawler: "App Search web crawler" + agent: "Elastic Agent" + agents: "Elastic Agents" + fleet: "Fleet" + fleet-server: "Fleet Server" + integrations-server: "Integrations Server" + ingest-manager: "Ingest Manager" + ingest-management: "ingest management" + package-manager: "Elastic Package Manager" + integrations: "Integrations" + package-registry: "Elastic Package Registry" + artifact-registry: "Elastic Artifact Registry" + aws: "AWS" + stack: "Elastic Stack" + xpack: "X-Pack" + es: "Elasticsearch" + kib: "Kibana" + esms: "Elastic Stack Monitoring Service" + esms-init: "ESMS" + ls: "Logstash" + beats: "Beats" + auditbeat: "Auditbeat" + filebeat: "Filebeat" + heartbeat: "Heartbeat" + metricbeat: "Metricbeat" + packetbeat: "Packetbeat" + winlogbeat: "Winlogbeat" + functionbeat: "Functionbeat" + journalbeat: "Journalbeat" + es-sql: "Elasticsearch SQL" + esql: "ES|QL" + elastic-agent: "Elastic Agent" + k8s: "Kubernetes" + log-driver-long: "Elastic Logging Plugin for Docker" + security: "X-Pack security" + security-features: "security features" + operator-feature: "operator privileges feature" + es-security-features: "Elasticsearch security features" + stack-security-features: "Elastic Stack security features" + endpoint-sec: "Endpoint Security" + endpoint-cloud-sec: "Endpoint and Cloud Security" + elastic-defend: "Elastic Defend" + elastic-sec: "Elastic Security" + elastic-endpoint: "Elastic Endpoint" + swimlane: "Swimlane" + sn: "ServiceNow" + sn-itsm: "ServiceNow ITSM" + sn-itom: "ServiceNow ITOM" + sn-sir: "ServiceNow SecOps" + jira: "Jira" + ibm-r: "IBM Resilient" + webhook: "Webhook" + webhook-cm: "Webhook - Case Management" + opsgenie: "Opsgenie" + bedrock: "Amazon Bedrock" + gemini: "Google Gemini" + hive: "TheHive" + monitoring: "X-Pack monitoring" + monitor-features: "monitoring features" + stack-monitor-features: "Elastic Stack monitoring features" + watcher: "Watcher" + alert-features: "alerting features" + reporting: "X-Pack reporting" + report-features: "reporting features" + graph: "X-Pack graph" + graph-features: "graph analytics features" + searchprofiler: "Search Profiler" + xpackml: "X-Pack machine learning" + ml: "machine learning" + ml-cap: "Machine learning" + ml-init: "ML" + ml-features: "machine learning features" + stack-ml-features: "Elastic Stack machine learning features" + ccr: "cross-cluster replication" + ccr-cap: "Cross-cluster replication" + ccr-init: "CCR" + ccs: "cross-cluster search" + ccs-cap: "Cross-cluster search" + ccs-init: "CCS" + ilm: "index lifecycle management" + ilm-cap: "Index lifecycle management" + ilm-init: "ILM" + dlm: "data lifecycle management" + dlm-cap: "Data lifecycle management" + dlm-init: "DLM" + search-snap: "searchable snapshot" + search-snaps: "searchable snapshots" + search-snaps-cap: "Searchable snapshots" + slm: "snapshot lifecycle management" + slm-cap: "Snapshot lifecycle management" + slm-init: "SLM" + rollup-features: "data rollup features" + ipm: "index pattern management" + ipm-cap: "Index pattern" + rollup: "rollup" + rollup-cap: "Rollup" + rollups: "rollups" + rollups-cap: "Rollups" + rollup-job: "rollup job" + rollup-jobs: "rollup jobs" + rollup-jobs-cap: "Rollup jobs" + dfeed: "datafeed" + dfeeds: "datafeeds" + dfeed-cap: "Datafeed" + dfeeds-cap: "Datafeeds" + ml-jobs: "machine learning jobs" + ml-jobs-cap: "Machine learning jobs" + anomaly-detect: "anomaly detection" + anomaly-detect-cap: "Anomaly detection" + anomaly-job: "anomaly detection job" + anomaly-jobs: "anomaly detection jobs" + anomaly-jobs-cap: "Anomaly detection jobs" + dataframe: "data frame" + dataframes: "data frames" + dataframe-cap: "Data frame" + dataframes-cap: "Data frames" + watcher-transform: "payload transform" + watcher-transforms: "payload transforms" + watcher-transform-cap: "Payload transform" + watcher-transforms-cap: "Payload transforms" + transform: "transform" + transforms: "transforms" + transform-cap: "Transform" + transforms-cap: "Transforms" + dataframe-transform: "transform" + dataframe-transform-cap: "Transform" + dataframe-transforms: "transforms" + dataframe-transforms-cap: "Transforms" + dfanalytics-cap: "Data frame analytics" + dfanalytics: "data frame analytics" + dataframe-analytics-config: "'{dataframe} analytics config'" + dfanalytics-job: "'{dataframe} analytics job'" + dfanalytics-jobs: "'{dataframe} analytics jobs'" + dfanalytics-jobs-cap: "'{dataframe-cap} analytics jobs'" + cdataframe: "continuous data frame" + cdataframes: "continuous data frames" + cdataframe-cap: "Continuous data frame" + cdataframes-cap: "Continuous data frames" + cdataframe-transform: "continuous transform" + cdataframe-transforms: "continuous transforms" + cdataframe-transforms-cap: "Continuous transforms" + ctransform: "continuous transform" + ctransform-cap: "Continuous transform" + ctransforms: "continuous transforms" + ctransforms-cap: "Continuous transforms" + oldetection: "outlier detection" + oldetection-cap: "Outlier detection" + olscore: "outlier score" + olscores: "outlier scores" + fiscore: "feature influence score" + evaluatedf-api: "evaluate {dataframe} analytics API" + evaluatedf-api-cap: "Evaluate {dataframe} analytics API" + binarysc: "binary soft classification" + binarysc-cap: "Binary soft classification" + regression: "regression" + regression-cap: "Regression" + reganalysis: "regression analysis" + reganalysis-cap: "Regression analysis" + depvar: "dependent variable" + feature-var: "feature variable" + feature-vars: "feature variables" + feature-vars-cap: "Feature variables" + classification: "classification" + classification-cap: "Classification" + classanalysis: "classification analysis" + classanalysis-cap: "Classification analysis" + infer-cap: "Inference" + infer: "inference" + lang-ident-cap: "Language identification" + lang-ident: "language identification" + data-viz: "Data Visualizer" + file-data-viz: "File Data Visualizer" + feat-imp: "feature importance" + feat-imp-cap: "Feature importance" + nlp: "natural language processing" + nlp-cap: "Natural language processing" + apm-agent: "APM agent" + apm-go-agent: "Elastic APM Go agent" + apm-go-agents: "Elastic APM Go agents" + apm-ios-agent: "Elastic APM iOS agent" + apm-ios-agents: "Elastic APM iOS agents" + apm-java-agent: "Elastic APM Java agent" + apm-java-agents: "Elastic APM Java agents" + apm-dotnet-agent: "Elastic APM .NET agent" + apm-dotnet-agents: "Elastic APM .NET agents" + apm-node-agent: "Elastic APM Node.js agent" + apm-node-agents: "Elastic APM Node.js agents" + apm-php-agent: "Elastic APM PHP agent" + apm-php-agents: "Elastic APM PHP agents" + apm-py-agent: "Elastic APM Python agent" + apm-py-agents: "Elastic APM Python agents" + apm-ruby-agent: "Elastic APM Ruby agent" + apm-ruby-agents: "Elastic APM Ruby agents" + apm-rum-agent: "Elastic APM Real User Monitoring (RUM) JavaScript agent" + apm-rum-agents: "Elastic APM RUM JavaScript agents" + apm-lambda-ext: "Elastic APM AWS Lambda extension" + project-monitors: "project monitors" + project-monitors-cap: "Project monitors" + private-location: "Private Location" + private-locations: "Private Locations" + pwd: "YOUR_PASSWORD" + esh: "ES-Hadoop" + default-dist: "default distribution" + oss-dist: "OSS-only distribution" + observability: "Observability" + api-request-title: "Request" + api-prereq-title: "Prerequisites" + api-description-title: "Description" + api-path-parms-title: "Path parameters" + api-query-parms-title: "Query parameters" + api-request-body-title: "Request body" + api-response-codes-title: "Response codes" + api-response-body-title: "Response body" + api-example-title: "Example" + api-examples-title: "Examples" + api-definitions-title: "Properties" + multi-arg: "†footnoteref:[multi-arg,This parameter accepts multiple arguments.]" + multi-arg-ref: "†footnoteref:[multi-arg]" + yes-icon: "image:https://doc-icons.s3.us-east-2.amazonaws.com/icon-yes.png[Yes,20,15]" + no-icon: "image:https://doc-icons.s3.us-east-2.amazonaws.com/icon-no.png[No,20,15]" + es-repo: "https://github.com/elastic/elasticsearch/" + es-issue: "https://github.com/elastic/elasticsearch/issues/" + es-pull: "https://github.com/elastic/elasticsearch/pull/" + es-commit: "https://github.com/elastic/elasticsearch/commit/" + kib-repo: "https://github.com/elastic/kibana/" + kib-issue: "https://github.com/elastic/kibana/issues/" + kibana-issue: "'{kib-repo}issues/'" + kib-pull: "https://github.com/elastic/kibana/pull/" + kibana-pull: "'{kib-repo}pull/'" + kib-commit: "https://github.com/elastic/kibana/commit/" + ml-repo: "https://github.com/elastic/ml-cpp/" + ml-issue: "https://github.com/elastic/ml-cpp/issues/" + ml-pull: "https://github.com/elastic/ml-cpp/pull/" + ml-commit: "https://github.com/elastic/ml-cpp/commit/" + apm-repo: "https://github.com/elastic/apm-server/" + apm-issue: "https://github.com/elastic/apm-server/issues/" + apm-pull: "https://github.com/elastic/apm-server/pull/" + kibana-blob: "https://github.com/elastic/kibana/blob/current/" + apm-get-started-ref: "https://www.elastic.co/guide/en/apm/get-started/current" + apm-server-ref: "https://www.elastic.co/guide/en/apm/server/current" + apm-server-ref-v: "https://www.elastic.co/guide/en/apm/server/current" + apm-server-ref-m: "https://www.elastic.co/guide/en/apm/server/master" + apm-server-ref-62: "https://www.elastic.co/guide/en/apm/server/6.2" + apm-server-ref-64: "https://www.elastic.co/guide/en/apm/server/6.4" + apm-server-ref-70: "https://www.elastic.co/guide/en/apm/server/7.0" + apm-overview-ref-v: "https://www.elastic.co/guide/en/apm/get-started/current" + apm-overview-ref-70: "https://www.elastic.co/guide/en/apm/get-started/7.0" + apm-overview-ref-m: "https://www.elastic.co/guide/en/apm/get-started/master" + infra-guide: "https://www.elastic.co/guide/en/infrastructure/guide/current" + a-data-source: "a data view" + icon-bug: "pass:[]" + icon-checkInCircleFilled: "pass:[]" + icon-warningFilled: "pass:[]" diff --git a/docs/flask.asciidoc b/docs/flask.asciidoc deleted file mode 100644 index b99ddd198..000000000 --- a/docs/flask.asciidoc +++ /dev/null @@ -1,245 +0,0 @@ -[[flask-support]] -=== Flask support - -Getting Elastic APM set up for your Flask project is easy, -and there are various ways you can tweak it to fit to your needs. - -[float] -[[flask-installation]] -==== Installation - -Install the Elastic APM agent using pip: - -[source,bash] ----- -$ pip install "elastic-apm[flask]" ----- - -or add `elastic-apm[flask]` to your project's `requirements.txt` file. - -NOTE: For apm-server 6.2+, make sure you use version 2.0 or higher of `elastic-apm`. - -NOTE: If you use Flask with uwsgi, make sure to -http://uwsgi-docs.readthedocs.org/en/latest/Options.html#enable-threads[enable -threads]. - -NOTE: If you see an error log that mentions `psutil not found`, you can install -`psutil` using `pip install psutil`, or add `psutil` to your `requirements.txt` -file. - -[float] -[[flask-setup]] -==== Setup - -To set up the agent, you need to initialize it with appropriate settings. - -The settings are configured either via environment variables, -the application's settings, or as initialization arguments. - -You can find a list of all available settings in the <> page. - -To initialize the agent for your application using environment variables: - -[source,python] ----- -from elasticapm.contrib.flask import ElasticAPM - -app = Flask(__name__) - -apm = ElasticAPM(app) ----- - -To configure the agent using `ELASTIC_APM` in your application's settings: - -[source,python] ----- -from elasticapm.contrib.flask import ElasticAPM - -app.config['ELASTIC_APM'] = { - 'SERVICE_NAME': '', - 'SECRET_TOKEN': '', -} -apm = ElasticAPM(app) ----- - -The final option is to initialize the agent with the settings as arguments: - -[source,python] ----- -from elasticapm.contrib.flask import ElasticAPM - -apm = ElasticAPM(app, service_name='', secret_token='') ----- - -[float] -[[flask-debug-mode]] -===== Debug mode - -NOTE: Please note that errors and transactions will only be sent to the APM Server if your app is *not* in -https://flask.palletsprojects.com/en/3.0.x/quickstart/#debug-mode[Flask debug mode]. - -To force the agent to send data while the app is in debug mode, -set the value of `DEBUG` in the `ELASTIC_APM` dictionary to `True`: - -[source,python] ----- -app.config['ELASTIC_APM'] = { - 'SERVICE_NAME': '', - 'SECRET_TOKEN': '', - 'DEBUG': True -} ----- - -[float] -[[flask-building-applications-on-the-fly]] -===== Building applications on the fly? - -You can use the agent's `init_app` hook for adding the application on the fly: - -[source,python] ----- -from elasticapm.contrib.flask import ElasticAPM -apm = ElasticAPM() - -def create_app(): - app = Flask(__name__) - apm.init_app(app, service_name='', secret_token='') - return app ----- - -[float] -[[flask-usage]] -==== Usage - -Once you have configured the agent, -it will automatically track transactions and capture uncaught exceptions within Flask. -If you want to send additional events, -a couple of shortcuts are provided on the ElasticAPM Flask middleware object -by raising an exception or logging a generic message. - -Capture an arbitrary exception by calling `capture_exception`: - -[source,python] ----- -try: - 1 / 0 -except ZeroDivisionError: - apm.capture_exception() ----- - -Log a generic message with `capture_message`: - -[source,python] ----- -apm.capture_message('hello, world!') ----- - -[float] -[[flask-logging]] -==== Shipping Logs to Elasticsearch - -This feature has been deprecated and will be removed in a future version. - -Please see our <> documentation for other supported ways to ship -logs to Elasticsearch. - -Note that you can always send exceptions and messages to the APM Server with -<> and and -<>. - -[source,python] ----- -from elasticapm import get_client - -@app.route('/') -def bar(): - try: - 1 / 0 - except ZeroDivisionError: - get_client().capture_exception() ----- - -[float] -[[flask-extra-data]] -===== Extra data - -In addition to what the agents log by default, you can send extra information: - -[source,python] ----- -@app.route('/') -def bar(): - try: - 1 / 0 - except ZeroDivisionError: - app.logger.error('Math is hard', - exc_info=True, - extra={ - 'good_at_math': False, - } - ) - ) ----- - -[float] -[[flask-celery-tasks]] -===== Celery tasks - -The Elastic APM agent will automatically send errors and performance data from your Celery tasks to the APM Server. - -[float] -[[flask-performance-metrics]] -==== Performance metrics - -If you've followed the instructions above, the agent has already hooked -into the right signals and should be reporting performance metrics. - -[float] -[[flask-ignoring-specific-views]] -===== Ignoring specific routes - -You can use the <> configuration option to ignore specific routes. -The list given should be a list of regular expressions which are matched against the transaction name: - -[source,python] ----- -app.config['ELASTIC_APM'] = { - ... - 'TRANSACTIONS_IGNORE_PATTERNS': ['^OPTIONS ', '/api/'] - ... -} ----- - -This would ignore any requests using the `OPTIONS` method -and any requests containing `/api/`. - - -[float] -[[flask-integrating-with-the-rum-agent]] -===== Integrating with the RUM Agent - -To correlate performance measurement in the browser with measurements in your Flask app, -you can help the RUM (Real User Monitoring) agent by configuring it with the Trace ID and Span ID of the backend request. -We provide a handy template context processor which adds all the necessary bits into the context of your templates. - -The context processor is installed automatically when you initialize `ElasticAPM`. -All that is left to do is to update the call to initialize the RUM agent (which probably happens in your base template) like this: - -[source,javascript] ----- -elasticApm.init({ - serviceName: "my-frontend-service", - pageLoadTraceId: "{{ apm["trace_id"] }}", - pageLoadSpanId: "{{ apm["span_id"]() }}", - pageLoadSampled: {{ apm["is_sampled_js"] }} -}) - ----- - -See the {apm-rum-ref}[JavaScript RUM agent documentation] for more information. - -[float] -[[supported-flask-and-python-versions]] -==== Supported Flask and Python versions - -A list of supported <> and <> versions can be found on our <> page. diff --git a/docs/getting-started.asciidoc b/docs/getting-started.asciidoc deleted file mode 100644 index ec8a88bf8..000000000 --- a/docs/getting-started.asciidoc +++ /dev/null @@ -1,32 +0,0 @@ -[[getting-started]] -== Introduction - -The Elastic APM Python agent sends performance metrics and error logs to the APM Server. -It has built-in support for Django and Flask performance metrics and error logging, as well as generic support of other WSGI frameworks for error logging. - -[float] -[[how-it-works]] -=== How does the Agent work? - -The Python Agent instruments your application to collect APM events in a few different ways: - -To collect data about incoming requests and background tasks, the Agent integrates with <> to make use of hooks and signals provided by the framework. -These framework integrations require limited code changes in your application. - -To collect data from database drivers, HTTP libraries etc., -we instrument certain functions and methods in these libraries. -Instrumentations are set up automatically and do not require any code changes. - -In addition to APM and error data, -the Python agent also collects system and application metrics in regular intervals. -This collection happens in a background thread that is started by the agent. - -More detailed information on how the Agent works can be found in the <>. - -[float] -[[additional-components]] -=== Additional components - -APM Agents work in conjunction with the {apm-guide-ref}/index.html[APM Server], {ref}/index.html[Elasticsearch], and {kibana-ref}/index.html[Kibana]. -The {apm-guide-ref}/index.html[APM Guide] provides details on how these components work together, -and provides a matrix outlining {apm-guide-ref}/agent-server-compatibility.html[Agent and Server compatibility]. diff --git a/docs/grpc.asciidoc b/docs/grpc.asciidoc deleted file mode 100644 index 4b79e15f0..000000000 --- a/docs/grpc.asciidoc +++ /dev/null @@ -1,65 +0,0 @@ -[[grpc-support]] -=== GRPC Support - -Incorporating Elastic APM into your GRPC project only requires a few easy -steps. - -NOTE: currently, only unary-unary RPC calls are instrumented. Streaming requests or responses are not captured. - -[float] -[[grpc-installation]] -==== Installation - -Install the Elastic APM agent using pip: - -[source,bash] ----- -$ pip install elastic-apm ----- - -or add `elastic-apm` to your project's `requirements.txt` file. - - -[float] -[[grpc-setup]] -==== Setup - -Elastic APM can be used both in GRPC server apps, and in GRPC client apps. - -[float] -[[grpc-setup-client]] -===== GRPC Client - -If you use one of our <>, no further steps are needed. - -For other use cases, see <>. -To ensure that our instrumentation is in place, call `elasticapm.instrument()` *before* creating any GRPC channels. - -[float] -[[grpc-setup-server]] -===== GRPC Server - -To set up the agent, you need to initialize it with appropriate settings. - -The settings are configured either via environment variables, or as -initialization arguments. - -You can find a list of all available settings in the -<> page. - -To initialize the agent for your application using environment variables: - -[source,python] ----- -import elasticapm -from elasticapm.contrib.grpc import GRPCApmClient - -elasticapm.instrument() - -client = GRPCApmClient(service_name="my-grpc-server") ----- - - -Once you have configured the agent, it will automatically track transactions -and capture uncaught exceptions within GRPC requests. - diff --git a/docs/how-the-agent-works.asciidoc b/docs/how-the-agent-works.asciidoc deleted file mode 100644 index 796815144..000000000 --- a/docs/how-the-agent-works.asciidoc +++ /dev/null @@ -1,72 +0,0 @@ -[[how-the-agent-works]] -=== How the Agent works - -To gather APM events (called transactions and spans), errors and metrics, -the Python agent instruments your application in a few different ways. -These events, are then sent to the APM Server. -The APM Server converts them to a format suitable for Elasticsearch, and sends them to an Elasticsearch cluster. -You can then use the APM app in Kibana to gain insight into latency issues and error culprits within your application. - -Broadly, we differentiate between three different approaches to collect the necessary data: -framework integration, instrumentation, and background collection. - -[float] -[[how-it-works-framework-integration]] -==== Framework integration - -To collect data about incoming requests and background tasks, -we integrate with frameworks like <>, <> and Celery. -Whenever possible, framework integrations make use of hooks and signals provided by the framework. -Examples of this are: - - * `request_started`, `request_finished`, and `got_request_exception` signals from `django.core.signals` - * `request_started`, `request_finished`, and `got_request_exception` signals from `flask.signals` - * `task_prerun`, `task_postrun`, and `task_failure` signals from `celery.signals` - -Framework integrations require some limited code changes in your app. -E.g. for Django, you need to add `elasticapm.contrib.django` to `INSTALLED_APPS`. - -[float] -[[how-it-works-no-framework]] -==== What if you are not using a framework - -If you're not using a supported framework, for example, a simple Python script, you can still -leverage the agent's <>. Check out -our docs on <>. - -[float] -[[how-it-works-instrumentation]] -==== Instrumentation - -To collect data from database drivers, HTTP libraries etc., -we instrument certain functions and methods in these libraries. -Our instrumentation wraps these callables and collects additional data, like - - * time spent in the call - * the executed query for database drivers - * the fetched URL for HTTP libraries - -We use a 3rd party library, https://github.com/GrahamDumpleton/wrapt[`wrapt`], to wrap the callables. -You can read more on how `wrapt` works in Graham Dumpleton's -excellent series of http://blog.dscpl.com.au/search/label/wrapt[blog posts]. - -Instrumentations are set up automatically and do not require any code changes. -See <> to learn more about which libraries we support. - -[float] -[[how-it-works-background-collection]] -==== Background collection - -In addition to APM and error data, -the Python agent also collects system and application metrics in regular intervals. -This collection happens in a background thread that is started by the agent. - -In addition to the metrics collection background thread, -the agent starts two additional threads per process: - - * a thread to regularly fetch remote configuration from the APM Server - * a thread to process the collected data and send it to the APM Server via HTTP. - -Note that every process that instantiates the agent will have these three threads. -This means that when you e.g. use gunicorn or uwsgi workers, -each worker will have three threads started by the Python agent. diff --git a/docs/images/choose-a-layer.png b/docs/images/choose-a-layer.png new file mode 100644 index 0000000000000000000000000000000000000000..49cfd9917c5fa0a88be2f27310dd9703b138bf7d GIT binary patch literal 135763 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�#HaF|wz%0j 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 z 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 AB>(^b literal 0 HcmV?d00001 diff --git a/docs/index.asciidoc b/docs/index.asciidoc deleted file mode 100644 index 544415367..000000000 --- a/docs/index.asciidoc +++ /dev/null @@ -1,40 +0,0 @@ -include::{asciidoc-dir}/../../shared/versions/stack/current.asciidoc[] -include::{asciidoc-dir}/../../shared/attributes.asciidoc[] - -ifdef::env-github[] -NOTE: For the best reading experience, -please view this documentation at https://www.elastic.co/guide/en/apm/agent/python/current/index.html[elastic.co] -endif::[] - -= APM Python Agent Reference - -NOTE: Python 2.7 reached End of Life on January 1, 2020. -The Elastic APM agent will stop supporting Python 2.7 starting in version 6.0.0. - -include::./getting-started.asciidoc[] - -include::./set-up.asciidoc[] - -include::./supported-technologies.asciidoc[] - -include::./configuration.asciidoc[] - -include::./advanced-topics.asciidoc[] - -include::./api.asciidoc[] - -include::./metrics.asciidoc[] - -include::./opentelemetry.asciidoc[] - -include::./logging.asciidoc[] - -include::./tuning.asciidoc[] - -include::./troubleshooting.asciidoc[] - -include::./upgrading.asciidoc[] - -include::./release-notes.asciidoc[] - -include::./redirects.asciidoc[] diff --git a/docs/lambda/configure-lambda-widget.asciidoc b/docs/lambda/configure-lambda-widget.asciidoc deleted file mode 100644 index 9763f49f8..000000000 --- a/docs/lambda/configure-lambda-widget.asciidoc +++ /dev/null @@ -1,118 +0,0 @@ -++++ -

-++++ \ No newline at end of file diff --git a/docs/lambda/configure-lambda.asciidoc b/docs/lambda/configure-lambda.asciidoc deleted file mode 100644 index 09377dcee..000000000 --- a/docs/lambda/configure-lambda.asciidoc +++ /dev/null @@ -1,113 +0,0 @@ -// tag::console-with-agent[] - -To configure APM through the AWS Management Console: - -1. Navigate to your function in the AWS Management Console -2. Click on the _Configuration_ tab -3. Click on _Environment variables_ -4. Add the following required variables: - -[source,bash] ----- -AWS_LAMBDA_EXEC_WRAPPER = /opt/python/bin/elasticapm-lambda # use this exact fixed value -ELASTIC_APM_LAMBDA_APM_SERVER = # this is your APM Server URL -ELASTIC_APM_SECRET_TOKEN = # this is your APM secret token -ELASTIC_APM_SEND_STRATEGY = background <1> ----- - --- -include::{apm-aws-lambda-root}/docs/images/images.asciidoc[tag=python-env-vars] --- - -// end::console-with-agent[] - -// tag::cli-with-agent[] - -To configure APM through the AWS command line interface execute the following command: - -[source,bash] ----- -aws lambda update-function-configuration --function-name yourLambdaFunctionName \ - --environment "Variables={AWS_LAMBDA_EXEC_WRAPPER=/opt/python/bin/elasticapm-lambda,ELASTIC_APM_LAMBDA_APM_SERVER=,ELASTIC_APM_SECRET_TOKEN=,ELASTIC_APM_SEND_STRATEGY=background}" <1> ----- - -// end::cli-with-agent[] - -// tag::sam-with-agent[] - -In your SAM `template.yml` file configure the following environment variables: - -[source,yml] ----- -... -Resources: - yourLambdaFunction: - Type: AWS::Serverless::Function - Properties: - ... - Environment: - Variables: - AWS_LAMBDA_EXEC_WRAPPER: /opt/python/bin/elasticapm-lambda - ELASTIC_APM_LAMBDA_APM_SERVER: - ELASTIC_APM_SECRET_TOKEN: - ELASTIC_APM_SEND_STRATEGY: background <1> -... ----- - -// end::sam-with-agent[] - -// tag::serverless-with-agent[] - -In your `serverless.yml` file configure the following environment variables: - -[source,yml] ----- -... -functions: - yourLambdaFunction: - ... - environment: - AWS_LAMBDA_EXEC_WRAPPER: /opt/python/bin/elasticapm-lambda - ELASTIC_APM_LAMBDA_APM_SERVER: - ELASTIC_APM_SECRET_TOKEN: - ELASTIC_APM_SEND_STRATEGY: background <1> -... ----- - -// end::serverless-with-agent[] - -// tag::terraform-with-agent[] -In your Terraform file configure the following environment variables: - -[source,terraform] ----- -... -resource "aws_lambda_function" "your_lambda_function" { - ... - environment { - variables = { - AWS_LAMBDA_EXEC_WRAPPER = /opt/python/bin/elasticapm-lambda - ELASTIC_APM_LAMBDA_APM_SERVER = "" - ELASTIC_APM_SECRET_TOKEN = "" - ELASTIC_APM_SEND_STRATEGY = "background" <1> - } - } -} -... ----- - -// end::terraform-with-agent[] - -// tag::container-with-agent[] -Environment variables configured for an AWS Lambda function are passed to the container running the lambda function. -You can use one of the other options (through AWS Web Console, AWS CLI, etc.) to configure the following environment variables: - -[source,bash] ----- -AWS_LAMBDA_EXEC_WRAPPER = /opt/python/bin/elasticapm-lambda # use this exact fixed value -ELASTIC_APM_LAMBDA_APM_SERVER = # this is your APM Server URL -ELASTIC_APM_SECRET_TOKEN = # this is your APM secret token -ELASTIC_APM_SEND_STRATEGY = background <1> ----- - -// end::container-with-agent[] diff --git a/docs/lambda/python-arn-replacement.asciidoc b/docs/lambda/python-arn-replacement.asciidoc deleted file mode 100644 index 24d9d1a7f..000000000 --- a/docs/lambda/python-arn-replacement.asciidoc +++ /dev/null @@ -1,9 +0,0 @@ -++++ - -++++ \ No newline at end of file diff --git a/docs/logging.asciidoc b/docs/logging.asciidoc deleted file mode 100644 index 8f51edd50..000000000 --- a/docs/logging.asciidoc +++ /dev/null @@ -1,175 +0,0 @@ -[[logs]] -== Logs - -Elastic Python APM Agent provides the following log features: - -- <> : Automatically inject correlation IDs that allow navigation between logs, traces and services. -- <> : Automatically reformat plaintext logs in {ecs-logging-ref}/intro.html[ECS logging] format. - -NOTE: Elastic Python APM Agent does not send the logs to Elasticsearch. It only -injects correlation IDs and reformats the logs. You must use another ingestion -strategy. We recommend https://www.elastic.co/beats/filebeat[Filebeat] for that purpose. - -Those features are part of {observability-guide}/application-logs.html[Application log ingestion strategies]. - -The {ecs-logging-python-ref}/intro.html[`ecs-logging-python`] library can also be used to use the {ecs-logging-ref}/intro.html[ECS logging] format without an APM agent. -When deployed with the Python APM agent, the agent will provide <> IDs. - -[float] -[[log-correlation-ids]] -=== Log correlation - -{apm-guide-ref}/log-correlation.html[Log correlation] allows you to navigate to all logs belonging to a particular trace -and vice-versa: for a specific log, see in which context it has been logged and which parameters the user provided. - -The Agent provides integrations with both the default Python logging library, -as well as http://www.structlog.org/en/stable/[`structlog`]. - -* <> -* <> - -[float] -[[logging-integrations]] -==== Logging integrations - -[float] -[[logging]] -===== `logging` - -We use https://docs.python.org/3/library/logging.html#logging.setLogRecordFactory[`logging.setLogRecordFactory()`] -to decorate the default LogRecordFactory to automatically add new attributes to -each LogRecord object: - -* `elasticapm_transaction_id` -* `elasticapm_trace_id` -* `elasticapm_span_id` - -This factory also adds these fields to a dictionary attribute, -`elasticapm_labels`, using the official ECS https://www.elastic.co/guide/en/ecs/current/ecs-tracing.html[tracing fields]. - -You can disable this automatic behavior by using the -<> setting -in your configuration. - -[float] -[[structlog]] -===== `structlog` - -We provide a http://www.structlog.org/en/stable/processors.html[processor] for -http://www.structlog.org/en/stable/[`structlog`] which will add three new keys -to the event_dict of any processed event: - -* `transaction.id` -* `trace.id` -* `span.id` - -[source,python] ----- -from structlog import PrintLogger, wrap_logger -from structlog.processors import JSONRenderer -from elasticapm.handlers.structlog import structlog_processor - -wrapped_logger = PrintLogger() -logger = wrap_logger(wrapped_logger, processors=[structlog_processor, JSONRenderer()]) -log = logger.new() -log.msg("some_event") ----- - -[float] -===== Use structlog for agent-internal logging - -The Elastic APM Python agent uses logging to log internal events and issues. -By default, it will use a `logging` logger. -If your project uses structlog, you can tell the agent to use a structlog logger -by setting the environment variable `ELASTIC_APM_USE_STRUCTLOG` to `true`. - -[float] -[[log-correlation-in-es]] -=== Log correlation in Elasticsearch - -In order to correlate logs from your app with transactions captured by the -Elastic APM Python Agent, your logs must contain one or more of the following -identifiers: - -* `transaction.id` -* `trace.id` -* `span.id` - -If you're using structured logging, either https://docs.python.org/3/howto/logging-cookbook.html#implementing-structured-logging[with a custom solution] -or with http://www.structlog.org/en/stable/[structlog] (recommended), then this -is fairly easy. Throw the http://www.structlog.org/en/stable/api.html#structlog.processors.JSONRenderer[JSONRenderer] -in, and use {blog-ref}structured-logging-filebeat[Filebeat] -to pull these logs into Elasticsearch. - -Without structured logging the task gets a little trickier. Here we -recommend first making sure your LogRecord objects have the elasticapm -attributes (see <>), and then you'll want to combine some specific -formatting with a Grok pattern, either in Elasticsearch using -{ref}/grok-processor.html[the grok processor], -or in {logstash-ref}/plugins-filters-grok.html[logstash with a plugin]. - -Say you have a https://docs.python.org/3/library/logging.html#logging.Formatter[Formatter] -that looks like this: - -[source,python] ----- -import logging - -fh = logging.FileHandler('spam.log') -formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") -fh.setFormatter(formatter) ----- - -You can add the APM identifiers by simply switching out the `Formatter` object -for the one that we provide: - -[source,python] ----- -import logging -from elasticapm.handlers.logging import Formatter - -fh = logging.FileHandler('spam.log') -formatter = Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") -fh.setFormatter(formatter) ----- - -This will automatically append apm-specific fields to your format string: - -[source,python] ----- -formatstring = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" -formatstring = formatstring + " | elasticapm " \ - "transaction.id=%(elasticapm_transaction_id)s " \ - "trace.id=%(elasticapm_trace_id)s " \ - "span.id=%(elasticapm_span_id)s" ----- - -Then, you could use a grok pattern like this (for the -{ref}/grok-processor.html[Elasticsearch Grok Processor]): - -[source, json] ----- -{ - "description" : "...", - "processors": [ - { - "grok": { - "field": "message", - "patterns": ["%{GREEDYDATA:msg} | elasticapm transaction.id=%{DATA:transaction.id} trace.id=%{DATA:trace.id} span.id=%{DATA:span.id}"] - } - } - ] -} ----- - -[float] -[[log-reformatting]] -=== Log reformatting (experimental) - -Starting in version 6.16.0, the agent can automatically reformat application -logs to ECS format with no changes to dependencies. Prior versions must install -the `ecs_logging` dependency. - -Log reformatting is controlled by the <> configuration option, and is disabled by default. - -The reformatted logs will include both the <> IDs. diff --git a/docs/metrics.asciidoc b/docs/metrics.asciidoc deleted file mode 100644 index 2d7ae6216..000000000 --- a/docs/metrics.asciidoc +++ /dev/null @@ -1,215 +0,0 @@ -[[metrics]] -== Metrics - -With Elastic APM, you can capture system and process metrics. -These metrics will be sent regularly to the APM Server and from there to Elasticsearch - -[float] -[[metric-sets]] -=== Metric sets - -* <> -* <> -* <> -* <> - -[float] -[[cpu-memory-metricset]] -==== CPU/Memory metric set - -`elasticapm.metrics.sets.cpu.CPUMetricSet` - -This metric set collects various system metrics and metrics of the current process. - -NOTE: if you do *not* use Linux, you need to install https://pypi.org/project/psutil/[`psutil`] for this metric set. - - -*`system.cpu.total.norm.pct`*:: -+ --- -type: scaled_float - -format: percent - -The percentage of CPU time in states other than Idle and IOWait, normalized by the number of cores. --- - - -*`system.process.cpu.total.norm.pct`*:: -+ --- -type: scaled_float - -format: percent - -The percentage of CPU time spent by the process since the last event. -This value is normalized by the number of CPU cores and it ranges from 0 to 100%. --- - -*`system.memory.total`*:: -+ --- -type: long - -format: bytes - -Total memory. --- - -*`system.memory.actual.free`*:: -+ --- -type: long - -format: bytes - -Actual free memory in bytes. --- - -*`system.process.memory.size`*:: -+ --- -type: long - -format: bytes - -The total virtual memory the process has. --- - -*`system.process.memory.rss.bytes`*:: -+ --- -type: long - -format: bytes - -The Resident Set Size. The amount of memory the process occupied in main memory (RAM). --- - -[float] -[[cpu-memory-cgroup-metricset]] -===== Linux’s cgroup metrics - -*`system.process.cgroup.memory.mem.limit.bytes`*:: -+ --- -type: long - -format: bytes - -Memory limit for current cgroup slice. --- - -*`system.process.cgroup.memory.mem.usage.bytes`*:: -+ --- -type: long - -format: bytes - -Memory usage in current cgroup slice. --- - - -[float] -[[breakdown-metricset]] -==== Breakdown metric set - -NOTE: Tracking and collection of this metric set can be disabled using the <> setting. - -*`span.self_time`*:: -+ --- -type: simple timer - -This timer tracks the span self-times and is the basis of the transaction breakdown visualization. - -Fields: - -* `sum`: The sum of all span self-times in ms since the last report (the delta) -* `count`: The count of all span self-times since the last report (the delta) - -You can filter and group by these dimensions: - -* `transaction.name`: The name of the transaction -* `transaction.type`: The type of the transaction, for example `request` -* `span.type`: The type of the span, for example `app`, `template` or `db` -* `span.subtype`: The sub-type of the span, for example `mysql` (optional) - --- -[float] -[[prometheus-metricset]] -==== Prometheus metric set (beta) - -beta[] - -If you use https://github.com/prometheus/client_python[`prometheus_client`] to collect metrics, the agent can -collect them as well and make them available in Elasticsearch. - -The following types of metrics are supported: - - * Counters - * Gauges - * Summaries - * Histograms (requires APM Server / Elasticsearch / Kibana 7.14+) - -To use the Prometheus metric set, you have to enable it with the <> configuration option. - -All metrics collected from `prometheus_client` are prefixed with `"prometheus.metrics."`. This can be changed using the <> configuration option. - -[float] -[[prometheus-metricset-beta]] -===== Beta limitations - * The metrics format may change without backwards compatibility in future releases. - -[float] -[[custom-metrics]] -=== Custom Metrics - -Custom metrics allow you to send your own metrics to Elasticsearch. - -The most common way to send custom metrics is with the -<>. However, you can also use your -own metric set. If you collect the metrics manually in your code, you can use -the base `MetricSet` class: - -[source,python] ----- -from elasticapm.metrics.base_metrics import MetricSet - -client = elasticapm.Client() -metricset = client.metrics.register(MetricSet) - -for x in range(10): - metricset.counter("my_counter").inc() ----- - -Alternatively, you can create your own MetricSet class which inherits from the -base class. In this case, you'll usually want to override the `before_collect` -method, where you can gather and set metrics before they are collected and sent -to Elasticsearch. - -You can add your `MetricSet` class as shown in the example above, or you can -add an import string for your class to the <> -configuration option: - -[source,bash] ----- -ELASTIC_APM_METRICS_SETS="elasticapm.metrics.sets.cpu.CPUMetricSet,myapp.metrics.MyMetricSet" ----- - -Your MetricSet might look something like this: - -[source,python] ----- -from elasticapm.metrics.base_metrics import MetricSet - -class MyAwesomeMetricSet(MetricSet): - def before_collect(self): - self.gauge("my_gauge").set(myapp.some_value) ----- - -In the example above, the MetricSet would look up `myapp.some_value` and set -the metric `my_gauge` to that value. This would happen whenever metrics are -collected/sent, which is controlled by the -<> setting. \ No newline at end of file diff --git a/docs/opentelemetry.asciidoc b/docs/opentelemetry.asciidoc deleted file mode 100644 index ee3800376..000000000 --- a/docs/opentelemetry.asciidoc +++ /dev/null @@ -1,76 +0,0 @@ -[[opentelemetry-bridge]] -== OpenTelemetry API Bridge - -The Elastic APM OpenTelemetry bridge allows you to create Elastic APM `Transactions` and `Spans`, -using the OpenTelemetry API. This allows users to utilize the Elastic APM agent's -automatic instrumentations, while keeping custom instrumentations vendor neutral. - -If a span is created while there is no transaction active, it will result in an -Elastic APM {apm-guide-ref}/data-model-transactions.html[`Transaction`]. Inner spans -are mapped to Elastic APM {apm-guide-ref}/data-model-spans.html[`Span`]. - -[float] -[[opentelemetry-getting-started]] -=== Getting started -The first step in getting started with the OpenTelemetry bridge is to install the `opentelemetry` libraries: - -[source,bash] ----- -pip install elastic-apm[opentelemetry] ----- - -Or if you already have installed `elastic-apm`: - - -[source,bash] ----- -pip install opentelemetry-api opentelemetry-sdk ----- - - -[float] -[[opentelemetry-usage]] -=== Usage - -[source,python] ----- -from elasticapm.contrib.opentelemetry import Tracer - -tracer = Tracer(__name__) -with tracer.start_as_current_span("test"): - # Do some work ----- - -or - -[source,python] ----- -from elasticapm.contrib.opentelemetry import trace - -tracer = trace.get_tracer(__name__) -with tracer.start_as_current_span("test"): - # Do some work ----- - - -`Tracer` and `get_tracer()` accept the following optional arguments: - - * `elasticapm_client`: an already instantiated Elastic APM client - * `config`: a configuration dictionary, which will be used to instantiate a new Elastic APM client, - e.g. `{"SERVER_URL": "https://example.org"}`. See <> for more information. - -The `Tracer` object mirrors the upstream interface on the -https://opentelemetry-python.readthedocs.io/en/latest/api/trace.html#opentelemetry.trace.Tracer[OpenTelemetry `Tracer` object.] - - -[float] -[[opentelemetry-caveats]] -=== Caveats -Not all features of the OpenTelemetry API are supported. - -Processors, exporters, metrics, logs, span events, and span links are not supported. - -Additionally, due to implementation details, the global context API only works -when a span is included in the activated context, and tokens are not used. -Instead, the global context works as a stack, and when a context is detached the -previously-active context will automatically be activated. diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc deleted file mode 100644 index c924b6efe..000000000 --- a/docs/redirects.asciidoc +++ /dev/null @@ -1,14 +0,0 @@ -["appendix",role="exclude",id="redirects"] -== Deleted pages - -The following pages have moved or been deleted. - -[role="exclude",id="opentracing-bridge"] -=== OpenTracing API - -Refer to <> instead. - -[role="exclude",id="log-correlation"] -=== Log correlation - -Refer to <> instead. diff --git a/docs/reference/advanced-topics.md b/docs/reference/advanced-topics.md new file mode 100644 index 000000000..aade7f2df --- /dev/null +++ b/docs/reference/advanced-topics.md @@ -0,0 +1,16 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/advanced-topics.html +--- + +# Advanced topics [advanced-topics] + +* [Instrumenting custom code](/reference/instrumenting-custom-code.md) +* [Sanitizing data](/reference/sanitizing-data.md) +* [How the Agent works](/reference/how-agent-works.md) +* [Run Tests Locally](/reference/run-tests-locally.md) + + + + + diff --git a/docs/reference/aiohttp-server-support.md b/docs/reference/aiohttp-server-support.md new file mode 100644 index 000000000..fcde2cdab --- /dev/null +++ b/docs/reference/aiohttp-server-support.md @@ -0,0 +1,112 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/aiohttp-server-support.html +--- + +# Aiohttp Server support [aiohttp-server-support] + +Getting Elastic APM set up for your Aiohttp Server project is easy, and there are various ways you can tweak it to fit to your needs. + + +## Installation [aiohttp-server-installation] + +Install the Elastic APM agent using pip: + +```bash +$ pip install elastic-apm +``` + +or add `elastic-apm` to your project’s `requirements.txt` file. + + +## Setup [aiohttp-server-setup] + +To set up the agent, you need to initialize it with appropriate settings. + +The settings are configured either via environment variables, the application’s settings, or as initialization arguments. + +You can find a list of all available settings in the [Configuration](/reference/configuration.md) page. + +To initialize the agent for your application using environment variables: + +```python +from aiohttp import web + +from elasticapm.contrib.aiohttp import ElasticAPM + +app = web.Application() + +apm = ElasticAPM(app) +``` + +To configure the agent using `ELASTIC_APM` in your application’s settings: + +```python +from aiohttp import web + +from elasticapm.contrib.aiohttp import ElasticAPM + +app = web.Application() + +app['ELASTIC_APM'] = { + 'SERVICE_NAME': '', + 'SECRET_TOKEN': '', +} +apm = ElasticAPM(app) +``` + + +## Usage [aiohttp-server-usage] + +Once you have configured the agent, it will automatically track transactions and capture uncaught exceptions within aiohttp. + +Capture an arbitrary exception by calling [`capture_exception`](/reference/api-reference.md#client-api-capture-exception): + +```python +try: + 1 / 0 +except ZeroDivisionError: + apm.client.capture_exception() +``` + +Log a generic message with [`capture_message`](/reference/api-reference.md#client-api-capture-message): + +```python +apm.client.capture_message('hello, world!') +``` + + +## Performance metrics [aiohttp-server-performance-metrics] + +If you’ve followed the instructions above, the agent has already installed our middleware. This will measure response times, as well as detailed performance data for all supported technologies. + +::::{note} +due to the fact that `asyncio` drivers are usually separate from their synchronous counterparts, specific instrumentation is needed for all drivers. The support for asynchronous drivers is currently quite limited. +:::: + + + +### Ignoring specific routes [aiohttp-server-ignoring-specific-views] + +You can use the [`TRANSACTIONS_IGNORE_PATTERNS`](/reference/configuration.md#config-transactions-ignore-patterns) configuration option to ignore specific routes. The list given should be a list of regular expressions which are matched against the transaction name: + +```python +app['ELASTIC_APM'] = { + # ... + 'TRANSACTIONS_IGNORE_PATTERNS': ['^OPTIONS ', '/api/'] + # ... +} +``` + +This would ignore any requests using the `OPTIONS` method and any requests containing `/api/`. + + +## Supported aiohttp and Python versions [supported-aiohttp-and-python-versions] + +A list of supported [aiohttp](/reference/supported-technologies.md#supported-aiohttp) and [Python](/reference/supported-technologies.md#supported-python) versions can be found on our [Supported Technologies](/reference/supported-technologies.md) page. + +::::{note} +Elastic APM only supports `asyncio` when using Python 3.7+ +:::: + + diff --git a/docs/reference/api-reference.md b/docs/reference/api-reference.md new file mode 100644 index 000000000..23cc9cc58 --- /dev/null +++ b/docs/reference/api-reference.md @@ -0,0 +1,463 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/api.html +--- + +# API reference [api] + +The Elastic APM Python agent has several public APIs. Most of the public API functionality is not needed when using one of our [supported frameworks](/reference/supported-technologies.md#framework-support), but they allow customized usage. + + +## Client API [client-api] + +The public Client API consists of several methods on the `Client` class. This API can be used to track exceptions and log messages, as well as to mark the beginning and end of transactions. + + +### Instantiation [client-api-init] + +Added in v1.0.0. + +To create a `Client` instance, import it and call its constructor: + +```python +from elasticapm import Client + +client = Client({'SERVICE_NAME': 'example'}, **defaults) +``` + +* `config`: A dictionary, with key/value configuration. For the possible configuration keys, see [Configuration](/reference/configuration.md). +* `**defaults`: default values for configuration. These can be omitted in most cases, and take the least precedence. + +::::{note} +framework integrations like [Django](/reference/django-support.md) and [Flask](/reference/flask-support.md) instantiate the client automatically. +:::: + + + +#### `elasticapm.get_client()` [api-get-client] + +[small]#Added in v6.1.0. + +Retrieves the `Client` singleton. This is useful for many framework integrations, where the client is instantiated automatically. + +```python +client = elasticapm.get_client() +client.capture_message('foo') +``` + + +### Errors [error-api] + + +#### `Client.capture_exception()` [client-api-capture-exception] + +Added in v1.0.0. `handled` added in v2.0.0. + +Captures an exception object: + +```python +try: + x = int("five") +except ValueError: + client.capture_exception() +``` + +* `exc_info`: A `(type, value, traceback)` tuple as returned by [`sys.exc_info()`](https://docs.python.org/3/library/sys.html#sys.exc_info). If not provided, it will be captured automatically. +* `date`: A `datetime.datetime` object representing the occurrence time of the error. If left empty, it defaults to `datetime.datetime.utcnow()`. +* `context`: A dictionary with contextual information. This dictionary must follow the [Context](docs-content://solutions/observability/apps/elastic-apm-events-intake-api.md#apm-api-error) schema definition. +* `custom`: A dictionary of custom data you want to attach to the event. +* `handled`: A boolean to indicate if this exception was handled or not. + +Returns the id of the error as a string. + + +#### `Client.capture_message()` [client-api-capture-message] + +Added in v1.0.0. + +Captures a message with optional added contextual data. Example: + +```python +client.capture_message('Billing process succeeded.') +``` + +* `message`: The message as a string. +* `param_message`: Alternatively, a parameterized message as a dictionary. The dictionary contains two values: `message`, and `params`. This allows the APM Server to group messages together that share the same parameterized message. Example: + + ```python + client.capture_message(param_message={ + 'message': 'Billing process for %s succeeded. Amount: %s', + 'params': (customer.id, order.total_amount), + }) + ``` + +* `stack`: If set to `True` (the default), a stacktrace from the call site will be captured. +* `exc_info`: A `(type, value, traceback)` tuple as returned by [`sys.exc_info()`](https://docs.python.org/3/library/sys.html#sys.exc_info). If not provided, it will be captured automatically, if `capture_message()` was called in an `except` block. +* `date`: A `datetime.datetime` object representing the occurrence time of the error. If left empty, it defaults to `datetime.datetime.utcnow()`. +* `context`: A dictionary with contextual information. This dictionary must follow the [Context](docs-content://solutions/observability/apps/elastic-apm-events-intake-api.md#apm-api-error) schema definition. +* `custom`: A dictionary of custom data you want to attach to the event. + +Returns the id of the message as a string. + +::::{note} +Either the `message` or the `param_message` argument is required. +:::: + + + +### Transactions [transaction-api] + + +#### `Client.begin_transaction()` [client-api-begin-transaction] + +Added in v1.0.0. `trace_parent` support added in v5.6.0. + +Begin tracking a transaction. Should be called e.g. at the beginning of a request or when starting a background task. Example: + +```python +client.begin_transaction('processors') +``` + +* `transaction_type`: (**required**) A string describing the type of the transaction, e.g. `'request'` or `'celery'`. +* `trace_parent`: (**optional**) A `TraceParent` object. See [TraceParent generation](#traceparent-api). +* `links`: (**optional**) A list of `TraceParent` objects to which this transaction is causally linked. + + +#### `Client.end_transaction()` [client-api-end-transaction] + +Added in v1.0.0. + +End tracking the transaction. Should be called e.g. at the end of a request or when ending a background task. Example: + +```python +client.end_transaction('myapp.billing_process', processor.status) +``` + +* `name`: (**optional**) A string describing the name of the transaction, e.g. `process_order`. This is typically the name of the view/controller that handles the request, or the route name. +* `result`: (**optional**) A string describing the result of the transaction. This is typically the HTTP status code, or e.g. `'success'` for a background task. + +::::{note} +if `name` and `result` are not set in the `end_transaction()` call, they have to be set beforehand by calling [`elasticapm.set_transaction_name()`](#api-set-transaction-name) and [`elasticapm.set_transaction_result()`](#api-set-transaction-result) during the transaction. +:::: + + + +### `TraceParent` [traceparent-api] + +Transactions can be started with a `TraceParent` object. This creates a transaction that is a child of the `TraceParent`, which is essential for distributed tracing. + + +#### `elasticapm.trace_parent_from_string()` [api-traceparent-from-string] + +Added in v5.6.0. + +Create a `TraceParent` object from the string representation generated by `TraceParent.to_string()`: + +```python +parent = elasticapm.trace_parent_from_string('00-03d67dcdd62b7c0f7a675424347eee3a-5f0e87be26015733-01') +client.begin_transaction('processors', trace_parent=parent) +``` + +* `traceparent_string`: (**required**) A string representation of a `TraceParent` object. + + +#### `elasticapm.trace_parent_from_headers()` [api-traceparent-from-headers] + +Added in v5.6.0. + +Create a `TraceParent` object from HTTP headers (usually generated by another Elastic APM agent): + +```python +parent = elasticapm.trace_parent_from_headers(headers_dict) +client.begin_transaction('processors', trace_parent=parent) +``` + +* `headers`: (**required**) HTTP headers formed as a dictionary. + + +#### `elasticapm.get_trace_parent_header()` [api-traceparent-get-header] + +Added in v5.10.0. + +Return the string representation of the current transaction `TraceParent` object: + +```python +elasticapm.get_trace_parent_header() +``` + + +## Other APIs [api-other] + + +### `elasticapm.instrument()` [api-elasticapm-instrument] + +Added in v1.0.0. + +Instruments libraries automatically. This includes a wide range of standard library and 3rd party modules. A list of instrumented modules can be found in `elasticapm.instrumentation.register`. This function should be called as early as possibly in the startup of your application. For [supported frameworks](/reference/supported-technologies.md#framework-support), this is called automatically. Example: + +```python +import elasticapm + +elasticapm.instrument() +``` + + +### `elasticapm.set_transaction_name()` [api-set-transaction-name] + +Added in v1.0.0. + +Set the name of the current transaction. For supported frameworks, the transaction name is determined automatically, and can be overridden using this function. Example: + +```python +import elasticapm + +elasticapm.set_transaction_name('myapp.billing_process') +``` + +* `name`: (**required**) A string describing name of the transaction +* `override`: if `True` (the default), overrides any previously set transaction name. If `False`, only sets the name if the transaction name hasn’t already been set. + + +### `elasticapm.set_transaction_result()` [api-set-transaction-result] + +Added in v2.2.0. + +Set the result of the current transaction. For supported frameworks, the transaction result is determined automatically, and can be overridden using this function. Example: + +```python +import elasticapm + +elasticapm.set_transaction_result('SUCCESS') +``` + +* `result`: (**required**) A string describing the result of the transaction, e.g. `HTTP 2xx` or `SUCCESS` +* `override`: if `True` (the default), overrides any previously set result. If `False`, only sets the result if the result hasn’t already been set. + + +### `elasticapm.set_transaction_outcome()` [api-set-transaction-outcome] + +Added in v5.9.0. + +Sets the outcome of the transaction. The value can either be `"success"`, `"failure"` or `"unknown"`. This should only be called at the end of a transaction after the outcome is determined. + +The `outcome` is used for error rate calculations. `success` denotes that a transaction has concluded successful, while `failure` indicates that the transaction failed to finish successfully. If the `outcome` is set to `unknown`, the transaction will not be included in error rate calculations. + +For supported web frameworks, the transaction outcome is set automatically if it has not been set yet, based on the HTTP status code. A status code below `500` is considered a `success`, while any value of `500` or higher is counted as a `failure`. + +If your transaction results in an HTTP response, you can alternatively provide the HTTP status code. + +::::{note} +While the `outcome` and `result` field look very similar, they serve different purposes. Other than the `result` field, which canhold an arbitrary string value, `outcome` is limited to three different values, `"success"`, `"failure"` and `"unknown"`. This allows the APM app to perform error rate calculations on these values. +:::: + + +Example: + +```python +import elasticapm + +elasticapm.set_transaction_outcome("success") + +# Using an HTTP status code +elasticapm.set_transaction_outcome(http_status_code=200) + +# Using predefined constants: + +from elasticapm.conf.constants import OUTCOME + +elasticapm.set_transaction_outcome(OUTCOME.SUCCESS) +elasticapm.set_transaction_outcome(OUTCOME.FAILURE) +elasticapm.set_transaction_outcome(OUTCOME.UNKNOWN) +``` + +* `outcome`: One of `"success"`, `"failure"` or `"unknown"`. Can be omitted if `http_status_code` is provided. +* `http_status_code`: if the transaction represents an HTTP response, its status code can be provided to determine the `outcome` automatically. +* `override`: if `True` (the default), any previously set `outcome` will be overriden. If `False`, the outcome will only be set if it was not set before. + + +### `elasticapm.get_transaction_id()` [api-get-transaction-id] + +Added in v5.2.0. + +Get the id of the current transaction. Example: + +```python +import elasticapm + +transaction_id = elasticapm.get_transaction_id() +``` + + +### `elasticapm.get_trace_id()` [api-get-trace-id] + +Added in v5.2.0. + +Get the `trace_id` of the current transaction’s trace. Example: + +```python +import elasticapm + +trace_id = elasticapm.get_trace_id() +``` + + +### `elasticapm.get_span_id()` [api-get-span-id] + +Added in v5.2.0. + +Get the id of the current span. Example: + +```python +import elasticapm + +span_id = elasticapm.get_span_id() +``` + + +### `elasticapm.set_custom_context()` [api-set-custom-context] + +Added in v2.0.0. + +Attach custom contextual data to the current transaction and errors. Supported frameworks will automatically attach information about the HTTP request and the logged in user. You can attach further data using this function. + +::::{tip} +Before using custom context, ensure you understand the different types of [metadata](docs-content://solutions/observability/apps/metadata.md) that are available. +:::: + + +Example: + +```python +import elasticapm + +elasticapm.set_custom_context({'billing_amount': product.price * item_count}) +``` + +* `data`: (**required**) A dictionary with the data to be attached. This should be a flat key/value `dict` object. + +::::{note} +`.`, `*`, and `"` are invalid characters for key names and will be replaced with `_`. +:::: + + +Errors that happen after this call will also have the custom context attached to them. You can call this function multiple times, new context data will be merged with existing data, following the `update()` semantics of Python dictionaries. + + +### `elasticapm.set_user_context()` [api-set-user-context] + +Added in v2.0.0. + +Attach information about the currently logged in user to the current transaction and errors. Example: + +```python +import elasticapm + +elasticapm.set_user_context(username=user.username, email=user.email, user_id=user.id) +``` + +* `username`: The username of the logged in user +* `email`: The email of the logged in user +* `user_id`: The unique identifier of the logged in user, e.g. the primary key value + +Errors that happen after this call will also have the user context attached to them. You can call this function multiple times, new user data will be merged with existing data, following the `update()` semantics of Python dictionaries. + + +### `elasticapm.capture_span` [api-capture-span] + +Added in v4.1.0. + +Capture a custom span. This can be used either as a function decorator or as a context manager (in a `with` statement). When used as a decorator, the name of the span will be set to the name of the function. When used as a context manager, a name has to be provided. + +```python +import elasticapm + +@elasticapm.capture_span() +def coffee_maker(strength): + fetch_water() + + with elasticapm.capture_span('near-to-machine', labels={"type": "arabica"}): + insert_filter() + for i in range(strength): + pour_coffee() + + start_drip() + + fresh_pots() +``` + +* `name`: The name of the span. Defaults to the function name if used as a decorator. +* `span_type`: (**optional**) The type of the span, usually in a dot-separated hierarchy of `type`, `subtype`, and `action`, e.g. `db.mysql.query`. Alternatively, type, subtype and action can be provided as three separate arguments, see `span_subtype` and `span_action`. +* `skip_frames`: (**optional**) The number of stack frames to skip when collecting stack traces. Defaults to `0`. +* `leaf`: (**optional**) if `True`, all spans nested bellow this span will be ignored. Defaults to `False`. +* `labels`: (**optional**) a dictionary of labels. Keys must be strings, values can be strings, booleans, or numerical (`int`, `float`, `decimal.Decimal`). Defaults to `None`. +* `span_subtype`: (**optional**) subtype of the span, e.g. name of the database. Defaults to `None`. +* `span_action`: (**optional**) action of the span, e.g. `query`. Defaults to `None`. +* `links`: (**optional**) A list of `TraceParent` objects to which this span is causally linked. + + +### `elasticapm.async_capture_span` [api-async-capture-span] + +Added in v5.4.0. + +Capture a custom async-aware span. This can be used either as a function decorator or as a context manager (in an `async with` statement). When used as a decorator, the name of the span will be set to the name of the function. When used as a context manager, a name has to be provided. + +```python +import elasticapm + +@elasticapm.async_capture_span() +async def coffee_maker(strength): + await fetch_water() + + async with elasticapm.async_capture_span('near-to-machine', labels={"type": "arabica"}): + await insert_filter() + async for i in range(strength): + await pour_coffee() + + start_drip() + + fresh_pots() +``` + +* `name`: The name of the span. Defaults to the function name if used as a decorator. +* `span_type`: (**optional**) The type of the span, usually in a dot-separated hierarchy of `type`, `subtype`, and `action`, e.g. `db.mysql.query`. Alternatively, type, subtype and action can be provided as three separate arguments, see `span_subtype` and `span_action`. +* `skip_frames`: (**optional**) The number of stack frames to skip when collecting stack traces. Defaults to `0`. +* `leaf`: (**optional**) if `True`, all spans nested bellow this span will be ignored. Defaults to `False`. +* `labels`: (**optional**) a dictionary of labels. Keys must be strings, values can be strings, booleans, or numerical (`int`, `float`, `decimal.Decimal`). Defaults to `None`. +* `span_subtype`: (**optional**) subtype of the span, e.g. name of the database. Defaults to `None`. +* `span_action`: (**optional**) action of the span, e.g. `query`. Defaults to `None`. +* `links`: (**optional**) A list of `TraceParent` objects to which this span is causally linked. + +::::{note} +`asyncio` is only supported for Python 3.7+. +:::: + + + +### `elasticapm.label()` [api-label] + +Added in v5.0.0. + +Attach labels to the the current transaction and errors. + +::::{tip} +Before using custom labels, ensure you understand the different types of [metadata](docs-content://solutions/observability/apps/metadata.md) that are available. +:::: + + +Example: + +```python +import elasticapm + +elasticapm.label(ecommerce=True, dollar_value=47.12) +``` + +Errors that happen after this call will also have the labels attached to them. You can call this function multiple times, new labels will be merged with existing labels, following the `update()` semantics of Python dictionaries. + +Keys must be strings, values can be strings, booleans, or numerical (`int`, `float`, `decimal.Decimal`) `.`, `*`, and `"` are invalid characters for label names and will be replaced with `_`. + +::::{warning} +Avoid defining too many user-specified labels. Defining too many unique fields in an index is a condition that can lead to a [mapping explosion](docs-content://manage-data/data-store/mapping.md#mapping-limit-settings). +:::: + + diff --git a/docs/reference/asgi-middleware.md b/docs/reference/asgi-middleware.md new file mode 100644 index 000000000..852f12565 --- /dev/null +++ b/docs/reference/asgi-middleware.md @@ -0,0 +1,66 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/asgi-middleware.html +--- + +# ASGI Middleware [asgi-middleware] + +::::{warning} +This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. +:::: + + +Incorporating Elastic APM into your ASGI-based project only requires a few easy steps. + +::::{note} +Several ASGI frameworks are supported natively. Please check [Supported Technologies](/reference/supported-technologies.md) for more information +:::: + + + +## Installation [asgi-installation] + +Install the Elastic APM agent using pip: + +```bash +$ pip install elastic-apm +``` + +or add `elastic-apm` to your project’s `requirements.txt` file. + + +## Setup [asgi-setup] + +To set up the agent, you need to initialize it with appropriate settings. + +The settings are configured either via environment variables, or as initialization arguments. + +You can find a list of all available settings in the [Configuration](/reference/configuration.md) page. + +To set up the APM agent, wrap your ASGI app with the `ASGITracingMiddleware`: + +```python +from elasticapm.contrib.asgi import ASGITracingMiddleware + +app = MyGenericASGIApp() # depending on framework + +app = ASGITracingMiddleware(app) +``` + +Make sure to call [`elasticapm.set_transaction_name()`](/reference/api-reference.md#api-set-transaction-name) with an appropriate transaction name in all your routes. + +::::{note} +Currently, the agent doesn’t support automatic capturing of exceptions. You can follow progress on this issue on [Github](https://github.com/elastic/apm-agent-python/issues/1548). +:::: + + + +## Supported Python versions [supported-python-versions] + +A list of supported [Python](/reference/supported-technologies.md#supported-python) versions can be found on our [Supported Technologies](/reference/supported-technologies.md) page. + +::::{note} +Elastic APM only supports `asyncio` when using Python 3.7+ +:::: + + diff --git a/docs/reference/azure-functions-support.md b/docs/reference/azure-functions-support.md new file mode 100644 index 000000000..88a5d7234 --- /dev/null +++ b/docs/reference/azure-functions-support.md @@ -0,0 +1,53 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/azure-functions-support.html +--- + +# Monitoring Azure Functions [azure-functions-support] + + +## Prerequisites [_prerequisites_2] + +You need an APM Server to which you can send APM data. Follow the [APM Quick start](docs-content://solutions/observability/apps/fleet-managed-apm-server.md) if you have not set one up yet. For the best-possible performance, we recommend setting up APM on {{ecloud}} in the same Azure region as your Azure Functions app. + +::::{note} +Currently, only HTTP and timer triggers are supported. Other trigger types may be captured as well, but the amount of captured contextual data may differ. +:::: + + + +## Step 1: Enable Worker Extensions [_step_1_enable_worker_extensions] + +Elastic APM uses [Worker Extensions](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-python?tabs=asgi%2Capplication-level&pivots=python-mode-configuration#python-worker-extensions) to instrument Azure Functions. This feature is not enabled by default, and must be enabled in your Azure Functions App. Please follow the instructions in the [Azure docs](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-python?tabs=asgi%2Capplication-level&pivots=python-mode-configuration#using-extensions). + +Once you have enabled Worker Extensions, these two lines of code will enable Elastic APM’s extension: + +```python +from elasticapm.contrib.serverless.azure import ElasticAPMExtension + +ElasticAPMExtension.configure() +``` + +Put them somewhere at the top of your Python file, before the function definitions. + + +## Step 2: Install the APM Python Agent [_step_2_install_the_apm_python_agent] + +You need to add `elastic-apm` as a dependency for your Functions app. Simply add `elastic-apm` to your `requirements.txt` file. We recommend pinning the version to the current newest version of the agent, and periodically updating the version. + + +## Step 3: Configure APM on Azure Functions [_step_3_configure_apm_on_azure_functions] + +The APM Python agent is configured through [App Settings](https://learn.microsoft.com/en-us/azure/azure-functions/functions-how-to-use-azure-function-app-settings?tabs=portal#settings). These are then picked up by the agent as environment variables. + +For the minimal configuration, you will need the [`ELASTIC_APM_SERVER_URL`](/reference/configuration.md#config-server-url) to set the destination for APM data and a [`ELASTIC_APM_SECRET_TOKEN`](/reference/configuration.md#config-secret-token). If you prefer to use an [APM API key](docs-content://solutions/observability/apps/api-keys.md) instead of the APM secret token, use the [`ELASTIC_APM_API_KEY`](/reference/configuration.md#config-api-key) environment variable instead of `ELASTIC_APM_SECRET_TOKEN` in the following example configuration. + +```bash +$ az functionapp config appsettings set --settings ELASTIC_APM_SERVER_URL=https://example.apm.northeurope.azure.elastic-cloud.com:443 +$ az functionapp config appsettings set --settings ELASTIC_APM_SECRET_TOKEN=verysecurerandomstring +``` + +You can optionally [fine-tune the Python agent](/reference/configuration.md). + +That’s it; Once the agent is installed and working, spans will be captured for [supported technologies](/reference/supported-technologies.md). You can also use [`capture_span`](/reference/api-reference.md#api-capture-span) to capture custom spans, and you can retrieve the `Client` object for capturing exceptions/messages using [`get_client`](/reference/api-reference.md#api-get-client). + diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md new file mode 100644 index 000000000..2930b1587 --- /dev/null +++ b/docs/reference/configuration.md @@ -0,0 +1,1067 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/configuration.html +--- + +# Configuration [configuration] + +To adapt the Elastic APM agent to your needs, configure it using environment variables or framework-specific configuration. + +You can either configure the agent by setting environment variables: + +```bash +ELASTIC_APM_SERVICE_NAME=foo python manage.py runserver +``` + +or with inline configuration: + +```python +apm_client = Client(service_name="foo") +``` + +or by using framework specific configuration e.g. in your Django `settings.py` file: + +```python +ELASTIC_APM = { + "SERVICE_NAME": "foo", +} +``` + +The precedence is as follows: + +* [Central configuration](#config-central_config) (supported options are marked with [![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration)) +* Environment variables +* Inline configuration +* Framework-specific configuration +* Default value + + +## Dynamic configuration [dynamic-configuration] + +Configuration options marked with the ![dynamic config](../images/dynamic-config.svg "") badge can be changed at runtime when set from a supported source. + +The Python Agent supports [Central configuration](docs-content://solutions/observability/apps/apm-agent-central-configuration.md), which allows you to fine-tune certain configurations from in the APM app. This feature is enabled in the Agent by default with [`central_config`](#config-central_config). + + +## Django [django-configuration] + +To configure Django, add an `ELASTIC_APM` dictionary to your `settings.py`: + +```python +ELASTIC_APM = { + 'SERVICE_NAME': 'my-app', + 'SECRET_TOKEN': 'changeme', +} +``` + + +## Flask [flask-configuration] + +To configure Flask, add an `ELASTIC_APM` dictionary to your `app.config`: + +```python +app.config['ELASTIC_APM'] = { + 'SERVICE_NAME': 'my-app', + 'SECRET_TOKEN': 'changeme', +} + +apm = ElasticAPM(app) +``` + + +## Core options [core-options] + + +### `service_name` [config-service-name] + +| Environment | Django/Flask | Default | Example | +| --- | --- | --- | --- | +| `ELASTIC_APM_SERVICE_NAME` | `SERVICE_NAME` | `unknown-python-service` | `my-app` | + +The name of your service. This is used to keep all the errors and transactions of your service together and is the primary filter in the Elastic APM user interface. + +While a default is provided, it is essential that you override this default with something more descriptive and unique across your infrastructure. + +::::{note} +The service name must conform to this regular expression: `^[a-zA-Z0-9 _-]+$`. In other words, the service name must only contain characters from the ASCII alphabet, numbers, dashes, underscores, and spaces. It cannot be an empty string or whitespace-only. +:::: + + + +### `server_url` [config-server-url] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_SERVER_URL` | `SERVER_URL` | `'http://127.0.0.1:8200'` | + +The URL for your APM Server. The URL must be fully qualified, including protocol (`http` or `https`) and port. Note: Do not set this if you are using APM in an AWS lambda function. APM Agents are designed to proxy their calls to the APM Server through the lambda extension. Instead, set `ELASTIC_APM_LAMBDA_APM_SERVER`. For more info, see [AWS Lambda](/reference/lambda-support.md). + + +## `enabled` [config-enabled] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_ENABLED` | `ENABLED` | `true` | + +Enable or disable the agent. When set to false, the agent will not collect any data or start any background threads. + + +## `recording` [config-recording] + +[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_RECORDING` | `RECORDING` | `true` | + +Enable or disable recording of events. If set to false, then the Python agent does not send any events to the Elastic APM server, and instrumentation overhead is minimized. The agent will continue to poll the server for configuration changes. + + +## Logging Options [logging-options] + + +### `log_level` [config-log_level] + +[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_LOG_LEVEL` | `LOG_LEVEL` | | + +The `logging.logLevel` at which the `elasticapm` logger will log. The available options are: + +* `"off"` (sets `logging.logLevel` to 1000) +* `"critical"` +* `"error"` +* `"warning"` +* `"info"` +* `"debug"` +* `"trace"` (sets `logging.log_level` to 5) + +Options are case-insensitive + +Note that this option doesn’t do anything with logging handlers. In order for any logs to be visible, you must either configure a handler ([`logging.basicConfig`](https://docs.python.org/3/library/logging.html#logging.basicConfig) will do this for you) or set [`log_file`](#config-log_file). This will also override any log level your app has set for the `elasticapm` logger. + + +### `log_file` [config-log_file] + +| Environment | Django/Flask | Default | Example | +| --- | --- | --- | --- | +| `ELASTIC_APM_LOG_FILE` | `LOG_FILE` | `""` | `"/var/log/elasticapm/log.txt"` | + +This enables the agent to log to a file. This is disabled by default. The agent will log at the `logging.logLevel` configured with [`log_level`](#config-log_level). Use [`log_file_size`](#config-log_file_size) to configure the maximum size of the log file. This log file will automatically rotate. + +Note that setting [`log_level`](#config-log_level) is required for this setting to do anything. + +If [`ecs_logging`](https://github.com/elastic/ecs-logging-python) is installed, the logs will automatically be formatted as ecs-compatible json. + + +### `log_file_size` [config-log_file_size] + +| Environment | Django/Flask | Default | Example | +| --- | --- | --- | --- | +| `ELASTIC_APM_LOG_FILE_SIZE` | `LOG_FILE_SIZE` | `"50mb"` | `"100mb"` | + +The size of the log file if [`log_file`](#config-log_file) is set. + +The agent always keeps one backup file when rotating, so the maximum space that the log files will consume is twice the value of this setting. + + +### `log_ecs_reformatting` [config-log_ecs_reformatting] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_LOG_ECS_REFORMATTING` | `LOG_ECS_REFORMATTING` | `"off"` | + +::::{warning} +This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. +:::: + + +Valid options: + +* `"off"` +* `"override"` + +If [`ecs_logging`](https://github.com/elastic/ecs-logging-python) is installed, setting this to `"override"` will cause the agent to automatically attempt to enable ecs-formatted logging. + +For base `logging` from the standard library, the agent will get the root logger, find any attached handlers, and for each, set the formatter to `ecs_logging.StdlibFormatter()`. + +If `structlog` is installed, the agent will override any configured processors with `ecs_logging.StructlogFormatter()`. + +Note that this is a very blunt instrument that could have unintended side effects. If problems arise, please apply these formatters manually and leave this setting as `"off"`. See the [`ecs_logging` docs](ecs-logging-python://reference/installation.md) for more information about using these formatters. + +Also note that this setting does not facilitate shipping logs to Elasticsearch. We recommend [Filebeat](https://www.elastic.co/beats/filebeat) for that purpose. + + +## Other options [other-options] + + +### `transport_class` [config-transport-class] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_TRANSPORT_CLASS` | `TRANSPORT_CLASS` | `elasticapm.transport.http.Transport` | + +The transport class to use when sending events to the APM Server. + + +### `service_node_name` [config-service-node-name] + +| Environment | Django/Flask | Default | Example | +| --- | --- | --- | --- | +| `ELASTIC_APM_SERVICE_NODE_NAME` | `SERVICE_NODE_NAME` | `None` | `"redis1"` | + +The name of the given service node. This is optional and if omitted, the APM Server will fall back on `system.container.id` if available, and `host.name` if necessary. + +This option allows you to set the node name manually to ensure it is unique and meaningful. + + +### `environment` [config-environment] + +| Environment | Django/Flask | Default | Example | +| --- | --- | --- | --- | +| `ELASTIC_APM_ENVIRONMENT` | `ENVIRONMENT` | `None` | `"production"` | + +The name of the environment this service is deployed in, e.g. "production" or "staging". + +Environments allow you to easily filter data on a global level in the APM app. It’s important to be consistent when naming environments across agents. See [environment selector](docs-content://solutions/observability/apps/filter-application-data.md#apm-filter-your-data-service-environment-filter) in the APM app for more information. + +::::{note} +This feature is fully supported in the APM app in Kibana versions >= 7.2. You must use the query bar to filter for a specific environment in versions prior to 7.2. +:::: + + + +### `cloud_provider` [config-cloud-provider] + +| Environment | Django/Flask | Default | Example | +| --- | --- | --- | --- | +| `ELASTIC_APM_CLOUD_PROVIDER` | `CLOUD_PROVIDER` | `"auto"` | `"aws"` | + +This config value allows you to specify which cloud provider should be assumed for metadata collection. By default, the agent will attempt to detect the cloud provider or, if that fails, will use trial and error to collect the metadata. + +Valid options are `"auto"`, `"aws"`, `"gcp"`, and `"azure"`. If this config value is set to `"none"`, then no cloud metadata will be collected. + + +### `secret_token` [config-secret-token] + +| Environment | Django/Flask | Default | Example | +| --- | --- | --- | --- | +| `ELASTIC_APM_SECRET_TOKEN` | `SECRET_TOKEN` | `None` | A random string | + +This string is used to ensure that only your agents can send data to your APM Server. Both the agents and the APM Server have to be configured with the same secret token. An example to generate a secure secret token is: + +```bash +python -c "import secrets; print(secrets.token_urlsafe(32))" +``` + +::::{warning} +Secret tokens only provide any security if your APM Server uses TLS. +:::: + + + +### `api_key` [config-api-key] + +| Environment | Django/Flask | Default | Example | +| --- | --- | --- | --- | +| `ELASTIC_APM_API_KEY` | `API_KEY` | `None` | A base64-encoded string | + +::::{warning} +This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. +:::: + + +This base64-encoded string is used to ensure that only your agents can send data to your APM Server. The API key must be created using the [APM server command-line tool](docs-content://solutions/observability/apps/api-keys.md). + +::::{warning} +API keys only provide any real security if your APM Server uses TLS. +:::: + + + +### `service_version` [config-service-version] + +| Environment | Django/Flask | Default | Example | +| --- | --- | --- | --- | +| `ELASTIC_APM_SERVICE_VERSION` | `SERVICE_VERSION` | `None` | A string indicating the version of the deployed service | + +A version string for the currently deployed version of the service. If youre deploys are not versioned, the recommended value for this field is the commit identifier of the deployed revision, e.g. the output of `git rev-parse HEAD`. + + +### `framework_name` [config-framework-name] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_FRAMEWORK_NAME` | `FRAMEWORK_NAME` | Depending on framework | + +The name of the used framework. For Django and Flask, this defaults to `django` and `flask` respectively, otherwise, the default is `None`. + + +### `framework_version` [config-framework-version] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_FRAMEWORK_VERSION` | `FRAMEWORK_VERSION` | Depending on framework | + +The version number of the used framework. For Django and Flask, this defaults to the used version of the framework, otherwise, the default is `None`. + + +### `filter_exception_types` [config-filter-exception-types] + +| Environment | Django/Flask | Default | Example | +| --- | --- | --- | --- | +| `ELASTIC_APM_FILTER_EXCEPTION_TYPES` | `FILTER_EXCEPTION_TYPES` | `[]` | `['OperationalError', 'mymodule.SomeoneElsesProblemError']` | +| multiple values separated by commas, without spaces | | | | + +A list of exception types to be filtered. Exceptions of these types will not be sent to the APM Server. + + +### `transaction_ignore_urls` [config-transaction-ignore-urls] + +[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) + +| Environment | Django/Flask | Default | Example | +| --- | --- | --- | --- | +| `ELASTIC_APM_TRANSACTION_IGNORE_URLS` | `TRANSACTION_IGNORE_URLS` | `[]` | `['/api/ping', '/static/*']` | +| multiple values separated by commas, without spaces | | | | + +A list of URLs for which the agent should not capture any transaction data. + +Optionally, `*` can be used to match multiple URLs at once. + + +### `transactions_ignore_patterns` [config-transactions-ignore-patterns] + +| Environment | Django/Flask | Default | Example | +| --- | --- | --- | --- | +| `ELASTIC_APM_TRANSACTIONS_IGNORE_PATTERNS` | `TRANSACTIONS_IGNORE_PATTERNS` | `[]` | `['^OPTIONS ', 'myviews.Healthcheck']` | +| multiple values separated by commas, without spaces | | | | + +A list of regular expressions. Transactions with a name that matches any of the configured patterns will be ignored and not sent to the APM Server. + +::::{note} +as the the name of the transaction can only be determined at the end of the transaction, the agent might still cause overhead for transactions ignored through this setting. If agent overhead is a concern, we recommend [`transaction_ignore_urls`](#config-transaction-ignore-urls) instead. +:::: + + + +### `server_timeout` [config-server-timeout] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_SERVER_TIMEOUT` | `SERVER_TIMEOUT` | `"5s"` | + +A timeout for requests to the APM Server. The setting has to be provided in **[duration format](#config-format-duration)**. If a request to the APM Server takes longer than the configured timeout, the request is cancelled and the event (exception or transaction) is discarded. Set to `None` to disable timeouts. + +::::{warning} +If timeouts are disabled or set to a high value, your app could experience memory issues if the APM Server times out. +:::: + + + +### `hostname` [config-hostname] + +| Environment | Django/Flask | Default | Example | +| --- | --- | --- | --- | +| `ELASTIC_APM_HOSTNAME` | `HOSTNAME` | `socket.gethostname()` | `app-server01.example.com` | + +The host name to use when sending error and transaction data to the APM Server. + + +### `auto_log_stacks` [config-auto-log-stacks] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_AUTO_LOG_STACKS` | `AUTO_LOG_STACKS` | `True` | +| set to `"true"` / `"false"` | | | + +If set to `True` (the default), the agent will add a stack trace to each log event, indicating where the log message has been issued. + +This setting can be overridden on an individual basis by setting the `extra`-key `stack`: + +```python +logger.info('something happened', extra={'stack': False}) +``` + + +### `collect_local_variables` [config-collect-local-variables] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_COLLECT_LOCAL_VARIABLES` | `COLLECT_LOCAL_VARIABLES` | `errors` | + +Possible values: `errors`, `transactions`, `all`, `off` + +The Elastic APM Python agent can collect local variables for stack frames. By default, this is only done for errors. + +::::{note} +Collecting local variables has a non-trivial overhead. Collecting local variables for transactions in production environments can have adverse effects for the performance of your service. +:::: + + + +### `local_var_max_length` [config-local-var-max-length] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_LOCAL_VAR_MAX_LENGTH` | `LOCAL_VAR_MAX_LENGTH` | `200` | + +When collecting local variables, they will be converted to strings. This setting allows you to limit the length of the resulting string. + + +### `local_var_list_max_length` [config-local-list-var-max-length] + +| | | | +| --- | --- | --- | +| Environment | Django/Flask | Default | +| `ELASTIC_APM_LOCAL_VAR_LIST_MAX_LENGTH` | `LOCAL_VAR_LIST_MAX_LENGTH` | `10` | + +This setting allows you to limit the length of lists in local variables. + + +### `local_var_dict_max_length` [config-local-dict-var-max-length] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_LOCAL_VAR_DICT_MAX_LENGTH` | `LOCAL_VAR_DICT_MAX_LENGTH` | `10` | + +This setting allows you to limit the length of dicts in local variables. + + +### `source_lines_error_app_frames` [config-source-lines-error-app-frames] + + +### `source_lines_error_library_frames` [config-source-lines-error-library-frames] + + +### `source_lines_span_app_frames` [config-source-lines-span-app-frames] + + +### `source_lines_span_library_frames` [config-source-lines-span-library-frames] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_SOURCE_LINES_ERROR_APP_FRAMES` | `SOURCE_LINES_ERROR_APP_FRAMES` | `5` | +| `ELASTIC_APM_SOURCE_LINES_ERROR_LIBRARY_FRAMES` | `SOURCE_LINES_ERROR_LIBRARY_FRAMES` | `5` | +| `ELASTIC_APM_SOURCE_LINES_SPAN_APP_FRAMES` | `SOURCE_LINES_SPAN_APP_FRAMES` | `0` | +| `ELASTIC_APM_SOURCE_LINES_SPAN_LIBRARY_FRAMES` | `SOURCE_LINES_SPAN_LIBRARY_FRAMES` | `0` | + +By default, the APM agent collects source code snippets for errors. This setting allows you to modify the number of lines of source code that are being collected. + +We differ between errors and spans, as well as library frames and app frames. + +::::{warning} +Especially for spans, collecting source code can have a large impact on storage use in your Elasticsearch cluster. +:::: + + + +### `capture_body` [config-capture-body] + +[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_CAPTURE_BODY` | `CAPTURE_BODY` | `off` | + +For transactions that are HTTP requests, the Python agent can optionally capture the request body (e.g. `POST` variables). + +Possible values: `errors`, `transactions`, `all`, `off`. + +If the request has a body and this setting is disabled, the body will be shown as `[REDACTED]`. + +For requests with a content type of `multipart/form-data`, any uploaded files will be referenced in a special `_files` key. It contains the name of the field and the name of the uploaded file, if provided. + +::::{warning} +Request bodies often contain sensitive values like passwords and credit card numbers. If your service handles data like this, we advise to only enable this feature with care. +:::: + + + +### `capture_headers` [config-capture-headers] + +[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_CAPTURE_HEADERS` | `CAPTURE_HEADERS` | `true` | + +For transactions and errors that happen due to HTTP requests, the Python agent can optionally capture the request and response headers. + +Possible values: `true`, `false` + +::::{warning} +Request headers often contain sensitive values like session IDs and cookies. See [sanitizing data](/reference/sanitizing-data.md) for more information on how to filter out sensitive data. +:::: + + + +### `transaction_max_spans` [config-transaction-max-spans] + +[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_TRANSACTION_MAX_SPANS` | `TRANSACTION_MAX_SPANS` | `500` | + +This limits the amount of spans that are recorded per transaction. This is helpful in cases where a transaction creates a very high amount of spans (e.g. thousands of SQL queries). Setting an upper limit will prevent edge cases from overloading the agent and the APM Server. + + +### `stack_trace_limit` [config-stack-trace-limit] + +[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_STACK_TRACE_LIMIT` | `STACK_TRACE_LIMIT` | `50` | + +This limits the number of frames captured for each stack trace. + +Setting the limit to `0` will disable stack trace collection, while any positive integer value will be used as the maximum number of frames to collect. To disable the limit and always capture all frames, set the value to `-1`. + + +### `span_stack_trace_min_duration` [config-span-stack-trace-min-duration] + +[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_SPAN_STACK_TRACE_MIN_DURATION` | `SPAN_STACK_TRACE_MIN_DURATION` | `"5ms"` | + +By default, the APM agent collects a stack trace with every recorded span that has a duration equal to or longer than this configured threshold. While stack traces are very helpful to find the exact place in your code from which a span originates, collecting this stack trace does have some overhead. Tune this threshold to ensure that you only collect stack traces for spans that could be problematic. + +To collect traces for all spans, regardless of their length, set the value to `0`. + +To disable stack trace collection for spans completely, set the value to `-1`. + +Except for the special values `-1` and `0`, this setting should be provided in **[duration format](#config-format-duration)**. + + +### `span_frames_min_duration` [config-span-frames-min-duration] + +[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_SPAN_FRAMES_MIN_DURATION` | `SPAN_FRAMES_MIN_DURATION` | `"5ms"` | + +::::{note} +This config value is being deprecated. Use [`span_stack_trace_min_duration`](#config-span-stack-trace-min-duration) instead. +:::: + + + +### `span_compression_enabled` [config-span-compression-enabled] + +[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_SPAN_COMPRESSION_ENABLED` | `SPAN_COMPRESSION_ENABLED` | `True` | + +Enable/disable span compression. + +If enabled, the agent will compress very short, repeated spans into a single span, which is beneficial for storage and processing requirements. Some information is lost in this process, e.g. exact durations of each compressed span. + + +### `span_compression_exact_match_max_duration` [config-span-compression-exact-match-max_duration] + +[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_SPAN_COMPRESSION_EXACT_MATCH_MAX_DURATION` | `SPAN_COMPRESSION_EXACT_MATCH_MAX_DURATION` | `"50ms"` | + +Consecutive spans that are exact match and that are under this threshold will be compressed into a single composite span. This reduces the collection, processing, and storage overhead, and removes clutter from the UI. The tradeoff is that the DB statements of all the compressed spans will not be collected. + +Two spans are considered exact matches if the following attributes are identical: * span name * span type * span subtype * destination resource (e.g. the Database name) + + +### `span_compression_same_kind_max_duration` [config-span-compression-same-kind-max-duration] + +[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_SPAN_COMPRESSION_SAME_KIND_MAX_DURATION` | `SPAN_COMPRESSION_SAME_KIND_MAX_DURATION` | `"0ms"` (disabled) | + +Consecutive spans to the same destination that are under this threshold will be compressed into a single composite span. This reduces the collection, processing, and storage overhead, and removes clutter from the UI. The tradeoff is that metadata such as database statements of all the compressed spans will not be collected. + +Two spans are considered to be of the same kind if the following attributes are identical: * span type * span subtype * destination resource (e.g. the Database name) + + +### `exit_span_min_duration` [config-exit-span-min-duration] + +[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_EXIT_SPAN_MIN_DURATION` | `EXIT_SPAN_MIN_DURATION` | `"0ms"` | + +Exit spans are spans that represent a call to an external service, like a database. If such calls are very short, they are usually not relevant and can be ignored. + +This feature is disabled by default. + +::::{note} +if a span propagates distributed tracing IDs, it will not be ignored, even if it is shorter than the configured threshold. This is to ensure that no broken traces are recorded. +:::: + + + +### `api_request_size` [config-api-request-size] + +[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_API_REQUEST_SIZE` | `API_REQUEST_SIZE` | `"768kb"` | + +The maximum queue length of the request buffer before sending the request to the APM Server. A lower value will increase the load on your APM Server, while a higher value can increase the memory pressure of your app. A higher value also impacts the time until data is indexed and searchable in Elasticsearch. + +This setting is useful to limit memory consumption if you experience a sudden spike of traffic. It has to be provided in **[size format](#config-format-size)**. + +::::{note} +Due to internal buffering of gzip, the actual request size can be a few kilobytes larger than the given limit. By default, the APM Server limits request payload size to `1 MByte`. +:::: + + + +### `api_request_time` [config-api-request-time] + +[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_API_REQUEST_TIME` | `API_REQUEST_TIME` | `"10s"` | + +The maximum queue time of the request buffer before sending the request to the APM Server. A lower value will increase the load on your APM Server, while a higher value can increase the memory pressure of your app. A higher value also impacts the time until data is indexed and searchable in Elasticsearch. + +This setting is useful to limit memory consumption if you experience a sudden spike of traffic. It has to be provided in **[duration format](#config-format-duration)**. + +::::{note} +The actual time will vary between 90-110% of the given value, to avoid stampedes of instances that start at the same time. +:::: + + + +### `processors` [config-processors] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_PROCESSORS` | `PROCESSORS` | `['elasticapm.processors.sanitize_stacktrace_locals', 'elasticapm.processors.sanitize_http_request_cookies', 'elasticapm.processors.sanitize_http_headers', 'elasticapm.processors.sanitize_http_wsgi_env', 'elasticapm.processors.sanitize_http_request_body']` | + +A list of processors to process transactions and errors. For more information, see [Sanitizing Data](/reference/sanitizing-data.md). + +::::{warning} +We recommend always including the default set of validators if you customize this setting. +:::: + + + +### `sanitize_field_names` [config-sanitize-field-names] + +[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_SANITIZE_FIELD_NAMES` | `SANITIZE_FIELD_NAMES` | `["password", "passwd", "pwd", "secret", "*key", "*token*", "*session*", "*credit*", "*card*", "*auth*", "*principal*", "set-cookie"]` | + +A list of glob-matched field names to match and mask when using processors. For more information, see [Sanitizing Data](/reference/sanitizing-data.md). + +::::{warning} +We recommend always including the default set of field name matches if you customize this setting. +:::: + + + +### `transaction_sample_rate` [config-transaction-sample-rate] + +[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_TRANSACTION_SAMPLE_RATE` | `TRANSACTION_SAMPLE_RATE` | `1.0` | + +By default, the agent samples every transaction (e.g. request to your service). To reduce overhead and storage requirements, set the sample rate to a value between `0.0` and `1.0`. We still record overall time and the result for unsampled transactions, but no context information, labels, or spans. + +::::{note} +This setting will be automatically rounded to 4 decimals of precision. +:::: + + + +### `include_paths` [config-include-paths] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_INCLUDE_PATHS` | `INCLUDE_PATHS` | `[]` | +| multiple values separated by commas, without spaces | | | + +A set of paths, optionally using shell globs (see [`fnmatch`](https://docs.python.org/3/library/fnmatch.html) for a description of the syntax). These are matched against the absolute filename of every frame, and if a pattern matches, the frame is considered to be an "in-app frame". + +`include_paths` **takes precedence** over `exclude_paths`. + + +### `exclude_paths` [config-exclude-paths] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_EXCLUDE_PATHS` | `EXCLUDE_PATHS` | Varies on Python version and implementation | +| multiple values separated by commas, without spaces | | | + +A set of paths, optionally using shell globs (see [`fnmatch`](https://docs.python.org/3/library/fnmatch.html) for a description of the syntax). These are matched against the absolute filename of every frame, and if a pattern matches, the frame is considered to be a "library frame". + +`include_paths` **takes precedence** over `exclude_paths`. + +The default value varies based on your Python version and implementation, e.g.: + +* PyPy3: `['\*/lib-python/3/*', '\*/site-packages/*']` +* CPython 2.7: `['\*/lib/python2.7/*', '\*/lib64/python2.7/*']` + + +### `debug` [config-debug] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_DEBUG` | `DEBUG` | `False` | + +If your app is in debug mode (e.g. in Django with `settings.DEBUG = True` or in Flask with `app.debug = True`), the agent won’t send any data to the APM Server. You can override it by changing this setting to `True`. + + +### `disable_send` [config-disable-send] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_DISABLE_SEND` | `DISABLE_SEND` | `False` | + +If set to `True`, the agent won’t send any events to the APM Server, independent of any debug state. + + +### `instrument` [config-instrument] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_INSTRUMENT` | `INSTRUMENT` | `True` | + +If set to `False`, the agent won’t instrument any code. This disables most of the tracing functionality, but can be useful to debug possible instrumentation issues. + + +### `verify_server_cert` [config-verify-server-cert] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_VERIFY_SERVER_CERT` | `VERIFY_SERVER_CERT` | `True` | + +By default, the agent verifies the SSL certificate if an HTTPS connection to the APM Server is used. Verification can be disabled by changing this setting to `False`. This setting is ignored when [`server_cert`](#config-server-cert) is set. + + +### `server_cert` [config-server-cert] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_SERVER_CERT` | `SERVER_CERT` | `None` | + +If you have configured your APM Server with a self-signed TLS certificate, or you just wish to pin the server certificate, you can specify the path to the PEM-encoded certificate via the `ELASTIC_APM_SERVER_CERT` configuration. + +::::{note} +If this option is set, the agent only verifies that the certificate provided by the APM Server is identical to the one configured here. Validity of the certificate is not checked. +:::: + + + +### `server_ca_cert_file` [config-server-ca-cert-file] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_SERVER_CA_CERT_FILE` | `SERVER_CA_CERT_FILE` | `None` | + +By default, the agent will validate the TLS/SSL certificate of the APM Server using the well-known CAs curated by Mozilla, and provided by the [`certifi`](https://pypi.org/project/certifi/) package. + +You can set this option to the path of a file containing a CA certificate that will be used instead. + +Specifying this option is required when using self-signed certificates, unless server certificate validation is disabled. + + +### `use_certifi` [config-use-certifi] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_USE_CERTIFI` | `USE_CERTIFI` | `True` | + +By default, the Python Agent uses the [`certifi`](https://pypi.org/project/certifi/) certificate store. To use Python’s default mechanism for finding certificates, set this option to `False`. + + +### `metrics_interval` [config-metrics_interval] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_METRICS_INTERVAL` | `METRICS_INTERVAL` | `30s` | + +The interval in which the agent collects metrics. A shorter interval increases the granularity of metrics, but also increases the overhead of the agent, as well as storage requirements. + +It has to be provided in **[duration format](#config-format-duration)**. + + +### `disable_metrics` [config-disable_metrics] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_DISABLE_METRICS` | `DISABLE_METRICS` | `None` | + +A comma-separated list of dotted metrics names that should not be sent to the APM Server. You can use `*` to match multiple metrics; for example, to disable all CPU-related metrics, as well as the "total system memory" metric, set `disable_metrics` to: + +``` +"*.cpu.*,system.memory.total" +``` +::::{note} +This setting only disables the **sending** of the given metrics, not collection. +:::: + + + +### `breakdown_metrics` [config-breakdown_metrics] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_BREAKDOWN_METRICS` | `BREAKDOWN_METRICS` | `True` | + +Enable or disable the tracking and collection of breakdown metrics. Setting this to `False` disables the tracking of breakdown metrics, which can reduce the overhead of the agent. + +::::{note} +This feature requires APM Server and Kibana >= 7.3. +:::: + + + +### `prometheus_metrics` (Beta) [config-prometheus_metrics] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_PROMETHEUS_METRICS` | `PROMETHEUS_METRICS` | `False` | + +Enable/disable the tracking and collection of metrics from `prometheus_client`. + +See [Prometheus metric set (beta)](/reference/metrics.md#prometheus-metricset) for more information. + +::::{note} +This feature is currently in beta status. +:::: + + + +### `prometheus_metrics_prefix` (Beta) [config-prometheus_metrics_prefix] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_PROMETHEUS_METRICS_PREFIX` | `PROMETHEUS_METRICS_PREFIX` | `prometheus.metrics.` | + +A prefix to prepend to Prometheus metrics names. + +See [Prometheus metric set (beta)](/reference/metrics.md#prometheus-metricset) for more information. + +::::{note} +This feature is currently in beta status. +:::: + + + +### `metrics_sets` [config-metrics_sets] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_METRICS_SETS` | `METRICS_SETS` | ["elasticapm.metrics.sets.cpu.CPUMetricSet"] | + +List of import paths for the MetricSets that should be used to collect metrics. + +See [Custom Metrics](/reference/metrics.md#custom-metrics) for more information. + + +### `central_config` [config-central_config] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_CENTRAL_CONFIG` | `CENTRAL_CONFIG` | `True` | + +When enabled, the agent will make periodic requests to the APM Server to fetch updated configuration. + +See [Dynamic configuration](#dynamic-configuration) for more information. + +::::{note} +This feature requires APM Server and Kibana >= 7.3. +:::: + + + +### `global_labels` [config-global_labels] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_GLOBAL_LABELS` | `GLOBAL_LABELS` | `None` | + +Labels added to all events, with the format `key=value[,key=value[,...]]`. Any labels set by application via the API will override global labels with the same keys. + +::::{note} +This feature requires APM Server >= 7.2. +:::: + + + +### `disable_log_record_factory` [config-generic-disable-log-record-factory] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_DISABLE_LOG_RECORD_FACTORY` | `DISABLE_LOG_RECORD_FACTORY` | `False` | + +By default in python 3, the agent installs a [LogRecord factory](/reference/logs.md#logging) that automatically adds tracing fields to your log records. Disable this behavior by setting this to `True`. + + +### `use_elastic_traceparent_header` [config-use-elastic-traceparent-header] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_USE_ELASTIC_TRACEPARENT_HEADER` | `USE_ELASTIC_TRACEPARENT_HEADER` | `True` | + +To enable [distributed tracing](docs-content://solutions/observability/apps/traces.md), the agent sets a number of HTTP headers to outgoing requests made with [instrumented HTTP libraries](/reference/supported-technologies.md#automatic-instrumentation-http). These headers (`traceparent` and `tracestate`) are defined in the [W3C Trace Context](https://www.w3.org/TR/trace-context-1/) specification. + +Additionally, when this setting is set to `True`, the agent will set `elasticapm-traceparent` for backwards compatibility. + + +### `trace_continuation_strategy` [config-trace-continuation-strategy] + +[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_TRACE_CONTINUATION_STRATEGY` | `TRACE_CONTINUATION_STRATEGY` | `continue` | + +This option allows some control on how the APM agent handles W3C trace-context headers on incoming requests. By default, the `traceparent` and `tracestate` headers are used per W3C spec for distributed tracing. However, in certain cases it can be helpful to **not** use the incoming `traceparent` header. Some example use cases: + +* An Elastic-monitored service is receiving requests with `traceparent` headers from **unmonitored** services. +* An Elastic-monitored service is publicly exposed, and does not want tracing data (trace-ids, sampling decisions) to possibly be spoofed by user requests. + +Valid values are: + +* `'continue'`: The default behavior. An incoming `traceparent` value is used to continue the trace and determine the sampling decision. +* `'restart'`: Always ignores the `traceparent` header of incoming requests. A new trace-id will be generated and the sampling decision will be made based on [`transaction_sample_rate`](#config-transaction-sample-rate). A **span link** will be made to the incoming traceparent. +* `'restart_external'`: If an incoming request includes the `es` vendor flag in `tracestate`, then any *traceparent* will be considered internal and will be handled as described for `'continue'` above. Otherwise, any `'traceparent'` is considered external and will be handled as described for `'restart'` above. + +Starting with Elastic Observability 8.2, span links will be visible in trace views. + + +### `use_elastic_excepthook` [config-use-elastic-excepthook] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_USE_ELASTIC_EXCEPTHOOK` | `USE_ELASTIC_EXCEPTHOOK` | `False` | + +If set to `True`, the agent will intercept the default `sys.excepthook`, which allows the agent to collect all uncaught exceptions. + + +### `include_process_args` [config-include-process-args] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_INCLUDE_PROCESS_ARGS` | `INCLUDE_PROCESS_ARGS` | `False` | + +Whether each transaction should have the process arguments attached. Disabled by default to save disk space. + + +## Django-specific configuration [config-django-specific] + + +### `django_transaction_name_from_route` [config-django-transaction-name-from-route] + +| Environment | Django | Default | +| --- | --- | --- | +| `ELASTIC_APM_DJANGO_TRANSACTION_NAME_FROM_ROUTE` | `DJANGO_TRANSACTION_NAME_FROM_ROUTE` | `False` | + +By default, we use the function or class name of the view as the transaction name. Starting with Django 2.2, Django makes the route (e.g. `users//`) available on the `request.resolver_match` object. If you want to use the route instead of the view name as the transaction name, set this config option to `true`. + +::::{note} +in versions previous to Django 2.2, changing this setting will have no effect. +:::: + + + +### `django_autoinsert_middleware` [config-django-autoinsert-middleware] + +| Environment | Django | Default | +| --- | --- | --- | +| `ELASTIC_APM_DJANGO_AUTOINSERT_MIDDLEWARE` | `DJANGO_AUTOINSERT_MIDDLEWARE` | `True` | + +To trace Django requests, the agent uses a middleware, `elasticapm.contrib.django.middleware.TracingMiddleware`. By default, this middleware is inserted automatically as the first item in `settings.MIDDLEWARES`. To disable the automatic insertion of the middleware, change this setting to `False`. + + +## Generic Environment variables [config-generic-environment] + +Some environment variables that are not specific to the APM agent can be used to configure the agent. + + +### `HTTP_PROXY` and `HTTPS_PROXY` [config-generic-http-proxy] + +By using `HTTP_PROXY` and `HTTPS_PROXY`, the agent can be instructed to use a proxy to connect to the APM Server. If both are set, `HTTPS_PROXY` takes precedence. + +::::{note} +The environment variables are case-insensitive. +:::: + + + +### `NO_PROXY` [config-generic-no-proxy] + +To instruct the agent to **not** use a proxy, you can use the `NO_PROXY` environment variable. You can either set it to a comma-separated list of hosts for which no proxy should be used (e.g. `localhost,example.com`) or use `*` to match any host. + +This is useful if `HTTP_PROXY` / `HTTPS_PROXY` is set for other reasons than agent / APM Server communication. + + +### `SSL_CERT_FILE` and `SSL_CERT_DIR` [config-ssl-cert-file] + +To tell the agent to use a different SSL certificate, you can use these environment variables. See also [OpenSSL docs](https://www.openssl.org/docs/manmaster/man7/openssl-env.html#SSL_CERT_DIR-SSL_CERT_FILE). + +Please note that these variables may apply to other SSL/TLS communication in your service, not just related to the APM agent. + +::::{note} +These environment variables only take effect if [`use_certifi`](#config-use-certifi) is set to `False`. +:::: + + + +## Configuration formats [config-formats] + +Some options require a unit, either duration or size. These need to be provided in a specific format. + + +### Duration format [config-format-duration] + +The *duration* format is used for options like timeouts. The unit is provided as a suffix directly after the number–without any separation by whitespace. + +**Example**: `5ms` + +**Supported units** + +* `us` (microseconds) +* `ms` (milliseconds) +* `s` (seconds) +* `m` (minutes) + + +### Size format [config-format-size] + +The *size* format is used for options like maximum buffer sizes. The unit is provided as suffix directly after the number, without and separation by whitespace. + +**Example**: `10kb` + +**Supported units**: + +* `b` (bytes) +* `kb` (kilobytes) +* `mb` (megabytes) +* `gb` (gigabytes) + +::::{note} +We use the power-of-two sizing convention, e.g. `1 kilobyte == 1024 bytes` +:::: + + diff --git a/docs/reference/django-support.md b/docs/reference/django-support.md new file mode 100644 index 000000000..9db46e864 --- /dev/null +++ b/docs/reference/django-support.md @@ -0,0 +1,327 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/django-support.html +--- + +# Django support [django-support] + +Getting Elastic APM set up for your Django project is easy, and there are various ways you can tweak it to fit to your needs. + + +## Installation [django-installation] + +Install the Elastic APM agent using pip: + +```bash +$ pip install elastic-apm +``` + +or add it to your project’s `requirements.txt` file. + +::::{note} +For apm-server 6.2+, make sure you use version 2.0 or higher of `elastic-apm`. +:::: + + +::::{note} +If you use Django with uwsgi, make sure to [enable threads](http://uwsgi-docs.readthedocs.org/en/latest/Options.html#enable-threads). +:::: + + + +## Setup [django-setup] + +Set up the Elastic APM agent in Django with these two steps: + +1. Add `elasticapm.contrib.django` to `INSTALLED_APPS` in your settings: + +```python +INSTALLED_APPS = ( + # ... + 'elasticapm.contrib.django', +) +``` + +1. Choose a service name, and set the secret token if needed. + +```python +ELASTIC_APM = { + 'SERVICE_NAME': '', + 'SECRET_TOKEN': '', +} +``` + +or as environment variables: + +```shell +ELASTIC_APM_SERVICE_NAME= +ELASTIC_APM_SECRET_TOKEN= +``` + +You now have basic error logging set up, and everything resulting in a 500 HTTP status code will be reported to the APM Server. + +You can find a list of all available settings in the [Configuration](/reference/configuration.md) page. + +::::{note} +The agent only captures and sends data if you have `DEBUG = False` in your settings. To force the agent to capture data in Django debug mode, set the [debug](/reference/configuration.md#config-debug) configuration option, e.g.: + +```python +ELASTIC_APM = { + 'SERVICE_NAME': '', + 'DEBUG': True, +} +``` + +:::: + + + +## Performance metrics [django-performance-metrics] + +In order to collect performance metrics, the agent automatically inserts a middleware at the top of your middleware list (`settings.MIDDLEWARE` in current versions of Django, `settings.MIDDLEWARE_CLASSES` in some older versions). To disable the automatic insertion of the middleware, see [django_autoinsert_middleware](/reference/configuration.md#config-django-autoinsert-middleware). + +::::{note} +For automatic insertion to work, your list of middlewares (`settings.MIDDLEWARE` or `settings.MIDDLEWARE_CLASSES`) must be of type `list` or `tuple`. +:::: + + +In addition to broad request metrics (what will appear in the APM app as transactions), the agent also collects fine grained metrics on template rendering, database queries, HTTP requests, etc. You can find more information on what we instrument in the [Automatic Instrumentation](/reference/supported-technologies.md#automatic-instrumentation) section. + + +### Instrumenting custom Python code [django-instrumenting-custom-python-code] + +To gain further insights into the performance of your code, please see [instrumenting custom code](/reference/instrumenting-custom-code.md). + + +### Ignoring specific views [django-ignoring-specific-views] + +You can use the `TRANSACTIONS_IGNORE_PATTERNS` configuration option to ignore specific views. The list given should be a list of regular expressions which are matched against the transaction name as seen in the Elastic APM user interface: + +```python +ELASTIC_APM['TRANSACTIONS_IGNORE_PATTERNS'] = ['^OPTIONS ', 'views.api.v2'] +``` + +This example ignores any requests using the `OPTIONS` method and any requests containing `views.api.v2`. + + +### Using the route as transaction name [django-transaction-name-route] + +By default, we use the function or class name of the view as the transaction name. Starting with Django 2.2, Django makes the route (e.g. `users//`) available on the `request.resolver_match` object. If you want to use the route instead of the view name as the transaction name, you can set the [`django_transaction_name_from_route`](/reference/configuration.md#config-django-transaction-name-from-route) config option to `true`. + +```python +ELASTIC_APM['DJANGO_TRANSACTION_NAME_FROM_ROUTE'] = True +``` + +::::{note} +in versions previous to Django 2.2, changing this setting will have no effect. +:::: + + + +### Integrating with the RUM Agent [django-integrating-with-the-rum-agent] + +To correlate performance measurement in the browser with measurements in your Django app, you can help the RUM (Real User Monitoring) agent by configuring it with the Trace ID and Span ID of the backend request. We provide a handy template context processor which adds all the necessary bits into the context of your templates. + +To enable this feature, first add the `rum_tracing` context processor to your `TEMPLATES` setting. You most likely already have a list of `context_processors`, in which case you can simply append ours to the list. + +```python +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'OPTIONS': { + 'context_processors': [ + # ... + 'elasticapm.contrib.django.context_processors.rum_tracing', + ], + }, + }, +] +``` + +Then, update the call to initialize the RUM agent (which probably happens in your base template) like this: + +```javascript +elasticApm.init({ + serviceName: "my-frontend-service", + pageLoadTraceId: "{{ apm.trace_id }}", + pageLoadSpanId: "{{ apm.span_id }}", + pageLoadSampled: {{ apm.is_sampled_js }} +}) +``` + +See the [JavaScript RUM agent documentation](apm-agent-rum-js://reference/index.md) for more information. + + +## Enabling and disabling the agent [django-enabling-and-disabling-the-agent] + +The easiest way to disable the agent is to set Django’s `DEBUG` option to `True` in your development configuration. No errors or metrics will be logged to Elastic APM. + +However, if during debugging you would like to force logging of errors to Elastic APM, then you can set `DEBUG` to `True` inside of the Elastic APM configuration dictionary, like this: + +```python +ELASTIC_APM = { + # ... + 'DEBUG': True, +} +``` + + +## Integrating with Python logging [django-logging] + +To easily send Python `logging` messages as "error" objects to Elasticsearch, we provide a `LoggingHandler` which you can use in your logging setup. The log messages will be enriched with a stack trace, data from the request, and more. + +::::{note} +the intended use case for this handler is to send high priority log messages (e.g. log messages with level `ERROR`) to Elasticsearch. For normal log shipping, we recommend using [filebeat](beats://reference/filebeat/filebeat-overview.md). +:::: + + +If you are new to how the `logging` module works together with Django, read more [in the Django documentation](https://docs.djangoproject.com/en/2.1/topics/logging/). + +An example of how your `LOGGING` setting could look: + +```python +LOGGING = { + 'version': 1, + 'disable_existing_loggers': True, + 'formatters': { + 'verbose': { + 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' + }, + }, + 'handlers': { + 'elasticapm': { + 'level': 'WARNING', + 'class': 'elasticapm.contrib.django.handlers.LoggingHandler', + }, + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'formatter': 'verbose' + } + }, + 'loggers': { + 'django.db.backends': { + 'level': 'ERROR', + 'handlers': ['console'], + 'propagate': False, + }, + 'mysite': { + 'level': 'WARNING', + 'handlers': ['elasticapm'], + 'propagate': False, + }, + # Log errors from the Elastic APM module to the console (recommended) + 'elasticapm.errors': { + 'level': 'ERROR', + 'handlers': ['console'], + 'propagate': False, + }, + }, +} +``` + +With this configuration, logging can be done like this in any module in the `myapp` django app: + +You can now use the logger in any module in the `myapp` Django app, for instance `myapp/views.py`: + +```python +import logging +logger = logging.getLogger('mysite') + +try: + instance = MyModel.objects.get(pk=42) +except MyModel.DoesNotExist: + logger.error( + 'Could not find instance, doing something else', + exc_info=True + ) +``` + +Note that `exc_info=True` adds the exception information to the data that gets sent to Elastic APM. Without it, only the message is sent. + + +### Extra data [django-extra-data] + +If you want to send more data than what you get with the agent by default, logging can be done like so: + +```python +import logging +logger = logging.getLogger('mysite') + +try: + instance = MyModel.objects.get(pk=42) +except MyModel.DoesNotExist: + logger.error( + 'There was some crazy error', + exc_info=True, + extra={ + 'datetime': str(datetime.now()), + } + ) +``` + + +## Celery integration [django-celery-integration] + +For a general guide on how to set up Django with Celery, head over to Celery’s [Django documentation](http://celery.readthedocs.org/en/latest/django/first-steps-with-django.html#django-first-steps). + +Elastic APM will automatically log errors from your celery tasks, record performance data and keep the trace.id when the task is launched from an already started Elastic transaction. + + +## Logging "HTTP 404 Not Found" errors [django-logging-http-404-not-found-errors] + +By default, Elastic APM does not log HTTP 404 errors. If you wish to log these errors, add `'elasticapm.contrib.django.middleware.Catch404Middleware'` to `MIDDLEWARE` in your settings: + +```python +MIDDLEWARE = ( + # ... + 'elasticapm.contrib.django.middleware.Catch404Middleware', + # ... +) +``` + +Note that this middleware respects Django’s [`IGNORABLE_404_URLS`](https://docs.djangoproject.com/en/1.11/ref/settings/#ignorable-404-urls) setting. + + +## Disable the agent during tests [django-disable-agent-during-tests] + +To prevent the agent from sending any data to the APM Server during tests, set the `ELASTIC_APM_DISABLE_SEND` environment variable to `true`, e.g.: + +```python +ELASTIC_APM_DISABLE_SEND=true python manage.py test +``` + + +## Troubleshooting [django-troubleshooting] + +Elastic APM comes with a Django command that helps troubleshooting your setup. To check your configuration, run + +```bash +python manage.py elasticapm check +``` + +To send a test exception using the current settings, run + +```bash +python manage.py elasticapm test +``` + +If the command succeeds in sending a test exception, it will print a success message: + +```bash +python manage.py elasticapm test + +Trying to send a test error using these settings: + +SERVICE_NAME: +SECRET_TOKEN: +SERVER: http://127.0.0.1:8200 + +Success! We tracked the error successfully! You should be able to see it in a few seconds. +``` + + +## Supported Django and Python versions [supported-django-and-python-versions] + +A list of supported [Django](/reference/supported-technologies.md#supported-django) and [Python](/reference/supported-technologies.md#supported-python) versions can be found on our [Supported Technologies](/reference/supported-technologies.md) page. + diff --git a/docs/reference/flask-support.md b/docs/reference/flask-support.md new file mode 100644 index 000000000..e99e32994 --- /dev/null +++ b/docs/reference/flask-support.md @@ -0,0 +1,215 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/flask-support.html +--- + +# Flask support [flask-support] + +Getting Elastic APM set up for your Flask project is easy, and there are various ways you can tweak it to fit to your needs. + + +## Installation [flask-installation] + +Install the Elastic APM agent using pip: + +```bash +$ pip install "elastic-apm[flask]" +``` + +or add `elastic-apm[flask]` to your project’s `requirements.txt` file. + +::::{note} +For apm-server 6.2+, make sure you use version 2.0 or higher of `elastic-apm`. +:::: + + +::::{note} +If you use Flask with uwsgi, make sure to [enable threads](http://uwsgi-docs.readthedocs.org/en/latest/Options.html#enable-threads). +:::: + + +::::{note} +If you see an error log that mentions `psutil not found`, you can install `psutil` using `pip install psutil`, or add `psutil` to your `requirements.txt` file. +:::: + + + +## Setup [flask-setup] + +To set up the agent, you need to initialize it with appropriate settings. + +The settings are configured either via environment variables, the application’s settings, or as initialization arguments. + +You can find a list of all available settings in the [Configuration](/reference/configuration.md) page. + +To initialize the agent for your application using environment variables: + +```python +from elasticapm.contrib.flask import ElasticAPM + +app = Flask(__name__) + +apm = ElasticAPM(app) +``` + +To configure the agent using `ELASTIC_APM` in your application’s settings: + +```python +from elasticapm.contrib.flask import ElasticAPM + +app.config['ELASTIC_APM'] = { + 'SERVICE_NAME': '', + 'SECRET_TOKEN': '', +} +apm = ElasticAPM(app) +``` + +The final option is to initialize the agent with the settings as arguments: + +```python +from elasticapm.contrib.flask import ElasticAPM + +apm = ElasticAPM(app, service_name='', secret_token='') +``` + + +### Debug mode [flask-debug-mode] + +::::{note} +Please note that errors and transactions will only be sent to the APM Server if your app is **not** in [Flask debug mode](https://flask.palletsprojects.com/en/3.0.x/quickstart/#debug-mode). +:::: + + +To force the agent to send data while the app is in debug mode, set the value of `DEBUG` in the `ELASTIC_APM` dictionary to `True`: + +```python +app.config['ELASTIC_APM'] = { + 'SERVICE_NAME': '', + 'SECRET_TOKEN': '', + 'DEBUG': True +} +``` + + +### Building applications on the fly? [flask-building-applications-on-the-fly] + +You can use the agent’s `init_app` hook for adding the application on the fly: + +```python +from elasticapm.contrib.flask import ElasticAPM +apm = ElasticAPM() + +def create_app(): + app = Flask(__name__) + apm.init_app(app, service_name='', secret_token='') + return app +``` + + +## Usage [flask-usage] + +Once you have configured the agent, it will automatically track transactions and capture uncaught exceptions within Flask. If you want to send additional events, a couple of shortcuts are provided on the ElasticAPM Flask middleware object by raising an exception or logging a generic message. + +Capture an arbitrary exception by calling `capture_exception`: + +```python +try: + 1 / 0 +except ZeroDivisionError: + apm.capture_exception() +``` + +Log a generic message with `capture_message`: + +```python +apm.capture_message('hello, world!') +``` + + +## Shipping Logs to Elasticsearch [flask-logging] + +This feature has been deprecated and will be removed in a future version. + +Please see our [Logging](/reference/logs.md) documentation for other supported ways to ship logs to Elasticsearch. + +Note that you can always send exceptions and messages to the APM Server with [`capture_exception`](/reference/api-reference.md#client-api-capture-exception) and and [`capture_message`](/reference/api-reference.md#client-api-capture-message). + +```python +from elasticapm import get_client + +@app.route('/') +def bar(): + try: + 1 / 0 + except ZeroDivisionError: + get_client().capture_exception() +``` + + +### Extra data [flask-extra-data] + +In addition to what the agents log by default, you can send extra information: + +```python +@app.route('/') +def bar(): + try: + 1 / 0 + except ZeroDivisionError: + app.logger.error('Math is hard', + exc_info=True, + extra={ + 'good_at_math': False, + } + ) + ) +``` + + +### Celery tasks [flask-celery-tasks] + +The Elastic APM agent will automatically send errors and performance data from your Celery tasks to the APM Server. + + +## Performance metrics [flask-performance-metrics] + +If you’ve followed the instructions above, the agent has already hooked into the right signals and should be reporting performance metrics. + + +### Ignoring specific routes [flask-ignoring-specific-views] + +You can use the [`TRANSACTIONS_IGNORE_PATTERNS`](/reference/configuration.md#config-transactions-ignore-patterns) configuration option to ignore specific routes. The list given should be a list of regular expressions which are matched against the transaction name: + +```python +app.config['ELASTIC_APM'] = { + ... + 'TRANSACTIONS_IGNORE_PATTERNS': ['^OPTIONS ', '/api/'] + ... +} +``` + +This would ignore any requests using the `OPTIONS` method and any requests containing `/api/`. + + +### Integrating with the RUM Agent [flask-integrating-with-the-rum-agent] + +To correlate performance measurement in the browser with measurements in your Flask app, you can help the RUM (Real User Monitoring) agent by configuring it with the Trace ID and Span ID of the backend request. We provide a handy template context processor which adds all the necessary bits into the context of your templates. + +The context processor is installed automatically when you initialize `ElasticAPM`. All that is left to do is to update the call to initialize the RUM agent (which probably happens in your base template) like this: + +```javascript +elasticApm.init({ + serviceName: "my-frontend-service", + pageLoadTraceId: "{{ apm["trace_id"] }}", + pageLoadSpanId: "{{ apm["span_id"]() }}", + pageLoadSampled: {{ apm["is_sampled_js"] }} +}) +``` + +See the [JavaScript RUM agent documentation](apm-agent-rum-js://reference/index.md) for more information. + + +## Supported Flask and Python versions [supported-flask-and-python-versions] + +A list of supported [Flask](/reference/supported-technologies.md#supported-flask) and [Python](/reference/supported-technologies.md#supported-python) versions can be found on our [Supported Technologies](/reference/supported-technologies.md) page. + diff --git a/docs/reference/how-agent-works.md b/docs/reference/how-agent-works.md new file mode 100644 index 000000000..6c0f597ca --- /dev/null +++ b/docs/reference/how-agent-works.md @@ -0,0 +1,52 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/how-the-agent-works.html +--- + +# How the Agent works [how-the-agent-works] + +To gather APM events (called transactions and spans), errors and metrics, the Python agent instruments your application in a few different ways. These events, are then sent to the APM Server. The APM Server converts them to a format suitable for Elasticsearch, and sends them to an Elasticsearch cluster. You can then use the APM app in Kibana to gain insight into latency issues and error culprits within your application. + +Broadly, we differentiate between three different approaches to collect the necessary data: framework integration, instrumentation, and background collection. + + +## Framework integration [how-it-works-framework-integration] + +To collect data about incoming requests and background tasks, we integrate with frameworks like [Django](/reference/django-support.md), [Flask](/reference/flask-support.md) and Celery. Whenever possible, framework integrations make use of hooks and signals provided by the framework. Examples of this are: + +* `request_started`, `request_finished`, and `got_request_exception` signals from `django.core.signals` +* `request_started`, `request_finished`, and `got_request_exception` signals from `flask.signals` +* `task_prerun`, `task_postrun`, and `task_failure` signals from `celery.signals` + +Framework integrations require some limited code changes in your app. E.g. for Django, you need to add `elasticapm.contrib.django` to `INSTALLED_APPS`. + + +## What if you are not using a framework [how-it-works-no-framework] + +If you’re not using a supported framework, for example, a simple Python script, you can still leverage the agent’s [automatic instrumentation](/reference/supported-technologies.md#automatic-instrumentation). Check out our docs on [instrumenting custom code](/reference/instrumenting-custom-code.md). + + +## Instrumentation [how-it-works-instrumentation] + +To collect data from database drivers, HTTP libraries etc., we instrument certain functions and methods in these libraries. Our instrumentation wraps these callables and collects additional data, like + +* time spent in the call +* the executed query for database drivers +* the fetched URL for HTTP libraries + +We use a 3rd party library, [`wrapt`](https://github.com/GrahamDumpleton/wrapt), to wrap the callables. You can read more on how `wrapt` works in Graham Dumpleton’s excellent series of [blog posts](http://blog.dscpl.com.au/search/label/wrapt). + +Instrumentations are set up automatically and do not require any code changes. See [Automatic Instrumentation](/reference/supported-technologies.md#automatic-instrumentation) to learn more about which libraries we support. + + +## Background collection [how-it-works-background-collection] + +In addition to APM and error data, the Python agent also collects system and application metrics in regular intervals. This collection happens in a background thread that is started by the agent. + +In addition to the metrics collection background thread, the agent starts two additional threads per process: + +* a thread to regularly fetch remote configuration from the APM Server +* a thread to process the collected data and send it to the APM Server via HTTP. + +Note that every process that instantiates the agent will have these three threads. This means that when you e.g. use gunicorn or uwsgi workers, each worker will have three threads started by the Python agent. + diff --git a/docs/reference/index.md b/docs/reference/index.md new file mode 100644 index 000000000..9e6d65f56 --- /dev/null +++ b/docs/reference/index.md @@ -0,0 +1,28 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/getting-started.html + - https://www.elastic.co/guide/en/apm/agent/python/current/index.html +--- + +# APM Python agent [getting-started] + +The Elastic APM Python agent sends performance metrics and error logs to the APM Server. It has built-in support for Django and Flask performance metrics and error logging, as well as generic support of other WSGI frameworks for error logging. + + +## How does the Agent work? [how-it-works] + +The Python Agent instruments your application to collect APM events in a few different ways: + +To collect data about incoming requests and background tasks, the Agent integrates with [supported technologies](/reference/supported-technologies.md) to make use of hooks and signals provided by the framework. These framework integrations require limited code changes in your application. + +To collect data from database drivers, HTTP libraries etc., we instrument certain functions and methods in these libraries. Instrumentations are set up automatically and do not require any code changes. + +In addition to APM and error data, the Python agent also collects system and application metrics in regular intervals. This collection happens in a background thread that is started by the agent. + +More detailed information on how the Agent works can be found in the [advanced topics](/reference/how-agent-works.md). + + +## Additional components [additional-components] + +APM Agents work in conjunction with the [APM Server](docs-content://solutions/observability/apps/application-performance-monitoring-apm.md), [Elasticsearch](docs-content://get-started/introduction.md#what-is-es), and [Kibana](docs-content://get-started/introduction.md#what-is-kib). The [APM documentation](docs-content://solutions/observability/apps/application-performance-monitoring-apm.md) provides details on how these components work together, and provides a matrix outlining [Agent and Server compatibility](docs-content://solutions/observability/apps/apm-agent-compatibility.md). + diff --git a/docs/reference/instrumenting-custom-code.md b/docs/reference/instrumenting-custom-code.md new file mode 100644 index 000000000..ad6afc8fb --- /dev/null +++ b/docs/reference/instrumenting-custom-code.md @@ -0,0 +1,118 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/instrumenting-custom-code.html +--- + +# Instrumenting custom code [instrumenting-custom-code] + + +## Creating Additional Spans in a Transaction [instrumenting-custom-code-spans] + +Elastic APM instruments a variety of libraries out of the box, but sometimes you need to know how long a specific function took or how often it gets called. + +Assuming you’re using one of our [supported frameworks](/reference/set-up-apm-python-agent.md), you can apply the `@elasticapm.capture_span()` decorator to achieve exactly that. If you’re not using a supported framework, see [Creating New Transactions](#instrumenting-custom-code-transactions). + +`elasticapm.capture_span` can be used either as a decorator or as a context manager. The following example uses it both ways: + +```python +import elasticapm + +@elasticapm.capture_span() +def coffee_maker(strength): + fetch_water() + + with elasticapm.capture_span('near-to-machine'): + insert_filter() + for i in range(strength): + pour_coffee() + + start_drip() + + fresh_pots() +``` + +Similarly, you can use `elasticapm.async_capture_span` for instrumenting `async` workloads: + +```python +import elasticapm + +@elasticapm.async_capture_span() +async def coffee_maker(strength): + await fetch_water() + + async with elasticapm.async_capture_span('near-to-machine'): + await insert_filter() + async for i in range(strength): + await pour_coffee() + + start_drip() + + fresh_pots() +``` + +::::{note} +`asyncio` support is only available in Python 3.7+. +:::: + + +See [the API docs](/reference/api-reference.md#api-capture-span) for more information on `capture_span`. + + +## Creating New Transactions [instrumenting-custom-code-transactions] + +It’s important to note that `elasticapm.capture_span` only works if there is an existing transaction. If you’re not using one of our [supported frameworks](/reference/set-up-apm-python-agent.md), you need to create a `Client` object and begin and end the transactions yourself. You can even utilize the agent’s [automatic instrumentation](/reference/supported-technologies.md#automatic-instrumentation)! + +To collect the spans generated by the supported libraries, you need to invoke `elasticapm.instrument()` (just once, at the initialization stage of your application) and create at least one transaction. It is up to you to determine what you consider a transaction within your application — it can be the whole execution of the script or a part of it. + +The example below will consider the whole execution as a single transaction with two HTTP request spans in it. The config for `elasticapm.Client` can be passed in programmatically, and it will also utilize any config environment variables available to it automatically. + +```python +import requests +import time +import elasticapm + +def main(): + sess = requests.Session() + for url in [ 'https://www.elastic.co', 'https://benchmarks.elastic.co' ]: + resp = sess.get(url) + time.sleep(1) + +if __name__ == '__main__': + client = elasticapm.Client(service_name="foo", server_url="https://example.com:8200") + elasticapm.instrument() # Only call this once, as early as possible. + client.begin_transaction(transaction_type="script") + main() + client.end_transaction(name=__name__, result="success") +``` + +Note that you don’t need to do anything to send the data — the `Client` object will handle that before the script exits. Additionally, the `Client` object should be treated as a singleton — you should only create one instance and store/pass around that instance for all transaction handling. + + +## Distributed Tracing [instrumenting-custom-code-distributed-tracing] + +When instrumenting custom code across multiple services, you should propagate the TraceParent where possible. This allows Elastic APM to bundle the various transactions into a single distributed trace. The Python Agent will automatically add TraceParent information to the headers of outgoing HTTP requests, which can then be used on the receiving end to add that TraceParent information to new manually-created transactions. + +Additionally, the Python Agent provides utilities for propagating the TraceParent in string format. + +```python +import elasticapm + +client = elasticapm.Client(service_name="foo", server_url="https://example.com:8200") + +# Retrieve the current TraceParent as a string, requires active transaction +traceparent_string = elasticapm.get_trace_parent_header() + +# Create a TraceParent object from a string and use it for a new transaction +parent = elasticapm.trace_parent_from_string(traceparent_string) +client.begin_transaction(transaction_type="script", trace_parent=parent) +# Do some work +client.end_transaction(name=__name__, result="success") + +# Create a TraceParent object from a dictionary of headers, provided +# automatically by the sending service if it is using an Elastic APM Agent. +parent = elasticapm.trace_parent_from_headers(headers_dict) +client.begin_transaction(transaction_type="script", trace_parent=parent) +# Do some work +client.end_transaction(name=__name__, result="success") +``` + diff --git a/docs/reference/lambda-support.md b/docs/reference/lambda-support.md new file mode 100644 index 000000000..f720b9ccc --- /dev/null +++ b/docs/reference/lambda-support.md @@ -0,0 +1,263 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/lambda-support.html +sub: + apm-lambda-ext-v: ver-1-5-7 + apm-python-v: ver-6-23-0 +--- + +# Monitoring AWS Lambda Python Functions [lambda-support] + +The Python APM Agent can be used with AWS Lambda to monitor the execution of your AWS Lambda functions. + +:::{note} +The Centralized Agent Configuration on the Elasticsearch APM currently does NOT support AWS Lambda. +::: + + +## Prerequisites [_prerequisites] + +You need an APM Server to send APM data to. Follow the [APM Quick start](docs-content://solutions/observability/apps/fleet-managed-apm-server.md) if you have not set one up yet. For the best-possible performance, we recommend setting up APM on {{ecloud}} in the same AWS region as your AWS Lambda functions. + +## Step 1: Add the APM Layers to your Lambda function [add_the_apm_layers_to_your_lambda_function] + +Both the [{{apm-lambda-ext}}](apm-aws-lambda://reference/index.md) and the Python APM Agent are added to your Lambda function as [AWS Lambda Layers](https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html). Therefore, you need to add the corresponding Layer ARNs (identifiers) to your Lambda function. + +:::::::{tab-set} + +::::::{tab-item} AWS Web Console +To add the layers to your Lambda function through the AWS Management Console: + +1. Navigate to your function in the AWS Management Console +2. Scroll to the Layers section and click the *Add a layer* button ![image of layer configuration section in AWS Console](../images/config-layer.png "") +3. Choose the *Specify an ARN* radio button +4. Copy and paste the following ARNs of the {{apm-lambda-ext}} layer and the APM agent layer in the *Specify an ARN* text input: + * APM Extension layer: + ``` + arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-extension-{{apm-lambda-ext-v}}-{ARCHITECTURE}:1 <1> + ``` + 1. Replace `{AWS_REGION}` with the AWS region of your Lambda function and `{ARCHITECTURE}` with its architecture. + + * APM agent layer: + ``` + arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-python-{{apm-python-v}}:1 <1> + ``` + 1. Replace `{AWS_REGION}` with the AWS region of your Lambda function. + + ![image of choosing a layer in AWS Console](../images/choose-a-layer.png "") +5. Click the *Add* button +:::::: + +::::::{tab-item} AWS CLI +To add the Layer ARNs of the {{apm-lambda-ext}} and the APM agent through the AWS command line interface execute the following command: + +```bash +aws lambda update-function-configuration --function-name yourLambdaFunctionName \ +--layers arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-extension-{{apm-lambda-ext-v}}-{ARCHITECTURE}:1 \ <1> +arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-python-{{apm-python-v}}:1 <2> +``` +1. Replace `{AWS_REGION}` with the AWS region of your Lambda function and `{ARCHITECTURE}` with its architecture. +2. Replace `{AWS_REGION}` with the AWS region of your Lambda function. +:::::: + +::::::{tab-item} SAM +In your SAM `template.yml` file add the Layer ARNs of the {{apm-lambda-ext}} and the APM agent as follows: + +```yaml +... +Resources: + yourLambdaFunction: + Type: AWS::Serverless::Function + Properties: + ... + Layers: + - arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-extension-{{apm-lambda-ext-v}}-{ARCHITECTURE}:1 <1> + - arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-python-{{apm-python-v}}:1 <2> +... +``` +1. Replace `{AWS_REGION}` with the AWS region of your Lambda function and `{ARCHITECTURE}` with its architecture. +2. Replace `{AWS_REGION}` with the AWS region of your Lambda function. +:::::: + +::::::{tab-item} Serverless +In your `serverless.yml` file add the Layer ARNs of the {{apm-lambda-ext}} and the APM agent to your function as follows: + +```yaml +... +functions: + yourLambdaFunction: + handler: ... + layers: + - arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-extension-{{apm-lambda-ext-v}}-{ARCHITECTURE}:1 <1> + - arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-python-{{apm-python-v}}:1 <2> +... +``` +1. Replace `{AWS_REGION}` with the AWS region of your Lambda function and `{ARCHITECTURE}` with its architecture. +2. Replace `{AWS_REGION}` with the AWS region of your Lambda function. +:::::: + +::::::{tab-item} Terraform +To add the{{apm-lambda-ext}} and the APM agent to your function add the ARNs to the `layers` property in your Terraform file: + +```yaml +... +resource "aws_lambda_function" "your_lambda_function" { + ... + layers = ["arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-extension-{{apm-lambda-ext-v}}-{ARCHITECTURE}:1", "arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-python-{{apm-python-v}}:1"] <1> +} +... +``` +1. Replace `{AWS_REGION}` with the AWS region of your Lambda function and `{ARCHITECTURE}` with its architecture. +:::::: + +::::::{tab-item} Container Image +To add the {{apm-lambda-ext}} and the APM agent to your container-based function extend the Dockerfile of your function image as follows: + +```Dockerfile +FROM docker.elastic.co/observability/apm-lambda-extension-{IMAGE_ARCH}:latest AS lambda-extension <1> +FROM docker.elastic.co/observability/apm-agent-python:latest AS python-agent + +# FROM ... <-- this is the base image of your Lambda function + +COPY --from=lambda-extension /opt/elastic-apm-extension /opt/extensions/elastic-apm-extension +COPY --from=python-agent /opt/python/ /opt/python/ + +# ... +``` +1. Replace `{IMAGE_ARCH}` with the architecture of the image. +:::::: + +::::::: + +## Step 2: Configure APM on AWS Lambda [configure_apm_on_aws_lambda] + +The {{apm-lambda-ext}} and the APM Python agent are configured through environment variables on the AWS Lambda function. + +For the minimal configuration, you will need the *APM Server URL* to set the destination for APM data and an [APM Secret Token](docs-content://solutions/observability/apps/secret-token.md). If you prefer to use an [APM API key](docs-content://solutions/observability/apps/api-keys.md) instead of the APM secret token, use the `ELASTIC_APM_API_KEY` environment variable instead of `ELASTIC_APM_SECRET_TOKEN` in the following configuration. + +For production environments, we recommend [using the AWS Secrets Manager to store your APM authentication key](apm-aws-lambda://reference/aws-lambda-secrets-manager.md) instead of providing the secret value as plaintext in the environment variables. + +:::::::{tab-set} + +::::::{tab-item} AWS Web Console +To configure APM through the AWS Management Console: + +1. Navigate to your function in the AWS Management Console +2. Click on the *Configuration* tab +3. Click on *Environment variables* +4. Add the following required variables: + +```bash +AWS_LAMBDA_EXEC_WRAPPER = /opt/python/bin/elasticapm-lambda <1> +ELASTIC_APM_LAMBDA_APM_SERVER = <2> +ELASTIC_APM_SECRET_TOKEN = <3> +ELASTIC_APM_SEND_STRATEGY = background <4> +``` + +1. Use this exact fixed value. +2. This is your APM Server URL. +3. This is your APM secret token. +4. The [ELASTIC_APM_SEND_STRATEGY](apm-aws-lambda://reference/aws-lambda-config-options.md#_elastic_apm_send_strategy) defines when APM data is sent to your Elastic APM backend. To reduce the execution time of your lambda functions, we recommend to use the background strategy in production environments with steady load scenarios. + +![Python environment variables configuration section in AWS Console](../images/python-lambda-env-vars.png "") +:::::: + +::::::{tab-item} AWS CLI +To configure APM through the AWS command line interface execute the following command: + +```bash +aws lambda update-function-configuration --function-name yourLambdaFunctionName \ + --environment "Variables={AWS_LAMBDA_EXEC_WRAPPER=/opt/python/bin/elasticapm-lambda,ELASTIC_APM_LAMBDA_APM_SERVER=,ELASTIC_APM_SECRET_TOKEN=,ELASTIC_APM_SEND_STRATEGY=background}" <1> +``` +1. The [ELASTIC_APM_SEND_STRATEGY](apm-aws-lambda://reference/aws-lambda-config-options.md#_elastic_apm_send_strategy) defines when APM data is sent to your Elastic APM backend. To reduce the execution time of your lambda functions, we recommend to use the background strategy in production environments with steady load scenarios. +:::::: + +::::::{tab-item} SAM +In your SAM `template.yml` file configure the following environment variables: + +```yaml +... +Resources: + yourLambdaFunction: + Type: AWS::Serverless::Function + Properties: + ... + Environment: + Variables: + AWS_LAMBDA_EXEC_WRAPPER: /opt/python/bin/elasticapm-lambda + ELASTIC_APM_LAMBDA_APM_SERVER: + ELASTIC_APM_SECRET_TOKEN: + ELASTIC_APM_SEND_STRATEGY: background <1> +... +``` + +1. The [ELASTIC_APM_SEND_STRATEGY](apm-aws-lambda://reference/aws-lambda-config-options.md#_elastic_apm_send_strategy) defines when APM data is sent to your Elastic APM backend. To reduce the execution time of your lambda functions, we recommend to use the background strategy in production environments with steady load scenarios. + +:::::: + +::::::{tab-item} Serverless +In your `serverless.yml` file configure the following environment variables: + +```yaml +... +functions: + yourLambdaFunction: + ... + environment: + AWS_LAMBDA_EXEC_WRAPPER: /opt/python/bin/elasticapm-lambda + ELASTIC_APM_LAMBDA_APM_SERVER: + ELASTIC_APM_SECRET_TOKEN: + ELASTIC_APM_SEND_STRATEGY: background <1> +... +``` + +1. The [ELASTIC_APM_SEND_STRATEGY](apm-aws-lambda://reference/aws-lambda-config-options.md#_elastic_apm_send_strategy) defines when APM data is sent to your Elastic APM backend. To reduce the execution time of your lambda functions, we recommend to use the background strategy in production environments with steady load scenarios. + +:::::: + +::::::{tab-item} Terraform +In your Terraform file configure the following environment variables: + +```yaml +... +resource "aws_lambda_function" "your_lambda_function" { + ... + environment { + variables = { + AWS_LAMBDA_EXEC_WRAPPER = /opt/python/bin/elasticapm-lambda + ELASTIC_APM_LAMBDA_APM_SERVER = "" + ELASTIC_APM_SECRET_TOKEN = "" + ELASTIC_APM_SEND_STRATEGY = "background" <1> + } + } +} +... +``` + +1. The [ELASTIC_APM_SEND_STRATEGY](apm-aws-lambda://reference/aws-lambda-config-options.md#_elastic_apm_send_strategy) defines when APM data is sent to your Elastic APM backend. To reduce the execution time of your lambda functions, we recommend to use the background strategy in production environments with steady load scenarios. + +:::::: + +::::::{tab-item} Container Image +Environment variables configured for an AWS Lambda function are passed to the container running the lambda function. You can use one of the other options (through AWS Web Console, AWS CLI, etc.) to configure the following environment variables: + +```bash +AWS_LAMBDA_EXEC_WRAPPER = /opt/python/bin/elasticapm-lambda <1> +ELASTIC_APM_LAMBDA_APM_SERVER = <2> +ELASTIC_APM_SECRET_TOKEN = <3> +ELASTIC_APM_SEND_STRATEGY = background <4> +``` + +1. Use this exact fixed value. +2. This is your APM Server URL. +3. This is your APM secret token. +4. The [ELASTIC_APM_SEND_STRATEGY](apm-aws-lambda://reference/aws-lambda-config-options.md#_elastic_apm_send_strategy) defines when APM data is sent to your Elastic APM backend. To reduce the execution time of your lambda functions, we recommend to use the background strategy in production environments with steady load scenarios. + +:::::: + +::::::: + +You can optionally [fine-tune the Python agent](/reference/configuration.md) or the [configuration of the {{apm-lambda-ext}}](apm-aws-lambda://reference/aws-lambda-config-options.md). + +That’s it. After following the steps above, you’re ready to go! Your Lambda function invocations should be traced from now on. Spans will be captured for [supported technologies](/reference/supported-technologies.md). You can also use [`capture_span`](/reference/api-reference.md#api-capture-span) to capture custom spans, and you can retrieve the `Client` object for capturing exceptions/messages using [`get_client`](/reference/api-reference.md#api-get-client). + diff --git a/docs/reference/logs.md b/docs/reference/logs.md new file mode 100644 index 000000000..a3bcba170 --- /dev/null +++ b/docs/reference/logs.md @@ -0,0 +1,141 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/logs.html +--- + +# Logs [logs] + +Elastic Python APM Agent provides the following log features: + +* [Log correlation](#log-correlation-ids) : Automatically inject correlation IDs that allow navigation between logs, traces and services. +* [Log reformatting (experimental)](#log-reformatting) : Automatically reformat plaintext logs in [ECS logging](ecs-logging://reference/intro.md) format. + +::::{note} +Elastic Python APM Agent does not send the logs to Elasticsearch. It only injects correlation IDs and reformats the logs. You must use another ingestion strategy. We recommend [Filebeat](https://www.elastic.co/beats/filebeat) for that purpose. +:::: + + +Those features are part of [Application log ingestion strategies](docs-content://solutions/observability/logs/stream-application-logs.md). + +The [`ecs-logging-python`](ecs-logging-python://reference/index.md) library can also be used to use the [ECS logging](ecs-logging://reference/intro.md) format without an APM agent. When deployed with the Python APM agent, the agent will provide [log correlation](#log-correlation-ids) IDs. + + +## Log correlation [log-correlation-ids] + +[Log correlation](docs-content://solutions/observability/logs/stream-application-logs.md) allows you to navigate to all logs belonging to a particular trace and vice-versa: for a specific log, see in which context it has been logged and which parameters the user provided. + +The Agent provides integrations with both the default Python logging library, as well as [`structlog`](http://www.structlog.org/en/stable/). + +* [Logging integrations](#logging-integrations) +* [Log correlation in Elasticsearch](#log-correlation-in-es) + + +### Logging integrations [logging-integrations] + + +#### `logging` [logging] + +We use [`logging.setLogRecordFactory()`](https://docs.python.org/3/library/logging.html#logging.setLogRecordFactory) to decorate the default LogRecordFactory to automatically add new attributes to each LogRecord object: + +* `elasticapm_transaction_id` +* `elasticapm_trace_id` +* `elasticapm_span_id` + +This factory also adds these fields to a dictionary attribute, `elasticapm_labels`, using the official ECS [tracing fields](ecs://reference/ecs-tracing.md). + +You can disable this automatic behavior by using the [`disable_log_record_factory`](/reference/configuration.md#config-generic-disable-log-record-factory) setting in your configuration. + + +#### `structlog` [structlog] + +We provide a [processor](http://www.structlog.org/en/stable/processors.html) for [`structlog`](http://www.structlog.org/en/stable/) which will add three new keys to the event_dict of any processed event: + +* `transaction.id` +* `trace.id` +* `span.id` + +```python +from structlog import PrintLogger, wrap_logger +from structlog.processors import JSONRenderer +from elasticapm.handlers.structlog import structlog_processor + +wrapped_logger = PrintLogger() +logger = wrap_logger(wrapped_logger, processors=[structlog_processor, JSONRenderer()]) +log = logger.new() +log.msg("some_event") +``` + + +#### Use structlog for agent-internal logging [_use_structlog_for_agent_internal_logging] + +The Elastic APM Python agent uses logging to log internal events and issues. By default, it will use a `logging` logger. If your project uses structlog, you can tell the agent to use a structlog logger by setting the environment variable `ELASTIC_APM_USE_STRUCTLOG` to `true`. + + +## Log correlation in Elasticsearch [log-correlation-in-es] + +In order to correlate logs from your app with transactions captured by the Elastic APM Python Agent, your logs must contain one or more of the following identifiers: + +* `transaction.id` +* `trace.id` +* `span.id` + +If you’re using structured logging, either [with a custom solution](https://docs.python.org/3/howto/logging-cookbook.html#implementing-structured-logging) or with [structlog](http://www.structlog.org/en/stable/) (recommended), then this is fairly easy. Throw the [JSONRenderer](http://www.structlog.org/en/stable/api.html#structlog.processors.JSONRenderer) in, and use [Filebeat](https://www.elastic.co/blog/structured-logging-filebeat) to pull these logs into Elasticsearch. + +Without structured logging the task gets a little trickier. Here we recommend first making sure your LogRecord objects have the elasticapm attributes (see [`logging`](#logging)), and then you’ll want to combine some specific formatting with a Grok pattern, either in Elasticsearch using [the grok processor](elasticsearch://reference/ingestion-tools/enrich-processor/grok-processor.md), or in [logstash with a plugin](logstash://reference/plugins-filters-grok.md). + +Say you have a [Formatter](https://docs.python.org/3/library/logging.html#logging.Formatter) that looks like this: + +```python +import logging + +fh = logging.FileHandler('spam.log') +formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") +fh.setFormatter(formatter) +``` + +You can add the APM identifiers by simply switching out the `Formatter` object for the one that we provide: + +```python +import logging +from elasticapm.handlers.logging import Formatter + +fh = logging.FileHandler('spam.log') +formatter = Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") +fh.setFormatter(formatter) +``` + +This will automatically append apm-specific fields to your format string: + +```python +formatstring = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" +formatstring = formatstring + " | elasticapm " \ + "transaction.id=%(elasticapm_transaction_id)s " \ + "trace.id=%(elasticapm_trace_id)s " \ + "span.id=%(elasticapm_span_id)s" +``` + +Then, you could use a grok pattern like this (for the [Elasticsearch Grok Processor](elasticsearch://reference/ingestion-tools/enrich-processor/grok-processor.md)): + +```json +{ + "description" : "...", + "processors": [ + { + "grok": { + "field": "message", + "patterns": ["%{GREEDYDATA:msg} | elasticapm transaction.id=%{DATA:transaction.id} trace.id=%{DATA:trace.id} span.id=%{DATA:span.id}"] + } + } + ] +} +``` + + +## Log reformatting (experimental) [log-reformatting] + +Starting in version 6.16.0, the agent can automatically reformat application logs to ECS format with no changes to dependencies. Prior versions must install the `ecs_logging` dependency. + +Log reformatting is controlled by the [`log_ecs_reformatting`](/reference/configuration.md#config-log_ecs_reformatting) configuration option, and is disabled by default. + +The reformatted logs will include both the [trace and service correlation](#log-correlation-ids) IDs. + diff --git a/docs/reference/metrics.md b/docs/reference/metrics.md new file mode 100644 index 000000000..4f1ff2b27 --- /dev/null +++ b/docs/reference/metrics.md @@ -0,0 +1,185 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/metrics.html +--- + +# Metrics [metrics] + +With Elastic APM, you can capture system and process metrics. These metrics will be sent regularly to the APM Server and from there to Elasticsearch + + +## Metric sets [metric-sets] + +* [CPU/Memory metric set](#cpu-memory-metricset) +* [Breakdown metric set](#breakdown-metricset) +* [Prometheus metric set (beta)](#prometheus-metricset) +* [Custom Metrics](#custom-metrics) + + +### CPU/Memory metric set [cpu-memory-metricset] + +`elasticapm.metrics.sets.cpu.CPUMetricSet` + +This metric set collects various system metrics and metrics of the current process. + +::::{note} +if you do **not** use Linux, you need to install [`psutil`](https://pypi.org/project/psutil/) for this metric set. +:::: + + +**`system.cpu.total.norm.pct`** +: type: scaled_float + +format: percent + +The percentage of CPU time in states other than Idle and IOWait, normalized by the number of cores. + + +**`system.process.cpu.total.norm.pct`** +: type: scaled_float + +format: percent + +The percentage of CPU time spent by the process since the last event. This value is normalized by the number of CPU cores and it ranges from 0 to 100%. + + +**`system.memory.total`** +: type: long + +format: bytes + +Total memory. + + +**`system.memory.actual.free`** +: type: long + +format: bytes + +Actual free memory in bytes. + + +**`system.process.memory.size`** +: type: long + +format: bytes + +The total virtual memory the process has. + + +**`system.process.memory.rss.bytes`** +: type: long + +format: bytes + +The Resident Set Size. The amount of memory the process occupied in main memory (RAM). + + + +#### Linux’s cgroup metrics [cpu-memory-cgroup-metricset] + +**`system.process.cgroup.memory.mem.limit.bytes`** +: type: long + +format: bytes + +Memory limit for current cgroup slice. + + +**`system.process.cgroup.memory.mem.usage.bytes`** +: type: long + +format: bytes + +Memory usage in current cgroup slice. + + + +### Breakdown metric set [breakdown-metricset] + +::::{note} +Tracking and collection of this metric set can be disabled using the [`breakdown_metrics`](/reference/configuration.md#config-breakdown_metrics) setting. +:::: + + +**`span.self_time`** +: type: simple timer + +This timer tracks the span self-times and is the basis of the transaction breakdown visualization. + +Fields: + +* `sum`: The sum of all span self-times in ms since the last report (the delta) +* `count`: The count of all span self-times since the last report (the delta) + +You can filter and group by these dimensions: + +* `transaction.name`: The name of the transaction +* `transaction.type`: The type of the transaction, for example `request` +* `span.type`: The type of the span, for example `app`, `template` or `db` +* `span.subtype`: The sub-type of the span, for example `mysql` (optional) + + + +### Prometheus metric set (beta) [prometheus-metricset] + +::::{warning} +This functionality is in beta and is subject to change. The design and code is less mature than official GA features and is being provided as-is with no warranties. Beta features are not subject to the support SLA of official GA features. +:::: + + +If you use [`prometheus_client`](https://github.com/prometheus/client_python) to collect metrics, the agent can collect them as well and make them available in Elasticsearch. + +The following types of metrics are supported: + +* Counters +* Gauges +* Summaries +* Histograms (requires APM Server / Elasticsearch / Kibana 7.14+) + +To use the Prometheus metric set, you have to enable it with the [`prometheus_metrics`](/reference/configuration.md#config-prometheus_metrics) configuration option. + +All metrics collected from `prometheus_client` are prefixed with `"prometheus.metrics."`. This can be changed using the [`prometheus_metrics_prefix`](/reference/configuration.md#config-prometheus_metrics_prefix) configuration option. + + +#### Beta limitations [prometheus-metricset-beta] + +* The metrics format may change without backwards compatibility in future releases. + + +## Custom Metrics [custom-metrics] + +Custom metrics allow you to send your own metrics to Elasticsearch. + +The most common way to send custom metrics is with the [Prometheus metric set](#prometheus-metricset). However, you can also use your own metric set. If you collect the metrics manually in your code, you can use the base `MetricSet` class: + +```python +from elasticapm.metrics.base_metrics import MetricSet + +client = elasticapm.Client() +metricset = client.metrics.register(MetricSet) + +for x in range(10): + metricset.counter("my_counter").inc() +``` + +Alternatively, you can create your own MetricSet class which inherits from the base class. In this case, you’ll usually want to override the `before_collect` method, where you can gather and set metrics before they are collected and sent to Elasticsearch. + +You can add your `MetricSet` class as shown in the example above, or you can add an import string for your class to the [`metrics_sets`](/reference/configuration.md#config-metrics_sets) configuration option: + +```bash +ELASTIC_APM_METRICS_SETS="elasticapm.metrics.sets.cpu.CPUMetricSet,myapp.metrics.MyMetricSet" +``` + +Your MetricSet might look something like this: + +```python +from elasticapm.metrics.base_metrics import MetricSet + +class MyAwesomeMetricSet(MetricSet): + def before_collect(self): + self.gauge("my_gauge").set(myapp.some_value) +``` + +In the example above, the MetricSet would look up `myapp.some_value` and set the metric `my_gauge` to that value. This would happen whenever metrics are collected/sent, which is controlled by the [`metrics_interval`](/reference/configuration.md#config-metrics_interval) setting. + diff --git a/docs/reference/opentelemetry-api-bridge.md b/docs/reference/opentelemetry-api-bridge.md new file mode 100644 index 000000000..7564f85fa --- /dev/null +++ b/docs/reference/opentelemetry-api-bridge.md @@ -0,0 +1,63 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/opentelemetry-bridge.html +--- + +# OpenTelemetry API Bridge [opentelemetry-bridge] + +The Elastic APM OpenTelemetry bridge allows you to create Elastic APM `Transactions` and `Spans`, using the OpenTelemetry API. This allows users to utilize the Elastic APM agent’s automatic instrumentations, while keeping custom instrumentations vendor neutral. + +If a span is created while there is no transaction active, it will result in an Elastic APM [`Transaction`](docs-content://solutions/observability/apps/transactions.md). Inner spans are mapped to Elastic APM [`Span`](docs-content://solutions/observability/apps/spans.md). + + +## Getting started [opentelemetry-getting-started] + +The first step in getting started with the OpenTelemetry bridge is to install the `opentelemetry` libraries: + +```bash +pip install elastic-apm[opentelemetry] +``` + +Or if you already have installed `elastic-apm`: + +```bash +pip install opentelemetry-api opentelemetry-sdk +``` + + +## Usage [opentelemetry-usage] + +```python +from elasticapm.contrib.opentelemetry import Tracer + +tracer = Tracer(__name__) +with tracer.start_as_current_span("test"): + # Do some work +``` + +or + +```python +from elasticapm.contrib.opentelemetry import trace + +tracer = trace.get_tracer(__name__) +with tracer.start_as_current_span("test"): + # Do some work +``` + +`Tracer` and `get_tracer()` accept the following optional arguments: + +* `elasticapm_client`: an already instantiated Elastic APM client +* `config`: a configuration dictionary, which will be used to instantiate a new Elastic APM client, e.g. `{"SERVER_URL": "https://example.org"}`. See [configuration](/reference/configuration.md) for more information. + +The `Tracer` object mirrors the upstream interface on the [OpenTelemetry `Tracer` object.](https://opentelemetry-python.readthedocs.io/en/latest/api/trace.html#opentelemetry.trace.Tracer) + + +## Caveats [opentelemetry-caveats] + +Not all features of the OpenTelemetry API are supported. + +Processors, exporters, metrics, logs, span events, and span links are not supported. + +Additionally, due to implementation details, the global context API only works when a span is included in the activated context, and tokens are not used. Instead, the global context works as a stack, and when a context is detached the previously-active context will automatically be activated. + diff --git a/docs/reference/performance-tuning.md b/docs/reference/performance-tuning.md new file mode 100644 index 000000000..04ce10e64 --- /dev/null +++ b/docs/reference/performance-tuning.md @@ -0,0 +1,86 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/tuning-and-overhead.html +--- + +# Performance tuning [tuning-and-overhead] + +Using an APM solution comes with certain trade-offs, and the Python agent for Elastic APM is no different. Instrumenting your code, measuring timings, recording context data, etc., all need resources: + +* CPU time +* memory +* bandwidth use +* Elasticsearch storage + +We invested and continue to invest a lot of effort to keep the overhead of using Elastic APM as low as possible. But because every deployment is different, there are some knobs you can turn to adapt it to your specific needs. + + +## Transaction Sample Rate [tuning-sample-rate] + +The easiest way to reduce the overhead of the agent is to tell the agent to do less. If you set the [`transaction_sample_rate`](/reference/configuration.md#config-transaction-sample-rate) to a value below `1.0`, the agent will randomly sample only a subset of transactions. Unsampled transactions only record the name of the transaction, the overall transaction time, and the result: + +| Field | Sampled | Unsampled | +| --- | --- | --- | +| Transaction name | yes | yes | +| Duration | yes | yes | +| Result | yes | yes | +| Context | yes | no | +| Tags | yes | no | +| Spans | yes | no | + +Reducing the sample rate to a fraction of all transactions can make a huge difference in all four of the mentioned resource types. + + +## Transaction Queue [tuning-queue] + +To reduce the load on the APM Server, the agent does not send every transaction up as it happens. Instead, it queues them up and flushes the queue periodically, or when it reaches a maximum size, using a background thread. + +While this reduces the load on the APM Server (and to a certain extent on the agent), holding on to the transaction data in a queue uses memory. If you notice that using the Python agent results in a large increase of memory use, you can use these settings: + +* [`api_request_time`](/reference/configuration.md#config-api-request-time) to reduce the time between queue flushes +* [`api_request_size`](/reference/configuration.md#config-api-request-size) to reduce the maximum size of the queue + +The first setting, `api_request_time`, is helpful if you have a sustained high number of transactions. The second setting, `api_request_size`, can help if you experience peaks of transactions (a large number of transactions in a short period of time). + +Keep in mind that reducing the value of either setting will cause the agent to send more HTTP requests to the APM Server, potentially causing a higher load. + + +## Spans per transaction [tuning-max-spans] + +The average amount of spans per transaction can influence how much time the agent spends in each transaction collecting contextual data for each span, and the storage space needed in Elasticsearch. In our experience, most *usual* transactions should have well below 100 spans. In some cases, however, the number of spans can explode: + +* long-running transactions +* unoptimized code, e.g. doing hundreds of SQL queries in a loop + +To avoid these edge cases overloading both the agent and the APM Server, the agent stops recording spans when a specified limit is reached. You can configure this limit by changing the [`transaction_max_spans`](/reference/configuration.md#config-transaction-max-spans) setting. + + +## Span Stack Trace Collection [tuning-span-stack-trace-collection] + +Collecting stack traces for spans can be fairly costly from a performance standpoint. Stack traces are very useful for pinpointing which part of your code is generating a span; however, these stack traces are less useful for very short spans (as problematic spans tend to be longer). + +You can define a minimal threshold for span duration using the [`span_stack_trace_min_duration`](/reference/configuration.md#config-span-stack-trace-min-duration) setting. If a span’s duration is less than this config value, no stack frames will be collected for this span. + + +## Collecting Frame Context [tuning-frame-context] + +When a stack trace is captured, the agent will also capture several lines of source code around each frame location in the stack trace. This allows the APM app to give greater insight into where exactly the error or span happens. + +There are four settings you can modify to control this behavior: + +* [`source_lines_error_app_frames`](/reference/configuration.md#config-source-lines-error-app-frames) +* [`source_lines_error_library_frames`](/reference/configuration.md#config-source-lines-error-library-frames) +* [`source_lines_span_app_frames`](/reference/configuration.md#config-source-lines-span-app-frames) +* [`source_lines_span_library_frames`](/reference/configuration.md#config-source-lines-span-library-frames) + +As you can see, these settings are divided between app frames, which represent your application code, and library frames, which represent the code of your dependencies. Each of these categories are also split into separate error and span settings. + +Reading source files inside a running application can cause a lot of disk I/O, and sending up source lines for each frame will have a network and storage cost that is quite high. Turning down these limits will help prevent excessive memory usage. + + +## Collecting headers and request body [tuning-body-headers] + +You can configure the Elastic APM agent to capture headers of both requests and responses ([`capture_headers`](/reference/configuration.md#config-capture-headers)), as well as request bodies ([`capture_body`](/reference/configuration.md#config-capture-body)). By default, capturing request bodies is disabled. Enabling it for transactions may introduce noticeable overhead, as well as increased storage use, depending on the nature of your POST requests. In most scenarios, we advise against enabling request body capturing for transactions, and only enable it if necessary for errors. + +Capturing request/response headers has less overhead on the agent, but can have an impact on storage use. If storage use is a problem for you, it might be worth disabling. + diff --git a/docs/reference/run-tests-locally.md b/docs/reference/run-tests-locally.md new file mode 100644 index 000000000..f72432d7e --- /dev/null +++ b/docs/reference/run-tests-locally.md @@ -0,0 +1,72 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/run-tests-locally.html +--- + +# Run Tests Locally [run-tests-locally] + +To run tests locally you can make use of the docker images also used when running the whole test suite with Jenkins. Running the full test suite first does some linting and then runs the actual tests with different versions of Python and different web frameworks. For a full overview of the test matrix and supported versions have a look at [Jenkins Configuration](https://github.com/elastic/apm-agent-python/blob/main/Jenkinsfile). + + +### Pre Commit [pre-commit] + +We run our git hooks on every commit to automatically point out issues in code. Those issues are also detected within the GitHub actions. Please follow the installation steps stated in [https://pre-commit.com/#install](https://pre-commit.com/#install). + + +### Code Linter [coder-linter] + +We run two code linters `isort` and `flake8`. You can trigger each single one locally by running: + +```bash +$ pre-commit run -a isort +``` + +```bash +$ pre-commit run -a flake8 +``` + + +### Code Formatter [coder-formatter] + +We test that the code is formatted using `black`. You can trigger this check by running: + +```bash +$ pre-commit run -a black +``` + + +### Test Documentation [test-documentation] + +We test that the documentation can be generated without errors. You can trigger this check by running: + +```bash +$ ./tests/scripts/docker/docs.sh +``` + + +### Running Tests [running-tests] + +We run the test suite on different combinations of Python versions and web frameworks. For triggering the test suite for a specific combination locally you can run: + +```bash +$ ./tests/scripts/docker/run_tests.sh python-version framework-version +``` + +::::{note} +The `python-version` must be of format `python-version`, e.g. `python-3.6` or `pypy-2`. The `framework` must be of format `framework-version`, e.g. `django-1.10` or `flask-0.12`. +:::: + + +You can also run the unit tests outside of docker, by installing the relevant [requirements file](https://github.com/elastic/apm-agent-python/tree/main/tests/requirements) and then running `py.test` from the project root. + +## Integration testing [_integration_testing] + +Check out [https://github.com/elastic/apm-integration-testing](https://github.com/elastic/apm-integration-testing) for resources for setting up full end-to-end testing environments. For example, to spin up an environment with the [opbeans Django app](https://github.com/basepi/opbeans-python), with version 7.3 of the elastic stack and the apm-python-agent from your local checkout, you might do something like this: + +```bash +$ ./scripts/compose.py start 7.3 \ + --with-agent-python-django --with-opbeans-python \ + --opbeans-python-agent-local-repo=~/elastic/apm-agent-python +``` + + diff --git a/docs/reference/sanic-support.md b/docs/reference/sanic-support.md new file mode 100644 index 000000000..515bf6837 --- /dev/null +++ b/docs/reference/sanic-support.md @@ -0,0 +1,140 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/sanic-support.html +--- + +# Sanic Support [sanic-support] + +Incorporating Elastic APM into your Sanic project only requires a few easy steps. + + +## Installation [sanic-installation] + +Install the Elastic APM agent using pip: + +```bash +$ pip install elastic-apm +``` + +or add `elastic-apm` to your project’s `requirements.txt` file. + + +## Setup [sanic-setup] + +To set up the agent, you need to initialize it with appropriate settings. + +The settings are configured either via environment variables, or as initialization arguments. + +You can find a list of all available settings in the [Configuration](/reference/configuration.md) page. + +To initialize the agent for your application using environment variables: + +```python +from sanic import Sanic +from elasticapm.contrib.sanic import ElasticAPM + +app = Sanic(name="elastic-apm-sample") +apm = ElasticAPM(app=app) +``` + +To configure the agent using initialization arguments and Sanic’s Configuration infrastructure: + +```python +# Create a file named external_config.py in your application +# If you want this module based configuration to be used for APM, prefix them with ELASTIC_APM_ +ELASTIC_APM_SERVER_URL = "https://serverurl.apm.com:443" +ELASTIC_APM_SECRET_TOKEN = "sometoken" +``` + +```python +from sanic import Sanic +from elasticapm.contrib.sanic import ElasticAPM + +app = Sanic(name="elastic-apm-sample") +app.config.update_config("path/to/external_config.py") +apm = ElasticAPM(app=app) +``` + + +## Usage [sanic-usage] + +Once you have configured the agent, it will automatically track transactions and capture uncaught exceptions within sanic. + +Capture an arbitrary exception by calling [`capture_exception`](/reference/api-reference.md#client-api-capture-exception): + +```python +from sanic import Sanic +from elasticapm.contrib.sanic import ElasticAPM + +app = Sanic(name="elastic-apm-sample") +apm = ElasticAPM(app=app) + +try: + 1 / 0 +except ZeroDivisionError: + apm.capture_exception() +``` + +Log a generic message with [`capture_message`](/reference/api-reference.md#client-api-capture-message): + +```python +from sanic import Sanic +from elasticapm.contrib.sanic import ElasticAPM + +app = Sanic(name="elastic-apm-sample") +apm = ElasticAPM(app=app) + +apm.capture_message('hello, world!') +``` + + +## Performance metrics [sanic-performance-metrics] + +If you’ve followed the instructions above, the agent has installed our instrumentation middleware which will process all requests through your app. This will measure response times, as well as detailed performance data for all supported technologies. + +::::{note} +Due to the fact that `asyncio` drivers are usually separate from their synchronous counterparts, specific instrumentation is needed for all drivers. The support for asynchronous drivers is currently quite limited. +:::: + + + +### Ignoring specific routes [sanic-ignoring-specific-views] + +You can use the [`TRANSACTIONS_IGNORE_PATTERNS`](/reference/configuration.md#config-transactions-ignore-patterns) configuration option to ignore specific routes. The list given should be a list of regular expressions which are matched against the transaction name: + +```python +from sanic import Sanic +from elasticapm.contrib.sanic import ElasticAPM + +app = Sanic(name="elastic-apm-sample") +apm = ElasticAPM(app=app, config={ + 'TRANSACTIONS_IGNORE_PATTERNS': ['^GET /secret', '/extra_secret'], +}) +``` + +This would ignore any requests using the `GET /secret` route and any requests containing `/extra_secret`. + + +## Extended Sanic APM Client Usage [extended-sanic-usage] + +Sanic’s contributed APM client also provides a few extendable way to configure selective behaviors to enhance the information collected as part of the transactions being tracked by the APM. + +In order to enable this behavior, the APM Client middleware provides a few callback functions that you can leverage in order to simplify the process of generating additional contexts into the traces being collected. + +| Callback Name | Callback Invocation Format | Expected Return Format | Is Async | +| --- | --- | --- | --- | +| transaction_name_callback | transaction_name_callback(request) | string | false | +| user_context_callback | user_context_callback(request) | (username_string, user_email_string, userid_string) | true | +| custom_context_callback | custom_context_callback(request) or custom_context_callback(response) | dict(str=str) | true | +| label_info_callback | label_info_callback() | dict(str=str) | true | + + +## Supported Sanic and Python versions [supported-stanic-and-python-versions] + +A list of supported [Sanic](/reference/supported-technologies.md#supported-sanic) and [Python](/reference/supported-technologies.md#supported-python) versions can be found on our [Supported Technologies](/reference/supported-technologies.md) page. + +::::{note} +Elastic APM only supports `asyncio` when using Python 3.7+ +:::: + + diff --git a/docs/sanitizing-data.asciidoc b/docs/reference/sanitizing-data.md similarity index 72% rename from docs/sanitizing-data.asciidoc rename to docs/reference/sanitizing-data.md index 4daa9eb8f..b41d89148 100644 --- a/docs/sanitizing-data.asciidoc +++ b/docs/reference/sanitizing-data.md @@ -1,22 +1,21 @@ -[[sanitizing-data]] -=== Sanitizing data +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/sanitizing-data.html +--- -Sometimes it is necessary to sanitize the data sent to Elastic APM, -e.g. remove sensitive data. +# Sanitizing data [sanitizing-data] -To do this with the Elastic APM module, you create a processor. -A processor is a function that takes a `client` instance as well as an event (an error, a transaction, a span, or a metricset), -and returns the modified event. +Sometimes it is necessary to sanitize the data sent to Elastic APM, e.g. remove sensitive data. + +To do this with the Elastic APM module, you create a processor. A processor is a function that takes a `client` instance as well as an event (an error, a transaction, a span, or a metricset), and returns the modified event. To completely drop an event, your processor should return `False` (or any other "falsy" value) instead of the event. -An event will also be dropped if any processor raises an exception while processing it. -A log message with level `WARNING` will be issued in this case. +An event will also be dropped if any processor raises an exception while processing it. A log message with level `WARNING` will be issued in this case. This is an example of a processor that removes the exception stacktrace from an error: -[source,python] ----- +```python from elasticapm.conf.constants import ERROR from elasticapm.processors import for_events @@ -25,16 +24,13 @@ def my_processor(client, event): if 'exception' in event and 'stacktrace' in event['exception']: event['exception'].pop('stacktrace') return event ----- +``` -You can use the `@for_events` decorator to limit for which event type the processor should be called. -Possible choices are `ERROR`, `TRANSACTION`, `SPAN` and `METRICSET`, -all of which are defined in `elasticapm.conf.constants`. +You can use the `@for_events` decorator to limit for which event type the processor should be called. Possible choices are `ERROR`, `TRANSACTION`, `SPAN` and `METRICSET`, all of which are defined in `elasticapm.conf.constants`. To use this processor, update your `ELASTIC_APM` settings like this: -[source,python] ----- +```python ELASTIC_APM = { 'SERVICE_NAME': '', 'SECRET_TOKEN': '', @@ -47,14 +43,16 @@ ELASTIC_APM = { 'elasticapm.processors.sanitize_http_request_body', ), } ----- +``` + +::::{note} +We recommend using the above list of processors that sanitize passwords and secrets in different places of the event object. +:::: -NOTE: We recommend using the above list of processors that sanitize passwords and secrets in different places of the event object. The default set of processors sanitize fields based on a set of defaults defined in `elasticapm.conf.constants`. This set can be configured with the `SANITIZE_FIELD_NAMES` configuration option. For example, if your application produces a sensitive field called `My-Sensitive-Field`, the default processors can be used to automatically sanitize this field. You can specify what fields to santize within default processors like this: -[source,python] ----- +```python ELASTIC_APM = { 'SERVICE_NAME': '', 'SECRET_TOKEN': '', @@ -72,8 +70,12 @@ ELASTIC_APM = { "set-cookie", ), } ----- +``` + +::::{note} +We recommend to use the above list of fields to sanitize various parts of the event object in addition to your specified fields. +:::: -NOTE: We recommend to use the above list of fields to sanitize various parts of the event object in addition to your specified fields. When choosing fields names to sanitize, you can specify values that will match certain wildcards. For example, passing `base` as a field name to be sanitized will also sanitize all fields whose names match the regex pattern `\*base*`. + diff --git a/docs/reference/set-up-apm-python-agent.md b/docs/reference/set-up-apm-python-agent.md new file mode 100644 index 000000000..a74e45208 --- /dev/null +++ b/docs/reference/set-up-apm-python-agent.md @@ -0,0 +1,32 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/set-up.html +--- + +# Set up the APM Python Agent [set-up] + +To get you off the ground, we’ve prepared guides for setting up the Agent with different frameworks: + +* [Django](/reference/django-support.md) +* [Flask](/reference/flask-support.md) +* [aiohttp](/reference/aiohttp-server-support.md) +* [Tornado](/reference/tornado-support.md) +* [Starlette/FastAPI](/reference/starlette-support.md) +* [Sanic](/reference/sanic-support.md) +* [AWS Lambda](/reference/lambda-support.md) +* [Azure Functions](/reference/azure-functions-support.md) +* [Wrapper (Experimental)](/reference/wrapper-support.md) +* [ASGI Middleware](/reference/asgi-middleware.md) + +For custom instrumentation, see [Instrumenting Custom Code](/reference/instrumenting-custom-code.md). + + + + + + + + + + + diff --git a/docs/reference/starlette-support.md b/docs/reference/starlette-support.md new file mode 100644 index 000000000..673ab79db --- /dev/null +++ b/docs/reference/starlette-support.md @@ -0,0 +1,127 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/starlette-support.html +--- + +# Starlette/FastAPI Support [starlette-support] + +Incorporating Elastic APM into your Starlette project only requires a few easy steps. + + +## Installation [starlette-installation] + +Install the Elastic APM agent using pip: + +```bash +$ pip install elastic-apm +``` + +or add `elastic-apm` to your project’s `requirements.txt` file. + + +## Setup [starlette-setup] + +To set up the agent, you need to initialize it with appropriate settings. + +The settings are configured either via environment variables, or as initialization arguments. + +You can find a list of all available settings in the [Configuration](/reference/configuration.md) page. + +To initialize the agent for your application using environment variables, add the ElasticAPM middleware to your Starlette application: + +```python +from starlette.applications import Starlette +from elasticapm.contrib.starlette import ElasticAPM + +app = Starlette() +app.add_middleware(ElasticAPM) +``` + +::::{warning} +`BaseHTTPMiddleware` breaks `contextvar` propagation, as noted [here](https://www.starlette.io/middleware/#limitations). This means the ElasticAPM middleware must be above any `BaseHTTPMiddleware` in the final middleware list. If you’re calling `add_middleware` repeatedly, add the ElasticAPM middleware last. If you’re passing in a list of middleware, ElasticAPM should be first on that list. +:::: + + +To configure the agent using initialization arguments: + +```python +from starlette.applications import Starlette +from elasticapm.contrib.starlette import make_apm_client, ElasticAPM + +apm = make_apm_client({ + 'SERVICE_NAME': '', + 'SECRET_TOKEN': '', + 'SERVER_URL': '', +}) +app = Starlette() +app.add_middleware(ElasticAPM, client=apm) +``` + + +## FastAPI [starlette-fastapi] + +Because FastAPI supports Starlette middleware, using the agent with FastAPI is almost exactly the same as with Starlette: + +```python +from fastapi import FastAPI +from elasticapm.contrib.starlette import ElasticAPM + +app = FastAPI() +app.add_middleware(ElasticAPM) +``` + + +## Usage [starlette-usage] + +Once you have configured the agent, it will automatically track transactions and capture uncaught exceptions within starlette. + +Capture an arbitrary exception by calling [`capture_exception`](/reference/api-reference.md#client-api-capture-exception): + +```python +try: + 1 / 0 +except ZeroDivisionError: + apm.capture_exception() +``` + +Log a generic message with [`capture_message`](/reference/api-reference.md#client-api-capture-message): + +```python +apm.capture_message('hello, world!') +``` + + +## Performance metrics [starlette-performance-metrics] + +If you’ve followed the instructions above, the agent has installed our instrumentation middleware which will process all requests through your app. This will measure response times, as well as detailed performance data for all supported technologies. + +::::{note} +Due to the fact that `asyncio` drivers are usually separate from their synchronous counterparts, specific instrumentation is needed for all drivers. The support for asynchronous drivers is currently quite limited. +:::: + + + +### Ignoring specific routes [starlette-ignoring-specific-views] + +You can use the [`TRANSACTIONS_IGNORE_PATTERNS`](/reference/configuration.md#config-transactions-ignore-patterns) configuration option to ignore specific routes. The list given should be a list of regular expressions which are matched against the transaction name: + +```python +apm = make_apm_client({ + # ... + 'TRANSACTIONS_IGNORE_PATTERNS': ['^GET /secret', '/extra_secret'] + # ... +}) +``` + +This would ignore any requests using the `GET /secret` route and any requests containing `/extra_secret`. + + +## Supported Starlette and Python versions [supported-starlette-and-python-versions] + +A list of supported [Starlette](/reference/supported-technologies.md#supported-starlette) and [Python](/reference/supported-technologies.md#supported-python) versions can be found on our [Supported Technologies](/reference/supported-technologies.md) page. + +::::{note} +Elastic APM only supports `asyncio` when using Python 3.7+ +:::: + + diff --git a/docs/reference/supported-technologies.md b/docs/reference/supported-technologies.md new file mode 100644 index 000000000..715c6a76f --- /dev/null +++ b/docs/reference/supported-technologies.md @@ -0,0 +1,631 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/supported-technologies.html +--- + +# Supported technologies [supported-technologies] + +$$$framework-support$$$ +The Elastic APM Python Agent comes with support for the following frameworks: + +* [Django](/reference/django-support.md) +* [Flask](/reference/flask-support.md) +* [Aiohttp Server](#supported-aiohttp) +* [Tornado](#supported-tornado) +* [Starlette/FastAPI](#supported-starlette) +* [Sanic](#supported-sanic) +* [GRPC](#supported-grpc) + +For other frameworks and custom Python code, the agent exposes a set of [APIs](/reference/api-reference.md) for integration. + + +### Python [supported-python] + +The following Python versions are supported: + +* 3.6 +* 3.7 +* 3.8 +* 3.9 +* 3.10 +* 3.11 +* 3.12 + + +### Django [supported-django] + +We support these Django versions: + +* 1.11 +* 2.0 +* 2.1 +* 2.2 +* 3.0 +* 3.1 +* 3.2 +* 4.0 +* 4.2 +* 5.0 + +For upcoming Django versions, we generally aim to ensure compatibility starting with the first Release Candidate. + +::::{note} +we currently don’t support Django running in ASGI mode. +:::: + + + +### Flask [supported-flask] + +We support these Flask versions: + +* 0.10 (Deprecated) +* 0.11 (Deprecated) +* 0.12 (Deprecated) +* 1.0 +* 1.1 +* 2.0 +* 2.1 +* 2.2 +* 2.3 +* 3.0 + + +### Aiohttp Server [supported-aiohttp] + +We support these aiohttp versions: + +* 3.0+ + + +### Tornado [supported-tornado] + +We support these tornado versions: + +* 6.0+ + + +### Sanic [supported-sanic] + +We support these sanic versions: + +* 20.12.2+ + + +### Starlette/FastAPI [supported-starlette] + +We support these Starlette versions: + +* 0.13.0+ + +Any FastAPI version which uses a supported Starlette version should also be supported. + + +### GRPC [supported-grpc] + +We support these `grpcio` versions: + +* 1.24.0+ + + +## Automatic Instrumentation [automatic-instrumentation] + +The Python APM agent comes with automatic instrumentation of various 3rd party modules and standard library modules. + + +### Scheduling [automatic-instrumentation-scheduling] + + +##### Celery [automatic-instrumentation-scheduling-celery] + +We support these Celery versions: + +* 4.x (deprecated) +* 5.x + +Celery tasks will be recorded automatically with Django and Flask only. + + +### Databases [automatic-instrumentation-db] + + +#### Elasticsearch [automatic-instrumentation-db-elasticsearch] + +Instrumented methods: + +* `elasticsearch.transport.Transport.perform_request` +* `elasticsearch.connection.http_urllib3.Urllib3HttpConnection.perform_request` +* `elasticsearch.connection.http_requests.RequestsHttpConnection.perform_request` +* `elasticsearch._async.transport.AsyncTransport.perform_request` +* `elasticsearch_async.connection.AIOHttpConnection.perform_request` + +Additionally, the instrumentation wraps the following methods of the `Elasticsearch` client class: + +* `elasticsearch.client.Elasticsearch.delete_by_query` +* `elasticsearch.client.Elasticsearch.search` +* `elasticsearch.client.Elasticsearch.count` +* `elasticsearch.client.Elasticsearch.update` + +Collected trace data: + +* the query string (if available) +* the `query` element from the request body (if available) +* the response status code +* the count of affected rows (if available) + +We recommend using keyword arguments only with elasticsearch-py, as recommended by [the elasticsearch-py docs](https://elasticsearch-py.readthedocs.io/en/latest/api.html#api-documentation). If you are using positional arguments, we will be unable to gather the `query` element from the request body. + + +#### SQLite [automatic-instrumentation-db-sqlite] + +Instrumented methods: + +* `sqlite3.connect` +* `sqlite3.dbapi2.connect` +* `pysqlite2.dbapi2.connect` + +The instrumented `connect` method returns a wrapped connection/cursor which instruments the actual `Cursor.execute` calls. + +Collected trace data: + +* parametrized SQL query + + +#### MySQLdb [automatic-instrumentation-db-mysql] + +Library: `MySQLdb` + +Instrumented methods: + +* `MySQLdb.connect` + +The instrumented `connect` method returns a wrapped connection/cursor which instruments the actual `Cursor.execute` calls. + +Collected trace data: + +* parametrized SQL query + + +#### mysql-connector [automatic-instrumentation-db-mysql-connector] + +Library: `mysql-connector-python` + +Instrumented methods: + +* `mysql.connector.connect` + +The instrumented `connect` method returns a wrapped connection/cursor which instruments the actual `Cursor.execute` calls. + +Collected trace data: + +* parametrized SQL query + + +#### pymysql [automatic-instrumentation-db-pymysql] + +Library: `pymysql` + +Instrumented methods: + +* `pymysql.connect` + +The instrumented `connect` method returns a wrapped connection/cursor which instruments the actual `Cursor.execute` calls. + +Collected trace data: + +* parametrized SQL query + + +#### aiomysql [automatic-instrumentation-db-aiomysql] + +Library: `aiomysql` + +Instrumented methods: + +* `aiomysql.cursors.Cursor.execute` + +Collected trace data: + +* parametrized SQL query + + +#### PostgreSQL [automatic-instrumentation-db-postgres] + +Library: `psycopg2`, `psycopg2-binary` (`>=2.9`) + +Instrumented methods: + +* `psycopg2.connect` + +The instrumented `connect` method returns a wrapped connection/cursor which instruments the actual `Cursor.execute` calls. + +Collected trace data: + +* parametrized SQL query + + +#### aiopg [automatic-instrumentation-db-aiopg] + +Library: `aiopg` (`>=1.0`) + +Instrumented methods: + +* `aiopg.cursor.Cursor.execute` +* `aiopg.cursor.Cursor.callproc` + +Collected trace data: + +* parametrized SQL query + + +#### asyncpg [automatic-instrumentation-db-asyncg] + +Library: `asyncpg` (`>=0.20`) + +Instrumented methods: + +* `asyncpg.connection.Connection.execute` +* `asyncpg.connection.Connection.executemany` + +Collected trace data: + +* parametrized SQL query + + +#### PyODBC [automatic-instrumentation-db-pyodbc] + +Library: `pyodbc`, (`>=4.0`) + +Instrumented methods: + +* `pyodbc.connect` + +The instrumented `connect` method returns a wrapped connection/cursor which instruments the actual `Cursor.execute` calls. + +Collected trace data: + +* parametrized SQL query + + +#### MS-SQL [automatic-instrumentation-db-mssql] + +Library: `pymssql`, (`>=2.1.0`) + +Instrumented methods: + +* `pymssql.connect` + +The instrumented `connect` method returns a wrapped connection/cursor which instruments the actual `Cursor.execute` calls. + +Collected trace data: + +* parametrized SQL query + + +#### MongoDB [automatic-instrumentation-db-mongodb] + +Library: `pymongo`, `>=2.9,<3.8` + +Instrumented methods: + +* `pymongo.collection.Collection.aggregate` +* `pymongo.collection.Collection.bulk_write` +* `pymongo.collection.Collection.count` +* `pymongo.collection.Collection.create_index` +* `pymongo.collection.Collection.create_indexes` +* `pymongo.collection.Collection.delete_many` +* `pymongo.collection.Collection.delete_one` +* `pymongo.collection.Collection.distinct` +* `pymongo.collection.Collection.drop` +* `pymongo.collection.Collection.drop_index` +* `pymongo.collection.Collection.drop_indexes` +* `pymongo.collection.Collection.ensure_index` +* `pymongo.collection.Collection.find_and_modify` +* `pymongo.collection.Collection.find_one` +* `pymongo.collection.Collection.find_one_and_delete` +* `pymongo.collection.Collection.find_one_and_replace` +* `pymongo.collection.Collection.find_one_and_update` +* `pymongo.collection.Collection.group` +* `pymongo.collection.Collection.inline_map_reduce` +* `pymongo.collection.Collection.insert` +* `pymongo.collection.Collection.insert_many` +* `pymongo.collection.Collection.insert_one` +* `pymongo.collection.Collection.map_reduce` +* `pymongo.collection.Collection.reindex` +* `pymongo.collection.Collection.remove` +* `pymongo.collection.Collection.rename` +* `pymongo.collection.Collection.replace_one` +* `pymongo.collection.Collection.save` +* `pymongo.collection.Collection.update` +* `pymongo.collection.Collection.update_many` +* `pymongo.collection.Collection.update_one` + +Collected trace data: + +* database name +* method name + + +#### Redis [automatic-instrumentation-db-redis] + +Library: `redis` (`>=2.8`) + +Instrumented methods: + +* `redis.client.Redis.execute_command` +* `redis.client.Pipeline.execute` + +Collected trace data: + +* Redis command name + + +#### aioredis [automatic-instrumentation-db-aioredis] + +Library: `aioredis` (`<2.0`) + +Instrumented methods: + +* `aioredis.pool.ConnectionsPool.execute` +* `aioredis.commands.transaction.Pipeline.execute` +* `aioredis.connection.RedisConnection.execute` + +Collected trace data: + +* Redis command name + + +#### Cassandra [automatic-instrumentation-db-cassandra] + +Library: `cassandra-driver` (`>=3.4,<4.0`) + +Instrumented methods: + +* `cassandra.cluster.Session.execute` +* `cassandra.cluster.Cluster.connect` + +Collected trace data: + +* CQL query + + +#### Python Memcache [automatic-instrumentation-db-python-memcache] + +Library: `python-memcached` (`>=1.51`) + +Instrumented methods: + +* `memcache.Client.add` +* `memcache.Client.append` +* `memcache.Client.cas` +* `memcache.Client.decr` +* `memcache.Client.delete` +* `memcache.Client.delete_multi` +* `memcache.Client.disconnect_all` +* `memcache.Client.flush_all` +* `memcache.Client.get` +* `memcache.Client.get_multi` +* `memcache.Client.get_slabs` +* `memcache.Client.get_stats` +* `memcache.Client.gets` +* `memcache.Client.incr` +* `memcache.Client.prepend` +* `memcache.Client.replace` +* `memcache.Client.set` +* `memcache.Client.set_multi` +* `memcache.Client.touch` + +Collected trace data: + +* Destination (address and port) + + +#### pymemcache [automatic-instrumentation-db-pymemcache] + +Library: `pymemcache` (`>=3.0`) + +Instrumented methods: + +* `pymemcache.client.base.Client.add` +* `pymemcache.client.base.Client.append` +* `pymemcache.client.base.Client.cas` +* `pymemcache.client.base.Client.decr` +* `pymemcache.client.base.Client.delete` +* `pymemcache.client.base.Client.delete_many` +* `pymemcache.client.base.Client.delete_multi` +* `pymemcache.client.base.Client.flush_all` +* `pymemcache.client.base.Client.get` +* `pymemcache.client.base.Client.get_many` +* `pymemcache.client.base.Client.get_multi` +* `pymemcache.client.base.Client.gets` +* `pymemcache.client.base.Client.gets_many` +* `pymemcache.client.base.Client.incr` +* `pymemcache.client.base.Client.prepend` +* `pymemcache.client.base.Client.quit` +* `pymemcache.client.base.Client.replace` +* `pymemcache.client.base.Client.set` +* `pymemcache.client.base.Client.set_many` +* `pymemcache.client.base.Client.set_multi` +* `pymemcache.client.base.Client.stats` +* `pymemcache.client.base.Client.touch` + +Collected trace data: + +* Destination (address and port) + + +#### kafka-python [automatic-instrumentation-db-kafka-python] + +Library: `kafka-python` (`>=2.0`) + +Instrumented methods: + +* `kafka.KafkaProducer.send`, +* `kafka.KafkaConsumer.poll`, +* `kafka.KafkaConsumer.\__next__` + +Collected trace data: + +* Destination (address and port) +* topic (if applicable) + + +### External HTTP requests [automatic-instrumentation-http] + + +#### Standard library [automatic-instrumentation-stdlib-urllib] + +Library: `urllib2` (Python 2) / `urllib.request` (Python 3) + +Instrumented methods: + +* `urllib2.AbstractHTTPHandler.do_open` / `urllib.request.AbstractHTTPHandler.do_open` + +Collected trace data: + +* HTTP method +* requested URL + + +#### urllib3 [automatic-instrumentation-urllib3] + +Library: `urllib3` + +Instrumented methods: + +* `urllib3.connectionpool.HTTPConnectionPool.urlopen` + +Additionally, we instrumented vendored instances of urllib3 in the following libraries: + +* `requests` +* `botocore` + +Both libraries have "unvendored" urllib3 in more recent versions, we recommend to use the newest versions. + +Collected trace data: + +* HTTP method +* requested URL + + +#### requests [automatic-instrumentation-requests] + +Instrumented methods: + +* `requests.sessions.Session.send` + +Collected trace data: + +* HTTP method +* requested URL + + +#### AIOHTTP Client [automatic-instrumentation-aiohttp-client] + +Instrumented methods: + +* `aiohttp.client.ClientSession._request` + +Collected trace data: + +* HTTP method +* requested URL + + +#### httpx [automatic-instrumentation-httpx] + +Instrumented methods: + +* `httpx.Client.send + +Collected trace data: + +* HTTP method +* requested URL + + +### Services [automatic-instrumentation-services] + + +#### AWS Boto3 / Botocore [automatic-instrumentation-boto3] + +Library: `boto3` (`>=1.0`) + +Instrumented methods: + +* `botocore.client.BaseClient._make_api_call` + +Collected trace data for all services: + +* AWS region (e.g. `eu-central-1`) +* AWS service name (e.g. `s3`) +* operation name (e.g. `ListBuckets`) + +Additionally, some services collect more specific data + + +#### AWS Aiobotocore [automatic-instrumentation-aiobotocore] + +Library: `aiobotocore` (`>=2.2.0`) + +Instrumented methods: + +* `aiobotocore.client.BaseClient._make_api_call` + +Collected trace data for all services: + +* AWS region (e.g. `eu-central-1`) +* AWS service name (e.g. `s3`) +* operation name (e.g. `ListBuckets`) + +Additionally, some services collect more specific data + + +##### S3 [automatic-instrumentation-s3] + +* Bucket name + + +##### DynamoDB [automatic-instrumentation-dynamodb] + +* Table name + + +##### SNS [automatic-instrumentation-sns] + +* Topic name + + +##### SQS [automatic-instrumentation-sqs] + +* Queue name + + +### Template Engines [automatic-instrumentation-template-engines] + + +#### Django Template Language [automatic-instrumentation-dtl] + +Library: `Django` (see [Django](#supported-django) for supported versions) + +Instrumented methods: + +* `django.template.Template.render` + +Collected trace data: + +* template name + + +#### Jinja2 [automatic-instrumentation-jinja2] + +Library: `jinja2` + +Instrumented methods: + +* `jinja2.Template.render` + +Collected trace data: + +* template name + diff --git a/docs/reference/toc.yml b/docs/reference/toc.yml new file mode 100644 index 000000000..9d4df720a --- /dev/null +++ b/docs/reference/toc.yml @@ -0,0 +1,33 @@ +project: 'APM Python agent reference' +toc: + - file: index.md + - file: set-up-apm-python-agent.md + children: + - file: django-support.md + - file: flask-support.md + - file: aiohttp-server-support.md + - file: tornado-support.md + - file: starlette-support.md + - file: sanic-support.md + - file: lambda-support.md + - file: azure-functions-support.md + - file: wrapper-support.md + - file: asgi-middleware.md + - file: supported-technologies.md + - file: configuration.md + - file: advanced-topics.md + children: + - file: instrumenting-custom-code.md + - file: sanitizing-data.md + - file: how-agent-works.md + - file: run-tests-locally.md + - file: api-reference.md + - file: metrics.md + - file: opentelemetry-api-bridge.md + - file: logs.md + - file: performance-tuning.md + - file: upgrading.md + children: + - file: upgrading-6-x.md + - file: upgrading-5-x.md + - file: upgrading-4-x.md \ No newline at end of file diff --git a/docs/reference/tornado-support.md b/docs/reference/tornado-support.md new file mode 100644 index 000000000..bae66762b --- /dev/null +++ b/docs/reference/tornado-support.md @@ -0,0 +1,108 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/tornado-support.html +--- + +# Tornado Support [tornado-support] + +Incorporating Elastic APM into your Tornado project only requires a few easy steps. + + +## Installation [tornado-installation] + +Install the Elastic APM agent using pip: + +```bash +$ pip install elastic-apm +``` + +or add `elastic-apm` to your project’s `requirements.txt` file. + + +## Setup [tornado-setup] + +To set up the agent, you need to initialize it with appropriate settings. + +The settings are configured either via environment variables, the application’s settings, or as initialization arguments. + +You can find a list of all available settings in the [Configuration](/reference/configuration.md) page. + +To initialize the agent for your application using environment variables: + +```python +import tornado.web +from elasticapm.contrib.tornado import ElasticAPM + +app = tornado.web.Application() +apm = ElasticAPM(app) +``` + +To configure the agent using `ELASTIC_APM` in your application’s settings: + +```python +import tornado.web +from elasticapm.contrib.tornado import ElasticAPM + +app = tornado.web.Application() +app.settings['ELASTIC_APM'] = { + 'SERVICE_NAME': '', + 'SECRET_TOKEN': '', +} +apm = ElasticAPM(app) +``` + + +## Usage [tornado-usage] + +Once you have configured the agent, it will automatically track transactions and capture uncaught exceptions within tornado. + +Capture an arbitrary exception by calling [`capture_exception`](/reference/api-reference.md#client-api-capture-exception): + +```python +try: + 1 / 0 +except ZeroDivisionError: + apm.client.capture_exception() +``` + +Log a generic message with [`capture_message`](/reference/api-reference.md#client-api-capture-message): + +```python +apm.client.capture_message('hello, world!') +``` + + +## Performance metrics [tornado-performance-metrics] + +If you’ve followed the instructions above, the agent has installed our instrumentation within the base RequestHandler class in tornado.web. This will measure response times, as well as detailed performance data for all supported technologies. + +::::{note} +Due to the fact that `asyncio` drivers are usually separate from their synchronous counterparts, specific instrumentation is needed for all drivers. The support for asynchronous drivers is currently quite limited. +:::: + + + +### Ignoring specific routes [tornado-ignoring-specific-views] + +You can use the [`TRANSACTIONS_IGNORE_PATTERNS`](/reference/configuration.md#config-transactions-ignore-patterns) configuration option to ignore specific routes. The list given should be a list of regular expressions which are matched against the transaction name: + +```python +app.settings['ELASTIC_APM'] = { + # ... + 'TRANSACTIONS_IGNORE_PATTERNS': ['^GET SecretHandler', 'MainHandler'] + # ... +} +``` + +This would ignore any requests using the `GET SecretHandler` route and any requests containing `MainHandler`. + + +## Supported tornado and Python versions [supported-tornado-and-python-versions] + +A list of supported [tornado](/reference/supported-technologies.md#supported-tornado) and [Python](/reference/supported-technologies.md#supported-python) versions can be found on our [Supported Technologies](/reference/supported-technologies.md) page. + +::::{note} +Elastic APM only supports `asyncio` when using Python 3.7+ +:::: + + diff --git a/docs/reference/upgrading-4-x.md b/docs/reference/upgrading-4-x.md new file mode 100644 index 000000000..fafd8e576 --- /dev/null +++ b/docs/reference/upgrading-4-x.md @@ -0,0 +1,29 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/upgrading-4-x.html +--- + +# Upgrading to version 4 of the agent [upgrading-4-x] + +4.0 of the Elastic APM Python Agent comes with several backwards incompatible changes. + +## APM Server 6.5 required [upgrading-4-x-apm-server] + +This version of the agent is **only compatible with APM Server 6.5+**. To upgrade, we recommend to first upgrade APM Server, and then the agent. APM Server 6.5+ is backwards compatible with versions 2.x and 3.x of the agent. + + +## Configuration options [upgrading-4-x-configuration] + +Several configuration options have been removed, or renamed + +* `flush_interval` has been removed +* the `flush_interval` and `max_queue_size` settings have been removed. +* new settings introduced: `api_request_time` and `api_request_size`. +* Some settings now require a unit for duration or size. See [size format](configuration.md#config-format-size) and [duration format](configuration.md#config-format-duration). + + +## Processors [upgrading-4-x-processors] + +The method to write processors for sanitizing events has been changed. It will now be called for every type of event (transactions, spans and errors), unless the event types are limited using a decorator. See [Sanitizing data](sanitizing-data.md) for more information. + + diff --git a/docs/reference/upgrading-5-x.md b/docs/reference/upgrading-5-x.md new file mode 100644 index 000000000..5055b6790 --- /dev/null +++ b/docs/reference/upgrading-5-x.md @@ -0,0 +1,19 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/upgrading-5-x.html +--- + +# Upgrading to version 5 of the agent [upgrading-5-x] + +## APM Server 7.3 required for some features [_apm_server_7_3_required_for_some_features] + +APM Server and Kibana 7.3 introduced support for collecting breakdown metrics, and central configuration of APM agents. To use these features, please update the Python agent to 5.0+ and APM Server / Kibana to 7.3+ + + +## Tags renamed to Labels [_tags_renamed_to_labels] + +To better align with other parts of the Elastic Stack and the [Elastic Common Schema](ecs://reference/index.md), we renamed "tags" to "labels", and introduced limited support for typed labels. While tag values were only allowed to be strings, label values can be strings, booleans, or numerical. + +To benefit from this change, ensure that you run at least **APM Server 6.7**, and use `elasticapm.label()` instead of `elasticapm.tag()`. The `tag()` API will continue to work as before, but emit a `DeprecationWarning`. It will be removed in 6.0 of the agent. + + diff --git a/docs/reference/upgrading-6-x.md b/docs/reference/upgrading-6-x.md new file mode 100644 index 000000000..08a6e6e3c --- /dev/null +++ b/docs/reference/upgrading-6-x.md @@ -0,0 +1,22 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/upgrading-6-x.html +--- + +# Upgrading to version 6 of the agent [upgrading-6-x] + +## Python 2 no longer supported [_python_2_no_longer_supported] + +Please upgrade to Python 3.6+ to continue to receive regular updates. + + +## `SANITIZE_FIELD_NAMES` changes [_sanitize_field_names_changes] + +If you are using a non-default `sanitize_field_names` config, please note that your entries must be surrounded with stars (e.g. `*secret*`) in order to maintain previous behavior. + + +## Tags removed (in favor of labels) [_tags_removed_in_favor_of_labels] + +Tags were deprecated in the 5.x release (in favor of labels). They have now been removed. + + diff --git a/docs/reference/upgrading.md b/docs/reference/upgrading.md new file mode 100644 index 000000000..83ae39902 --- /dev/null +++ b/docs/reference/upgrading.md @@ -0,0 +1,19 @@ +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/upgrading.html +--- + +# Upgrading [upgrading] + +Upgrades between minor versions of the agent, like from 3.1 to 3.2 are always backwards compatible. Upgrades that involve a major version bump often come with some backwards incompatible changes. + +We highly recommend to always pin the version of `elastic-apm` in your `requirements.txt` or `Pipfile`. This avoids automatic upgrades to potentially incompatible versions. + + +## End of life dates [end-of-life-dates] + +We love all our products, but sometimes we must say goodbye to a release so that we can continue moving forward on future development and innovation. Our [End of life policy](https://www.elastic.co/support/eol) defines how long a given release is considered supported, as well as how long a release is considered still in active development or maintenance. + + + + diff --git a/docs/wrapper.asciidoc b/docs/reference/wrapper-support.md similarity index 50% rename from docs/wrapper.asciidoc rename to docs/reference/wrapper-support.md index 4658201c6..a8f01bbbc 100644 --- a/docs/wrapper.asciidoc +++ b/docs/reference/wrapper-support.md @@ -1,58 +1,56 @@ -[[wrapper-support]] -=== Wrapper Support +--- +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/wrapper-support.html +--- -experimental::[] +# Wrapper Support [wrapper-support] -The following frameworks are supported using our new wrapper script for -no-code-changes instrumentation: +::::{warning} +This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. +:::: - * Django - * Flask - * Starlette -Please keep in mind that these instrumentations are a work in progress! We'd -love to have feedback on our -https://github.com/elastic/apm-agent-python/issues/new/choose[issue tracker]. +The following frameworks are supported using our new wrapper script for no-code-changes instrumentation: -[[wrapper-usage]] -==== Usage +* Django +* Flask +* Starlette -When installing the agent, an entrypoint script, `elasticapm-run` is installed -as well. You can use this script to instrument your app (assuming it's using a -supported framework) without changing your code! +Please keep in mind that these instrumentations are a work in progress! We’d love to have feedback on our [issue tracker](https://github.com/elastic/apm-agent-python/issues/new/choose). -[source,bash] ----- +## Usage [wrapper-usage] + +When installing the agent, an entrypoint script, `elasticapm-run` is installed as well. You can use this script to instrument your app (assuming it’s using a supported framework) without changing your code! + +```bash $ elasticapm-run --version elasticapm-run 6.14.0 ----- +``` Alternatively, you can run the entrypoint directly: -[source,bash] ----- +```bash $ python -m elasticapm.instrumentation.wrapper --version elasticapm-run 6.14.0 ----- +``` The `elasticapm-run` script can be used to run any Python script or module: -[source,bash] ----- +```bash $ elasticapm-run flask run $ elasticapm-run python myapp.py ----- +``` Generally, config should be passed in via environment variables. For example, -[source,bash] ----- +```bash $ ELASTIC_APM_SERVICE_NAME=my_flask_app elasticapm-run flask run ----- +``` You can also pass config options as arguments to the script: -[source,bash] ----- +```bash $ elasticapm-run --config "service_name=my_flask_app" --config "debug=true" flask run ----- +``` + + diff --git a/docs/release-notes.asciidoc b/docs/release-notes.asciidoc deleted file mode 100644 index c8d212db6..000000000 --- a/docs/release-notes.asciidoc +++ /dev/null @@ -1,15 +0,0 @@ -:pull: https://github.com/elastic/apm-agent-python/pull/ - -[[release-notes]] -== Release notes - -All notable changes to this project will be documented here. - -* <> -* <> -* <> -* <> -* <> -* <> - -include::../CHANGELOG.asciidoc[] diff --git a/docs/release-notes/breaking-changes.md b/docs/release-notes/breaking-changes.md new file mode 100644 index 000000000..240495942 --- /dev/null +++ b/docs/release-notes/breaking-changes.md @@ -0,0 +1,28 @@ +--- +navigation_title: "Elastic APM Python Agent" +--- + +# Elastic APM Python Agent breaking changes [elastic-apm-python-agent-breaking-changes] +Before you upgrade, carefully review the Elastic APM RPython Agent breaking changes and take the necessary steps to mitigate any issues. + +% To learn how to upgrade, check out . + +% ## Next version [elastic-apm-python-agent-nextversion-breaking-changes] +% **Release date:** Month day, year + +% ::::{dropdown} Title of breaking change +% Description of the breaking change. +% For more information, check [PR #](PR link). +% **Impact**
Impact of the breaking change. +% **Action**
Steps for mitigating deprecation impact. +% :::: + +## 6.0.0 [elastic-apm-python-agent-600-breaking-changes] +**Release date:** February 1, 2021 + +* Python 2.7 and 3.5 support has been deprecated. The Python agent now requires Python 3.6+. For more information, check [#1021](https://github.com/elastic/apm-agent-python/pull/1021). +* No longer collecting body for `elasticsearch-py` update and `delete_by_query`. For more information, check [#1013](https://github.com/elastic/apm-agent-python/pull/1013). +* Align `sanitize_field_names` config with the [cross-agent spec](https://github.com/elastic/apm/blob/3fa78e2a1eeea81c73c2e16e96dbf6b2e79f3c64/specs/agents/sanitization.md). If you are using a non-default `sanitize_field_names`, surrounding each of your entries with stars (e.g. `*secret*`) will retain the old behavior. For more information, check [#982](https://github.com/elastic/apm-agent-python/pull/982). +* Remove credit card sanitization for field values. This improves performance, and the security value of this check was dubious anyway. For more information, check [#982](https://github.com/elastic/apm-agent-python/pull/982). +* Remove HTTP querystring sanitization. This improves performance, and is meant to standardize behavior across the agents, as defined in [#334](https://github.com/elastic/apm/pull/334). For more information, check [#982](https://github.com/elastic/apm-agent-python/pull/982). +* Remove `elasticapm.tag()` (deprecated since 5.0.0). For more information, check [#1034](https://github.com/elastic/apm-agent-python/pull/1034). \ No newline at end of file diff --git a/docs/release-notes/deprecations.md b/docs/release-notes/deprecations.md new file mode 100644 index 000000000..d6d248fc0 --- /dev/null +++ b/docs/release-notes/deprecations.md @@ -0,0 +1,38 @@ +--- +navigation_title: "Elastic APM Python Agent" +--- + +# Elastic APM Python Agent deprecations [elastic-apm-python-agent-deprecations] +Review the deprecated functionality for your Elastic APM Python Agent version. While deprecations have no immediate impact, we strongly encourage you update your implementation after you upgrade. + +% To learn how to upgrade, check out . + +% ## Next version +% **Release date:** Month day, year + +% ::::{dropdown} Deprecation title +% Description of the deprecation. +% For more information, check [PR #](PR link). +% **Impact**
Impact of deprecation. +% **Action**
Steps for mitigating deprecation impact. +% :::: + +## 6.23.0 [elastic-apm-python-agent-6230-deprecations] +**Release date:** July 30, 2024 + +* Python 3.6 support will be removed in version 7.0.0 of the agent. +* The log shipping LoggingHandler will be removed in version 7.0.0 of the agent. +* The log shipping feature in the Flask instrumentation will be removed in version 7.0.0 of the agent. +* The log shipping feature in the Django instrumentation will be removed in version 7.0.0 of the agent. +* The OpenTracing bridge will be removed in version 7.0.0 of the agent. +* Celery 4.0 support is deprecated because it’s not installable anymore with a modern pip. + +## 6.20.0 [elastic-apm-python-agent-6200-deprecations] +**Release date:** January 10, 2024 + +The log shipping LoggingHandler will be removed in version 7.0.0 of the agent. + +## 6.19.0 [elastic-apm-python-agent-6190-deprecations] +**Release date:** October 11, 2023 + +The log shipping feature in the Flask instrumentation will be removed in version 7.0.0 of the agent. \ No newline at end of file diff --git a/docs/release-notes/index.md b/docs/release-notes/index.md new file mode 100644 index 000000000..c229323b0 --- /dev/null +++ b/docs/release-notes/index.md @@ -0,0 +1,545 @@ +--- +navigation_title: "Elastic APM Python Agent" +mapped_pages: + - https://www.elastic.co/guide/en/apm/agent/python/current/release-notes-6.x.html +--- + +# Elastic APM Python Agent release notes [elastic-apm-python-agent-release-notes] + +Review the changes, fixes, and more in each version of Elastic APM Python Agent. + +To check for security updates, go to [Security announcements for the Elastic stack](https://discuss.elastic.co/c/announcements/security-announcements/31). + +% Release notes includes only features, enhancements, and fixes. Add breaking changes, deprecations, and known issues to the applicable release notes sections. + +% ## version.next [elastic-apm-python-agent-versionext-release-notes] +% **Release date:** Month day, year + +% ### Features and enhancements [elastic-apm-python-agent-versionext-features-enhancements] + +% ### Fixes [elastic-apm-python-agent-versionext-fixes] + +## 6.23.0 [elastic-apm-python-agent-6230-release-notes] +**Release date:** July 30, 2024 + +### Features and enhancements [elastic-apm-python-agent-6230-features-enhancements] +* Make published Docker images multi-platform with the addition of linux/arm64 [#2080](https://github.com/elastic/apm-agent-python/pull/2080) + +### Fixes [elastic-apm-python-agent-6230-fixes] +* Fix handling consumer iteration if transaction not sampled in kafka instrumentation [#2075](https://github.com/elastic/apm-agent-python/pull/2075) +* Fix race condition with urllib3 at shutdown [#2085](https://github.com/elastic/apm-agent-python/pull/2085) +* Fix compatibility with setuptools>=72 that removed test command [#2090](https://github.com/elastic/apm-agent-python/pull/2090) + +## 6.22.3 [elastic-apm-python-agent-6223-release-notes] +**Release date:** June 10, 2024 + +### Fixes [elastic-apm-python-agent-6223-fixes] +* Fix outcome in ASGI and Starlette apps on error status codes without an exception [#2060](https://github.com/elastic/apm-agent-python/pull/2060) + +## 6.22.2 [elastic-apm-python-agent-6222-release-notes] +**Release date:** May 20, 2024 + +### Fixes [elastic-apm-python-agent-6222-fixes] +* Fix CI release workflow [#2046](https://github.com/elastic/apm-agent-python/pull/2046) + +## 6.22.1 [elastic-apm-python-agent-6222-release-notes] +**Release date:** May 17, 2024 + +### Features and enhancements [elastic-apm-python-agent-6221-features-enhancements] +* Relax wrapt dependency to only exclude 1.15.0 [#2005](https://github.com/elastic/apm-agent-python/pull/2005) + +## 6.22.0 [elastic-apm-python-agent-6220-release-notes] +**Release date:** April 3, 2024 + +### Features and enhancements [elastic-apm-python-agent-6220-features-enhancements] +* Add ability to override default JSON serialization [#2018](https://github.com/elastic/apm-agent-python/pull/2018) + +## 6.21.4 [elastic-apm-python-agent-6214-release-notes] +**Release date:** March 19, 2024 + +### Fixes [elastic-apm-python-agent-6214-fixes] +* Fix urllib3 2.0.1+ crash with many args [#2002](https://github.com/elastic/apm-agent-python/pull/2002) + +## 6.21.3 [elastic-apm-python-agent-6213-release-notes] +**Release date:** March 8, 2024 + +### Fixes [elastic-apm-python-agent-6213-fixes] +* Fix artifacts download in CI workflows [#1996](https://github.com/elastic/apm-agent-python/pull/1996) + +## 6.21.2 [elastic-apm-python-agent-6212-release-notes] +**Release date:** March 7, 2024 + +### Fixes [elastic-apm-python-agent-6212-fixes] +* Fix artifacts upload in CI build-distribution workflow [#1993](https://github.com/elastic/apm-agent-python/pull/1993) + +## 6.21.1 [elastic-apm-python-agent-6211-release-notes] +**Release date:** March 7, 2024 + +### Fixes [elastic-apm-python-agent-6211-fixes] +* Fix CI release workflow [#1990](https://github.com/elastic/apm-agent-python/pull/1990) + +## 6.21.0 [elastic-apm-python-agent-6210-release-notes] +**Release date:** March 6, 2024 + +### Fixes [elastic-apm-python-agent-6210-fixes] +* Fix starlette middleware setup without client argument [#1952](https://github.com/elastic/apm-agent-python/pull/1952) +* Fix blocking of gRPC stream-to-stream requests [#1967](https://github.com/elastic/apm-agent-python/pull/1967) +* Always take into account body reading time for starlette requests [#1970](https://github.com/elastic/apm-agent-python/pull/1970) +* Make urllib3 transport tests more robust against local env [#1969](https://github.com/elastic/apm-agent-python/pull/1969) +* Clarify starlette integration documentation [#1956](https://github.com/elastic/apm-agent-python/pull/1956) +* Make dbapi2 query scanning for dollar quotes a bit more correct [#1976](https://github.com/elastic/apm-agent-python/pull/1976) +* Normalize headers in AWS Lambda integration on API Gateway v1 requests [#1982](https://github.com/elastic/apm-agent-python/pull/1982) + +## 6.20.0 [elastic-apm-python-agent-6200-release-notes] +**Release date:** January 10, 2024 + +### Features and enhancements [elastic-apm-python-agent-6200-features-enhancements] +* Async support for dbapi2 (starting with psycopg) [#1944](https://github.com/elastic/apm-agent-python/pull/1944) +* Add object name to procedure call spans in dbapi2 [#1938](https://github.com/elastic/apm-agent-python/pull/1938) +* Add support for python 3.10 and 3.11 lambda runtimes + +### Fixes [elastic-apm-python-agent-6200-fixes] +* Fix asyncpg support for 0.29+ [#1935](https://github.com/elastic/apm-agent-python/pull/1935) +* Fix dbapi2 signature extraction to handle square brackets in table name [#1947](https://github.com/elastic/apm-agent-python/pull/1947) + +## 6.19.0 [elastic-apm-python-agent-6190-release-notes] +**Release date:** October 11, 2023 + +### Features and enhancements [elastic-apm-python-agent-6190-features-enhancements] +* Add Python 3.12 support +* Collect the `configured_hostname` and `detected_hostname` separately, and switch to FQDN for the `detected_hostname`. [#1891](https://github.com/elastic/apm-agent-python/pull/1891) +* Improve postgres dollar-quote detection to be much faster [#1905](https://github.com/elastic/apm-agent-python/pull/1905) + +### Fixes [elastic-apm-python-agent-6190-fixes] +* Fix url argument fetching in aiohttp_client instrumentation [#1890](https://github.com/elastic/apm-agent-python/pull/1890) +* Fix a bug in the AWS Lambda instrumentation when `event["headers"] is None` [#1907](https://github.com/elastic/apm-agent-python/pull/1907) +* Fix a bug in AWS Lambda where metadata could be incomplete, causing validation errors with the APM Server [#1914](https://github.com/elastic/apm-agent-python/pull/1914) +* Fix a bug in AWS Lambda where sending the partial transaction would be recorded as an extra span [#1914](https://github.com/elastic/apm-agent-python/pull/1914) + +## 6.18.0 [elastic-apm-python-agent-6180-release-notes] +**Release date:** July 25, 2023 + +### Features and enhancements [elastic-apm-python-agent-6180-features-enhancements] +* Add support for grpc aio server interceptor [#1870](https://github.com/elastic/apm-agent-python/pull/1870) + +### Fixes [elastic-apm-python-agent-6180-fixes] +* Fix a bug in the Elasticsearch client instrumentation which was causing loss of database context (including statement) when interacting with Elastic Cloud [#1878](https://github.com/elastic/apm-agent-python/pull/1878) + +## 6.17.0 [elastic-apm-python-agent-6170-release-notes] +**Release date:** July 3, 2023 + +### Features and enhancements [elastic-apm-python-agent-6170-features-enhancements] +* Add `server_ca_cert_file` option to provide custom CA certificate [#1852](https://github.com/elastic/apm-agent-python/pull/1852) +* Add `include_process_args` option to allow users to opt-in to collecting process args [#1867](https://github.com/elastic/apm-agent-python/pull/1867) + +### Fixes [elastic-apm-python-agent-6170-fixes] +* Fix a bug in the GRPC instrumentation when reaching the maximum amount of spans per transaction [#1861](https://github.com/elastic/apm-agent-python/pull/1861) + +## 6.16.2 [elastic-apm-python-agent-6162-release-notes] +**Release date:** June 12, 2023 + +### Fixes [elastic-apm-python-agent-6162-fixes] +* Fix compatibility issue with older versions of OpenSSL in lambda runtimes [#1847](https://github.com/elastic/apm-agent-python/pull/1847) +* Add `latest` tag to docker images [#1848](https://github.com/elastic/apm-agent-python/pull/1848) +* Fix issue with redacting `user:pass` in URLs in Python 3.11.4 [#1850](https://github.com/elastic/apm-agent-python/pull/1850) + +## 6.16.1 [elastic-apm-python-agent-6161-release-notes] +**Release date:** June 6, 2023 + +### Fixes [elastic-apm-python-agent-6161-fixes] +* Fix release process for docker and the lambda layer [#1845](https://github.com/elastic/apm-agent-python/pull/1845) + +## 6.16.0 [elastic-apm-python-agent-6160-release-notes] +**Release date:** June 5, 2023 + +### Features and enhancements [elastic-apm-python-agent-6160-features-enhancements] +* Add lambda layer for instrumenting AWS Lambda functions [#1826](https://github.com/elastic/apm-agent-python/pull/1826) +* Implement instrumentation of Azure Functions [#1766](https://github.com/elastic/apm-agent-python/pull/1766) +* Add support for Django to wrapper script [#1780](https://github.com/elastic/apm-agent-python/pull/1780) +* Add support for Starlette to wrapper script [#1830](https://github.com/elastic/apm-agent-python/pull/1830) +* Add `transport_json_serializer` configuration option [#1777](https://github.com/elastic/apm-agent-python/pull/1777) +* Add S3 bucket and key name to OTel attributes [#1790](https://github.com/elastic/apm-agent-python/pull/1790) +* Implement partial transaction support in AWS lambda [#1784](https://github.com/elastic/apm-agent-python/pull/1784) +* Add instrumentation for redis.asyncio [#1807](https://github.com/elastic/apm-agent-python/pull/1807) +* Add support for urllib3 v2.0.1+ [#1822](https://github.com/elastic/apm-agent-python/pull/1822) +* Add `service.environment` to log correlation [#1833](https://github.com/elastic/apm-agent-python/pull/1833) +* Add `ecs_logging` as a dependency [#1840](https://github.com/elastic/apm-agent-python/pull/1840) +* Add support for synchronous psycopg3 [#1841](https://github.com/elastic/apm-agent-python/pull/1841) + +### Fixes [elastic-apm-python-agent-6160-fixes] +* Fix spans being dropped if they don’t have a name [#1770](https://github.com/elastic/apm-agent-python/pull/1770) +* Fix AWS Lambda support when `event` is not a dict [#1775](https://github.com/elastic/apm-agent-python/pull/1775) +* Fix deprecation warning with urllib3 2.0.0 pre-release versions [#1778](https://github.com/elastic/apm-agent-python/pull/1778) +* Fix `activation_method` to only send to APM server 8.7.1+ [#1787](https://github.com/elastic/apm-agent-python/pull/1787) +* Fix span.context.destination.service.resource for S3 spans to have an "s3/" prefix. [#1783](https://github.com/elastic/apm-agent-python/pull/1783) + +**Note**: While this is considered a bugfix, it can potentially be a breaking change in the Kibana APM app: It can break the history of the S3-Spans / metrics for users relying on `context.destination.service.resource`. If users happen to run agents both with and without this fix (for same or different languages), the same S3-buckets can appear twice in the service map (with and without s3-prefix). + +* Fix instrumentation to not bubble up exceptions during instrumentation [#1791](https://github.com/elastic/apm-agent-python/pull/1791) +* Fix HTTP transport to not print useless and confusing stack trace [#1809](https://github.com/elastic/apm-agent-python/pull/1809) + +## 6.15.1 [elastic-apm-python-agent-6151-release-notes] +**Release date:** March 6, 2023 + +### Fixes [elastic-apm-python-agent-6151-fixes] +* Fix issue with botocore instrumentation creating spans with an incorrect `service.name` [#1765](https://github.com/elastic/apm-agent-python/pull/1765) +* Fix a bug in the GRPC instrumentation when the agent is disabled or not recording [#1761](https://github.com/elastic/apm-agent-python/pull/1761) + +## 6.15.0 [elastic-apm-python-agent-6150-release-notes] +**Release date:** February 16, 2023 + +### Features and enhancements [elastic-apm-python-agent-6150-features-enhancements] +* Add `service.agent.activation_method` to the metadata [#1743](https://github.com/elastic/apm-agent-python/pull/1743) + +### Fixes [elastic-apm-python-agent-6150-fixes] +* Small fix to underlying Starlette logic to prevent duplicate Client objects [#1735](https://github.com/elastic/apm-agent-python/pull/1735) +* Change `server_url` default to `http://127.0.0.1:8200` to avoid ipv6 ambiguity [#1744](https://github.com/elastic/apm-agent-python/pull/1744) +* Fix an issue in GRPC instrumentation with unsampled transactions [#1740](https://github.com/elastic/apm-agent-python/pull/1740) +* Fix error in async Elasticsearch instrumentation when spans are dropped [#1758](https://github.com/elastic/apm-agent-python/pull/1758) + +## 6.14.0 [elastic-apm-python-agent-6140-release-notes] +**Release date:** January 30, 2023 + +### Features and enhancements [elastic-apm-python-agent-6140-features-enhancements] +* GRPC support [#1703](https://github.com/elastic/apm-agent-python/pull/1703) +* Wrapper script Flask support (experimental) [#1709](https://github.com/elastic/apm-agent-python/pull/1709) + +### Fixes [elastic-apm-python-agent-6140-fixes] +* Fix an async issue with long elasticsearch queries [#1725](https://github.com/elastic/apm-agent-python/pull/1725) +* Fix a minor inconsistency with the W3C tracestate spec [#1728](https://github.com/elastic/apm-agent-python/pull/1728) +* Fix a cold start performance issue with our AWS Lambda integration [#1727](https://github.com/elastic/apm-agent-python/pull/1727) +* Mark `**kwargs` config usage in our AWS Lambda integration as deprecated [#1727](https://github.com/elastic/apm-agent-python/pull/1727) + +## 6.13.2 [elastic-apm-python-agent-6132-release-notes] +**Release date:** November 17, 2022 + +### Fixes [elastic-apm-python-agent-6132-fixes] +* Fix error in Elasticsearch instrumentation when spans are dropped [#1690](https://github.com/elastic/apm-agent-python/pull/1690) +* Lower log level for errors in APM Server version fetching [#1692](https://github.com/elastic/apm-agent-python/pull/1692) +* Fix for missing parent.id when logging from a DroppedSpan under a leaf span [#1695](https://github.com/elastic/apm-agent-python/pull/1695) + +## 6.13.1 [elastic-apm-python-agent-6131-release-notes] +**Release date:** November 3, 2022 + +### Fixes [elastic-apm-python-agent-6131-fixes] +* Fix elasticsearch instrumentation for track_total_hits=False [#1687](https://github.com/elastic/apm-agent-python/pull/1687) + +## 6.13.0 [elastic-apm-python-agent-6130-release-notes] +**Release date:** October 26, 2022 + +### Features and enhancements [elastic-apm-python-agent-6130-features-enhancements] +* Add support for Python 3.11 +* Add backend granularity data to SQL backends as well as Cassandra and pymongo [#1585](https://github.com/elastic/apm-agent-python/pull/1585), [#1639](https://github.com/elastic/apm-agent-python/pull/1639) +* Add support for instrumenting the Elasticsearch 8 Python client [#1642](https://github.com/elastic/apm-agent-python/pull/1642) +* Add `*principal*` to default `sanitize_field_names` configuration [#1664](https://github.com/elastic/apm-agent-python/pull/1664) +* Add docs and better support for custom metrics, including in AWS Lambda [#1643](https://github.com/elastic/apm-agent-python/pull/1643) +* Add support for capturing span links from AWS SQS in AWS Lambda [#1662](https://github.com/elastic/apm-agent-python/pull/1662) + +### Fixes [elastic-apm-python-agent-6130-fixes] +* Fix Django’s `manage.py check` when agent is disabled [#1632](https://github.com/elastic/apm-agent-python/pull/1632) +* Fix an issue with long body truncation for Starlette [#1635](https://github.com/elastic/apm-agent-python/pull/1635) +* Fix an issue with transaction outcomes in Flask for uncaught exceptions [#1637](https://github.com/elastic/apm-agent-python/pull/1637) +* Fix Starlette instrumentation to make sure transaction information is still present during exception handling [#1674](https://github.com/elastic/apm-agent-python/pull/1674) + +## 6.12.0 [elastic-apm-python-agent-6120-release-notes] +**Release date:** September 7, 2022 + +### Features and enhancements [elastic-apm-python-agent-6120-features-enhancements] +* Add redis query to context data for redis instrumentation [#1406](https://github.com/elastic/apm-agent-python/pull/1406) +* Add AWS request ID to all botocore spans (at `span.context.http.request.id`) [#1625](https://github.com/elastic/apm-agent-python/pull/1625) + +### Fixes [elastic-apm-python-agent-6120-fixes] +* Differentiate Lambda URLs from API Gateway in AWS Lambda integration [#1609](https://github.com/elastic/apm-agent-python/pull/1609) +* Restrict the size of Django request bodies to prevent APM Server rejection [#1610](https://github.com/elastic/apm-agent-python/pull/1610) +* Restrict length of `exception.message` for exceptions captured by the agent [#1619](https://github.com/elastic/apm-agent-python/pull/1619) +* Restrict length of Starlette request bodies [#1549](https://github.com/elastic/apm-agent-python/pull/1549) +* Fix error when using elasticsearch(sniff_on_start=True) [#1618](https://github.com/elastic/apm-agent-python/pull/1618) +* Improve handling of ignored URLs and capture_body=off for Starlette [#1549](https://github.com/elastic/apm-agent-python/pull/1549) +* Fix possible error in the transport flush for Lambda functions [#1628](https://github.com/elastic/apm-agent-python/pull/1628) + +## 6.11.0 [elastic-apm-python-agent-6110-release-notes] +**Release date:** August 9, 2022 + +### Features and enhancements [elastic-apm-python-agent-6110-features-enhancements] +* Added lambda support for ELB triggers [#1605](https://github.com/elastic/apm-agent-python/pull/1605) + +## 6.10.2 [elastic-apm-python-agent-6102-release-notes] +**Release date:** August 9, 2022 + +### Fixes [elastic-apm-python-agent-6102-fixes] +* Fixed an issue with non-integer ports in Django [#1590](https://github.com/elastic/apm-agent-python/pull/1590) +* Fixed an issue with non-integer ports in Redis [#1591](https://github.com/elastic/apm-agent-python/pull/1591) +* Fixed a performance issue for local variable shortening via `varmap()` [#1593](https://github.com/elastic/apm-agent-python/pull/1593) +* Fixed `elasticapm.label()` when a Client object is not available [#1596](https://github.com/elastic/apm-agent-python/pull/1596) + +## 6.10.1 [elastic-apm-python-agent-6101-release-notes] +**Release date:** June 30, 2022 + +### Fixes [elastic-apm-python-agent-6101-fixes] +* Fix an issue with Kafka instrumentation and unsampled transactions [#1579](https://github.com/elastic/apm-agent-python/pull/1579) + +## 6.10.0 [elastic-apm-python-agent-6100-release-notes] +**Release date:** June 22, 2022 + +### Features and enhancements [elastic-apm-python-agent-6100-features-enhancements] +* Add instrumentation for [`aiobotocore`](https://github.com/aio-libs/aiobotocore) [#1520](https://github.com/elastic/apm-agent-python/pull/1520) +* Add instrumentation for [`kafka-python`](https://kafka-python.readthedocs.io/en/master/) [#1555](https://github.com/elastic/apm-agent-python/pull/1555) +* Add API for span links, and implement span link support for OpenTelemetry bridge [#1562](https://github.com/elastic/apm-agent-python/pull/1562) +* Add span links to SQS ReceiveMessage call [#1575](https://github.com/elastic/apm-agent-python/pull/1575) +* Add specific instrumentation for SQS delete/batch-delete [#1567](https://github.com/elastic/apm-agent-python/pull/1567) +* Add `trace_continuation_strategy` setting [#1564](https://github.com/elastic/apm-agent-python/pull/1564) + +### Fixes [elastic-apm-python-agent-6100-fixes] +* Fix return for `opentelemetry.Span.is_recording()` [#1530](https://github.com/elastic/apm-agent-python/pull/1530) +* Fix error logging for bad SERVICE_NAME config [#1546](https://github.com/elastic/apm-agent-python/pull/1546) +* Do not instrument old versions of Tornado > 6.0 due to incompatibility [#1566](https://github.com/elastic/apm-agent-python/pull/1566) +* Fix transaction names for class based views in Django 4.0+ [#1571](https://github.com/elastic/apm-agent-python/pull/1571) +* Fix a problem with our logging handler failing to report internal errors in its emitter [#1568](https://github.com/elastic/apm-agent-python/pull/1568) + +## 6.9.1 [elastic-apm-python-agent-691-release-notes] +**Release date:** March 30, 2022 + +### Fixes [elastic-apm-python-agent-691-fixes] +* Fix `otel_attributes`-related regression with older versions of APM Server (<7.16) [#1510](https://github.com/elastic/apm-agent-python/pull/1510) + +## 6.9.0 [elastic-apm-python-agent-690-release-notes] +**Release date:** March 29, 2022 + +### Features and enhancements [elastic-apm-python-agent-690-features-enhancements] +* Add OpenTelemetry API bridge [#1411](https://github.com/elastic/apm-agent-python/pull/1411) +* Change default for `sanitize_field_names` to sanitize `*auth*` instead of `authorization` [#1494](https://github.com/elastic/apm-agent-python/pull/1494) +* Add `span_stack_trace_min_duration` to replace deprecated `span_frames_min_duration` [#1498](https://github.com/elastic/apm-agent-python/pull/1498) +* Enable exact_match span compression by default [#1504](https://github.com/elastic/apm-agent-python/pull/1504) +* Allow parent celery tasks to specify the downstream `parent_span_id` in celery headers [#1500](https://github.com/elastic/apm-agent-python/pull/1500) + +### Fixes [elastic-apm-python-agent-690-fixes] +* Fix Sanic integration to properly respect the `capture_body` config [#1485](https://github.com/elastic/apm-agent-python/pull/1485) +* Lambda fixes to align with the cross-agent spec [#1489](https://github.com/elastic/apm-agent-python/pull/1489) +* Lambda fix for custom `service_name` [#1493](https://github.com/elastic/apm-agent-python/pull/1493) +* Change default for `stack_trace_limit` from 500 to 50 [#1492](https://github.com/elastic/apm-agent-python/pull/1492) +* Switch all duration handling to use `datetime.timedelta` objects [#1488](https://github.com/elastic/apm-agent-python/pull/1488) + +## 6.8.1 [elastic-apm-python-agent-681-release-notes] +**Release date:** March 9, 2022 + +### Fixes [elastic-apm-python-agent-681-fixes] +* Fix `exit_span_min_duration` and disable by default [#1483](https://github.com/elastic/apm-agent-python/pull/1483) + +## 6.8.0 [elastic-apm-python-agent-680-release-notes] +**Release date:** February 22, 2022 + +### Features and enhancements [elastic-apm-python-agent-680-features-enhancements] +* use "unknown-python-service" as default service name if no service name is configured [#1438](https://github.com/elastic/apm-agent-python/pull/1438) +* add transaction name to error objects [#1441](https://github.com/elastic/apm-agent-python/pull/1441) +* don’t send unsampled transactions to APM Server 8.0+ [#1442](https://github.com/elastic/apm-agent-python/pull/1442) +* implement snapshotting of certain configuration during transaction lifetime [#1431](https://github.com/elastic/apm-agent-python/pull/1431) +* propagate traceparent IDs via Celery [#1371](https://github.com/elastic/apm-agent-python/pull/1371) +* removed Python 2 compatibility shims [#1463](https://github.com/elastic/apm-agent-python/pull/1463) + +**Note:** Python 2 support was already removed with version 6.0 of the agent, this now removes unused compatibilit shims. + +### Fixes [elastic-apm-python-agent-680-fixes] +* fix span compression for redis, mongodb, cassandra and memcached [#1444](https://github.com/elastic/apm-agent-python/pull/1444) +* fix recording of status_code for starlette [#1466](https://github.com/elastic/apm-agent-python/pull/1466) +* fix aioredis span context handling [#1462](https://github.com/elastic/apm-agent-python/pull/1462) + +## 6.7.2 [elastic-apm-python-agent-672-release-notes] +**Release date:** December 7, 2021 + +### Fixes [elastic-apm-python-agent-672-fixes] +* fix AttributeError in sync instrumentation of httpx [#1423](https://github.com/elastic/apm-agent-python/pull/1423) +* add setting to disable span compression, default to disabled [#1429](https://github.com/elastic/apm-agent-python/pull/1429) + +## 6.7.1 [elastic-apm-python-agent-671-release-notes] +**Release date:** November 29, 2021 + +### Fixes [elastic-apm-python-agent-671-fixes] +* fix an issue with Sanic exception tracking [#1414](https://github.com/elastic/apm-agent-python/pull/1414) +* asyncpg: Limit SQL queries in context data to 10000 characters [#1416](https://github.com/elastic/apm-agent-python/pull/1416) + +## 6.7.0 [elastic-apm-python-agent-670-release-notes] +**Release date:** November 17, 2021 + +### Features and enhancements [elastic-apm-python-agent-670-features-enhancements] +* Add support for Sanic framework [#1390](https://github.com/elastic/apm-agent-python/pull/1390) + +### Fixes [elastic-apm-python-agent-670-fixes] +* fix compatibility issues with httpx 0.21 [#1403](https://github.com/elastic/apm-agent-python/pull/1403) +* fix `span_compression_exact_match_max_duration` default value [#1407](https://github.com/elastic/apm-agent-python/pull/1407) + +## 6.6.3 [elastic-apm-python-agent-663-release-notes] +**Release date:** November 15, 2021 + +### Fixes [elastic-apm-python-agent-663-fixes] +* fix an issue with `metrics_sets` configuration referencing the `TransactionMetricSet` removed in 6.6.2 [#1397](https://github.com/elastic/apm-agent-python/pull/1397) + +## 6.6.2 [elastic-apm-python-agent-662-release-notes] +**Release date:** November 10, 2021 + +### Fixes [elastic-apm-python-agent-662-fixes] +* Fix an issue where compressed spans would count against `transaction_max_spans` [#1377](https://github.com/elastic/apm-agent-python/pull/1377) +* Make sure HTTP connections are not re-used after a process fork [#1374](https://github.com/elastic/apm-agent-python/pull/1374) +* Fix an issue with psycopg2 instrumentation when multiple hosts are defined [#1386](https://github.com/elastic/apm-agent-python/pull/1386) +* Update the `User-Agent` header to the new [spec](https://github.com/elastic/apm/pull/514) [#1378](https://github.com/elastic/apm-agent-python/pull/1378) +* Improve status_code handling in AWS Lambda integration [#1382](https://github.com/elastic/apm-agent-python/pull/1382) +* Fix `aiohttp` exception handling to allow for non-500 responses including `HTTPOk` [#1384](https://github.com/elastic/apm-agent-python/pull/1384) +* Force transaction names to strings [#1389](https://github.com/elastic/apm-agent-python/pull/1389) +* Remove unused `http.request.socket.encrypted` context field [#1332](https://github.com/elastic/apm-agent-python/pull/1332) +* Remove unused transaction metrics (APM Server handles these metrics instead) [#1388](https://github.com/elastic/apm-agent-python/pull/1388) + +## 6.6.1 [elastic-apm-python-agent-661-release-notes] +**Release date:** November 2, 2021 + +### Fixes [elastic-apm-python-agent-661-fixes] +* Fix some context fields and metadata handling in AWS Lambda support [#1368](https://github.com/elastic/apm-agent-python/pull/1368) + +## 6.6.0 [elastic-apm-python-agent-660-release-notes] +**Release date:** October 18, 2021 + +### Features and enhancements [elastic-apm-python-agent-660-features-enhancements] +* Add experimental support for AWS lambda instrumentation [#1193](https://github.com/elastic/apm-agent-python/pull/1193) +* Add support for span compression [#1321](https://github.com/elastic/apm-agent-python/pull/1321) +* Auto-infer destination resources for easier instrumentation of new resources [#1359](https://github.com/elastic/apm-agent-python/pull/1359) +* Add support for dropped span statistics [#1327](https://github.com/elastic/apm-agent-python/pull/1327) + +### Fixes [elastic-apm-python-agent-660-fixes] +* Ensure that Prometheus histograms are encoded correctly for APM Server [#1354](https://github.com/elastic/apm-agent-python/pull/1354) +* Remove problematic (and duplicate) `event.dataset` from logging integrations [#1365](https://github.com/elastic/apm-agent-python/pull/1365) +* Fix for memcache instrumentation when configured with a unix socket [#1357](https://github.com/elastic/apm-agent-python/pull/1357) + +## 6.5.0 [elastic-apm-python-agent-650-release-notes] +**Release date:** October 4, 2021 + +### Features and enhancements [elastic-apm-python-agent-650-features-enhancements] +* Add instrumentation for Azure Storage (blob/table/fileshare) and Azure Queue [#1316](https://github.com/elastic/apm-agent-python/pull/1316) + +### Fixes [elastic-apm-python-agent-650-fixes] +* Improve span coverage for `asyncpg` [#1328](https://github.com/elastic/apm-agent-python/pull/1328) +* aiohttp: Correctly pass custom client to tracing middleware [#1345](https://github.com/elastic/apm-agent-python/pull/1345) +* Fixed an issue with httpx instrumentation [#1337](https://github.com/elastic/apm-agent-python/pull/1337) +* Fixed an issue with Django 4.0 removing a private method [#1347](https://github.com/elastic/apm-agent-python/pull/1347) + +## 6.4.0 [elastic-apm-python-agent-640-release-notes] +**Release date:** August 31, 2021 + +### Features and enhancements [elastic-apm-python-agent-640-features-enhancements] +* Rename the experimental `log_ecs_formatting` config to `log_ecs_reformatting` [#1300](https://github.com/elastic/apm-agent-python/pull/1300) +* Add support for Prometheus histograms [#1165](https://github.com/elastic/apm-agent-python/pull/1165) + +### Fixes [elastic-apm-python-agent-640-fixes] +* Fixed cookie sanitization when Cookie is capitalized [#1301](https://github.com/elastic/apm-agent-python/pull/1301) +* Fix a bug with exception capturing for bad UUIDs [#1304](https://github.com/elastic/apm-agent-python/pull/1304) +* Fix potential errors in json serialization [#1203](https://github.com/elastic/apm-agent-python/pull/1203) +* Fix an issue with certain aioredis commands [#1308](https://github.com/elastic/apm-agent-python/pull/1308) + +## 6.3.3 [elastic-apm-python-agent-633-release-notes] +**Release date:** July 14, 2021 + +### Fixes [elastic-apm-python-agent-633-fixes] +* ensure that the elasticsearch instrumentation handles DroppedSpans correctly [#1190](https://github.com/elastic/apm-agent-python/pull/1190) + +## 6.3.2 [elastic-apm-python-agent-632-release-notes] +**Release date:** July 7, 2021 + +### Fixes [elastic-apm-python-agent-632-fixes] +* Fix handling of non-http scopes in Starlette/FastAPI middleware [#1187](https://github.com/elastic/apm-agent-python/pull/1187) + +## 6.3.1 [elastic-apm-python-agent-631-release-notes] +**Release date:** July 7, 2021 + +### Fixes [elastic-apm-python-agent-631-fixes] +* Fix issue with Starlette/FastAPI hanging on startup [#1185](https://github.com/elastic/apm-agent-python/pull/1185) + +## 6.3.0 [elastic-apm-python-agent-630-release-notes] +**Release date:** July 6, 2021 + +### Features and enhancements [elastic-apm-python-agent-630-features-enhancements] +* Add additional context information about elasticsearch client requests [#1108](https://github.com/elastic/apm-agent-python/pull/1108) +* Add `use_certifi` config option to allow users to disable `certifi` [#1163](https://github.com/elastic/apm-agent-python/pull/1163) + +### Fixes [elastic-apm-python-agent-630-fixes] +* Fix for Starlette 0.15.0 error collection [#1174](https://github.com/elastic/apm-agent-python/pull/1174) +* Fix for Starlette static files [#1137](https://github.com/elastic/apm-agent-python/pull/1137) + +## 6.2.3 [elastic-apm-python-agent-623-release-notes] +**Release date:** June 28, 2021 + +### Fixes [elastic-apm-python-agent-623-fixes] +* suppress the default_app_config attribute in Django 3.2+ [#1155](https://github.com/elastic/apm-agent-python/pull/1155) +* bump log level for multiple set_client calls to WARNING [#1164](https://github.com/elastic/apm-agent-python/pull/1164) +* fix issue with adding disttracing to SQS messages when dropping spans [#1170](https://github.com/elastic/apm-agent-python/pull/1170) + +## 6.2.2 [elastic-apm-python-agent-622-release-notes] +**Release date:** June 7, 2021 + +### Fixes [elastic-apm-python-agent-622-fixes] +* Fix an attribute access bug introduced in 6.2.0 [#1149](https://github.com/elastic/apm-agent-python/pull/1149) + +## 6.2.1 [elastic-apm-python-agent-621-release-notes] +**Release date:** June 3, 2021 + +### Fixes [elastic-apm-python-agent-621-fixes] +* catch and log exceptions in interval timer threads [#1145](https://github.com/elastic/apm-agent-python/pull/1145) + +## 6.2.0 [elastic-apm-python-agent-620-release-notes] +**Release date:** May 31, 2021 + +### Features and enhancements [elastic-apm-python-agent-620-features-enhancements] +* Added support for aioredis 1.x [#2526](https://github.com/elastic/apm-agent-python/pull/1082) +* Added support for aiomysql [#1107](https://github.com/elastic/apm-agent-python/pull/1107) +* Added Redis pub/sub instrumentation [#1129](https://github.com/elastic/apm-agent-python/pull/1129) +* Added specific instrumentation for AWS SQS [#1123](https://github.com/elastic/apm-agent-python/pull/1123) + +### Fixes [elastic-apm-python-agent-620-fixes] +* ensure metrics are flushed before agent shutdown [#1139](https://github.com/elastic/apm-agent-python/pull/1139) +* added safeguard for exceptions in processors [#1138](https://github.com/elastic/apm-agent-python/pull/1138) +* ensure sockets are closed which were opened for cloud environment detection [#1134](https://github.com/elastic/apm-agent-python/pull/1134) + +## 6.1.3 [elastic-apm-python-agent-613-release-notes] +**Release date:** April 28, 2021 + +### Fixes [elastic-apm-python-agent-613-fixes] +* added destination information to asyncpg instrumentation [#1115](https://github.com/elastic/apm-agent-python/pull/1115) +* fixed issue with collecting request meta data with Django REST Framework [#1117](https://github.com/elastic/apm-agent-python/pull/1117) +* fixed httpx instrumentation for newly released httpx 0.18.0 [#1118](https://github.com/elastic/apm-agent-python/pull/1118) + +## 6.1.2 [elastic-apm-python-agent-612-release-notes] +**Release date:** April 14, 2021 + +### Fixes [elastic-apm-python-agent-612-fixes] +* fixed issue with empty transaction name for the root route with Django [#1095](https://github.com/elastic/apm-agent-python/pull/1095) +* fixed on-the-fly initialisation of Flask apps [#1099](https://github.com/elastic/apm-agent-python/pull/1099) + +## 6.1.1 [elastic-apm-python-agent-611-release-notes] +**Release date:** April 8, 2021 + +### Fixes [elastic-apm-python-agent-611-fixes] +* fixed a validation issue with the newly introduced instrumentation for S3, SNS and DynamoDB [#1090](https://github.com/elastic/apm-agent-python/pull/1090) + +## 6.1.0 [elastic-apm-python-agent-610-release-notes] +**Release date:** March 31, 2021 + +### Features and enhancements [elastic-apm-python-agent-610-features-enhancements] +* Add global access to Client singleton object at `elasticapm.get_client()` [#1043](https://github.com/elastic/apm-agent-python/pull/1043) +* Add `log_ecs_formatting` config option [#1058](https://github.com/elastic/apm-agent-python/pull/1058) [#1063](https://github.com/elastic/apm-agent-python/pull/1063) +* Add instrumentation for httplib2 [#1031](https://github.com/elastic/apm-agent-python/pull/1031) +* Add better instrumentation for some AWS services (S3, SNS, DynamoDB) [#1054](https://github.com/elastic/apm-agent-python/pull/1054) +* Added beta support for collecting metrics from prometheus_client [#1083](https://github.com/elastic/apm-agent-python/pull/1083) + +### Fixes [elastic-apm-python-agent-610-fixes] +* Fix for potential `capture_body: error` hang in Starlette/FastAPI [#1038](https://github.com/elastic/apm-agent-python/pull/1038) +* Fix a rare error around processing stack frames [#1012](https://github.com/elastic/apm-agent-python/pull/1012) +* Fix for Starlette/FastAPI to correctly capture request bodies as strings [#1041](https://github.com/elastic/apm-agent-python/pull/1042) +* Fix transaction names for Starlette Mount routes [#1037](https://github.com/elastic/apm-agent-python/pull/1037) +* Fix for elastic excepthook arguments [#1050](https://github.com/elastic/apm-agent-python/pull/1050) +* Fix issue with remote configuration when resetting config values [#1068](https://github.com/elastic/apm-agent-python/pull/1068) +* Use a label for the elasticapm Django app that is compatible with Django 3.2 validation [#1064](https://github.com/elastic/apm-agent-python/pull/1064) +* Fix an issue with undefined routes in Starlette [#1076](https://github.com/elastic/apm-agent-python/pull/1076) + +## 6.0.0 [elastic-apm-python-agent-600-release-notes] +**Release date:** February 1, 2021 + +### Fixes [elastic-apm-python-agent-600-fixes] +* Fix for GraphQL span spamming from scalar fields with required flag [#1015](https://github.com/elastic/apm-agent-python/pull/1015) + + diff --git a/docs/release-notes/known-issues.md b/docs/release-notes/known-issues.md new file mode 100644 index 000000000..1b1d48435 --- /dev/null +++ b/docs/release-notes/known-issues.md @@ -0,0 +1,19 @@ +--- +navigation_title: "Elastic APM Python Agent" +--- + +# Elastic APM Python Agent known issues [elastic-apm-python-agent-known-issues] + +% Use the following template to add entries to this page. + +% :::{dropdown} Title of known issue +% **Details** +% On [Month/Day/Year], a known issue was discovered that [description of known issue]. + +% **Workaround** +% Workaround description. + +% **Resolved** +% On [Month/Day/Year], this issue was resolved. + +::: diff --git a/docs/release-notes/toc.yml b/docs/release-notes/toc.yml new file mode 100644 index 000000000..a41006794 --- /dev/null +++ b/docs/release-notes/toc.yml @@ -0,0 +1,5 @@ +toc: + - file: index.md + - file: known-issues.md + - file: breaking-changes.md + - file: deprecations.md \ No newline at end of file diff --git a/docs/run-tests-locally.asciidoc b/docs/run-tests-locally.asciidoc deleted file mode 100644 index fd3aa1eea..000000000 --- a/docs/run-tests-locally.asciidoc +++ /dev/null @@ -1,78 +0,0 @@ -[[run-tests-locally]] -=== Run Tests Locally - -To run tests locally you can make use of the docker images also used when running the whole test suite with Jenkins. -Running the full test suite first does some linting and then runs the actual tests with different versions of Python and different web frameworks. -For a full overview of the test matrix and supported versions have a look at -https://github.com/elastic/apm-agent-python/blob/main/Jenkinsfile[Jenkins Configuration]. - -[float] -[[pre-commit]] -==== Pre Commit -We run our git hooks on every commit to automatically point out issues in code. Those issues are also detected within the GitHub actions. -Please follow the installation steps stated in https://pre-commit.com/#install. - -[float] -[[coder-linter]] -==== Code Linter -We run two code linters `isort` and `flake8`. You can trigger each single one locally by running: - -[source,bash] ----- -$ pre-commit run -a isort ----- - -[source,bash] ----- -$ pre-commit run -a flake8 ----- - -[float] -[[coder-formatter]] -==== Code Formatter -We test that the code is formatted using `black`. You can trigger this check by running: - -[source,bash] ----- -$ pre-commit run -a black ----- - -[float] -[[test-documentation]] -==== Test Documentation -We test that the documentation can be generated without errors. You can trigger this check by running: -[source,bash] ----- -$ ./tests/scripts/docker/docs.sh ----- - -[float] -[[running-tests]] -==== Running Tests -We run the test suite on different combinations of Python versions and web frameworks. For triggering the test suite for a specific combination locally you can run: - -[source,bash] ----- -$ ./tests/scripts/docker/run_tests.sh python-version framework-version ----- -NOTE: The `python-version` must be of format `python-version`, e.g. `python-3.6` or `pypy-2`. -The `framework` must be of format `framework-version`, e.g. `django-1.10` or `flask-0.12`. - -You can also run the unit tests outside of docker, by installing the relevant -https://github.com/elastic/apm-agent-python/tree/main/tests/requirements[requirements file] -and then running `py.test` from the project root. - -==== Integration testing - -Check out https://github.com/elastic/apm-integration-testing for resources for -setting up full end-to-end testing environments. For example, to spin up -an environment with the https://github.com/basepi/opbeans-python[opbeans Django app], -with version 7.3 of the elastic stack and the apm-python-agent from your local -checkout, you might do something like this: - -[source,bash] ----- -$ ./scripts/compose.py start 7.3 \ - --with-agent-python-django --with-opbeans-python \ - --opbeans-python-agent-local-repo=~/elastic/apm-agent-python ----- diff --git a/docs/sanic.asciidoc b/docs/sanic.asciidoc deleted file mode 100644 index 83f8fd540..000000000 --- a/docs/sanic.asciidoc +++ /dev/null @@ -1,179 +0,0 @@ -[[sanic-support]] -=== Sanic Support - -Incorporating Elastic APM into your Sanic project only requires a few easy -steps. - -[float] -[[sanic-installation]] -==== Installation - -Install the Elastic APM agent using pip: - -[source,bash] ----- -$ pip install elastic-apm ----- - -or add `elastic-apm` to your project's `requirements.txt` file. - - -[float] -[[sanic-setup]] -==== Setup - -To set up the agent, you need to initialize it with appropriate settings. - -The settings are configured either via environment variables, or as -initialization arguments. - -You can find a list of all available settings in the -<> page. - -To initialize the agent for your application using environment variables: - -[source,python] ----- -from sanic import Sanic -from elasticapm.contrib.sanic import ElasticAPM - -app = Sanic(name="elastic-apm-sample") -apm = ElasticAPM(app=app) ----- - -To configure the agent using initialization arguments and Sanic's Configuration infrastructure: - -[source,python] ----- -# Create a file named external_config.py in your application -# If you want this module based configuration to be used for APM, prefix them with ELASTIC_APM_ -ELASTIC_APM_SERVER_URL = "https://serverurl.apm.com:443" -ELASTIC_APM_SECRET_TOKEN = "sometoken" ----- - -[source,python] ----- -from sanic import Sanic -from elasticapm.contrib.sanic import ElasticAPM - -app = Sanic(name="elastic-apm-sample") -app.config.update_config("path/to/external_config.py") -apm = ElasticAPM(app=app) ----- - -[float] -[[sanic-usage]] -==== Usage - -Once you have configured the agent, it will automatically track transactions -and capture uncaught exceptions within sanic. - -Capture an arbitrary exception by calling -<>: - -[source,python] ----- -from sanic import Sanic -from elasticapm.contrib.sanic import ElasticAPM - -app = Sanic(name="elastic-apm-sample") -apm = ElasticAPM(app=app) - -try: - 1 / 0 -except ZeroDivisionError: - apm.capture_exception() ----- - -Log a generic message with <>: - -[source,python] ----- -from sanic import Sanic -from elasticapm.contrib.sanic import ElasticAPM - -app = Sanic(name="elastic-apm-sample") -apm = ElasticAPM(app=app) - -apm.capture_message('hello, world!') ----- - -[float] -[[sanic-performance-metrics]] -==== Performance metrics - -If you've followed the instructions above, the agent has installed our -instrumentation middleware which will process all requests through your app. -This will measure response times, as well as detailed performance data for -all supported technologies. - -NOTE: Due to the fact that `asyncio` drivers are usually separate from their -synchronous counterparts, specific instrumentation is needed for all drivers. -The support for asynchronous drivers is currently quite limited. - -[float] -[[sanic-ignoring-specific-views]] -===== Ignoring specific routes - -You can use the -<> -configuration option to ignore specific routes. The list given should be a -list of regular expressions which are matched against the transaction name: - -[source,python] ----- -from sanic import Sanic -from elasticapm.contrib.sanic import ElasticAPM - -app = Sanic(name="elastic-apm-sample") -apm = ElasticAPM(app=app, config={ - 'TRANSACTIONS_IGNORE_PATTERNS': ['^GET /secret', '/extra_secret'], -}) ----- - -This would ignore any requests using the `GET /secret` route -and any requests containing `/extra_secret`. - -[float] -[[extended-sanic-usage]] -==== Extended Sanic APM Client Usage - -Sanic's contributed APM client also provides a few extendable way to configure selective behaviors to enhance the -information collected as part of the transactions being tracked by the APM. - -In order to enable this behavior, the APM Client middleware provides a few callback functions that you can leverage -in order to simplify the process of generating additional contexts into the traces being collected. -[cols="1,1,1,1"] -|=== -| Callback Name | Callback Invocation Format | Expected Return Format | Is Async - -| transaction_name_callback -| transaction_name_callback(request) -| string -| false - -| user_context_callback -| user_context_callback(request) -| (username_string, user_email_string, userid_string) -| true - -| custom_context_callback -| custom_context_callback(request) or custom_context_callback(response) -| dict(str=str) -| true - -| label_info_callback -| label_info_callback() -| dict(str=str) -| true -|=== - -[float] -[[supported-stanic-and-python-versions]] -==== Supported Sanic and Python versions - -A list of supported <> and -<> versions can be found on our -<> page. - -NOTE: Elastic APM only supports `asyncio` when using Python 3.7+ diff --git a/docs/serverless-azure-functions.asciidoc b/docs/serverless-azure-functions.asciidoc deleted file mode 100644 index b137c91c7..000000000 --- a/docs/serverless-azure-functions.asciidoc +++ /dev/null @@ -1,61 +0,0 @@ -[[azure-functions-support]] -=== Monitoring Azure Functions - -[float] -==== Prerequisites - -You need an APM Server to which you can send APM data. -Follow the {apm-guide-ref}/apm-quick-start.html[APM Quick start] if you have not set one up yet. -For the best-possible performance, we recommend setting up APM on {ecloud} in the same Azure region as your Azure Functions app. - -NOTE: Currently, only HTTP and timer triggers are supported. -Other trigger types may be captured as well, but the amount of captured contextual data may differ. - -[float] -==== Step 1: Enable Worker Extensions - -Elastic APM uses https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-python?tabs=asgi%2Capplication-level&pivots=python-mode-configuration#python-worker-extensions[Worker Extensions] -to instrument Azure Functions. -This feature is not enabled by default, and must be enabled in your Azure Functions App. -Please follow the instructions in the https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-python?tabs=asgi%2Capplication-level&pivots=python-mode-configuration#using-extensions[Azure docs]. - -Once you have enabled Worker Extensions, these two lines of code will enable Elastic APM's extension: - -[source,python] ----- -from elasticapm.contrib.serverless.azure import ElasticAPMExtension - -ElasticAPMExtension.configure() ----- - -Put them somewhere at the top of your Python file, before the function definitions. - -[float] -==== Step 2: Install the APM Python Agent - -You need to add `elastic-apm` as a dependency for your Functions app. -Simply add `elastic-apm` to your `requirements.txt` file. -We recommend pinning the version to the current newest version of the agent, and periodically updating the version. - -[float] -==== Step 3: Configure APM on Azure Functions - -The APM Python agent is configured through https://learn.microsoft.com/en-us/azure/azure-functions/functions-how-to-use-azure-function-app-settings?tabs=portal#settings[App Settings]. -These are then picked up by the agent as environment variables. - -For the minimal configuration, you will need the <> to set the destination for APM data and a <>. -If you prefer to use an {apm-guide-ref}/api-key.html[APM API key] instead of the APM secret token, use the <> environment variable instead of `ELASTIC_APM_SECRET_TOKEN` in the following example configuration. - -[source,bash] ----- -$ az functionapp config appsettings set --settings ELASTIC_APM_SERVER_URL=https://example.apm.northeurope.azure.elastic-cloud.com:443 -$ az functionapp config appsettings set --settings ELASTIC_APM_SECRET_TOKEN=verysecurerandomstring ----- - -You can optionally <>. - -That's it; Once the agent is installed and working, spans will be captured for -<>. You can also use -<> to capture custom spans, and -you can retrieve the `Client` object for capturing exceptions/messages -using <>. diff --git a/docs/serverless-lambda.asciidoc b/docs/serverless-lambda.asciidoc deleted file mode 100644 index 732abb2b4..000000000 --- a/docs/serverless-lambda.asciidoc +++ /dev/null @@ -1,53 +0,0 @@ -[[lambda-support]] -=== Monitoring AWS Lambda Python Functions -:layer-section-type: with-agent -:apm-aws-repo-dir: ./lambda - -The Python APM Agent can be used with AWS Lambda to monitor the execution of your AWS Lambda functions. - -``` -Note: The Centralized Agent Configuration on the Elasticsearch APM currently does NOT support AWS Lambda. -``` - - -[float] -==== Prerequisites - -You need an APM Server to send APM data to. Follow the {apm-guide-ref}/apm-quick-start.html[APM Quick start] if you have not set one up yet. For the best-possible performance, we recommend setting up APM on {ecloud} in the same AWS region as your AWS Lambda functions. - -[float] -==== Step 1: Select the AWS Region and Architecture - -include::{apm-aws-lambda-root}/docs/lambda-selector/lambda-attributes-selector.asciidoc[] - -[float] -==== Step 2: Add the APM Layers to your Lambda function - -include::{apm-aws-lambda-root}/docs/lambda-selector/extension-arn-replacement.asciidoc[] -include::./lambda/python-arn-replacement.asciidoc[] - -Both the {apm-lambda-ref}/aws-lambda-arch.html[{apm-lambda-ext}] and the Python APM Agent are added to your Lambda function as https://docs.aws.amazon.com/lambda/latest/dg/invocation-layers.html[AWS Lambda Layers]. Therefore, you need to add the corresponding Layer ARNs (identifiers) to your Lambda function. - -include::{apm-aws-lambda-root}/docs/add-extension/add-extension-layer-widget.asciidoc[] - -[float] -==== Step 3: Configure APM on AWS Lambda - -The {apm-lambda-ext} and the APM Python agent are configured through environment variables on the AWS Lambda function. - -For the minimal configuration, you will need the _APM Server URL_ to set the destination for APM data and an _{apm-guide-ref}/secret-token.html[APM Secret Token]_. -If you prefer to use an {apm-guide-ref}/api-key.html[APM API key] instead of the APM secret token, use the `ELASTIC_APM_API_KEY` environment variable instead of `ELASTIC_APM_SECRET_TOKEN` in the following configuration. - -For production environments, we recommend {apm-lambda-ref}/aws-lambda-secrets-manager.html[using the AWS Secrets Manager to store your APM authentication key] instead of providing the secret value as plaintext in the environment variables. - -include::./lambda/configure-lambda-widget.asciidoc[] -<1> The {apm-lambda-ref}/aws-lambda-config-options.html#_elastic_apm_send_strategy[`ELASTIC_APM_SEND_STRATEGY`] defines when APM data is sent to your Elastic APM backend. To reduce the execution time of your lambda functions, we recommend to use the `background` strategy in production environments with steady load scenarios. - -You can optionally <> or the {apm-lambda-ref}/aws-lambda-config-options.html[configuration of the {apm-lambda-ext}]. - -That's it. After following the steps above, you're ready to go! Your Lambda -function invocations should be traced from now on. Spans will be captured for -<>. You can also use -<> to capture custom spans, and you can -retrieve the `Client` object for capturing exceptions/messages using -<>. diff --git a/docs/set-up.asciidoc b/docs/set-up.asciidoc deleted file mode 100644 index 58e74294b..000000000 --- a/docs/set-up.asciidoc +++ /dev/null @@ -1,37 +0,0 @@ -[[set-up]] -== Set up the Agent - -To get you off the ground, we’ve prepared guides for setting up the Agent with different frameworks: - - * <> - * <> - * <> - * <> - * <> - * <> - * <> - * <> - * <> - * <> - -For custom instrumentation, see <>. - -include::./django.asciidoc[] - -include::./flask.asciidoc[] - -include::./aiohttp-server.asciidoc[] - -include::./tornado.asciidoc[] - -include::./starlette.asciidoc[] - -include::./sanic.asciidoc[] - -include::./serverless-lambda.asciidoc[] - -include::./serverless-azure-functions.asciidoc[] - -include::./wrapper.asciidoc[] - -include::./asgi-middleware.asciidoc[] diff --git a/docs/starlette.asciidoc b/docs/starlette.asciidoc deleted file mode 100644 index 941bf6d7a..000000000 --- a/docs/starlette.asciidoc +++ /dev/null @@ -1,152 +0,0 @@ -[[starlette-support]] -=== Starlette/FastAPI Support - -Incorporating Elastic APM into your Starlette project only requires a few easy -steps. - -[float] -[[starlette-installation]] -==== Installation - -Install the Elastic APM agent using pip: - -[source,bash] ----- -$ pip install elastic-apm ----- - -or add `elastic-apm` to your project's `requirements.txt` file. - - -[float] -[[starlette-setup]] -==== Setup - -To set up the agent, you need to initialize it with appropriate settings. - -The settings are configured either via environment variables, or as -initialization arguments. - -You can find a list of all available settings in the -<> page. - -To initialize the agent for your application using environment variables, add -the ElasticAPM middleware to your Starlette application: - -[source,python] ----- -from starlette.applications import Starlette -from elasticapm.contrib.starlette import ElasticAPM - -app = Starlette() -app.add_middleware(ElasticAPM) ----- - -WARNING: `BaseHTTPMiddleware` breaks `contextvar` propagation, as noted -https://www.starlette.io/middleware/#limitations[here]. This means the -ElasticAPM middleware must be above any `BaseHTTPMiddleware` in the final -middleware list. If you're calling `add_middleware` repeatedly, add the -ElasticAPM middleware last. If you're passing in a list of middleware, -ElasticAPM should be first on that list. - -To configure the agent using initialization arguments: - -[source,python] ----- -from starlette.applications import Starlette -from elasticapm.contrib.starlette import make_apm_client, ElasticAPM - -apm = make_apm_client({ - 'SERVICE_NAME': '', - 'SECRET_TOKEN': '', - 'SERVER_URL': '', -}) -app = Starlette() -app.add_middleware(ElasticAPM, client=apm) ----- - -[float] -[[starlette-fastapi]] -==== FastAPI - -Because FastAPI supports Starlette middleware, using the agent with FastAPI -is almost exactly the same as with Starlette: - -[source,python] ----- -from fastapi import FastAPI -from elasticapm.contrib.starlette import ElasticAPM - -app = FastAPI() -app.add_middleware(ElasticAPM) ----- - -[float] -[[starlette-usage]] -==== Usage - -Once you have configured the agent, it will automatically track transactions -and capture uncaught exceptions within starlette. - -Capture an arbitrary exception by calling -<>: - -[source,python] ----- -try: - 1 / 0 -except ZeroDivisionError: - apm.capture_exception() ----- - -Log a generic message with <>: - -[source,python] ----- -apm.capture_message('hello, world!') ----- - -[float] -[[starlette-performance-metrics]] -==== Performance metrics - -If you've followed the instructions above, the agent has installed our -instrumentation middleware which will process all requests through your app. -This will measure response times, as well as detailed performance data for -all supported technologies. - -NOTE: Due to the fact that `asyncio` drivers are usually separate from their -synchronous counterparts, specific instrumentation is needed for all drivers. -The support for asynchronous drivers is currently quite limited. - -[float] -[[starlette-ignoring-specific-views]] -===== Ignoring specific routes - -You can use the -<> -configuration option to ignore specific routes. The list given should be a -list of regular expressions which are matched against the transaction name: - -[source,python] ----- -apm = make_apm_client({ - # ... - 'TRANSACTIONS_IGNORE_PATTERNS': ['^GET /secret', '/extra_secret'] - # ... -}) ----- - -This would ignore any requests using the `GET /secret` route -and any requests containing `/extra_secret`. - - -[float] -[[supported-starlette-and-python-versions]] -==== Supported Starlette and Python versions - -A list of supported <> and -<> versions can be found on our -<> page. - -NOTE: Elastic APM only supports `asyncio` when using Python 3.7+ diff --git a/docs/supported-technologies.asciidoc b/docs/supported-technologies.asciidoc deleted file mode 100644 index 50198a102..000000000 --- a/docs/supported-technologies.asciidoc +++ /dev/null @@ -1,683 +0,0 @@ -[[supported-technologies]] -== Supported Technologies - -[[framework-support]] -The Elastic APM Python Agent comes with support for the following frameworks: - - * <> - * <> - * <> - * <> - * <> - * <> - * <> - -For other frameworks and custom Python code, the agent exposes a set of <> for integration. - -[float] -[[supported-python]] -=== Python - -The following Python versions are supported: - - * 3.6 - * 3.7 - * 3.8 - * 3.9 - * 3.10 - * 3.11 - * 3.12 - -[float] -[[supported-django]] -=== Django - -We support these Django versions: - - * 1.11 - * 2.0 - * 2.1 - * 2.2 - * 3.0 - * 3.1 - * 3.2 - * 4.0 - * 4.2 - * 5.0 - -For upcoming Django versions, we generally aim to ensure compatibility starting with the first Release Candidate. - -NOTE: we currently don't support Django running in ASGI mode. - -[float] -[[supported-flask]] -=== Flask - -We support these Flask versions: - - * 0.10 (Deprecated) - * 0.11 (Deprecated) - * 0.12 (Deprecated) - * 1.0 - * 1.1 - * 2.0 - * 2.1 - * 2.2 - * 2.3 - * 3.0 - -[float] -[[supported-aiohttp]] -=== Aiohttp Server - -We support these aiohttp versions: - - * 3.0+ - -[float] -[[supported-tornado]] -=== Tornado - -We support these tornado versions: - - * 6.0+ - - -[float] -[[supported-sanic]] -=== Sanic - -We support these sanic versions: - - * 20.12.2+ - - -[float] -[[supported-starlette]] -=== Starlette/FastAPI - -We support these Starlette versions: - - * 0.13.0+ - -Any FastAPI version which uses a supported Starlette version should also -be supported. - -[float] -[[supported-grpc]] -=== GRPC - -We support these `grpcio` versions: - - * 1.24.0+ - - -[float] -[[automatic-instrumentation]] -== Automatic Instrumentation - -The Python APM agent comes with automatic instrumentation of various 3rd party modules and standard library modules. - -[float] -[[automatic-instrumentation-scheduling]] -=== Scheduling - -[float] -[[automatic-instrumentation-scheduling-celery]] -===== Celery - -We support these Celery versions: - -* 4.x (deprecated) -* 5.x - -Celery tasks will be recorded automatically with Django and Flask only. - -[float] -[[automatic-instrumentation-db]] -=== Databases - -[float] -[[automatic-instrumentation-db-elasticsearch]] -==== Elasticsearch - -Instrumented methods: - - * `elasticsearch.transport.Transport.perform_request` - * `elasticsearch.connection.http_urllib3.Urllib3HttpConnection.perform_request` - * `elasticsearch.connection.http_requests.RequestsHttpConnection.perform_request` - * `elasticsearch._async.transport.AsyncTransport.perform_request` - * `elasticsearch_async.connection.AIOHttpConnection.perform_request` - -Additionally, the instrumentation wraps the following methods of the `Elasticsearch` client class: - - * `elasticsearch.client.Elasticsearch.delete_by_query` - * `elasticsearch.client.Elasticsearch.search` - * `elasticsearch.client.Elasticsearch.count` - * `elasticsearch.client.Elasticsearch.update` - -Collected trace data: - - * the query string (if available) - * the `query` element from the request body (if available) - * the response status code - * the count of affected rows (if available) - -We recommend using keyword arguments only with elasticsearch-py, as recommended by -https://elasticsearch-py.readthedocs.io/en/master/api.html#api-documentation[the elasticsearch-py docs]. -If you are using positional arguments, we will be unable to gather the `query` -element from the request body. - -[float] -[[automatic-instrumentation-db-sqlite]] -==== SQLite - -Instrumented methods: - - * `sqlite3.connect` - * `sqlite3.dbapi2.connect` - * `pysqlite2.dbapi2.connect` - -The instrumented `connect` method returns a wrapped connection/cursor which instruments the actual `Cursor.execute` calls. - -Collected trace data: - - * parametrized SQL query - - -[float] -[[automatic-instrumentation-db-mysql]] -==== MySQLdb - -Library: `MySQLdb` - -Instrumented methods: - - * `MySQLdb.connect` - -The instrumented `connect` method returns a wrapped connection/cursor which instruments the actual `Cursor.execute` calls. - -Collected trace data: - - * parametrized SQL query - -[float] -[[automatic-instrumentation-db-mysql-connector]] -==== mysql-connector - -Library: `mysql-connector-python` - -Instrumented methods: - - * `mysql.connector.connect` - -The instrumented `connect` method returns a wrapped connection/cursor which instruments the actual `Cursor.execute` calls. - -Collected trace data: - - * parametrized SQL query - -[float] -[[automatic-instrumentation-db-pymysql]] -==== pymysql - -Library: `pymysql` - -Instrumented methods: - - * `pymysql.connect` - -The instrumented `connect` method returns a wrapped connection/cursor which instruments the actual `Cursor.execute` calls. - -Collected trace data: - - * parametrized SQL query - -[float] -[[automatic-instrumentation-db-aiomysql]] -==== aiomysql - -Library: `aiomysql` - -Instrumented methods: - - * `aiomysql.cursors.Cursor.execute` - -Collected trace data: - - * parametrized SQL query - -[float] -[[automatic-instrumentation-db-postgres]] -==== PostgreSQL - -Library: `psycopg2`, `psycopg2-binary` (`>=2.9`) - -Instrumented methods: - - * `psycopg2.connect` - -The instrumented `connect` method returns a wrapped connection/cursor which instruments the actual `Cursor.execute` calls. - -Collected trace data: - - * parametrized SQL query - -[float] -[[automatic-instrumentation-db-aiopg]] -==== aiopg - -Library: `aiopg` (`>=1.0`) - -Instrumented methods: - - * `aiopg.cursor.Cursor.execute` - * `aiopg.cursor.Cursor.callproc` - -Collected trace data: - - * parametrized SQL query - -[float] -[[automatic-instrumentation-db-asyncg]] -==== asyncpg - -Library: `asyncpg` (`>=0.20`) - -Instrumented methods: - - * `asyncpg.connection.Connection.execute` - * `asyncpg.connection.Connection.executemany` - - -Collected trace data: - - * parametrized SQL query - -[float] -[[automatic-instrumentation-db-pyodbc]] -==== PyODBC - -Library: `pyodbc`, (`>=4.0`) - -Instrumented methods: - - * `pyodbc.connect` - -The instrumented `connect` method returns a wrapped connection/cursor which instruments the actual `Cursor.execute` calls. - -Collected trace data: - - * parametrized SQL query - -[float] -[[automatic-instrumentation-db-mssql]] -==== MS-SQL - -Library: `pymssql`, (`>=2.1.0`) - -Instrumented methods: - - * `pymssql.connect` - -The instrumented `connect` method returns a wrapped connection/cursor which instruments the actual `Cursor.execute` calls. - -Collected trace data: - - * parametrized SQL query - -[float] -[[automatic-instrumentation-db-mongodb]] -==== MongoDB - -Library: `pymongo`, `>=2.9,<3.8` - -Instrumented methods: - - * `pymongo.collection.Collection.aggregate` - * `pymongo.collection.Collection.bulk_write` - * `pymongo.collection.Collection.count` - * `pymongo.collection.Collection.create_index` - * `pymongo.collection.Collection.create_indexes` - * `pymongo.collection.Collection.delete_many` - * `pymongo.collection.Collection.delete_one` - * `pymongo.collection.Collection.distinct` - * `pymongo.collection.Collection.drop` - * `pymongo.collection.Collection.drop_index` - * `pymongo.collection.Collection.drop_indexes` - * `pymongo.collection.Collection.ensure_index` - * `pymongo.collection.Collection.find_and_modify` - * `pymongo.collection.Collection.find_one` - * `pymongo.collection.Collection.find_one_and_delete` - * `pymongo.collection.Collection.find_one_and_replace` - * `pymongo.collection.Collection.find_one_and_update` - * `pymongo.collection.Collection.group` - * `pymongo.collection.Collection.inline_map_reduce` - * `pymongo.collection.Collection.insert` - * `pymongo.collection.Collection.insert_many` - * `pymongo.collection.Collection.insert_one` - * `pymongo.collection.Collection.map_reduce` - * `pymongo.collection.Collection.reindex` - * `pymongo.collection.Collection.remove` - * `pymongo.collection.Collection.rename` - * `pymongo.collection.Collection.replace_one` - * `pymongo.collection.Collection.save` - * `pymongo.collection.Collection.update` - * `pymongo.collection.Collection.update_many` - * `pymongo.collection.Collection.update_one` - -Collected trace data: - - * database name - * method name - - -[float] -[[automatic-instrumentation-db-redis]] -==== Redis - -Library: `redis` (`>=2.8`) - -Instrumented methods: - - * `redis.client.Redis.execute_command` - * `redis.client.Pipeline.execute` - -Collected trace data: - - * Redis command name - - -[float] -[[automatic-instrumentation-db-aioredis]] -==== aioredis - -Library: `aioredis` (`<2.0`) - -Instrumented methods: - - * `aioredis.pool.ConnectionsPool.execute` - * `aioredis.commands.transaction.Pipeline.execute` - * `aioredis.connection.RedisConnection.execute` - -Collected trace data: - - * Redis command name - -[float] -[[automatic-instrumentation-db-cassandra]] -==== Cassandra - -Library: `cassandra-driver` (`>=3.4,<4.0`) - -Instrumented methods: - - * `cassandra.cluster.Session.execute` - * `cassandra.cluster.Cluster.connect` - -Collected trace data: - - * CQL query - -[float] -[[automatic-instrumentation-db-python-memcache]] -==== Python Memcache - -Library: `python-memcached` (`>=1.51`) - -Instrumented methods: - -* `memcache.Client.add` -* `memcache.Client.append` -* `memcache.Client.cas` -* `memcache.Client.decr` -* `memcache.Client.delete` -* `memcache.Client.delete_multi` -* `memcache.Client.disconnect_all` -* `memcache.Client.flush_all` -* `memcache.Client.get` -* `memcache.Client.get_multi` -* `memcache.Client.get_slabs` -* `memcache.Client.get_stats` -* `memcache.Client.gets` -* `memcache.Client.incr` -* `memcache.Client.prepend` -* `memcache.Client.replace` -* `memcache.Client.set` -* `memcache.Client.set_multi` -* `memcache.Client.touch` - -Collected trace data: - -* Destination (address and port) - -[float] -[[automatic-instrumentation-db-pymemcache]] -==== pymemcache - -Library: `pymemcache` (`>=3.0`) - -Instrumented methods: - -* `pymemcache.client.base.Client.add` -* `pymemcache.client.base.Client.append` -* `pymemcache.client.base.Client.cas` -* `pymemcache.client.base.Client.decr` -* `pymemcache.client.base.Client.delete` -* `pymemcache.client.base.Client.delete_many` -* `pymemcache.client.base.Client.delete_multi` -* `pymemcache.client.base.Client.flush_all` -* `pymemcache.client.base.Client.get` -* `pymemcache.client.base.Client.get_many` -* `pymemcache.client.base.Client.get_multi` -* `pymemcache.client.base.Client.gets` -* `pymemcache.client.base.Client.gets_many` -* `pymemcache.client.base.Client.incr` -* `pymemcache.client.base.Client.prepend` -* `pymemcache.client.base.Client.quit` -* `pymemcache.client.base.Client.replace` -* `pymemcache.client.base.Client.set` -* `pymemcache.client.base.Client.set_many` -* `pymemcache.client.base.Client.set_multi` -* `pymemcache.client.base.Client.stats` -* `pymemcache.client.base.Client.touch` - -Collected trace data: - -* Destination (address and port) - -[float] -[[automatic-instrumentation-db-kafka-python]] -==== kafka-python - -Library: `kafka-python` (`>=2.0`) - -Instrumented methods: - - * `kafka.KafkaProducer.send`, - * `kafka.KafkaConsumer.poll`, - * `kafka.KafkaConsumer.\\__next__` - -Collected trace data: - - * Destination (address and port) - * topic (if applicable) - - -[float] -[[automatic-instrumentation-http]] -=== External HTTP requests - -[float] -[[automatic-instrumentation-stdlib-urllib]] -==== Standard library - -Library: `urllib2` (Python 2) / `urllib.request` (Python 3) - -Instrumented methods: - - * `urllib2.AbstractHTTPHandler.do_open` / `urllib.request.AbstractHTTPHandler.do_open` - -Collected trace data: - - * HTTP method - * requested URL - -[float] -[[automatic-instrumentation-urllib3]] -==== urllib3 - -Library: `urllib3` - -Instrumented methods: - - * `urllib3.connectionpool.HTTPConnectionPool.urlopen` - -Additionally, we instrumented vendored instances of urllib3 in the following libraries: - - * `requests` - * `botocore` - -Both libraries have "unvendored" urllib3 in more recent versions, we recommend to use the newest versions. - -Collected trace data: - - * HTTP method - * requested URL - -[float] -[[automatic-instrumentation-requests]] -==== requests - -Instrumented methods: - - * `requests.sessions.Session.send` - -Collected trace data: - - * HTTP method - * requested URL - -[float] -[[automatic-instrumentation-aiohttp-client]] -==== AIOHTTP Client - -Instrumented methods: - - * `aiohttp.client.ClientSession._request` - -Collected trace data: - - * HTTP method - * requested URL - -[float] -[[automatic-instrumentation-httpx]] -==== httpx - -Instrumented methods: - - * `httpx.Client.send - -Collected trace data: - - * HTTP method - * requested URL - - -[float] -[[automatic-instrumentation-services]] -=== Services - -[float] -[[automatic-instrumentation-boto3]] -==== AWS Boto3 / Botocore - -Library: `boto3` (`>=1.0`) - -Instrumented methods: - - * `botocore.client.BaseClient._make_api_call` - -Collected trace data for all services: - - * AWS region (e.g. `eu-central-1`) - * AWS service name (e.g. `s3`) - * operation name (e.g. `ListBuckets`) - -Additionally, some services collect more specific data - -[float] -[[automatic-instrumentation-aiobotocore]] -==== AWS Aiobotocore - -Library: `aiobotocore` (`>=2.2.0`) - -Instrumented methods: - - * `aiobotocore.client.BaseClient._make_api_call` - -Collected trace data for all services: - - * AWS region (e.g. `eu-central-1`) - * AWS service name (e.g. `s3`) - * operation name (e.g. `ListBuckets`) - -Additionally, some services collect more specific data - -[float] -[[automatic-instrumentation-s3]] -===== S3 - - * Bucket name - -[float] -[[automatic-instrumentation-dynamodb]] -===== DynamoDB - - * Table name - - -[float] -[[automatic-instrumentation-sns]] -===== SNS - - * Topic name - -[float] -[[automatic-instrumentation-sqs]] -===== SQS - - * Queue name - -[float] -[[automatic-instrumentation-template-engines]] -=== Template Engines - -[float] -[[automatic-instrumentation-dtl]] -==== Django Template Language - -Library: `Django` (see <> for supported versions) - -Instrumented methods: - - * `django.template.Template.render` - -Collected trace data: - - * template name - -[float] -[[automatic-instrumentation-jinja2]] -==== Jinja2 - -Library: `jinja2` - -Instrumented methods: - - * `jinja2.Template.render` - -Collected trace data: - - * template name diff --git a/docs/tornado.asciidoc b/docs/tornado.asciidoc deleted file mode 100644 index c7281761f..000000000 --- a/docs/tornado.asciidoc +++ /dev/null @@ -1,125 +0,0 @@ -[[tornado-support]] -=== Tornado Support - -Incorporating Elastic APM into your Tornado project only requires a few easy -steps. - -[float] -[[tornado-installation]] -==== Installation - -Install the Elastic APM agent using pip: - -[source,bash] ----- -$ pip install elastic-apm ----- - -or add `elastic-apm` to your project's `requirements.txt` file. - - -[float] -[[tornado-setup]] -==== Setup - -To set up the agent, you need to initialize it with appropriate settings. - -The settings are configured either via environment variables, -the application's settings, or as initialization arguments. - -You can find a list of all available settings in the -<> page. - -To initialize the agent for your application using environment variables: - -[source,python] ----- -import tornado.web -from elasticapm.contrib.tornado import ElasticAPM - -app = tornado.web.Application() -apm = ElasticAPM(app) ----- - -To configure the agent using `ELASTIC_APM` in your application's settings: - -[source,python] ----- -import tornado.web -from elasticapm.contrib.tornado import ElasticAPM - -app = tornado.web.Application() -app.settings['ELASTIC_APM'] = { - 'SERVICE_NAME': '', - 'SECRET_TOKEN': '', -} -apm = ElasticAPM(app) ----- - -[float] -[[tornado-usage]] -==== Usage - -Once you have configured the agent, it will automatically track transactions -and capture uncaught exceptions within tornado. - -Capture an arbitrary exception by calling -<>: - -[source,python] ----- -try: - 1 / 0 -except ZeroDivisionError: - apm.client.capture_exception() ----- - -Log a generic message with <>: - -[source,python] ----- -apm.client.capture_message('hello, world!') ----- - -[float] -[[tornado-performance-metrics]] -==== Performance metrics - -If you've followed the instructions above, the agent has installed our -instrumentation within the base RequestHandler class in tornado.web. This will -measure response times, as well as detailed performance data for all supported -technologies. - -NOTE: Due to the fact that `asyncio` drivers are usually separate from their -synchronous counterparts, specific instrumentation is needed for all drivers. -The support for asynchronous drivers is currently quite limited. - -[float] -[[tornado-ignoring-specific-views]] -===== Ignoring specific routes - -You can use the -<> -configuration option to ignore specific routes. The list given should be a -list of regular expressions which are matched against the transaction name: - -[source,python] ----- -app.settings['ELASTIC_APM'] = { - # ... - 'TRANSACTIONS_IGNORE_PATTERNS': ['^GET SecretHandler', 'MainHandler'] - # ... -} ----- - -This would ignore any requests using the `GET SecretHandler` route -and any requests containing `MainHandler`. - - -[float] -[[supported-tornado-and-python-versions]] -==== Supported tornado and Python versions - -A list of supported <> and <> versions can be found on our <> page. - -NOTE: Elastic APM only supports `asyncio` when using Python 3.7+ diff --git a/docs/troubleshooting.asciidoc b/docs/troubleshooting.asciidoc deleted file mode 100644 index 40b8ed8fe..000000000 --- a/docs/troubleshooting.asciidoc +++ /dev/null @@ -1,172 +0,0 @@ -[[troubleshooting]] -== Troubleshooting - -Below are some resources and tips for troubleshooting and debugging the -python agent. - -* <> -* <> -* <> -* <> - -[float] -[[easy-fixes]] -=== Easy Fixes - -Before you try anything else, go through the following sections to ensure that -the agent is configured correctly. This is not an exhaustive list, but rather -a list of common problems that users run into. - -[float] -[[debug-mode]] -==== Debug Mode - -Most frameworks support a debug mode. Generally, this mode is intended for -non-production environments and provides detailed error messages and logging of -potentially sensitive data. Because of these security issues, the agent will -not collect traces if the app is in debug mode by default. - -You can override this behavior with the <> configuration. - -Note that configuration of the agent should occur before creation of any -`ElasticAPM` objects: - -[source,python] ----- -app = Flask(__name__) -app.config["ELASTIC_APM"] = {"DEBUG": True} -apm = ElasticAPM(app, service_name="flask-app") ----- - -[float] -[[psutil-metrics]] -==== `psutil` for Metrics - -To get CPU and system metrics on non-Linux systems, `psutil` must be -installed. The agent should automatically show a warning on start if it is -not installed, but sometimes this warning can be suppressed. Install `psutil` -and metrics should be collected by the agent and sent to the APM Server. - -[source,bash] ----- -python3 -m pip install psutil ----- - -[float] -[[apm-server-credentials]] -==== Credential issues - -In order for the agent to send data to the APM Server, it may need an -<> or a <>. Double -check your APM Server settings and make sure that your credentials are -configured correctly. Additionally, check that <> -is correct. - -[float] -[[django-test]] -=== Django `check` and `test` - -When used with Django, the agent provides two management commands to help debug -common issues. Head over to the <> -for more information. - -[float] -[[agent-logging]] -=== Agent logging - -To get the agent to log more data, all that is needed is a -https://docs.python.org/3/library/logging.html#handler-objects[Handler] which -is attached either to the `elasticapm` logger or to the root logger. - -Note that if you attach the handler to the root logger, you also need to -explicitly set the log level of the `elasticapm` logger: - -[source,python] ----- -import logging -apm_logger = logging.getLogger("elasticapm") -apm_logger.setLevel(logging.DEBUG) ----- - -[float] -[[django-agent-logging]] -==== Django - -The simplest way to log more data from the agent is to add a console logging -Handler to the `elasticapm` logger. Here's a (very simplified) example: - -[source,python] ----- -LOGGING = { - 'handlers': { - 'console': { - 'level': 'DEBUG', - 'class': 'logging.StreamHandler' - } - }, - 'loggers': { - 'elasticapm': { - 'level': 'DEBUG', - 'handlers': ['console'] - }, - }, -} ----- - -[float] -[[flask-agent-logging]] -==== Flask - -Flask https://flask.palletsprojects.com/en/1.1.x/logging/[recommends using `dictConfig()`] -to set up logging. If you're using this format, adding logging for the agent -will be very similar to the <>. - -Otherwise, you can use the <>. - -[float] -[[generic-agent-logging]] -==== Generic instructions - -Creating a console Handler and adding it to the `elasticapm` logger is easy: - -[source,python] ----- -import logging - -elastic_apm_logger = logging.getLogger("elasticapm") -console_handler = logging.StreamHandler() -console_handler.setLevel(logging.DEBUG) -elastic_apm_logger.addHandler(console_handler) ----- - -You can also just add the console Handler to the root logger. This will apply -that handler to all log messages from all modules. - -[source,python] ----- -import logging - -logger = logging.getLogger() -console_handler = logging.StreamHandler() -console_handler.setLevel(logging.DEBUG) -logger.addHandler(console_handler) ----- - -See the https://docs.python.org/3/library/logging.html[python logging docs] -for more details about Handlers (and information on how to format your logs -using Formatters). - -[float] -[[disable-agent]] -=== Disable the Agent - -In the unlikely event the agent causes disruptions to a production application, -you can disable the agent while you troubleshoot. - -If you have access to <>, -you can disable the recording of events by setting <> to `false`. -When changed at runtime from a supported source, there's no need to restart your application. - -If that doesn't work, or you don't have access to dynamic configuration, you can disable the agent by setting -<> to `false`. -You'll need to restart your application for the changes to take effect. diff --git a/docs/tuning.asciidoc b/docs/tuning.asciidoc deleted file mode 100644 index 6030f29cc..000000000 --- a/docs/tuning.asciidoc +++ /dev/null @@ -1,115 +0,0 @@ -[[tuning-and-overhead]] -== Performance tuning - -Using an APM solution comes with certain trade-offs, and the Python agent for Elastic APM is no different. -Instrumenting your code, measuring timings, recording context data, etc., all need resources: - - * CPU time - * memory - * bandwidth use - * Elasticsearch storage - -We invested and continue to invest a lot of effort to keep the overhead of using Elastic APM as low as possible. -But because every deployment is different, there are some knobs you can turn to adapt it to your specific needs. - -[float] -[[tuning-sample-rate]] -=== Transaction Sample Rate - -The easiest way to reduce the overhead of the agent is to tell the agent to do less. -If you set the <> to a value below `1.0`, -the agent will randomly sample only a subset of transactions. -Unsampled transactions only record the name of the transaction, the overall transaction time, and the result: - -[options="header"] -|============ -| Field | Sampled | Unsampled -| Transaction name | yes | yes -| Duration | yes | yes -| Result | yes | yes -| Context | yes | no -| Tags | yes | no -| Spans | yes | no -|============ - -Reducing the sample rate to a fraction of all transactions can make a huge difference in all four of the mentioned resource types. - -[float] -[[tuning-queue]] -=== Transaction Queue - -To reduce the load on the APM Server, the agent does not send every transaction up as it happens. -Instead, it queues them up and flushes the queue periodically, or when it reaches a maximum size, using a background thread. - -While this reduces the load on the APM Server (and to a certain extent on the agent), -holding on to the transaction data in a queue uses memory. -If you notice that using the Python agent results in a large increase of memory use, -you can use these settings: - - * <> to reduce the time between queue flushes - * <> to reduce the maximum size of the queue - -The first setting, `api_request_time`, is helpful if you have a sustained high number of transactions. -The second setting, `api_request_size`, can help if you experience peaks of transactions -(a large number of transactions in a short period of time). - -Keep in mind that reducing the value of either setting will cause the agent to send more HTTP requests to the APM Server, -potentially causing a higher load. - -[float] -[[tuning-max-spans]] -=== Spans per transaction - -The average amount of spans per transaction can influence how much time the agent spends in each transaction collecting contextual data for each span, -and the storage space needed in Elasticsearch. -In our experience, most _usual_ transactions should have well below 100 spans. -In some cases, however, the number of spans can explode: - - * long-running transactions - * unoptimized code, e.g. doing hundreds of SQL queries in a loop - -To avoid these edge cases overloading both the agent and the APM Server, -the agent stops recording spans when a specified limit is reached. -You can configure this limit by changing the <> setting. - -[float] -[[tuning-span-stack-trace-collection]] -=== Span Stack Trace Collection - -Collecting stack traces for spans can be fairly costly from a performance standpoint. -Stack traces are very useful for pinpointing which part of your code is generating a span; -however, these stack traces are less useful for very short spans (as problematic spans tend to be longer). - -You can define a minimal threshold for span duration -using the <> setting. -If a span's duration is less than this config value, no stack frames will be collected for this span. - -[float] -[[tuning-frame-context]] -=== Collecting Frame Context - -When a stack trace is captured, the agent will also capture several lines of source code around each frame location in the stack trace. This allows the APM app to give greater insight into where exactly the error or span happens. - -There are four settings you can modify to control this behavior: - -* <> -* <> -* <> -* <> - -As you can see, these settings are divided between app frames, which represent your application code, and library frames, which represent the code of your dependencies. Each of these categories are also split into separate error and span settings. - -Reading source files inside a running application can cause a lot of disk I/O, and sending up source lines for each frame will have a network and storage cost that is quite high. Turning down these limits will help prevent excessive memory usage. - -[float] -[[tuning-body-headers]] -=== Collecting headers and request body - -You can configure the Elastic APM agent to capture headers of both requests and responses (<>), -as well as request bodies (<>). -By default, capturing request bodies is disabled. -Enabling it for transactions may introduce noticeable overhead, as well as increased storage use, depending on the nature of your POST requests. -In most scenarios, we advise against enabling request body capturing for transactions, and only enable it if necessary for errors. - -Capturing request/response headers has less overhead on the agent, but can have an impact on storage use. -If storage use is a problem for you, it might be worth disabling. diff --git a/docs/upgrading.asciidoc b/docs/upgrading.asciidoc deleted file mode 100644 index 509116a06..000000000 --- a/docs/upgrading.asciidoc +++ /dev/null @@ -1,81 +0,0 @@ -[[upgrading]] -== Upgrading - -Upgrades between minor versions of the agent, like from 3.1 to 3.2 are always backwards compatible. -Upgrades that involve a major version bump often come with some backwards incompatible changes. - -We highly recommend to always pin the version of `elastic-apm` in your `requirements.txt` or `Pipfile`. -This avoids automatic upgrades to potentially incompatible versions. - -[float] -[[end-of-life-dates]] -=== End of life dates - -We love all our products, but sometimes we must say goodbye to a release so that we can continue moving -forward on future development and innovation. -Our https://www.elastic.co/support/eol[End of life policy] defines how long a given release is considered supported, -as well as how long a release is considered still in active development or maintenance. - -[[upgrading-6.x]] -=== Upgrading to version 6 of the agent - -==== Python 2 no longer supported - -Please upgrade to Python 3.6+ to continue to receive regular updates. - -==== `SANITIZE_FIELD_NAMES` changes - -If you are using a non-default `sanitize_field_names` config, please note -that your entries must be surrounded with stars (e.g. `*secret*`) in order to -maintain previous behavior. - -==== Tags removed (in favor of labels) - -Tags were deprecated in the 5.x release (in favor of labels). They have now been -removed. - -[[upgrading-5.x]] -=== Upgrading to version 5 of the agent - -==== APM Server 7.3 required for some features - -APM Server and Kibana 7.3 introduced support for collecting breakdown metrics, and central configuration of APM agents. -To use these features, please update the Python agent to 5.0+ and APM Server / Kibana to 7.3+ - -==== Tags renamed to Labels - -To better align with other parts of the Elastic Stack and the {ecs-ref}/index.html[Elastic Common Schema], -we renamed "tags" to "labels", and introduced limited support for typed labels. -While tag values were only allowed to be strings, label values can be strings, booleans, or numerical. - -To benefit from this change, ensure that you run at least *APM Server 6.7*, and use `elasticapm.label()` instead of `elasticapm.tag()`. -The `tag()` API will continue to work as before, but emit a `DeprecationWarning`. It will be removed in 6.0 of the agent. - -[[upgrading-4.x]] -=== Upgrading to version 4 of the agent - -4.0 of the Elastic APM Python Agent comes with several backwards incompatible changes. - -[[upgrading-4.x-apm-server]] -==== APM Server 6.5 required -This version of the agent is *only compatible with APM Server 6.5+*. -To upgrade, we recommend to first upgrade APM Server, and then the agent. -APM Server 6.5+ is backwards compatible with versions 2.x and 3.x of the agent. - -[[upgrading-4.x-configuration]] -==== Configuration options - -Several configuration options have been removed, or renamed - - * `flush_interval` has been removed - * the `flush_interval` and `max_queue_size` settings have been removed. - * new settings introduced: `api_request_time` and `api_request_size`. - * Some settings now require a unit for duration or size. See <> and <>. - -[[upgrading-4.x-processors]] -==== Processors - -The method to write processors for sanitizing events has been changed. -It will now be called for every type of event (transactions, spans and errors), -unless the event types are limited using a decorator. -See <> for more information. From 9b42cf59b484dce0986aac8d0065ad52ab2c9081 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 21 Mar 2025 09:19:53 +0100 Subject: [PATCH 203/409] tests/kafka: silence errors arounds create and delete topics (#2237) Use a big hammer in the meantime we find a proper solution and catch all exceptions. --- tests/instrumentation/kafka_tests.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/instrumentation/kafka_tests.py b/tests/instrumentation/kafka_tests.py index 0bfc5c496..54be2ee8e 100644 --- a/tests/instrumentation/kafka_tests.py +++ b/tests/instrumentation/kafka_tests.py @@ -54,9 +54,18 @@ def topics(): topics = ["test", "foo", "bar"] admin_client = KafkaAdminClient(bootstrap_servers=[f"{KAFKA_HOST}:9092"]) - admin_client.create_topics([NewTopic(name, num_partitions=1, replication_factor=1) for name in topics]) + # since kafka-python 2.1.0 we started to get failures in create_topics because topics were already there despite + # calls to delete_topics. In the meantime we found a proper fix use a big hammer and catch topics handling failures + # https://github.com/dpkp/kafka-python/issues/2557 + try: + admin_client.create_topics([NewTopic(name, num_partitions=1, replication_factor=1) for name in topics]) + except Exception: + pass yield topics - admin_client.delete_topics(topics) + try: + admin_client.delete_topics(topics) + except Exception: + pass @pytest.fixture() From 54baff8831ee034dc84eeef04764ebb510c4ce55 Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Fri, 21 Mar 2025 03:20:38 -0500 Subject: [PATCH 204/409] Updates navigation titles for release notes (#2239) --- docs/release-notes/breaking-changes.md | 2 +- docs/release-notes/deprecations.md | 2 +- docs/release-notes/known-issues.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/release-notes/breaking-changes.md b/docs/release-notes/breaking-changes.md index 240495942..0b7fa989a 100644 --- a/docs/release-notes/breaking-changes.md +++ b/docs/release-notes/breaking-changes.md @@ -1,5 +1,5 @@ --- -navigation_title: "Elastic APM Python Agent" +navigation_title: "Breaking changes" --- # Elastic APM Python Agent breaking changes [elastic-apm-python-agent-breaking-changes] diff --git a/docs/release-notes/deprecations.md b/docs/release-notes/deprecations.md index d6d248fc0..6efab6863 100644 --- a/docs/release-notes/deprecations.md +++ b/docs/release-notes/deprecations.md @@ -1,5 +1,5 @@ --- -navigation_title: "Elastic APM Python Agent" +navigation_title: "Deprecations" --- # Elastic APM Python Agent deprecations [elastic-apm-python-agent-deprecations] diff --git a/docs/release-notes/known-issues.md b/docs/release-notes/known-issues.md index 1b1d48435..5d9e80884 100644 --- a/docs/release-notes/known-issues.md +++ b/docs/release-notes/known-issues.md @@ -1,5 +1,5 @@ --- -navigation_title: "Elastic APM Python Agent" +navigation_title: "Known issues" --- # Elastic APM Python Agent known issues [elastic-apm-python-agent-known-issues] From 9e86c9df30b1461bc4dca9df600e05faa32d1692 Mon Sep 17 00:00:00 2001 From: kruskall <99559985+kruskall@users.noreply.github.com> Date: Fri, 21 Mar 2025 09:24:04 +0100 Subject: [PATCH 205/409] ci: pin actions to specific commits (#2236) replace mutable tag with commit hash to improve security and reproducibility Co-authored-by: Riccardo Magliocchetti --- .github/actions/build-distribution/action.yml | 4 +-- .github/actions/packages/action.yml | 4 +-- .github/workflows/docs-build.yml | 2 +- .github/workflows/docs-cleanup.yml | 2 +- .github/workflows/labeler.yml | 6 ++-- .github/workflows/matrix-command.yml | 2 +- .github/workflows/microbenchmark.yml | 2 +- .github/workflows/packages.yml | 2 +- .github/workflows/pre-commit.yml | 6 ++-- .github/workflows/release.yml | 28 +++++++++---------- .github/workflows/run-matrix.yml | 6 ++-- .github/workflows/test-docs.yml | 2 +- .github/workflows/test-fips.yml | 10 +++---- .github/workflows/test-reporter.yml | 2 +- .github/workflows/test.yml | 28 +++++++++---------- .github/workflows/updatecli.yml | 8 +++--- 16 files changed, 57 insertions(+), 57 deletions(-) diff --git a/.github/actions/build-distribution/action.yml b/.github/actions/build-distribution/action.yml index bc0d55c29..a44e09762 100644 --- a/.github/actions/build-distribution/action.yml +++ b/.github/actions/build-distribution/action.yml @@ -6,7 +6,7 @@ description: Run the build distribution runs: using: "composite" steps: - - uses: actions/setup-python@v5 + - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5 with: python-version: "3.10" @@ -14,7 +14,7 @@ runs: run: ./dev-utils/make-distribution.sh shell: bash - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 with: name: build-distribution path: ./build/ diff --git a/.github/actions/packages/action.yml b/.github/actions/packages/action.yml index 871f49c32..d46faa9ac 100644 --- a/.github/actions/packages/action.yml +++ b/.github/actions/packages/action.yml @@ -6,7 +6,7 @@ description: Run the packages runs: using: "composite" steps: - - uses: actions/setup-python@v5 + - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5 with: python-version: "3.10" - name: Override the version if there is no tag release. @@ -19,7 +19,7 @@ runs: run: ./dev-utils/make-packages.sh shell: bash - name: Upload Packages - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 with: name: packages path: | diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml index bb466166d..24fa38f94 100644 --- a/.github/workflows/docs-build.yml +++ b/.github/workflows/docs-build.yml @@ -9,7 +9,7 @@ on: jobs: docs-preview: - uses: elastic/docs-builder/.github/workflows/preview-build.yml@main + uses: elastic/docs-builder/.github/workflows/preview-build.yml@99b12f8bf7a82107ffcf59dacd199d00a965e9db # main with: path-pattern: docs/** permissions: diff --git a/.github/workflows/docs-cleanup.yml b/.github/workflows/docs-cleanup.yml index f83e017b5..c66c94994 100644 --- a/.github/workflows/docs-cleanup.yml +++ b/.github/workflows/docs-cleanup.yml @@ -7,7 +7,7 @@ on: jobs: docs-preview: - uses: elastic/docs-builder/.github/workflows/preview-cleanup.yml@main + uses: elastic/docs-builder/.github/workflows/preview-cleanup.yml@99b12f8bf7a82107ffcf59dacd199d00a965e9db # main permissions: contents: none id-token: write diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index fcab871c7..e1a3e1c95 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -26,18 +26,18 @@ jobs: "members": "read" } - name: Add agent-python label - uses: actions-ecosystem/action-add-labels@v1 + uses: actions-ecosystem/action-add-labels@18f1af5e3544586314bbe15c0273249c770b2daf # v1 with: labels: agent-python - id: is_elastic_member - uses: elastic/oblt-actions/github/is-member-of@v1 + uses: elastic/oblt-actions/github/is-member-of@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 with: github-org: "elastic" github-user: ${{ github.actor }} github-token: ${{ steps.get_token.outputs.token }} - name: Add community and triage labels if: contains(steps.is_elastic_member.outputs.result, 'false') && github.actor != 'dependabot[bot]' && github.actor != 'elastic-observability-automation[bot]' - uses: actions-ecosystem/action-add-labels@v1 + uses: actions-ecosystem/action-add-labels@18f1af5e3544586314bbe15c0273249c770b2daf # v1 with: labels: | community diff --git a/.github/workflows/matrix-command.yml b/.github/workflows/matrix-command.yml index f2c32658f..1a8e9849a 100644 --- a/.github/workflows/matrix-command.yml +++ b/.github/workflows/matrix-command.yml @@ -21,7 +21,7 @@ jobs: pull-requests: write steps: - name: Is comment allowed? - uses: actions/github-script@v7 + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 with: script: | const actorPermission = (await github.rest.repos.getCollaboratorPermissionLevel({ diff --git a/.github/workflows/microbenchmark.yml b/.github/workflows/microbenchmark.yml index e3f0a41d6..13daf5cb1 100644 --- a/.github/workflows/microbenchmark.yml +++ b/.github/workflows/microbenchmark.yml @@ -19,7 +19,7 @@ jobs: timeout-minutes: 5 steps: - name: Run microbenchmark - uses: elastic/oblt-actions/buildkite/run@v1 + uses: elastic/oblt-actions/buildkite/run@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 with: pipeline: "apm-agent-microbenchmark" token: ${{ secrets.BUILDKITE_TOKEN }} diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 496107508..637cb6a54 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -20,5 +20,5 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - uses: ./.github/actions/packages diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 926c21be6..5e389dba1 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -12,6 +12,6 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - - uses: pre-commit/action@v3.0.1 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5 + - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b4b105cc1..c1cf01f16 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: contents: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - uses: ./.github/actions/packages - name: generate build provenance uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3 @@ -40,8 +40,8 @@ jobs: permissions: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4 with: name: packages path: dist @@ -63,7 +63,7 @@ jobs: contents: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - uses: ./.github/actions/build-distribution - name: generate build provenance uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3 @@ -78,12 +78,12 @@ jobs: - build-distribution runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4 with: name: build-distribution path: ./build - - uses: elastic/oblt-actions/aws/auth@v1 + - uses: elastic/oblt-actions/aws/auth@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 with: aws-account-id: "267093732750" - name: Publish lambda layers to AWS @@ -94,7 +94,7 @@ jobs: VERSION=${VERSION//./-} ELASTIC_LAYER_NAME="elastic-apm-python-${VERSION}" .ci/publish-aws.sh - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 if: startsWith(github.ref, 'refs/tags') with: name: arn-file @@ -116,7 +116,7 @@ jobs: env: DOCKER_IMAGE_NAME: docker.elastic.co/observability/apm-agent-python steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 @@ -128,7 +128,7 @@ jobs: username: ${{ secrets.ELASTIC_DOCKER_USERNAME }} password: ${{ secrets.ELASTIC_DOCKER_PASSWORD }} - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4 with: name: build-distribution path: ./build @@ -172,8 +172,8 @@ jobs: if: startsWith(github.ref, 'refs/tags') runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4 with: name: arn-file - name: Create GitHub Draft Release @@ -196,11 +196,11 @@ jobs: - github-draft steps: - id: check - uses: elastic/oblt-actions/check-dependent-jobs@v1 + uses: elastic/oblt-actions/check-dependent-jobs@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 with: jobs: ${{ toJSON(needs) }} - if: startsWith(github.ref, 'refs/tags') - uses: elastic/oblt-actions/slack/notify-result@v1 + uses: elastic/oblt-actions/slack/notify-result@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 with: bot-token: ${{ secrets.SLACK_BOT_TOKEN }} channel-id: "#apm-agent-python" diff --git a/.github/workflows/run-matrix.yml b/.github/workflows/run-matrix.yml index 0b31f4318..014cf42c3 100644 --- a/.github/workflows/run-matrix.yml +++ b/.github/workflows/run-matrix.yml @@ -21,20 +21,20 @@ jobs: matrix: include: ${{ fromJSON(inputs.include) }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Run tests run: ./tests/scripts/docker/run_tests.sh ${{ matrix.version }} ${{ matrix.framework }} env: LOCALSTACK_VOLUME_DIR: localstack_data - if: success() || failure() name: Upload JUnit Test Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 with: name: test-results-${{ matrix.framework }}-${{ matrix.version }} path: "**/*-python-agent-junit.xml" - if: success() || failure() name: Upload Coverage Reports - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 with: name: coverage-reports-${{ matrix.framework }}-${{ matrix.version }} path: "**/.coverage*" diff --git a/.github/workflows/test-docs.yml b/.github/workflows/test-docs.yml index e1c4c4ae4..bb7a2fff8 100644 --- a/.github/workflows/test-docs.yml +++ b/.github/workflows/test-docs.yml @@ -36,7 +36,7 @@ jobs: ENDOFFILE - if: success() || failure() name: Upload JUnit Test Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 with: name: test-results-docs path: "docs-python-agent-junit.xml" diff --git a/.github/workflows/test-fips.yml b/.github/workflows/test-fips.yml index 3712f00d0..45ed3ad4d 100644 --- a/.github/workflows/test-fips.yml +++ b/.github/workflows/test-fips.yml @@ -16,9 +16,9 @@ jobs: outputs: matrix: ${{ steps.generate.outputs.matrix }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - id: generate - uses: elastic/oblt-actions/version-framework@v1 + uses: elastic/oblt-actions/version-framework@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 with: versions-file: .ci/.matrix_python_fips.yml frameworks-file: .ci/.matrix_framework_fips.yml @@ -40,7 +40,7 @@ jobs: max-parallel: 10 matrix: ${{ fromJSON(needs.create-matrix.outputs.matrix) }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: check that python has fips mode enabled run: | python3 -c 'import _hashlib; assert _hashlib.get_fips_mode() == 1' @@ -57,12 +57,12 @@ jobs: needs: test-fips steps: - id: check - uses: elastic/oblt-actions/check-dependent-jobs@v1 + uses: elastic/oblt-actions/check-dependent-jobs@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 with: jobs: ${{ toJSON(needs) }} - name: Notify in Slack if: steps.check.outputs.status == 'failure' - uses: elastic/oblt-actions/slack/notify-result@v1 + uses: elastic/oblt-actions/slack/notify-result@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 with: bot-token: ${{ secrets.SLACK_BOT_TOKEN }} status: ${{ steps.check.outputs.status }} diff --git a/.github/workflows/test-reporter.yml b/.github/workflows/test-reporter.yml index ffb1206a6..cd13279bf 100644 --- a/.github/workflows/test-reporter.yml +++ b/.github/workflows/test-reporter.yml @@ -17,7 +17,7 @@ jobs: report: runs-on: ubuntu-latest steps: - - uses: elastic/oblt-actions/test-report@v1 + - uses: elastic/oblt-actions/test-report@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 with: artifact: /test-results(.*)/ name: 'Test Report $1' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 36294b1f4..63db7600b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,7 +37,7 @@ jobs: build-distribution: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - uses: ./.github/actions/build-distribution @@ -48,11 +48,11 @@ jobs: data: ${{ steps.split.outputs.data }} chunks: ${{ steps.split.outputs.chunks }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: ref: ${{ inputs.ref || github.ref }} - id: generate - uses: elastic/oblt-actions/version-framework@v1 + uses: elastic/oblt-actions/version-framework@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 with: # Use .ci/.matrix_python_full.yml if it's a scheduled workflow, otherwise use .ci/.matrix_python.yml versions-file: .ci/.matrix_python${{ (github.event_name == 'schedule' || github.event_name == 'push' || inputs.full-matrix) && '_full' || '' }}.yml @@ -131,10 +131,10 @@ jobs: FRAMEWORK: ${{ matrix.framework }} ASYNCIO: ${{ matrix.asyncio }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: ref: ${{ inputs.ref || github.ref }} - - uses: actions/setup-python@v5 + - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5 with: python-version: ${{ matrix.version }} cache: pip @@ -145,14 +145,14 @@ jobs: run: .\scripts\run-tests.bat - if: success() || failure() name: Upload JUnit Test Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 with: name: test-results-${{ matrix.framework }}-${{ matrix.version }}-asyncio-${{ matrix.asyncio }} path: "**/*-python-agent-junit.xml" retention-days: 1 - if: success() || failure() name: Upload Coverage Reports - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 with: name: coverage-reports-${{ matrix.framework }}-${{ matrix.version }}-asyncio-${{ matrix.asyncio }} path: "**/.coverage*" @@ -171,12 +171,12 @@ jobs: - windows steps: - id: check - uses: elastic/oblt-actions/check-dependent-jobs@v1 + uses: elastic/oblt-actions/check-dependent-jobs@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 with: jobs: ${{ toJSON(needs) }} - run: ${{ steps.check.outputs.is-success }} - if: failure() && (github.event_name == 'schedule' || github.event_name == 'push') - uses: elastic/oblt-actions/slack/notify-result@v1 + uses: elastic/oblt-actions/slack/notify-result@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 with: bot-token: ${{ secrets.SLACK_BOT_TOKEN }} status: ${{ steps.check.outputs.status }} @@ -188,18 +188,18 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 with: ref: ${{ inputs.ref || github.ref }} - - uses: actions/setup-python@v5 + - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5 with: # Use latest Python, so it understands all syntax. python-version: 3.11 - run: python -Im pip install --upgrade coverage[toml] - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4 with: pattern: coverage-reports-* merge-multiple: true @@ -216,10 +216,10 @@ jobs: python -Im coverage report --fail-under=84 - name: Upload HTML report - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 with: name: html-coverage-report path: htmlcov - - uses: geekyeggo/delete-artifact@f275313e70c08f6120db482d7a6b98377786765b # 5.1.0 + - uses: geekyeggo/delete-artifact@f275313e70c08f6120db482d7a6b98377786765b # v5.1.0 with: name: coverage-reports-* diff --git a/.github/workflows/updatecli.yml b/.github/workflows/updatecli.yml index d6d1ed4c6..278961061 100644 --- a/.github/workflows/updatecli.yml +++ b/.github/workflows/updatecli.yml @@ -15,7 +15,7 @@ jobs: contents: read packages: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Get token id: get_token @@ -35,14 +35,14 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - uses: elastic/oblt-actions/updatecli/run@v1 + - uses: elastic/oblt-actions/updatecli/run@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 with: command: --experimental compose diff version-file: .tool-versions env: GITHUB_TOKEN: ${{ steps.get_token.outputs.token }} - - uses: elastic/oblt-actions/updatecli/run@v1 + - uses: elastic/oblt-actions/updatecli/run@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 with: command: --experimental compose apply version-file: .tool-versions @@ -50,7 +50,7 @@ jobs: GITHUB_TOKEN: ${{ steps.get_token.outputs.token }} - if: failure() - uses: elastic/oblt-actions/slack/send@v1 + uses: elastic/oblt-actions/slack/send@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 with: bot-token: ${{ secrets.SLACK_BOT_TOKEN }} channel-id: "#apm-agent-python" From 22372b7aa49e96eeae9bc79ff5479dd9e2a37e95 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 08:24:59 +0000 Subject: [PATCH 206/409] build(deps): bump docker/login-action in the github-actions group (#2234) Bumps the github-actions group with 1 update: [docker/login-action](https://github.com/docker/login-action). Updates `docker/login-action` from 3.3.0 to 3.4.0 - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/9780b0c442fbb1117ed29e0efdff1e18412f7567...74a5d142397b4f367a81961eba4e8cd7edddf772) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- .github/workflows/updatecli.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c1cf01f16..a004f812b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -122,7 +122,7 @@ jobs: uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 - name: Log in to the Elastic Container registry - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: registry: ${{ secrets.ELASTIC_DOCKER_REGISTRY }} username: ${{ secrets.ELASTIC_DOCKER_USERNAME }} diff --git a/.github/workflows/updatecli.yml b/.github/workflows/updatecli.yml index 278961061..dc62ed85d 100644 --- a/.github/workflows/updatecli.yml +++ b/.github/workflows/updatecli.yml @@ -29,7 +29,7 @@ jobs: "pull_requests": "write" } - - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: registry: ghcr.io username: ${{ github.actor }} From 01dcc66058dd77fc255273a370dc2afaac07dddd Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Tue, 25 Mar 2025 11:15:54 +0100 Subject: [PATCH 207/409] chore: deps(updatecli): Bump updatecli version to v0.96.0 (#2244) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 7133ace51..a744ca662 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.95.1 \ No newline at end of file +updatecli v0.96.0 \ No newline at end of file From 686d59c5692a0170444843042ac249fb4b3f09ae Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Tue, 25 Mar 2025 08:39:27 -0500 Subject: [PATCH 208/409] [docs] Miscellaneous docs clean up (#2240) * remove unused substitutions * move images * update redirected links * fix image paths --- docs/docset.yml | 477 ------------------ docs/reference/configuration.md | 40 +- docs/reference/django-support.md | 2 +- .../{ => reference}/images/choose-a-layer.png | Bin docs/{ => reference}/images/config-layer.png | Bin .../{ => reference}/images/dynamic-config.svg | 0 .../images/python-lambda-env-vars.png | Bin docs/reference/lambda-support.md | 6 +- 8 files changed, 24 insertions(+), 501 deletions(-) rename docs/{ => reference}/images/choose-a-layer.png (100%) rename docs/{ => reference}/images/config-layer.png (100%) rename docs/{ => reference}/images/dynamic-config.svg (100%) rename docs/{ => reference}/images/python-lambda-env-vars.png (100%) diff --git a/docs/docset.yml b/docs/docset.yml index 2c8aafee1..14f47bbb9 100644 --- a/docs/docset.yml +++ b/docs/docset.yml @@ -13,482 +13,5 @@ toc: - toc: reference - toc: release-notes subs: - ref: "https://www.elastic.co/guide/en/elasticsearch/reference/current" - ref-bare: "https://www.elastic.co/guide/en/elasticsearch/reference" - ref-8x: "https://www.elastic.co/guide/en/elasticsearch/reference/8.1" - ref-80: "https://www.elastic.co/guide/en/elasticsearch/reference/8.0" - ref-7x: "https://www.elastic.co/guide/en/elasticsearch/reference/7.17" - ref-70: "https://www.elastic.co/guide/en/elasticsearch/reference/7.0" - ref-60: "https://www.elastic.co/guide/en/elasticsearch/reference/6.0" - ref-64: "https://www.elastic.co/guide/en/elasticsearch/reference/6.4" - xpack-ref: "https://www.elastic.co/guide/en/x-pack/6.2" - logstash-ref: "https://www.elastic.co/guide/en/logstash/current" - kibana-ref: "https://www.elastic.co/guide/en/kibana/current" - kibana-ref-all: "https://www.elastic.co/guide/en/kibana" - beats-ref-root: "https://www.elastic.co/guide/en/beats" - beats-ref: "https://www.elastic.co/guide/en/beats/libbeat/current" - beats-ref-60: "https://www.elastic.co/guide/en/beats/libbeat/6.0" - beats-ref-63: "https://www.elastic.co/guide/en/beats/libbeat/6.3" - beats-devguide: "https://www.elastic.co/guide/en/beats/devguide/current" - auditbeat-ref: "https://www.elastic.co/guide/en/beats/auditbeat/current" - packetbeat-ref: "https://www.elastic.co/guide/en/beats/packetbeat/current" - metricbeat-ref: "https://www.elastic.co/guide/en/beats/metricbeat/current" - filebeat-ref: "https://www.elastic.co/guide/en/beats/filebeat/current" - functionbeat-ref: "https://www.elastic.co/guide/en/beats/functionbeat/current" - winlogbeat-ref: "https://www.elastic.co/guide/en/beats/winlogbeat/current" - heartbeat-ref: "https://www.elastic.co/guide/en/beats/heartbeat/current" - journalbeat-ref: "https://www.elastic.co/guide/en/beats/journalbeat/current" - ingest-guide: "https://www.elastic.co/guide/en/ingest/current" - fleet-guide: "https://www.elastic.co/guide/en/fleet/current" - apm-guide-ref: "https://www.elastic.co/guide/en/apm/guide/current" - apm-guide-7x: "https://www.elastic.co/guide/en/apm/guide/7.17" - apm-app-ref: "https://www.elastic.co/guide/en/kibana/current" - apm-agents-ref: "https://www.elastic.co/guide/en/apm/agent" - apm-android-ref: "https://www.elastic.co/guide/en/apm/agent/android/current" - apm-py-ref: "https://www.elastic.co/guide/en/apm/agent/python/current" - apm-py-ref-3x: "https://www.elastic.co/guide/en/apm/agent/python/3.x" - apm-node-ref-index: "https://www.elastic.co/guide/en/apm/agent/nodejs" - apm-node-ref: "https://www.elastic.co/guide/en/apm/agent/nodejs/current" - apm-node-ref-1x: "https://www.elastic.co/guide/en/apm/agent/nodejs/1.x" - apm-rum-ref: "https://www.elastic.co/guide/en/apm/agent/rum-js/current" - apm-ruby-ref: "https://www.elastic.co/guide/en/apm/agent/ruby/current" - apm-java-ref: "https://www.elastic.co/guide/en/apm/agent/java/current" - apm-go-ref: "https://www.elastic.co/guide/en/apm/agent/go/current" - apm-dotnet-ref: "https://www.elastic.co/guide/en/apm/agent/dotnet/current" - apm-php-ref: "https://www.elastic.co/guide/en/apm/agent/php/current" - apm-ios-ref: "https://www.elastic.co/guide/en/apm/agent/swift/current" - apm-lambda-ref: "https://www.elastic.co/guide/en/apm/lambda/current" - apm-attacher-ref: "https://www.elastic.co/guide/en/apm/attacher/current" - docker-logging-ref: "https://www.elastic.co/guide/en/beats/loggingplugin/current" - esf-ref: "https://www.elastic.co/guide/en/esf/current" - kinesis-firehose-ref: "https://www.elastic.co/guide/en/kinesis/{{kinesis_version}}" - estc-welcome-current: "https://www.elastic.co/guide/en/starting-with-the-elasticsearch-platform-and-its-solutions/current" - estc-welcome: "https://www.elastic.co/guide/en/starting-with-the-elasticsearch-platform-and-its-solutions/current" - estc-welcome-all: "https://www.elastic.co/guide/en/starting-with-the-elasticsearch-platform-and-its-solutions" - hadoop-ref: "https://www.elastic.co/guide/en/elasticsearch/hadoop/current" - stack-ref: "https://www.elastic.co/guide/en/elastic-stack/current" - stack-ref-67: "https://www.elastic.co/guide/en/elastic-stack/6.7" - stack-ref-68: "https://www.elastic.co/guide/en/elastic-stack/6.8" - stack-ref-70: "https://www.elastic.co/guide/en/elastic-stack/7.0" - stack-ref-80: "https://www.elastic.co/guide/en/elastic-stack/8.0" - stack-ov: "https://www.elastic.co/guide/en/elastic-stack-overview/current" - stack-gs: "https://www.elastic.co/guide/en/elastic-stack-get-started/current" - stack-gs-current: "https://www.elastic.co/guide/en/elastic-stack-get-started/current" - javaclient: "https://www.elastic.co/guide/en/elasticsearch/client/java-api/current" - java-api-client: "https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current" - java-rest: "https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current" - jsclient: "https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current" - jsclient-current: "https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current" - es-ruby-client: "https://www.elastic.co/guide/en/elasticsearch/client/ruby-api/current" - es-dotnet-client: "https://www.elastic.co/guide/en/elasticsearch/client/net-api/current" - es-php-client: "https://www.elastic.co/guide/en/elasticsearch/client/php-api/current" - es-python-client: "https://www.elastic.co/guide/en/elasticsearch/client/python-api/current" - defguide: "https://www.elastic.co/guide/en/elasticsearch/guide/2.x" - painless: "https://www.elastic.co/guide/en/elasticsearch/painless/current" - plugins: "https://www.elastic.co/guide/en/elasticsearch/plugins/current" - plugins-8x: "https://www.elastic.co/guide/en/elasticsearch/plugins/8.1" - plugins-7x: "https://www.elastic.co/guide/en/elasticsearch/plugins/7.17" - plugins-6x: "https://www.elastic.co/guide/en/elasticsearch/plugins/6.8" - glossary: "https://www.elastic.co/guide/en/elastic-stack-glossary/current" - upgrade_guide: "https://www.elastic.co/products/upgrade_guide" - blog-ref: "https://www.elastic.co/blog/" - curator-ref: "https://www.elastic.co/guide/en/elasticsearch/client/curator/current" - curator-ref-current: "https://www.elastic.co/guide/en/elasticsearch/client/curator/current" - metrics-ref: "https://www.elastic.co/guide/en/metrics/current" - metrics-guide: "https://www.elastic.co/guide/en/metrics/guide/current" - logs-ref: "https://www.elastic.co/guide/en/logs/current" - logs-guide: "https://www.elastic.co/guide/en/logs/guide/current" - uptime-guide: "https://www.elastic.co/guide/en/uptime/current" - observability-guide: "https://www.elastic.co/guide/en/observability/current" - observability-guide-all: "https://www.elastic.co/guide/en/observability" - siem-guide: "https://www.elastic.co/guide/en/siem/guide/current" - security-guide: "https://www.elastic.co/guide/en/security/current" - security-guide-all: "https://www.elastic.co/guide/en/security" - endpoint-guide: "https://www.elastic.co/guide/en/endpoint/current" - sql-odbc: "https://www.elastic.co/guide/en/elasticsearch/sql-odbc/current" - ecs-ref: "https://www.elastic.co/guide/en/ecs/current" - ecs-logging-ref: "https://www.elastic.co/guide/en/ecs-logging/overview/current" - ecs-logging-go-logrus-ref: "https://www.elastic.co/guide/en/ecs-logging/go-logrus/current" - ecs-logging-go-zap-ref: "https://www.elastic.co/guide/en/ecs-logging/go-zap/current" - ecs-logging-go-zerolog-ref: "https://www.elastic.co/guide/en/ecs-logging/go-zap/current" - ecs-logging-java-ref: "https://www.elastic.co/guide/en/ecs-logging/java/current" - ecs-logging-dotnet-ref: "https://www.elastic.co/guide/en/ecs-logging/dotnet/current" - ecs-logging-nodejs-ref: "https://www.elastic.co/guide/en/ecs-logging/nodejs/current" - ecs-logging-php-ref: "https://www.elastic.co/guide/en/ecs-logging/php/current" - ecs-logging-python-ref: "https://www.elastic.co/guide/en/ecs-logging/python/current" - ecs-logging-ruby-ref: "https://www.elastic.co/guide/en/ecs-logging/ruby/current" - ml-docs: "https://www.elastic.co/guide/en/machine-learning/current" - eland-docs: "https://www.elastic.co/guide/en/elasticsearch/client/eland/current" - eql-ref: "https://eql.readthedocs.io/en/latest/query-guide" - extendtrial: "https://www.elastic.co/trialextension" - wikipedia: "https://en.wikipedia.org/wiki" - forum: "https://discuss.elastic.co/" - xpack-forum: "https://discuss.elastic.co/c/50-x-pack" - security-forum: "https://discuss.elastic.co/c/x-pack/shield" - watcher-forum: "https://discuss.elastic.co/c/x-pack/watcher" - monitoring-forum: "https://discuss.elastic.co/c/x-pack/marvel" - graph-forum: "https://discuss.elastic.co/c/x-pack/graph" - apm-forum: "https://discuss.elastic.co/c/apm" - enterprise-search-ref: "https://www.elastic.co/guide/en/enterprise-search/current" - app-search-ref: "https://www.elastic.co/guide/en/app-search/current" - workplace-search-ref: "https://www.elastic.co/guide/en/workplace-search/current" - enterprise-search-node-ref: "https://www.elastic.co/guide/en/enterprise-search-clients/enterprise-search-node/current" - enterprise-search-php-ref: "https://www.elastic.co/guide/en/enterprise-search-clients/php/current" - enterprise-search-python-ref: "https://www.elastic.co/guide/en/enterprise-search-clients/python/current" - enterprise-search-ruby-ref: "https://www.elastic.co/guide/en/enterprise-search-clients/ruby/current" - elastic-maps-service: "https://maps.elastic.co" - integrations-docs: "https://docs.elastic.co/en/integrations" - integrations-devguide: "https://www.elastic.co/guide/en/integrations-developer/current" - time-units: "https://www.elastic.co/guide/en/elasticsearch/reference/current/api-conventions.html#time-units" - byte-units: "https://www.elastic.co/guide/en/elasticsearch/reference/current/api-conventions.html#byte-units" - apm-py-ref-v: "https://www.elastic.co/guide/en/apm/agent/python/current" - apm-node-ref-v: "https://www.elastic.co/guide/en/apm/agent/nodejs/current" - apm-rum-ref-v: "https://www.elastic.co/guide/en/apm/agent/rum-js/current" - apm-ruby-ref-v: "https://www.elastic.co/guide/en/apm/agent/ruby/current" - apm-java-ref-v: "https://www.elastic.co/guide/en/apm/agent/java/current" - apm-go-ref-v: "https://www.elastic.co/guide/en/apm/agent/go/current" - apm-ios-ref-v: "https://www.elastic.co/guide/en/apm/agent/swift/current" - apm-dotnet-ref-v: "https://www.elastic.co/guide/en/apm/agent/dotnet/current" - apm-php-ref-v: "https://www.elastic.co/guide/en/apm/agent/php/current" ecloud: "Elastic Cloud" - esf: "Elastic Serverless Forwarder" - ess: "Elasticsearch Service" - ece: "Elastic Cloud Enterprise" - eck: "Elastic Cloud on Kubernetes" - serverless-full: "Elastic Cloud Serverless" - serverless-short: "Serverless" - es-serverless: "Elasticsearch Serverless" - es3: "Elasticsearch Serverless" - obs-serverless: "Elastic Observability Serverless" - sec-serverless: "Elastic Security Serverless" - serverless-docs: "https://docs.elastic.co/serverless" - cloud: "https://www.elastic.co/guide/en/cloud/current" - ess-utm-params: "?page=docs&placement=docs-body" - ess-baymax: "?page=docs&placement=docs-body" - ess-trial: "https://cloud.elastic.co/registration?page=docs&placement=docs-body" - ess-product: "https://www.elastic.co/cloud/elasticsearch-service?page=docs&placement=docs-body" - ess-console: "https://cloud.elastic.co?page=docs&placement=docs-body" - ess-console-name: "Elasticsearch Service Console" - ess-deployments: "https://cloud.elastic.co/deployments?page=docs&placement=docs-body" - ece-ref: "https://www.elastic.co/guide/en/cloud-enterprise/current" - eck-ref: "https://www.elastic.co/guide/en/cloud-on-k8s/current" - ess-leadin: "You can run Elasticsearch on your own hardware or use our hosted Elasticsearch Service that is available on AWS, GCP, and Azure. https://cloud.elastic.co/registration{ess-utm-params}[Try the Elasticsearch Service for free]." - ess-leadin-short: "Our hosted Elasticsearch Service is available on AWS, GCP, and Azure, and you can https://cloud.elastic.co/registration{ess-utm-params}[try it for free]." - ess-icon: "image:https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg[link=\"https://cloud.elastic.co/registration{ess-utm-params}\", title=\"Supported on Elasticsearch Service\"]" - ece-icon: "image:https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud_ece.svg[link=\"https://cloud.elastic.co/registration{ess-utm-params}\", title=\"Supported on Elastic Cloud Enterprise\"]" - cloud-only: "This feature is designed for indirect use by https://cloud.elastic.co/registration{ess-utm-params}[Elasticsearch Service], https://www.elastic.co/guide/en/cloud-enterprise/{ece-version-link}[Elastic Cloud Enterprise], and https://www.elastic.co/guide/en/cloud-on-k8s/current[Elastic Cloud on Kubernetes]. Direct use is not supported." - ess-setting-change: "image:https://doc-icons.s3.us-east-2.amazonaws.com/logo_cloud.svg[link=\"{ess-trial}\", title=\"Supported on {ess}\"] indicates a change to a supported https://www.elastic.co/guide/en/cloud/current/ec-add-user-settings.html[user setting] for Elasticsearch Service." - ess-skip-section: "If you use Elasticsearch Service, skip this section. Elasticsearch Service handles these changes for you." - api-cloud: "https://www.elastic.co/docs/api/doc/cloud" - api-ece: "https://www.elastic.co/docs/api/doc/cloud-enterprise" - api-kibana-serverless: "https://www.elastic.co/docs/api/doc/serverless" - es-feature-flag: "This feature is in development and not yet available for use. This documentation is provided for informational purposes only." - es-ref-dir: "'{{elasticsearch-root}}/docs/reference'" - apm-app: "APM app" - uptime-app: "Uptime app" - synthetics-app: "Synthetics app" - logs-app: "Logs app" - metrics-app: "Metrics app" - infrastructure-app: "Infrastructure app" - siem-app: "SIEM app" - security-app: "Elastic Security app" - ml-app: "Machine Learning" - dev-tools-app: "Dev Tools" - ingest-manager-app: "Ingest Manager" - stack-manage-app: "Stack Management" - stack-monitor-app: "Stack Monitoring" - alerts-ui: "Alerts and Actions" - rules-ui: "Rules" - rac-ui: "Rules and Connectors" - connectors-ui: "Connectors" - connectors-feature: "Actions and Connectors" - stack-rules-feature: "Stack Rules" - user-experience: "User Experience" - ems: "Elastic Maps Service" - ems-init: "EMS" - hosted-ems: "Elastic Maps Server" - ipm-app: "Index Pattern Management" - ingest-pipelines: "ingest pipelines" - ingest-pipelines-app: "Ingest Pipelines" - ingest-pipelines-cap: "Ingest pipelines" - ls-pipelines: "Logstash pipelines" - ls-pipelines-app: "Logstash Pipelines" - maint-windows: "maintenance windows" - maint-windows-app: "Maintenance Windows" - maint-windows-cap: "Maintenance windows" - custom-roles-app: "Custom Roles" - data-source: "data view" - data-sources: "data views" - data-source-caps: "Data View" - data-sources-caps: "Data Views" - data-source-cap: "Data view" - data-sources-cap: "Data views" - project-settings: "Project settings" - manage-app: "Management" - index-manage-app: "Index Management" - data-views-app: "Data Views" - rules-app: "Rules" - saved-objects-app: "Saved Objects" - tags-app: "Tags" - api-keys-app: "API keys" - transforms-app: "Transforms" - connectors-app: "Connectors" - files-app: "Files" - reports-app: "Reports" - maps-app: "Maps" - alerts-app: "Alerts" - crawler: "Enterprise Search web crawler" - ents: "Enterprise Search" - app-search-crawler: "App Search web crawler" - agent: "Elastic Agent" - agents: "Elastic Agents" - fleet: "Fleet" - fleet-server: "Fleet Server" - integrations-server: "Integrations Server" - ingest-manager: "Ingest Manager" - ingest-management: "ingest management" - package-manager: "Elastic Package Manager" - integrations: "Integrations" - package-registry: "Elastic Package Registry" - artifact-registry: "Elastic Artifact Registry" - aws: "AWS" - stack: "Elastic Stack" - xpack: "X-Pack" - es: "Elasticsearch" - kib: "Kibana" - esms: "Elastic Stack Monitoring Service" - esms-init: "ESMS" - ls: "Logstash" - beats: "Beats" - auditbeat: "Auditbeat" - filebeat: "Filebeat" - heartbeat: "Heartbeat" - metricbeat: "Metricbeat" - packetbeat: "Packetbeat" - winlogbeat: "Winlogbeat" - functionbeat: "Functionbeat" - journalbeat: "Journalbeat" - es-sql: "Elasticsearch SQL" - esql: "ES|QL" - elastic-agent: "Elastic Agent" - k8s: "Kubernetes" - log-driver-long: "Elastic Logging Plugin for Docker" - security: "X-Pack security" - security-features: "security features" - operator-feature: "operator privileges feature" - es-security-features: "Elasticsearch security features" - stack-security-features: "Elastic Stack security features" - endpoint-sec: "Endpoint Security" - endpoint-cloud-sec: "Endpoint and Cloud Security" - elastic-defend: "Elastic Defend" - elastic-sec: "Elastic Security" - elastic-endpoint: "Elastic Endpoint" - swimlane: "Swimlane" - sn: "ServiceNow" - sn-itsm: "ServiceNow ITSM" - sn-itom: "ServiceNow ITOM" - sn-sir: "ServiceNow SecOps" - jira: "Jira" - ibm-r: "IBM Resilient" - webhook: "Webhook" - webhook-cm: "Webhook - Case Management" - opsgenie: "Opsgenie" - bedrock: "Amazon Bedrock" - gemini: "Google Gemini" - hive: "TheHive" - monitoring: "X-Pack monitoring" - monitor-features: "monitoring features" - stack-monitor-features: "Elastic Stack monitoring features" - watcher: "Watcher" - alert-features: "alerting features" - reporting: "X-Pack reporting" - report-features: "reporting features" - graph: "X-Pack graph" - graph-features: "graph analytics features" - searchprofiler: "Search Profiler" - xpackml: "X-Pack machine learning" - ml: "machine learning" - ml-cap: "Machine learning" - ml-init: "ML" - ml-features: "machine learning features" - stack-ml-features: "Elastic Stack machine learning features" - ccr: "cross-cluster replication" - ccr-cap: "Cross-cluster replication" - ccr-init: "CCR" - ccs: "cross-cluster search" - ccs-cap: "Cross-cluster search" - ccs-init: "CCS" - ilm: "index lifecycle management" - ilm-cap: "Index lifecycle management" - ilm-init: "ILM" - dlm: "data lifecycle management" - dlm-cap: "Data lifecycle management" - dlm-init: "DLM" - search-snap: "searchable snapshot" - search-snaps: "searchable snapshots" - search-snaps-cap: "Searchable snapshots" - slm: "snapshot lifecycle management" - slm-cap: "Snapshot lifecycle management" - slm-init: "SLM" - rollup-features: "data rollup features" - ipm: "index pattern management" - ipm-cap: "Index pattern" - rollup: "rollup" - rollup-cap: "Rollup" - rollups: "rollups" - rollups-cap: "Rollups" - rollup-job: "rollup job" - rollup-jobs: "rollup jobs" - rollup-jobs-cap: "Rollup jobs" - dfeed: "datafeed" - dfeeds: "datafeeds" - dfeed-cap: "Datafeed" - dfeeds-cap: "Datafeeds" - ml-jobs: "machine learning jobs" - ml-jobs-cap: "Machine learning jobs" - anomaly-detect: "anomaly detection" - anomaly-detect-cap: "Anomaly detection" - anomaly-job: "anomaly detection job" - anomaly-jobs: "anomaly detection jobs" - anomaly-jobs-cap: "Anomaly detection jobs" - dataframe: "data frame" - dataframes: "data frames" - dataframe-cap: "Data frame" - dataframes-cap: "Data frames" - watcher-transform: "payload transform" - watcher-transforms: "payload transforms" - watcher-transform-cap: "Payload transform" - watcher-transforms-cap: "Payload transforms" - transform: "transform" - transforms: "transforms" - transform-cap: "Transform" - transforms-cap: "Transforms" - dataframe-transform: "transform" - dataframe-transform-cap: "Transform" - dataframe-transforms: "transforms" - dataframe-transforms-cap: "Transforms" - dfanalytics-cap: "Data frame analytics" - dfanalytics: "data frame analytics" - dataframe-analytics-config: "'{dataframe} analytics config'" - dfanalytics-job: "'{dataframe} analytics job'" - dfanalytics-jobs: "'{dataframe} analytics jobs'" - dfanalytics-jobs-cap: "'{dataframe-cap} analytics jobs'" - cdataframe: "continuous data frame" - cdataframes: "continuous data frames" - cdataframe-cap: "Continuous data frame" - cdataframes-cap: "Continuous data frames" - cdataframe-transform: "continuous transform" - cdataframe-transforms: "continuous transforms" - cdataframe-transforms-cap: "Continuous transforms" - ctransform: "continuous transform" - ctransform-cap: "Continuous transform" - ctransforms: "continuous transforms" - ctransforms-cap: "Continuous transforms" - oldetection: "outlier detection" - oldetection-cap: "Outlier detection" - olscore: "outlier score" - olscores: "outlier scores" - fiscore: "feature influence score" - evaluatedf-api: "evaluate {dataframe} analytics API" - evaluatedf-api-cap: "Evaluate {dataframe} analytics API" - binarysc: "binary soft classification" - binarysc-cap: "Binary soft classification" - regression: "regression" - regression-cap: "Regression" - reganalysis: "regression analysis" - reganalysis-cap: "Regression analysis" - depvar: "dependent variable" - feature-var: "feature variable" - feature-vars: "feature variables" - feature-vars-cap: "Feature variables" - classification: "classification" - classification-cap: "Classification" - classanalysis: "classification analysis" - classanalysis-cap: "Classification analysis" - infer-cap: "Inference" - infer: "inference" - lang-ident-cap: "Language identification" - lang-ident: "language identification" - data-viz: "Data Visualizer" - file-data-viz: "File Data Visualizer" - feat-imp: "feature importance" - feat-imp-cap: "Feature importance" - nlp: "natural language processing" - nlp-cap: "Natural language processing" - apm-agent: "APM agent" - apm-go-agent: "Elastic APM Go agent" - apm-go-agents: "Elastic APM Go agents" - apm-ios-agent: "Elastic APM iOS agent" - apm-ios-agents: "Elastic APM iOS agents" - apm-java-agent: "Elastic APM Java agent" - apm-java-agents: "Elastic APM Java agents" - apm-dotnet-agent: "Elastic APM .NET agent" - apm-dotnet-agents: "Elastic APM .NET agents" - apm-node-agent: "Elastic APM Node.js agent" - apm-node-agents: "Elastic APM Node.js agents" - apm-php-agent: "Elastic APM PHP agent" - apm-php-agents: "Elastic APM PHP agents" - apm-py-agent: "Elastic APM Python agent" - apm-py-agents: "Elastic APM Python agents" - apm-ruby-agent: "Elastic APM Ruby agent" - apm-ruby-agents: "Elastic APM Ruby agents" - apm-rum-agent: "Elastic APM Real User Monitoring (RUM) JavaScript agent" - apm-rum-agents: "Elastic APM RUM JavaScript agents" apm-lambda-ext: "Elastic APM AWS Lambda extension" - project-monitors: "project monitors" - project-monitors-cap: "Project monitors" - private-location: "Private Location" - private-locations: "Private Locations" - pwd: "YOUR_PASSWORD" - esh: "ES-Hadoop" - default-dist: "default distribution" - oss-dist: "OSS-only distribution" - observability: "Observability" - api-request-title: "Request" - api-prereq-title: "Prerequisites" - api-description-title: "Description" - api-path-parms-title: "Path parameters" - api-query-parms-title: "Query parameters" - api-request-body-title: "Request body" - api-response-codes-title: "Response codes" - api-response-body-title: "Response body" - api-example-title: "Example" - api-examples-title: "Examples" - api-definitions-title: "Properties" - multi-arg: "†footnoteref:[multi-arg,This parameter accepts multiple arguments.]" - multi-arg-ref: "†footnoteref:[multi-arg]" - yes-icon: "image:https://doc-icons.s3.us-east-2.amazonaws.com/icon-yes.png[Yes,20,15]" - no-icon: "image:https://doc-icons.s3.us-east-2.amazonaws.com/icon-no.png[No,20,15]" - es-repo: "https://github.com/elastic/elasticsearch/" - es-issue: "https://github.com/elastic/elasticsearch/issues/" - es-pull: "https://github.com/elastic/elasticsearch/pull/" - es-commit: "https://github.com/elastic/elasticsearch/commit/" - kib-repo: "https://github.com/elastic/kibana/" - kib-issue: "https://github.com/elastic/kibana/issues/" - kibana-issue: "'{kib-repo}issues/'" - kib-pull: "https://github.com/elastic/kibana/pull/" - kibana-pull: "'{kib-repo}pull/'" - kib-commit: "https://github.com/elastic/kibana/commit/" - ml-repo: "https://github.com/elastic/ml-cpp/" - ml-issue: "https://github.com/elastic/ml-cpp/issues/" - ml-pull: "https://github.com/elastic/ml-cpp/pull/" - ml-commit: "https://github.com/elastic/ml-cpp/commit/" - apm-repo: "https://github.com/elastic/apm-server/" - apm-issue: "https://github.com/elastic/apm-server/issues/" - apm-pull: "https://github.com/elastic/apm-server/pull/" - kibana-blob: "https://github.com/elastic/kibana/blob/current/" - apm-get-started-ref: "https://www.elastic.co/guide/en/apm/get-started/current" - apm-server-ref: "https://www.elastic.co/guide/en/apm/server/current" - apm-server-ref-v: "https://www.elastic.co/guide/en/apm/server/current" - apm-server-ref-m: "https://www.elastic.co/guide/en/apm/server/master" - apm-server-ref-62: "https://www.elastic.co/guide/en/apm/server/6.2" - apm-server-ref-64: "https://www.elastic.co/guide/en/apm/server/6.4" - apm-server-ref-70: "https://www.elastic.co/guide/en/apm/server/7.0" - apm-overview-ref-v: "https://www.elastic.co/guide/en/apm/get-started/current" - apm-overview-ref-70: "https://www.elastic.co/guide/en/apm/get-started/7.0" - apm-overview-ref-m: "https://www.elastic.co/guide/en/apm/get-started/master" - infra-guide: "https://www.elastic.co/guide/en/infrastructure/guide/current" - a-data-source: "a data view" - icon-bug: "pass:[]" - icon-checkInCircleFilled: "pass:[]" - icon-warningFilled: "pass:[]" diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 2930b1587..8cc897b01 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -29,7 +29,7 @@ ELASTIC_APM = { The precedence is as follows: -* [Central configuration](#config-central_config) (supported options are marked with [![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration)) +* [Central configuration](#config-central_config) (supported options are marked with [![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration)) * Environment variables * Inline configuration * Framework-specific configuration @@ -38,7 +38,7 @@ The precedence is as follows: ## Dynamic configuration [dynamic-configuration] -Configuration options marked with the ![dynamic config](../images/dynamic-config.svg "") badge can be changed at runtime when set from a supported source. +Configuration options marked with the ![dynamic config](/reference/images/dynamic-config.svg "") badge can be changed at runtime when set from a supported source. The Python Agent supports [Central configuration](docs-content://solutions/observability/apps/apm-agent-central-configuration.md), which allows you to fine-tune certain configurations from in the APM app. This feature is enabled in the Agent by default with [`central_config`](#config-central_config). @@ -108,7 +108,7 @@ Enable or disable the agent. When set to false, the agent will not collect any d ## `recording` [config-recording] -[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -122,7 +122,7 @@ Enable or disable recording of events. If set to false, then the Python agent do ### `log_level` [config-log_level] -[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -320,7 +320,7 @@ A list of exception types to be filtered. Exceptions of these types will not be ### `transaction_ignore_urls` [config-transaction-ignore-urls] -[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | Example | | --- | --- | --- | --- | @@ -460,7 +460,7 @@ Especially for spans, collecting source code can have a large impact on storage ### `capture_body` [config-capture-body] -[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -482,7 +482,7 @@ Request bodies often contain sensitive values like passwords and credit card num ### `capture_headers` [config-capture-headers] -[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -500,7 +500,7 @@ Request headers often contain sensitive values like session IDs and cookies. See ### `transaction_max_spans` [config-transaction-max-spans] -[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -511,7 +511,7 @@ This limits the amount of spans that are recorded per transaction. This is helpf ### `stack_trace_limit` [config-stack-trace-limit] -[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -524,7 +524,7 @@ Setting the limit to `0` will disable stack trace collection, while any positive ### `span_stack_trace_min_duration` [config-span-stack-trace-min-duration] -[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -541,7 +541,7 @@ Except for the special values `-1` and `0`, this setting should be provided in * ### `span_frames_min_duration` [config-span-frames-min-duration] -[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -555,7 +555,7 @@ This config value is being deprecated. Use [`span_stack_trace_min_duration`](#co ### `span_compression_enabled` [config-span-compression-enabled] -[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -568,7 +568,7 @@ If enabled, the agent will compress very short, repeated spans into a single spa ### `span_compression_exact_match_max_duration` [config-span-compression-exact-match-max_duration] -[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -581,7 +581,7 @@ Two spans are considered exact matches if the following attributes are identical ### `span_compression_same_kind_max_duration` [config-span-compression-same-kind-max-duration] -[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -594,7 +594,7 @@ Two spans are considered to be of the same kind if the following attributes are ### `exit_span_min_duration` [config-exit-span-min-duration] -[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -612,7 +612,7 @@ if a span propagates distributed tracing IDs, it will not be ignored, even if it ### `api_request_size` [config-api-request-size] -[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -630,7 +630,7 @@ Due to internal buffering of gzip, the actual request size can be a few kilobyte ### `api_request_time` [config-api-request-time] -[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -662,7 +662,7 @@ We recommend always including the default set of validators if you customize thi ### `sanitize_field_names` [config-sanitize-field-names] -[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -678,7 +678,7 @@ We recommend always including the default set of field name matches if you custo ### `transaction_sample_rate` [config-transaction-sample-rate] -[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -930,7 +930,7 @@ Additionally, when this setting is set to `True`, the agent will set `elasticapm ### `trace_continuation_strategy` [config-trace-continuation-strategy] -[![dynamic config](../images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | diff --git a/docs/reference/django-support.md b/docs/reference/django-support.md index 9db46e864..e85f5a8a8 100644 --- a/docs/reference/django-support.md +++ b/docs/reference/django-support.md @@ -171,7 +171,7 @@ ELASTIC_APM = { To easily send Python `logging` messages as "error" objects to Elasticsearch, we provide a `LoggingHandler` which you can use in your logging setup. The log messages will be enriched with a stack trace, data from the request, and more. ::::{note} -the intended use case for this handler is to send high priority log messages (e.g. log messages with level `ERROR`) to Elasticsearch. For normal log shipping, we recommend using [filebeat](beats://reference/filebeat/filebeat-overview.md). +the intended use case for this handler is to send high priority log messages (e.g. log messages with level `ERROR`) to Elasticsearch. For normal log shipping, we recommend using [filebeat](beats://reference/filebeat/index.md). :::: diff --git a/docs/images/choose-a-layer.png b/docs/reference/images/choose-a-layer.png similarity index 100% rename from docs/images/choose-a-layer.png rename to docs/reference/images/choose-a-layer.png diff --git a/docs/images/config-layer.png b/docs/reference/images/config-layer.png similarity index 100% rename from docs/images/config-layer.png rename to docs/reference/images/config-layer.png diff --git a/docs/images/dynamic-config.svg b/docs/reference/images/dynamic-config.svg similarity index 100% rename from docs/images/dynamic-config.svg rename to docs/reference/images/dynamic-config.svg diff --git a/docs/images/python-lambda-env-vars.png b/docs/reference/images/python-lambda-env-vars.png similarity index 100% rename from docs/images/python-lambda-env-vars.png rename to docs/reference/images/python-lambda-env-vars.png diff --git a/docs/reference/lambda-support.md b/docs/reference/lambda-support.md index f720b9ccc..60f9eb114 100644 --- a/docs/reference/lambda-support.md +++ b/docs/reference/lambda-support.md @@ -29,7 +29,7 @@ Both the [{{apm-lambda-ext}}](apm-aws-lambda://reference/index.md) and the Pytho To add the layers to your Lambda function through the AWS Management Console: 1. Navigate to your function in the AWS Management Console -2. Scroll to the Layers section and click the *Add a layer* button ![image of layer configuration section in AWS Console](../images/config-layer.png "") +2. Scroll to the Layers section and click the *Add a layer* button ![image of layer configuration section in AWS Console](images/config-layer.png "") 3. Choose the *Specify an ARN* radio button 4. Copy and paste the following ARNs of the {{apm-lambda-ext}} layer and the APM agent layer in the *Specify an ARN* text input: * APM Extension layer: @@ -44,7 +44,7 @@ To add the layers to your Lambda function through the AWS Management Console: ``` 1. Replace `{AWS_REGION}` with the AWS region of your Lambda function. - ![image of choosing a layer in AWS Console](../images/choose-a-layer.png "") + ![image of choosing a layer in AWS Console](images/choose-a-layer.png "") 5. Click the *Add* button :::::: @@ -159,7 +159,7 @@ ELASTIC_APM_SEND_STRATEGY = background <4> 3. This is your APM secret token. 4. The [ELASTIC_APM_SEND_STRATEGY](apm-aws-lambda://reference/aws-lambda-config-options.md#_elastic_apm_send_strategy) defines when APM data is sent to your Elastic APM backend. To reduce the execution time of your lambda functions, we recommend to use the background strategy in production environments with steady load scenarios. -![Python environment variables configuration section in AWS Console](../images/python-lambda-env-vars.png "") +![Python environment variables configuration section in AWS Console](images/python-lambda-env-vars.png "") :::::: ::::::{tab-item} AWS CLI From 9f256e1674ec556a58bddb3cdff8dc7ad030e2ef Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Wed, 26 Mar 2025 12:23:17 -0500 Subject: [PATCH 209/409] update links (#2247) --- docs/docset.yml | 2 +- docs/reference/logs.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docset.yml b/docs/docset.yml index 14f47bbb9..0a3d71244 100644 --- a/docs/docset.yml +++ b/docs/docset.yml @@ -8,7 +8,7 @@ cross_links: - ecs-logging - ecs-logging-python - elasticsearch - - logstash + - logstash-docs-md toc: - toc: reference - toc: release-notes diff --git a/docs/reference/logs.md b/docs/reference/logs.md index a3bcba170..25d44be0c 100644 --- a/docs/reference/logs.md +++ b/docs/reference/logs.md @@ -81,7 +81,7 @@ In order to correlate logs from your app with transactions captured by the Elast If you’re using structured logging, either [with a custom solution](https://docs.python.org/3/howto/logging-cookbook.html#implementing-structured-logging) or with [structlog](http://www.structlog.org/en/stable/) (recommended), then this is fairly easy. Throw the [JSONRenderer](http://www.structlog.org/en/stable/api.html#structlog.processors.JSONRenderer) in, and use [Filebeat](https://www.elastic.co/blog/structured-logging-filebeat) to pull these logs into Elasticsearch. -Without structured logging the task gets a little trickier. Here we recommend first making sure your LogRecord objects have the elasticapm attributes (see [`logging`](#logging)), and then you’ll want to combine some specific formatting with a Grok pattern, either in Elasticsearch using [the grok processor](elasticsearch://reference/ingestion-tools/enrich-processor/grok-processor.md), or in [logstash with a plugin](logstash://reference/plugins-filters-grok.md). +Without structured logging the task gets a little trickier. Here we recommend first making sure your LogRecord objects have the elasticapm attributes (see [`logging`](#logging)), and then you’ll want to combine some specific formatting with a Grok pattern, either in Elasticsearch using [the grok processor](elasticsearch://reference/enrich-processor/grok-processor.md), or in [logstash with a plugin](logstash-docs-md://lsr/plugins-filters-grok.md). Say you have a [Formatter](https://docs.python.org/3/library/logging.html#logging.Formatter) that looks like this: From 0de4994ca553d5dfc8de07a41dd08c7f3abb23bf Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Wed, 26 Mar 2025 18:03:54 -0500 Subject: [PATCH 210/409] udpate mapped pages (#2248) --- docs/reference/upgrading-4-x.md | 2 +- docs/reference/upgrading-5-x.md | 2 +- docs/reference/upgrading-6-x.md | 2 +- docs/release-notes/index.md | 5 +++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/reference/upgrading-4-x.md b/docs/reference/upgrading-4-x.md index fafd8e576..9a1cbdbd6 100644 --- a/docs/reference/upgrading-4-x.md +++ b/docs/reference/upgrading-4-x.md @@ -1,6 +1,6 @@ --- mapped_pages: - - https://www.elastic.co/guide/en/apm/agent/python/current/upgrading-4-x.html + - https://www.elastic.co/guide/en/apm/agent/python/current/upgrading-4.x.html --- # Upgrading to version 4 of the agent [upgrading-4-x] diff --git a/docs/reference/upgrading-5-x.md b/docs/reference/upgrading-5-x.md index 5055b6790..b124841dd 100644 --- a/docs/reference/upgrading-5-x.md +++ b/docs/reference/upgrading-5-x.md @@ -1,6 +1,6 @@ --- mapped_pages: - - https://www.elastic.co/guide/en/apm/agent/python/current/upgrading-5-x.html + - https://www.elastic.co/guide/en/apm/agent/python/current/upgrading-5.x.html --- # Upgrading to version 5 of the agent [upgrading-5-x] diff --git a/docs/reference/upgrading-6-x.md b/docs/reference/upgrading-6-x.md index 08a6e6e3c..36b8a3393 100644 --- a/docs/reference/upgrading-6-x.md +++ b/docs/reference/upgrading-6-x.md @@ -1,6 +1,6 @@ --- mapped_pages: - - https://www.elastic.co/guide/en/apm/agent/python/current/upgrading-6-x.html + - https://www.elastic.co/guide/en/apm/agent/python/current/upgrading-6.x.html --- # Upgrading to version 6 of the agent [upgrading-6-x] diff --git a/docs/release-notes/index.md b/docs/release-notes/index.md index c229323b0..e9740effe 100644 --- a/docs/release-notes/index.md +++ b/docs/release-notes/index.md @@ -2,15 +2,16 @@ navigation_title: "Elastic APM Python Agent" mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/release-notes-6.x.html + - https://www.elastic.co/guide/en/apm/agent/python/current/index.html --- # Elastic APM Python Agent release notes [elastic-apm-python-agent-release-notes] -Review the changes, fixes, and more in each version of Elastic APM Python Agent. +Review the changes, fixes, and more in each version of Elastic APM Python Agent. To check for security updates, go to [Security announcements for the Elastic stack](https://discuss.elastic.co/c/announcements/security-announcements/31). -% Release notes includes only features, enhancements, and fixes. Add breaking changes, deprecations, and known issues to the applicable release notes sections. +% Release notes includes only features, enhancements, and fixes. Add breaking changes, deprecations, and known issues to the applicable release notes sections. % ## version.next [elastic-apm-python-agent-versionext-release-notes] % **Release date:** Month day, year From edadac97490559e913e1b254b829ad2d7469f075 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Thu, 27 Mar 2025 15:44:02 +0100 Subject: [PATCH 211/409] dependabot: use directories and use docker ecosystem (#2246) --- .github/dependabot.yml | 41 ++++++++++++++++------------------------- renovate.json | 6 ------ 2 files changed, 16 insertions(+), 31 deletions(-) delete mode 100644 renovate.json diff --git a/.github/dependabot.yml b/.github/dependabot.yml index afb941790..384f44ee4 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,5 +1,12 @@ --- version: 2 +registries: + docker-elastic: + type: docker-registry + url: https://docker.elastic.co + username: ${{secrets.ELASTIC_DOCKER_USERNAME}} + password: ${{secrets.ELASTIC_DOCKER_PASSWORD}} + updates: # Enable version updates for python - package-ecosystem: "pip" @@ -18,7 +25,9 @@ updates: # GitHub actions - package-ecosystem: "github-actions" - directory: "/" + directories: + - '/' + - '/.github/actions/*' reviewers: - "elastic/observablt-ci" schedule: @@ -30,29 +39,11 @@ updates: patterns: - "*" - # GitHub composite actions - - package-ecosystem: "github-actions" - directory: "/.github/actions/packages" + - package-ecosystem: "docker" + directories: + - '/' reviewers: - - "elastic/observablt-ci" - schedule: - interval: "weekly" - day: "sunday" - time: "22:00" - groups: - github-actions: - patterns: - - "*" - - - package-ecosystem: "github-actions" - directory: "/.github/actions/build-distribution" - reviewers: - - "elastic/observablt-ci" + - "elastic/apm-agent-python" + registries: "*" schedule: - interval: "weekly" - day: "sunday" - time: "22:00" - groups: - github-actions: - patterns: - - "*" + interval: "daily" diff --git a/renovate.json b/renovate.json deleted file mode 100644 index 10a37617c..000000000 --- a/renovate.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "github>elastic/renovate-config:only-chainguard" - ] -} From 5a8543ccd75f30dc9278f12a011d4eef38cc870a Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Thu, 27 Mar 2025 18:15:19 -0500 Subject: [PATCH 212/409] fixing my mistake in https://github.com/elastic/apm-agent-python/pull/2248 (#2252) --- docs/release-notes/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes/index.md b/docs/release-notes/index.md index e9740effe..1d40b1596 100644 --- a/docs/release-notes/index.md +++ b/docs/release-notes/index.md @@ -2,7 +2,7 @@ navigation_title: "Elastic APM Python Agent" mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/release-notes-6.x.html - - https://www.elastic.co/guide/en/apm/agent/python/current/index.html + - https://www.elastic.co/guide/en/apm/agent/python/current/release-notes.html --- # Elastic APM Python Agent release notes [elastic-apm-python-agent-release-notes] From 2d163e4cf69e21f1c3440d182b16a564c8a4559a Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Fri, 28 Mar 2025 15:12:43 -0500 Subject: [PATCH 213/409] remove reliance on redirects (#2253) --- docs/reference/logs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/logs.md b/docs/reference/logs.md index 25d44be0c..18a4f0803 100644 --- a/docs/reference/logs.md +++ b/docs/reference/logs.md @@ -114,7 +114,7 @@ formatstring = formatstring + " | elasticapm " \ "span.id=%(elasticapm_span_id)s" ``` -Then, you could use a grok pattern like this (for the [Elasticsearch Grok Processor](elasticsearch://reference/ingestion-tools/enrich-processor/grok-processor.md)): +Then, you could use a grok pattern like this (for the [Elasticsearch Grok Processor](elasticsearch://reference/enrich-processor/grok-processor.md)): ```json { From 1e293b8b0b56d18bcfaf77b23e395cdd93e582cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 14:45:02 +0200 Subject: [PATCH 214/409] build(deps): bump wolfi/chainguard-base from `bd40170` to `c4e10ec` (#2250) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `bd40170` to `c4e10ec`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 356dfb6fa..c826a54eb 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:bd401704a162a7937cd1015f755ca9da9aba0fdf967fc6bf90bf8d3f6b2eb557 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:c4e10ecf3d8a21cf4be2fb53a2f522de50e14c80ce1da487e3ffd13f4d48d24d ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From d4570ce366fd7ad4c5cba782f13e5e55cb71b175 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Mar 2025 18:01:52 +0200 Subject: [PATCH 215/409] build(deps): bump certifi from 2024.12.14 to 2025.1.31 in /dev-utils (#2207) Bumps [certifi](https://github.com/certifi/python-certifi) from 2024.12.14 to 2025.1.31. - [Commits](https://github.com/certifi/python-certifi/compare/2024.12.14...2025.01.31) --- updated-dependencies: - dependency-name: certifi dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Riccardo Magliocchetti --- dev-utils/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-utils/requirements.txt b/dev-utils/requirements.txt index 7ab878dff..de69d7314 100644 --- a/dev-utils/requirements.txt +++ b/dev-utils/requirements.txt @@ -1,4 +1,4 @@ # These are the pinned requirements for the lambda layer/docker image -certifi==2024.12.14 +certifi==2025.1.31 urllib3==1.26.20 wrapt==1.14.1 From 5d9a4616866335052b8b3071125436d0d38c4c48 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Tue, 1 Apr 2025 12:22:46 +0200 Subject: [PATCH 216/409] Revert "ci: pin actions to specific commits (#2236)" (#2254) This reverts commit 9e86c9df30b1461bc4dca9df600e05faa32d1692. --- .github/actions/build-distribution/action.yml | 4 +-- .github/actions/packages/action.yml | 4 +-- .github/workflows/docs-build.yml | 2 +- .github/workflows/docs-cleanup.yml | 2 +- .github/workflows/labeler.yml | 6 ++-- .github/workflows/matrix-command.yml | 2 +- .github/workflows/microbenchmark.yml | 2 +- .github/workflows/packages.yml | 2 +- .github/workflows/pre-commit.yml | 6 ++-- .github/workflows/release.yml | 28 +++++++++---------- .github/workflows/run-matrix.yml | 6 ++-- .github/workflows/test-docs.yml | 2 +- .github/workflows/test-fips.yml | 10 +++---- .github/workflows/test-reporter.yml | 2 +- .github/workflows/test.yml | 28 +++++++++---------- .github/workflows/updatecli.yml | 8 +++--- 16 files changed, 57 insertions(+), 57 deletions(-) diff --git a/.github/actions/build-distribution/action.yml b/.github/actions/build-distribution/action.yml index a44e09762..bc0d55c29 100644 --- a/.github/actions/build-distribution/action.yml +++ b/.github/actions/build-distribution/action.yml @@ -6,7 +6,7 @@ description: Run the build distribution runs: using: "composite" steps: - - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5 + - uses: actions/setup-python@v5 with: python-version: "3.10" @@ -14,7 +14,7 @@ runs: run: ./dev-utils/make-distribution.sh shell: bash - - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 + - uses: actions/upload-artifact@v4 with: name: build-distribution path: ./build/ diff --git a/.github/actions/packages/action.yml b/.github/actions/packages/action.yml index d46faa9ac..871f49c32 100644 --- a/.github/actions/packages/action.yml +++ b/.github/actions/packages/action.yml @@ -6,7 +6,7 @@ description: Run the packages runs: using: "composite" steps: - - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5 + - uses: actions/setup-python@v5 with: python-version: "3.10" - name: Override the version if there is no tag release. @@ -19,7 +19,7 @@ runs: run: ./dev-utils/make-packages.sh shell: bash - name: Upload Packages - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 + uses: actions/upload-artifact@v4 with: name: packages path: | diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml index 24fa38f94..bb466166d 100644 --- a/.github/workflows/docs-build.yml +++ b/.github/workflows/docs-build.yml @@ -9,7 +9,7 @@ on: jobs: docs-preview: - uses: elastic/docs-builder/.github/workflows/preview-build.yml@99b12f8bf7a82107ffcf59dacd199d00a965e9db # main + uses: elastic/docs-builder/.github/workflows/preview-build.yml@main with: path-pattern: docs/** permissions: diff --git a/.github/workflows/docs-cleanup.yml b/.github/workflows/docs-cleanup.yml index c66c94994..f83e017b5 100644 --- a/.github/workflows/docs-cleanup.yml +++ b/.github/workflows/docs-cleanup.yml @@ -7,7 +7,7 @@ on: jobs: docs-preview: - uses: elastic/docs-builder/.github/workflows/preview-cleanup.yml@99b12f8bf7a82107ffcf59dacd199d00a965e9db # main + uses: elastic/docs-builder/.github/workflows/preview-cleanup.yml@main permissions: contents: none id-token: write diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index e1a3e1c95..fcab871c7 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -26,18 +26,18 @@ jobs: "members": "read" } - name: Add agent-python label - uses: actions-ecosystem/action-add-labels@18f1af5e3544586314bbe15c0273249c770b2daf # v1 + uses: actions-ecosystem/action-add-labels@v1 with: labels: agent-python - id: is_elastic_member - uses: elastic/oblt-actions/github/is-member-of@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 + uses: elastic/oblt-actions/github/is-member-of@v1 with: github-org: "elastic" github-user: ${{ github.actor }} github-token: ${{ steps.get_token.outputs.token }} - name: Add community and triage labels if: contains(steps.is_elastic_member.outputs.result, 'false') && github.actor != 'dependabot[bot]' && github.actor != 'elastic-observability-automation[bot]' - uses: actions-ecosystem/action-add-labels@18f1af5e3544586314bbe15c0273249c770b2daf # v1 + uses: actions-ecosystem/action-add-labels@v1 with: labels: | community diff --git a/.github/workflows/matrix-command.yml b/.github/workflows/matrix-command.yml index 1a8e9849a..f2c32658f 100644 --- a/.github/workflows/matrix-command.yml +++ b/.github/workflows/matrix-command.yml @@ -21,7 +21,7 @@ jobs: pull-requests: write steps: - name: Is comment allowed? - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7 + uses: actions/github-script@v7 with: script: | const actorPermission = (await github.rest.repos.getCollaboratorPermissionLevel({ diff --git a/.github/workflows/microbenchmark.yml b/.github/workflows/microbenchmark.yml index 13daf5cb1..e3f0a41d6 100644 --- a/.github/workflows/microbenchmark.yml +++ b/.github/workflows/microbenchmark.yml @@ -19,7 +19,7 @@ jobs: timeout-minutes: 5 steps: - name: Run microbenchmark - uses: elastic/oblt-actions/buildkite/run@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 + uses: elastic/oblt-actions/buildkite/run@v1 with: pipeline: "apm-agent-microbenchmark" token: ${{ secrets.BUILDKITE_TOKEN }} diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 637cb6a54..496107508 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -20,5 +20,5 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@v4 - uses: ./.github/actions/packages diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 5e389dba1..926c21be6 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -12,6 +12,6 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5 - - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + - uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a004f812b..ac7e05c75 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: contents: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@v4 - uses: ./.github/actions/packages - name: generate build provenance uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3 @@ -40,8 +40,8 @@ jobs: permissions: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - - uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4 + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 with: name: packages path: dist @@ -63,7 +63,7 @@ jobs: contents: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@v4 - uses: ./.github/actions/build-distribution - name: generate build provenance uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3 @@ -78,12 +78,12 @@ jobs: - build-distribution runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - - uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4 + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 with: name: build-distribution path: ./build - - uses: elastic/oblt-actions/aws/auth@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 + - uses: elastic/oblt-actions/aws/auth@v1 with: aws-account-id: "267093732750" - name: Publish lambda layers to AWS @@ -94,7 +94,7 @@ jobs: VERSION=${VERSION//./-} ELASTIC_LAYER_NAME="elastic-apm-python-${VERSION}" .ci/publish-aws.sh - - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 + - uses: actions/upload-artifact@v4 if: startsWith(github.ref, 'refs/tags') with: name: arn-file @@ -116,7 +116,7 @@ jobs: env: DOCKER_IMAGE_NAME: docker.elastic.co/observability/apm-agent-python steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 @@ -128,7 +128,7 @@ jobs: username: ${{ secrets.ELASTIC_DOCKER_USERNAME }} password: ${{ secrets.ELASTIC_DOCKER_PASSWORD }} - - uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4 + - uses: actions/download-artifact@v4 with: name: build-distribution path: ./build @@ -172,8 +172,8 @@ jobs: if: startsWith(github.ref, 'refs/tags') runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - - uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4 + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 with: name: arn-file - name: Create GitHub Draft Release @@ -196,11 +196,11 @@ jobs: - github-draft steps: - id: check - uses: elastic/oblt-actions/check-dependent-jobs@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 + uses: elastic/oblt-actions/check-dependent-jobs@v1 with: jobs: ${{ toJSON(needs) }} - if: startsWith(github.ref, 'refs/tags') - uses: elastic/oblt-actions/slack/notify-result@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 + uses: elastic/oblt-actions/slack/notify-result@v1 with: bot-token: ${{ secrets.SLACK_BOT_TOKEN }} channel-id: "#apm-agent-python" diff --git a/.github/workflows/run-matrix.yml b/.github/workflows/run-matrix.yml index 014cf42c3..0b31f4318 100644 --- a/.github/workflows/run-matrix.yml +++ b/.github/workflows/run-matrix.yml @@ -21,20 +21,20 @@ jobs: matrix: include: ${{ fromJSON(inputs.include) }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@v4 - name: Run tests run: ./tests/scripts/docker/run_tests.sh ${{ matrix.version }} ${{ matrix.framework }} env: LOCALSTACK_VOLUME_DIR: localstack_data - if: success() || failure() name: Upload JUnit Test Results - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 + uses: actions/upload-artifact@v4 with: name: test-results-${{ matrix.framework }}-${{ matrix.version }} path: "**/*-python-agent-junit.xml" - if: success() || failure() name: Upload Coverage Reports - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 + uses: actions/upload-artifact@v4 with: name: coverage-reports-${{ matrix.framework }}-${{ matrix.version }} path: "**/.coverage*" diff --git a/.github/workflows/test-docs.yml b/.github/workflows/test-docs.yml index bb7a2fff8..e1c4c4ae4 100644 --- a/.github/workflows/test-docs.yml +++ b/.github/workflows/test-docs.yml @@ -36,7 +36,7 @@ jobs: ENDOFFILE - if: success() || failure() name: Upload JUnit Test Results - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 + uses: actions/upload-artifact@v4 with: name: test-results-docs path: "docs-python-agent-junit.xml" diff --git a/.github/workflows/test-fips.yml b/.github/workflows/test-fips.yml index 45ed3ad4d..3712f00d0 100644 --- a/.github/workflows/test-fips.yml +++ b/.github/workflows/test-fips.yml @@ -16,9 +16,9 @@ jobs: outputs: matrix: ${{ steps.generate.outputs.matrix }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@v4 - id: generate - uses: elastic/oblt-actions/version-framework@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 + uses: elastic/oblt-actions/version-framework@v1 with: versions-file: .ci/.matrix_python_fips.yml frameworks-file: .ci/.matrix_framework_fips.yml @@ -40,7 +40,7 @@ jobs: max-parallel: 10 matrix: ${{ fromJSON(needs.create-matrix.outputs.matrix) }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@v4 - name: check that python has fips mode enabled run: | python3 -c 'import _hashlib; assert _hashlib.get_fips_mode() == 1' @@ -57,12 +57,12 @@ jobs: needs: test-fips steps: - id: check - uses: elastic/oblt-actions/check-dependent-jobs@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 + uses: elastic/oblt-actions/check-dependent-jobs@v1 with: jobs: ${{ toJSON(needs) }} - name: Notify in Slack if: steps.check.outputs.status == 'failure' - uses: elastic/oblt-actions/slack/notify-result@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 + uses: elastic/oblt-actions/slack/notify-result@v1 with: bot-token: ${{ secrets.SLACK_BOT_TOKEN }} status: ${{ steps.check.outputs.status }} diff --git a/.github/workflows/test-reporter.yml b/.github/workflows/test-reporter.yml index cd13279bf..ffb1206a6 100644 --- a/.github/workflows/test-reporter.yml +++ b/.github/workflows/test-reporter.yml @@ -17,7 +17,7 @@ jobs: report: runs-on: ubuntu-latest steps: - - uses: elastic/oblt-actions/test-report@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 + - uses: elastic/oblt-actions/test-report@v1 with: artifact: /test-results(.*)/ name: 'Test Report $1' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 63db7600b..36294b1f4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,7 +37,7 @@ jobs: build-distribution: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@v4 - uses: ./.github/actions/build-distribution @@ -48,11 +48,11 @@ jobs: data: ${{ steps.split.outputs.data }} chunks: ${{ steps.split.outputs.chunks }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@v4 with: ref: ${{ inputs.ref || github.ref }} - id: generate - uses: elastic/oblt-actions/version-framework@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 + uses: elastic/oblt-actions/version-framework@v1 with: # Use .ci/.matrix_python_full.yml if it's a scheduled workflow, otherwise use .ci/.matrix_python.yml versions-file: .ci/.matrix_python${{ (github.event_name == 'schedule' || github.event_name == 'push' || inputs.full-matrix) && '_full' || '' }}.yml @@ -131,10 +131,10 @@ jobs: FRAMEWORK: ${{ matrix.framework }} ASYNCIO: ${{ matrix.asyncio }} steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@v4 with: ref: ${{ inputs.ref || github.ref }} - - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.version }} cache: pip @@ -145,14 +145,14 @@ jobs: run: .\scripts\run-tests.bat - if: success() || failure() name: Upload JUnit Test Results - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 + uses: actions/upload-artifact@v4 with: name: test-results-${{ matrix.framework }}-${{ matrix.version }}-asyncio-${{ matrix.asyncio }} path: "**/*-python-agent-junit.xml" retention-days: 1 - if: success() || failure() name: Upload Coverage Reports - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 + uses: actions/upload-artifact@v4 with: name: coverage-reports-${{ matrix.framework }}-${{ matrix.version }}-asyncio-${{ matrix.asyncio }} path: "**/.coverage*" @@ -171,12 +171,12 @@ jobs: - windows steps: - id: check - uses: elastic/oblt-actions/check-dependent-jobs@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 + uses: elastic/oblt-actions/check-dependent-jobs@v1 with: jobs: ${{ toJSON(needs) }} - run: ${{ steps.check.outputs.is-success }} - if: failure() && (github.event_name == 'schedule' || github.event_name == 'push') - uses: elastic/oblt-actions/slack/notify-result@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 + uses: elastic/oblt-actions/slack/notify-result@v1 with: bot-token: ${{ secrets.SLACK_BOT_TOKEN }} status: ${{ steps.check.outputs.status }} @@ -188,18 +188,18 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@v4 with: ref: ${{ inputs.ref || github.ref }} - - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5 + - uses: actions/setup-python@v5 with: # Use latest Python, so it understands all syntax. python-version: 3.11 - run: python -Im pip install --upgrade coverage[toml] - - uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4 + - uses: actions/download-artifact@v4 with: pattern: coverage-reports-* merge-multiple: true @@ -216,10 +216,10 @@ jobs: python -Im coverage report --fail-under=84 - name: Upload HTML report - uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4 + uses: actions/upload-artifact@v4 with: name: html-coverage-report path: htmlcov - - uses: geekyeggo/delete-artifact@f275313e70c08f6120db482d7a6b98377786765b # v5.1.0 + - uses: geekyeggo/delete-artifact@f275313e70c08f6120db482d7a6b98377786765b # 5.1.0 with: name: coverage-reports-* diff --git a/.github/workflows/updatecli.yml b/.github/workflows/updatecli.yml index dc62ed85d..a1109f743 100644 --- a/.github/workflows/updatecli.yml +++ b/.github/workflows/updatecli.yml @@ -15,7 +15,7 @@ jobs: contents: read packages: read steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 + - uses: actions/checkout@v4 - name: Get token id: get_token @@ -35,14 +35,14 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - uses: elastic/oblt-actions/updatecli/run@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 + - uses: elastic/oblt-actions/updatecli/run@v1 with: command: --experimental compose diff version-file: .tool-versions env: GITHUB_TOKEN: ${{ steps.get_token.outputs.token }} - - uses: elastic/oblt-actions/updatecli/run@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 + - uses: elastic/oblt-actions/updatecli/run@v1 with: command: --experimental compose apply version-file: .tool-versions @@ -50,7 +50,7 @@ jobs: GITHUB_TOKEN: ${{ steps.get_token.outputs.token }} - if: failure() - uses: elastic/oblt-actions/slack/send@31e93d1dfb82adc106fc7820f505db1afefe43b1 # v1 + uses: elastic/oblt-actions/slack/send@v1 with: bot-token: ${{ secrets.SLACK_BOT_TOKEN }} channel-id: "#apm-agent-python" From e97d37808340791cfe22dea4324104d8536516f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Apr 2025 14:34:09 +0200 Subject: [PATCH 217/409] build(deps): bump wolfi/chainguard-base from `c4e10ec` to `29150cd` (#2259) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `c4e10ec` to `29150cd`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index c826a54eb..ff7ec6586 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:c4e10ecf3d8a21cf4be2fb53a2f522de50e14c80ce1da487e3ffd13f4d48d24d +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:29150cd940cc7f69407d978d5a19c86f4d9e67cf44e4d6ded787a497e8f27c9a ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From e15e8c6c775fb580b4ea8c6bb870c1551869e2b5 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Tue, 1 Apr 2025 14:34:29 +0200 Subject: [PATCH 218/409] chore: deps(updatecli): Bump updatecli version to v0.97.0 (#2258) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index a744ca662..7d99e82c3 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.96.0 \ No newline at end of file +updatecli v0.97.0 \ No newline at end of file From ef06a16766137e075a93b0d40b71ed3f2b2a53cb Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Wed, 2 Apr 2025 13:53:40 +0200 Subject: [PATCH 219/409] github-actions: replace third-party actions (#2257) --- .github/workflows/labeler.yml | 13 +++++-------- .github/workflows/pre-commit.yml | 4 +--- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index fcab871c7..0d202d68a 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -14,6 +14,9 @@ permissions: jobs: triage: runs-on: ubuntu-latest + env: + NUMBER: ${{ github.event.issue.number }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Get token id: get_token @@ -26,9 +29,7 @@ jobs: "members": "read" } - name: Add agent-python label - uses: actions-ecosystem/action-add-labels@v1 - with: - labels: agent-python + run: gh issue edit "$NUMBER" --add-label "agent-python" - id: is_elastic_member uses: elastic/oblt-actions/github/is-member-of@v1 with: @@ -37,8 +38,4 @@ jobs: github-token: ${{ steps.get_token.outputs.token }} - name: Add community and triage labels if: contains(steps.is_elastic_member.outputs.result, 'false') && github.actor != 'dependabot[bot]' && github.actor != 'elastic-observability-automation[bot]' - uses: actions-ecosystem/action-add-labels@v1 - with: - labels: | - community - triage + run: gh issue edit "$NUMBER" --add-label "community,triage" diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 926c21be6..4839430e8 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -12,6 +12,4 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - - uses: pre-commit/action@v3.0.1 + - uses: elastic/oblt-actions/pre-commit@v1 From 6cbc173156f344457e6c46deb0d0e7a3552b62bf Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Fri, 4 Apr 2025 17:14:04 +0200 Subject: [PATCH 220/409] github-actions: support gh issues and prs (#2263) * github-actions: support gh issues and prs see https://docs.github.com/en/webhooks/webhook-events-and-payloads\#pull_request and https://docs.github.com/en/webhooks/webhook-events-and-payloads\#issues * use gh outside of the gh repository context --- .github/workflows/labeler.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 0d202d68a..b2e83005c 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -15,7 +15,7 @@ jobs: triage: runs-on: ubuntu-latest env: - NUMBER: ${{ github.event.issue.number }} + NUMBER: ${{ github.event.issue.number || github.event.pull_request.number }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Get token @@ -29,7 +29,7 @@ jobs: "members": "read" } - name: Add agent-python label - run: gh issue edit "$NUMBER" --add-label "agent-python" + run: gh issue edit "$NUMBER" --add-label "agent-python" --repo "${{ github.repository }}" - id: is_elastic_member uses: elastic/oblt-actions/github/is-member-of@v1 with: @@ -38,4 +38,4 @@ jobs: github-token: ${{ steps.get_token.outputs.token }} - name: Add community and triage labels if: contains(steps.is_elastic_member.outputs.result, 'false') && github.actor != 'dependabot[bot]' && github.actor != 'elastic-observability-automation[bot]' - run: gh issue edit "$NUMBER" --add-label "community,triage" + run: gh issue edit "$NUMBER" --add-label "community,triage" --repo "${{ github.repository }}" From 73d04c407c60bc64afb49443dbd5cbf866be0fed Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 7 Apr 2025 17:23:39 +0200 Subject: [PATCH 221/409] tests: enable transports tests poking with request_size for Python 3.12+ (#2261) I don't know if python 3.12 changed gzip compression or what but we get a flush call for each message we send so since our message is compressed to 10 bytes lower the limit to 9 bytes to pass the check deciding to flush the buffer. --- tests/transports/test_base.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/transports/test_base.py b/tests/transports/test_base.py index 457f68613..1aa1a8941 100644 --- a/tests/transports/test_base.py +++ b/tests/transports/test_base.py @@ -157,24 +157,26 @@ def test_api_request_time_dynamic(mock_send, caplog, elasticapm_client): assert mock_send.call_count == 0 -@pytest.mark.skipif(sys.version_info >= (3, 12), reason="Failing locally on 3.12.0rc1") # TODO py3.12 +def _cleanup_flush_mock_buffers(mock_flush): + args, kwargs = mock_flush.call_args + buffer = args[0] + buffer.close() + + @mock.patch("elasticapm.transport.base.Transport._flush") def test_api_request_size_dynamic(mock_flush, caplog, elasticapm_client): - elasticapm_client.config.update(version="1", api_request_size="100b") + elasticapm_client.config.update(version="1", api_request_size="9b") transport = Transport(client=elasticapm_client, queue_chill_count=1) transport.start_thread() try: with caplog.at_level("DEBUG", "elasticapm.transport"): - # we need to add lots of uncompressible data to fill up the gzip-internal buffer - for i in range(12): - transport.queue("error", "".join(random.choice(string.ascii_letters) for i in range(2000))) + transport.queue("error", "".join(random.choice(string.ascii_letters) for i in range(2000))) transport._flushed.wait(timeout=0.1) + _cleanup_flush_mock_buffers(mock_flush) assert mock_flush.call_count == 1 elasticapm_client.config.update(version="1", api_request_size="1mb") with caplog.at_level("DEBUG", "elasticapm.transport"): - # we need to add lots of uncompressible data to fill up the gzip-internal buffer - for i in range(12): - transport.queue("error", "".join(random.choice(string.ascii_letters) for i in range(2000))) + transport.queue("error", "".join(random.choice(string.ascii_letters) for i in range(2000))) transport._flushed.wait(timeout=0.1) # Should be unchanged because our buffer limit is much higher. assert mock_flush.call_count == 1 @@ -182,18 +184,16 @@ def test_api_request_size_dynamic(mock_flush, caplog, elasticapm_client): transport.close() -@pytest.mark.skipif(sys.version_info >= (3, 12), reason="Failing locally on 3.12.0rc1") # TODO py3.12 @mock.patch("elasticapm.transport.base.Transport._flush") -@pytest.mark.parametrize("elasticapm_client", [{"api_request_size": "100b"}], indirect=True) +@pytest.mark.parametrize("elasticapm_client", [{"api_request_size": "9b"}], indirect=True) def test_flush_time_size(mock_flush, caplog, elasticapm_client): transport = Transport(client=elasticapm_client, queue_chill_count=1) transport.start_thread() try: with caplog.at_level("DEBUG", "elasticapm.transport"): - # we need to add lots of uncompressible data to fill up the gzip-internal buffer - for i in range(12): - transport.queue("error", "".join(random.choice(string.ascii_letters) for i in range(2000))) + transport.queue("error", "".join(random.choice(string.ascii_letters) for i in range(2000))) transport._flushed.wait(timeout=0.1) + _cleanup_flush_mock_buffers(mock_flush) assert mock_flush.call_count == 1 finally: transport.close() From d3d505a4a832ded1f7a25be2e14c595edd0423a6 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 7 Apr 2025 18:24:17 +0200 Subject: [PATCH 222/409] Fix tests with python 3.13 (#2216) * elasticapm: properly cleanup buffer and data views With Python 3.13 our pattern of buffering revealed some issues because the underlying BytesIO fileobj may get released before gzip.GzipFile. This requires a fix in CPython but also some improvements on our side by properly closing the GzipFile in case of error and also releasing the memoryview we can from the BytesIO buffer. These problems manifests as following warnings from unraisable exceptions running tests: The closing of the gzip buffer helps with: Traceback (most recent call last): File "/usr/lib/python3.13/gzip.py", line 362, in close fileobj.write(self.compress.flush()) ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^ ValueError: I/O operation on closed file. Exception ignored in: <_io.BytesIO object at 0x7fbc4335fbf0> Traceback (most recent call last): File "/venv313/lib/python3.13/site-packages/ecs_logging/_stdlib.py", line 272, in _record_attribute def _record_attribute( BufferError: Existing exports of data: object cannot be re-sized Python 3.12 shows the same warnings with the `-X dev` flag. * Fix get_name_from_func for partialmethod in Python 3.13 Handle _partialmethod moving to __partialmethod__ in Python 3.13 --- elasticapm/transport/base.py | 3 +++ elasticapm/utils/__init__.py | 2 ++ tests/transports/test_base.py | 17 ++++++++++++----- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/elasticapm/transport/base.py b/elasticapm/transport/base.py index 24911c395..b81960907 100644 --- a/elasticapm/transport/base.py +++ b/elasticapm/transport/base.py @@ -250,6 +250,7 @@ def _flush(self, buffer, forced_flush=False) -> None: """ if not self.state.should_try(): logger.error("dropping flushed data due to transport failure back-off") + buffer.close() else: fileobj = buffer.fileobj # get a reference to the fileobj before closing the gzip file buffer.close() @@ -261,6 +262,8 @@ def _flush(self, buffer, forced_flush=False) -> None: except Exception as e: self.handle_transport_fail(e) + data.release() + def start_thread(self, pid=None) -> None: super(Transport, self).start_thread(pid=pid) if (not self._thread or self.pid != self._thread.pid) and not self._closed: diff --git a/elasticapm/utils/__init__.py b/elasticapm/utils/__init__.py index 0f7b52c0d..4403f5abd 100644 --- a/elasticapm/utils/__init__.py +++ b/elasticapm/utils/__init__.py @@ -78,6 +78,8 @@ def get_name_from_func(func: FunctionType) -> str: return "partial({})".format(get_name_from_func(func.func)) elif hasattr(func, "_partialmethod") and hasattr(func._partialmethod, "func"): return "partial({})".format(get_name_from_func(func._partialmethod.func)) + elif hasattr(func, "__partialmethod__") and hasattr(func.__partialmethod__, "func"): + return "partial({})".format(get_name_from_func(func.__partialmethod__.func)) module = func.__module__ diff --git a/tests/transports/test_base.py b/tests/transports/test_base.py index 1aa1a8941..2f77c3e95 100644 --- a/tests/transports/test_base.py +++ b/tests/transports/test_base.py @@ -107,18 +107,25 @@ def test_empty_queue_flush(mock_send, elasticapm_client): transport.close() -@mock.patch("elasticapm.transport.base.Transport.send") +@mock.patch("elasticapm.transport.base.Transport._flush") @pytest.mark.parametrize("elasticapm_client", [{"api_request_time": "5s"}], indirect=True) -def test_metadata_prepended(mock_send, elasticapm_client): +def test_metadata_prepended(mock_flush, elasticapm_client): transport = Transport(client=elasticapm_client, compress_level=0) transport.start_thread() transport.queue("error", {}, flush=True) transport.close() - assert mock_send.call_count == 1 - args, kwargs = mock_send.call_args - data = gzip.decompress(args[0]) + assert mock_flush.call_count == 1 + args, kwargs = mock_flush.call_args + buffer = args[0] + # this test used to mock send but after we fixed a leak for not releasing the memoryview containing + # the gzipped data we cannot read it anymore. So reimplement _flush and read the data ourselves + fileobj = buffer.fileobj + buffer.close() + compressed_data = fileobj.getbuffer() + data = gzip.decompress(compressed_data) data = data.decode("utf-8").split("\n") assert "metadata" in data[0] + compressed_data.release() @mock.patch("elasticapm.transport.base.Transport.send") From 0473d9b5592861ed4ca5dea43ecf6b66d4fa35ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Apr 2025 10:37:11 +0200 Subject: [PATCH 223/409] build(deps): bump wolfi/chainguard-base from `29150cd` to `c56628d` (#2264) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `29150cd` to `c56628d`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index ff7ec6586..6985ae0fd 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:29150cd940cc7f69407d978d5a19c86f4d9e67cf44e4d6ded787a497e8f27c9a +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:c56628d8102cc34eeb4aaaf6279e88d2b23775569f9deeacc915b52f28163b8f ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 8151a162b2cd8a22354a8834ecd22e0ea91ec931 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 9 Apr 2025 08:44:43 -0700 Subject: [PATCH 224/409] build(deps): bump alpine from `124c7d2` to `a8560b3` (#2251) Bumps alpine from `124c7d2` to `a8560b3`. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Riccardo Magliocchetti --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index a4752936a..9293d3347 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,3 @@ -# Pin to Alpine 3.17.3 -FROM alpine@sha256:124c7d2707904eea7431fffe91522a01e5a861a624ee31d03372cc1d138a3126 +FROM alpine@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From aabc250b2de9a454910f1e51ec5e942b6221cc47 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 9 Apr 2025 20:58:28 +0200 Subject: [PATCH 225/409] tests: bump pytest-localserver to latest (#2267) --- tests/requirements/reqs-base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/requirements/reqs-base.txt b/tests/requirements/reqs-base.txt index f59cbc088..d1105586a 100644 --- a/tests/requirements/reqs-base.txt +++ b/tests/requirements/reqs-base.txt @@ -9,7 +9,7 @@ coverage==7.3.1 ; python_version > '3.7' pytest-cov==4.0.0 ; python_version < '3.8' pytest-cov==4.1.0 ; python_version > '3.7' jinja2==3.1.5 ; python_version == '3.7' -pytest-localserver==0.5.0 +pytest-localserver==0.9.0 pytest-mock==3.6.1 ; python_version == '3.6' pytest-mock==3.10.0 ; python_version > '3.6' pytest-benchmark==3.4.1 ; python_version == '3.6' From 4495a40a07913e3657ce441a27c347cd565d9fa4 Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Thu, 10 Apr 2025 14:44:29 -0500 Subject: [PATCH 226/409] update apm links (#2268) --- docs/reference/api-reference.md | 8 ++++---- docs/reference/azure-functions-support.md | 4 ++-- docs/reference/configuration.md | 8 ++++---- docs/reference/index.md | 2 +- docs/reference/lambda-support.md | 4 ++-- docs/reference/opentelemetry-api-bridge.md | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/reference/api-reference.md b/docs/reference/api-reference.md index 23cc9cc58..63ddbb58f 100644 --- a/docs/reference/api-reference.md +++ b/docs/reference/api-reference.md @@ -64,7 +64,7 @@ except ValueError: * `exc_info`: A `(type, value, traceback)` tuple as returned by [`sys.exc_info()`](https://docs.python.org/3/library/sys.html#sys.exc_info). If not provided, it will be captured automatically. * `date`: A `datetime.datetime` object representing the occurrence time of the error. If left empty, it defaults to `datetime.datetime.utcnow()`. -* `context`: A dictionary with contextual information. This dictionary must follow the [Context](docs-content://solutions/observability/apps/elastic-apm-events-intake-api.md#apm-api-error) schema definition. +* `context`: A dictionary with contextual information. This dictionary must follow the [Context](docs-content://solutions/observability/apm/elastic-apm-events-intake-api.md#apm-api-error) schema definition. * `custom`: A dictionary of custom data you want to attach to the event. * `handled`: A boolean to indicate if this exception was handled or not. @@ -94,7 +94,7 @@ client.capture_message('Billing process succeeded.') * `stack`: If set to `True` (the default), a stacktrace from the call site will be captured. * `exc_info`: A `(type, value, traceback)` tuple as returned by [`sys.exc_info()`](https://docs.python.org/3/library/sys.html#sys.exc_info). If not provided, it will be captured automatically, if `capture_message()` was called in an `except` block. * `date`: A `datetime.datetime` object representing the occurrence time of the error. If left empty, it defaults to `datetime.datetime.utcnow()`. -* `context`: A dictionary with contextual information. This dictionary must follow the [Context](docs-content://solutions/observability/apps/elastic-apm-events-intake-api.md#apm-api-error) schema definition. +* `context`: A dictionary with contextual information. This dictionary must follow the [Context](docs-content://solutions/observability/apm/elastic-apm-events-intake-api.md#apm-api-error) schema definition. * `custom`: A dictionary of custom data you want to attach to the event. Returns the id of the message as a string. @@ -321,7 +321,7 @@ Added in v2.0.0. Attach custom contextual data to the current transaction and errors. Supported frameworks will automatically attach information about the HTTP request and the logged in user. You can attach further data using this function. ::::{tip} -Before using custom context, ensure you understand the different types of [metadata](docs-content://solutions/observability/apps/metadata.md) that are available. +Before using custom context, ensure you understand the different types of [metadata](docs-content://solutions/observability/apm/metadata.md) that are available. :::: @@ -440,7 +440,7 @@ Added in v5.0.0. Attach labels to the the current transaction and errors. ::::{tip} -Before using custom labels, ensure you understand the different types of [metadata](docs-content://solutions/observability/apps/metadata.md) that are available. +Before using custom labels, ensure you understand the different types of [metadata](docs-content://solutions/observability/apm/metadata.md) that are available. :::: diff --git a/docs/reference/azure-functions-support.md b/docs/reference/azure-functions-support.md index 88a5d7234..44a62416f 100644 --- a/docs/reference/azure-functions-support.md +++ b/docs/reference/azure-functions-support.md @@ -8,7 +8,7 @@ mapped_pages: ## Prerequisites [_prerequisites_2] -You need an APM Server to which you can send APM data. Follow the [APM Quick start](docs-content://solutions/observability/apps/fleet-managed-apm-server.md) if you have not set one up yet. For the best-possible performance, we recommend setting up APM on {{ecloud}} in the same Azure region as your Azure Functions app. +You need an APM Server to which you can send APM data. Follow the [APM Quick start](docs-content://solutions/observability/apm/get-started-fleet-managed-apm-server.md) if you have not set one up yet. For the best-possible performance, we recommend setting up APM on {{ecloud}} in the same Azure region as your Azure Functions app. ::::{note} Currently, only HTTP and timer triggers are supported. Other trigger types may be captured as well, but the amount of captured contextual data may differ. @@ -40,7 +40,7 @@ You need to add `elastic-apm` as a dependency for your Functions app. Simply add The APM Python agent is configured through [App Settings](https://learn.microsoft.com/en-us/azure/azure-functions/functions-how-to-use-azure-function-app-settings?tabs=portal#settings). These are then picked up by the agent as environment variables. -For the minimal configuration, you will need the [`ELASTIC_APM_SERVER_URL`](/reference/configuration.md#config-server-url) to set the destination for APM data and a [`ELASTIC_APM_SECRET_TOKEN`](/reference/configuration.md#config-secret-token). If you prefer to use an [APM API key](docs-content://solutions/observability/apps/api-keys.md) instead of the APM secret token, use the [`ELASTIC_APM_API_KEY`](/reference/configuration.md#config-api-key) environment variable instead of `ELASTIC_APM_SECRET_TOKEN` in the following example configuration. +For the minimal configuration, you will need the [`ELASTIC_APM_SERVER_URL`](/reference/configuration.md#config-server-url) to set the destination for APM data and a [`ELASTIC_APM_SECRET_TOKEN`](/reference/configuration.md#config-secret-token). If you prefer to use an [APM API key](docs-content://solutions/observability/apm/api-keys.md) instead of the APM secret token, use the [`ELASTIC_APM_API_KEY`](/reference/configuration.md#config-api-key) environment variable instead of `ELASTIC_APM_SECRET_TOKEN` in the following example configuration. ```bash $ az functionapp config appsettings set --settings ELASTIC_APM_SERVER_URL=https://example.apm.northeurope.azure.elastic-cloud.com:443 diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 8cc897b01..61ca512ce 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -40,7 +40,7 @@ The precedence is as follows: Configuration options marked with the ![dynamic config](/reference/images/dynamic-config.svg "") badge can be changed at runtime when set from a supported source. -The Python Agent supports [Central configuration](docs-content://solutions/observability/apps/apm-agent-central-configuration.md), which allows you to fine-tune certain configurations from in the APM app. This feature is enabled in the Agent by default with [`central_config`](#config-central_config). +The Python Agent supports [Central configuration](docs-content://solutions/observability/apm/apm-agent-central-configuration.md), which allows you to fine-tune certain configurations from in the APM app. This feature is enabled in the Agent by default with [`central_config`](#config-central_config). ## Django [django-configuration] @@ -225,7 +225,7 @@ This option allows you to set the node name manually to ensure it is unique and The name of the environment this service is deployed in, e.g. "production" or "staging". -Environments allow you to easily filter data on a global level in the APM app. It’s important to be consistent when naming environments across agents. See [environment selector](docs-content://solutions/observability/apps/filter-application-data.md#apm-filter-your-data-service-environment-filter) in the APM app for more information. +Environments allow you to easily filter data on a global level in the APM app. It’s important to be consistent when naming environments across agents. See [environment selector](docs-content://solutions/observability/apm/filter-data.md#apm-filter-your-data-service-environment-filter) in the APM app for more information. ::::{note} This feature is fully supported in the APM app in Kibana versions >= 7.2. You must use the query bar to filter for a specific environment in versions prior to 7.2. @@ -273,7 +273,7 @@ This functionality is in technical preview and may be changed or removed in a fu :::: -This base64-encoded string is used to ensure that only your agents can send data to your APM Server. The API key must be created using the [APM server command-line tool](docs-content://solutions/observability/apps/api-keys.md). +This base64-encoded string is used to ensure that only your agents can send data to your APM Server. The API key must be created using the [APM server command-line tool](docs-content://solutions/observability/apm/api-keys.md). ::::{warning} API keys only provide any real security if your APM Server uses TLS. @@ -923,7 +923,7 @@ By default in python 3, the agent installs a [LogRecord factory](/reference/logs | --- | --- | --- | | `ELASTIC_APM_USE_ELASTIC_TRACEPARENT_HEADER` | `USE_ELASTIC_TRACEPARENT_HEADER` | `True` | -To enable [distributed tracing](docs-content://solutions/observability/apps/traces.md), the agent sets a number of HTTP headers to outgoing requests made with [instrumented HTTP libraries](/reference/supported-technologies.md#automatic-instrumentation-http). These headers (`traceparent` and `tracestate`) are defined in the [W3C Trace Context](https://www.w3.org/TR/trace-context-1/) specification. +To enable [distributed tracing](docs-content://solutions/observability/apm/traces.md), the agent sets a number of HTTP headers to outgoing requests made with [instrumented HTTP libraries](/reference/supported-technologies.md#automatic-instrumentation-http). These headers (`traceparent` and `tracestate`) are defined in the [W3C Trace Context](https://www.w3.org/TR/trace-context-1/) specification. Additionally, when this setting is set to `True`, the agent will set `elasticapm-traceparent` for backwards compatibility. diff --git a/docs/reference/index.md b/docs/reference/index.md index 9e6d65f56..9e7eed840 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -24,5 +24,5 @@ More detailed information on how the Agent works can be found in the [advanced t ## Additional components [additional-components] -APM Agents work in conjunction with the [APM Server](docs-content://solutions/observability/apps/application-performance-monitoring-apm.md), [Elasticsearch](docs-content://get-started/introduction.md#what-is-es), and [Kibana](docs-content://get-started/introduction.md#what-is-kib). The [APM documentation](docs-content://solutions/observability/apps/application-performance-monitoring-apm.md) provides details on how these components work together, and provides a matrix outlining [Agent and Server compatibility](docs-content://solutions/observability/apps/apm-agent-compatibility.md). +APM Agents work in conjunction with the [APM Server](docs-content://solutions/observability/apm/index.md), [Elasticsearch](docs-content://get-started/introduction.md#what-is-es), and [Kibana](docs-content://get-started/introduction.md#what-is-kib). The [APM documentation](docs-content://solutions/observability/apm/index.md) provides details on how these components work together, and provides a matrix outlining [Agent and Server compatibility](docs-content://solutions/observability/apm/apm-agent-compatibility.md). diff --git a/docs/reference/lambda-support.md b/docs/reference/lambda-support.md index 60f9eb114..6d6799287 100644 --- a/docs/reference/lambda-support.md +++ b/docs/reference/lambda-support.md @@ -17,7 +17,7 @@ The Centralized Agent Configuration on the Elasticsearch APM currently does NOT ## Prerequisites [_prerequisites] -You need an APM Server to send APM data to. Follow the [APM Quick start](docs-content://solutions/observability/apps/fleet-managed-apm-server.md) if you have not set one up yet. For the best-possible performance, we recommend setting up APM on {{ecloud}} in the same AWS region as your AWS Lambda functions. +You need an APM Server to send APM data to. Follow the [APM Quick start](docs-content://solutions/observability/apm/get-started-fleet-managed-apm-server.md) if you have not set one up yet. For the best-possible performance, we recommend setting up APM on {{ecloud}} in the same AWS region as your AWS Lambda functions. ## Step 1: Add the APM Layers to your Lambda function [add_the_apm_layers_to_your_lambda_function] @@ -133,7 +133,7 @@ COPY --from=python-agent /opt/python/ /opt/python/ The {{apm-lambda-ext}} and the APM Python agent are configured through environment variables on the AWS Lambda function. -For the minimal configuration, you will need the *APM Server URL* to set the destination for APM data and an [APM Secret Token](docs-content://solutions/observability/apps/secret-token.md). If you prefer to use an [APM API key](docs-content://solutions/observability/apps/api-keys.md) instead of the APM secret token, use the `ELASTIC_APM_API_KEY` environment variable instead of `ELASTIC_APM_SECRET_TOKEN` in the following configuration. +For the minimal configuration, you will need the *APM Server URL* to set the destination for APM data and an [APM Secret Token](docs-content://solutions/observability/apm/secret-token.md). If you prefer to use an [APM API key](docs-content://solutions/observability/apm/api-keys.md) instead of the APM secret token, use the `ELASTIC_APM_API_KEY` environment variable instead of `ELASTIC_APM_SECRET_TOKEN` in the following configuration. For production environments, we recommend [using the AWS Secrets Manager to store your APM authentication key](apm-aws-lambda://reference/aws-lambda-secrets-manager.md) instead of providing the secret value as plaintext in the environment variables. diff --git a/docs/reference/opentelemetry-api-bridge.md b/docs/reference/opentelemetry-api-bridge.md index 7564f85fa..1d7c3e6f9 100644 --- a/docs/reference/opentelemetry-api-bridge.md +++ b/docs/reference/opentelemetry-api-bridge.md @@ -7,7 +7,7 @@ mapped_pages: The Elastic APM OpenTelemetry bridge allows you to create Elastic APM `Transactions` and `Spans`, using the OpenTelemetry API. This allows users to utilize the Elastic APM agent’s automatic instrumentations, while keeping custom instrumentations vendor neutral. -If a span is created while there is no transaction active, it will result in an Elastic APM [`Transaction`](docs-content://solutions/observability/apps/transactions.md). Inner spans are mapped to Elastic APM [`Span`](docs-content://solutions/observability/apps/spans.md). +If a span is created while there is no transaction active, it will result in an Elastic APM [`Transaction`](docs-content://solutions/observability/apm/transactions.md). Inner spans are mapped to Elastic APM [`Span`](docs-content://solutions/observability/apm/spans.md). ## Getting started [opentelemetry-getting-started] From c9248cf1bb1271fdd807365bda0d0c6d9d4b6910 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Apr 2025 16:58:13 +0200 Subject: [PATCH 227/409] build(deps): bump wolfi/chainguard-base from `c56628d` to `1c7f5aa` (#2271) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `c56628d` to `1c7f5aa`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 6985ae0fd..e7f3d4502 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:c56628d8102cc34eeb4aaaf6279e88d2b23775569f9deeacc915b52f28163b8f +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:1c7f5aa0e7997455b8500d095c7a90e617102d3941eb0757ac62cfea509e09b9 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From bc5106ae38704de0026ba1806981f826b9d3e2d6 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 14 Apr 2025 17:47:45 +0200 Subject: [PATCH 228/409] tests: skip client verify_server_cert disabling tests in fips mode (#2270) --- tests/client/client_tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/client/client_tests.py b/tests/client/client_tests.py index a61248c85..62e10d301 100644 --- a/tests/client/client_tests.py +++ b/tests/client/client_tests.py @@ -47,6 +47,7 @@ import elasticapm from elasticapm.base import Client +from elasticapm.conf import _in_fips_mode from elasticapm.conf.constants import ERROR try: @@ -350,6 +351,7 @@ def test_call_end_twice(elasticapm_client): elasticapm_client.end_transaction("test-transaction", 200) +@pytest.mark.skipif(_in_fips_mode() is True, reason="cannot disable verify_server_cert in fips mode") @pytest.mark.parametrize("elasticapm_client", [{"verify_server_cert": False}], indirect=True) def test_client_disables_ssl_verification(elasticapm_client): assert not elasticapm_client.config.verify_server_cert From ef9c62fce6c68d65f67a63278d543c691faee4de Mon Sep 17 00:00:00 2001 From: Kaarina Tungseth Date: Mon, 14 Apr 2025 13:14:38 -0500 Subject: [PATCH 229/409] [DOCS] Specifies no known issues (#2272) * [DOCS] Specifies no known issues * Changes PHP to Python --- docs/release-notes/known-issues.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/release-notes/known-issues.md b/docs/release-notes/known-issues.md index 5d9e80884..cc6f71b04 100644 --- a/docs/release-notes/known-issues.md +++ b/docs/release-notes/known-issues.md @@ -4,6 +4,8 @@ navigation_title: "Known issues" # Elastic APM Python Agent known issues [elastic-apm-python-agent-known-issues] +Known issues are significant defects or limitations that may impact your implementation. These issues are actively being worked on and will be addressed in a future release. Review the Elastic APM Python Agent known issues to help you make informed decisions, such as upgrading to a new version. + % Use the following template to add entries to this page. % :::{dropdown} Title of known issue @@ -17,3 +19,5 @@ navigation_title: "Known issues" % On [Month/Day/Year], this issue was resolved. ::: + +_No known issues_ \ No newline at end of file From 693318c3bb28e53e85d96352136b280e522a592d Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Tue, 15 Apr 2025 17:17:33 -0500 Subject: [PATCH 230/409] fix image paths for docs-assembler (#2274) --- docs/reference/configuration.md | 58 ++++++++++++++++----------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 61ca512ce..8da75ec6d 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -29,7 +29,7 @@ ELASTIC_APM = { The precedence is as follows: -* [Central configuration](#config-central_config) (supported options are marked with [![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration)) +* [Central configuration](#config-central_config) (supported options are marked with [![dynamic config](images/dynamic-config.svg "") ](#dynamic-configuration)) * Environment variables * Inline configuration * Framework-specific configuration @@ -38,7 +38,7 @@ The precedence is as follows: ## Dynamic configuration [dynamic-configuration] -Configuration options marked with the ![dynamic config](/reference/images/dynamic-config.svg "") badge can be changed at runtime when set from a supported source. +Configuration options marked with the ![dynamic config](images/dynamic-config.svg "") badge can be changed at runtime when set from a supported source. The Python Agent supports [Central configuration](docs-content://solutions/observability/apm/apm-agent-central-configuration.md), which allows you to fine-tune certain configurations from in the APM app. This feature is enabled in the Agent by default with [`central_config`](#config-central_config). @@ -94,7 +94,7 @@ The service name must conform to this regular expression: `^[a-zA-Z0-9 _-]+$`. I | --- | --- | --- | | `ELASTIC_APM_SERVER_URL` | `SERVER_URL` | `'http://127.0.0.1:8200'` | -The URL for your APM Server. The URL must be fully qualified, including protocol (`http` or `https`) and port. Note: Do not set this if you are using APM in an AWS lambda function. APM Agents are designed to proxy their calls to the APM Server through the lambda extension. Instead, set `ELASTIC_APM_LAMBDA_APM_SERVER`. For more info, see [AWS Lambda](/reference/lambda-support.md). +The URL for your APM Server. The URL must be fully qualified, including protocol (`http` or `https`) and port. Note: Do not set this if you are using APM in an AWS lambda function. APM Agents are designed to proxy their calls to the APM Server through the lambda extension. Instead, set `ELASTIC_APM_LAMBDA_APM_SERVER`. For more info, see [AWS Lambda](lambda-support.md). ## `enabled` [config-enabled] @@ -108,7 +108,7 @@ Enable or disable the agent. When set to false, the agent will not collect any d ## `recording` [config-recording] -[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -122,7 +122,7 @@ Enable or disable recording of events. If set to false, then the Python agent do ### `log_level` [config-log_level] -[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -320,7 +320,7 @@ A list of exception types to be filtered. Exceptions of these types will not be ### `transaction_ignore_urls` [config-transaction-ignore-urls] -[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | Example | | --- | --- | --- | --- | @@ -460,7 +460,7 @@ Especially for spans, collecting source code can have a large impact on storage ### `capture_body` [config-capture-body] -[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -482,7 +482,7 @@ Request bodies often contain sensitive values like passwords and credit card num ### `capture_headers` [config-capture-headers] -[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -493,14 +493,14 @@ For transactions and errors that happen due to HTTP requests, the Python agent c Possible values: `true`, `false` ::::{warning} -Request headers often contain sensitive values like session IDs and cookies. See [sanitizing data](/reference/sanitizing-data.md) for more information on how to filter out sensitive data. +Request headers often contain sensitive values like session IDs and cookies. See [sanitizing data](sanitizing-data.md) for more information on how to filter out sensitive data. :::: ### `transaction_max_spans` [config-transaction-max-spans] -[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -511,7 +511,7 @@ This limits the amount of spans that are recorded per transaction. This is helpf ### `stack_trace_limit` [config-stack-trace-limit] -[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -524,7 +524,7 @@ Setting the limit to `0` will disable stack trace collection, while any positive ### `span_stack_trace_min_duration` [config-span-stack-trace-min-duration] -[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -541,7 +541,7 @@ Except for the special values `-1` and `0`, this setting should be provided in * ### `span_frames_min_duration` [config-span-frames-min-duration] -[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -555,7 +555,7 @@ This config value is being deprecated. Use [`span_stack_trace_min_duration`](#co ### `span_compression_enabled` [config-span-compression-enabled] -[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -568,7 +568,7 @@ If enabled, the agent will compress very short, repeated spans into a single spa ### `span_compression_exact_match_max_duration` [config-span-compression-exact-match-max_duration] -[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -581,7 +581,7 @@ Two spans are considered exact matches if the following attributes are identical ### `span_compression_same_kind_max_duration` [config-span-compression-same-kind-max-duration] -[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -594,7 +594,7 @@ Two spans are considered to be of the same kind if the following attributes are ### `exit_span_min_duration` [config-exit-span-min-duration] -[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -612,7 +612,7 @@ if a span propagates distributed tracing IDs, it will not be ignored, even if it ### `api_request_size` [config-api-request-size] -[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -630,7 +630,7 @@ Due to internal buffering of gzip, the actual request size can be a few kilobyte ### `api_request_time` [config-api-request-time] -[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -652,7 +652,7 @@ The actual time will vary between 90-110% of the given value, to avoid stampedes | --- | --- | --- | | `ELASTIC_APM_PROCESSORS` | `PROCESSORS` | `['elasticapm.processors.sanitize_stacktrace_locals', 'elasticapm.processors.sanitize_http_request_cookies', 'elasticapm.processors.sanitize_http_headers', 'elasticapm.processors.sanitize_http_wsgi_env', 'elasticapm.processors.sanitize_http_request_body']` | -A list of processors to process transactions and errors. For more information, see [Sanitizing Data](/reference/sanitizing-data.md). +A list of processors to process transactions and errors. For more information, see [Sanitizing Data](sanitizing-data.md). ::::{warning} We recommend always including the default set of validators if you customize this setting. @@ -662,13 +662,13 @@ We recommend always including the default set of validators if you customize thi ### `sanitize_field_names` [config-sanitize-field-names] -[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | | `ELASTIC_APM_SANITIZE_FIELD_NAMES` | `SANITIZE_FIELD_NAMES` | `["password", "passwd", "pwd", "secret", "*key", "*token*", "*session*", "*credit*", "*card*", "*auth*", "*principal*", "set-cookie"]` | -A list of glob-matched field names to match and mask when using processors. For more information, see [Sanitizing Data](/reference/sanitizing-data.md). +A list of glob-matched field names to match and mask when using processors. For more information, see [Sanitizing Data](sanitizing-data.md). ::::{warning} We recommend always including the default set of field name matches if you customize this setting. @@ -678,7 +678,7 @@ We recommend always including the default set of field name matches if you custo ### `transaction_sample_rate` [config-transaction-sample-rate] -[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | @@ -843,7 +843,7 @@ This feature requires APM Server and Kibana >= 7.3. Enable/disable the tracking and collection of metrics from `prometheus_client`. -See [Prometheus metric set (beta)](/reference/metrics.md#prometheus-metricset) for more information. +See [Prometheus metric set (beta)](metrics.md#prometheus-metricset) for more information. ::::{note} This feature is currently in beta status. @@ -859,7 +859,7 @@ This feature is currently in beta status. A prefix to prepend to Prometheus metrics names. -See [Prometheus metric set (beta)](/reference/metrics.md#prometheus-metricset) for more information. +See [Prometheus metric set (beta)](metrics.md#prometheus-metricset) for more information. ::::{note} This feature is currently in beta status. @@ -875,7 +875,7 @@ This feature is currently in beta status. List of import paths for the MetricSets that should be used to collect metrics. -See [Custom Metrics](/reference/metrics.md#custom-metrics) for more information. +See [Custom Metrics](metrics.md#custom-metrics) for more information. ### `central_config` [config-central_config] @@ -914,7 +914,7 @@ This feature requires APM Server >= 7.2. | --- | --- | --- | | `ELASTIC_APM_DISABLE_LOG_RECORD_FACTORY` | `DISABLE_LOG_RECORD_FACTORY` | `False` | -By default in python 3, the agent installs a [LogRecord factory](/reference/logs.md#logging) that automatically adds tracing fields to your log records. Disable this behavior by setting this to `True`. +By default in python 3, the agent installs a [LogRecord factory](logs.md#logging) that automatically adds tracing fields to your log records. Disable this behavior by setting this to `True`. ### `use_elastic_traceparent_header` [config-use-elastic-traceparent-header] @@ -923,14 +923,14 @@ By default in python 3, the agent installs a [LogRecord factory](/reference/logs | --- | --- | --- | | `ELASTIC_APM_USE_ELASTIC_TRACEPARENT_HEADER` | `USE_ELASTIC_TRACEPARENT_HEADER` | `True` | -To enable [distributed tracing](docs-content://solutions/observability/apm/traces.md), the agent sets a number of HTTP headers to outgoing requests made with [instrumented HTTP libraries](/reference/supported-technologies.md#automatic-instrumentation-http). These headers (`traceparent` and `tracestate`) are defined in the [W3C Trace Context](https://www.w3.org/TR/trace-context-1/) specification. +To enable [distributed tracing](docs-content://solutions/observability/apm/traces.md), the agent sets a number of HTTP headers to outgoing requests made with [instrumented HTTP libraries](supported-technologies.md#automatic-instrumentation-http). These headers (`traceparent` and `tracestate`) are defined in the [W3C Trace Context](https://www.w3.org/TR/trace-context-1/) specification. Additionally, when this setting is set to `True`, the agent will set `elasticapm-traceparent` for backwards compatibility. ### `trace_continuation_strategy` [config-trace-continuation-strategy] -[![dynamic config](/reference/images/dynamic-config.svg "") ](#dynamic-configuration) +[![dynamic config](images/dynamic-config.svg "") ](#dynamic-configuration) | Environment | Django/Flask | Default | | --- | --- | --- | From 099fefe411cd449af9a1ea35529fb66c51de0b7b Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Thu, 17 Apr 2025 09:46:27 +0200 Subject: [PATCH 231/409] chore: deps(updatecli): Bump updatecli version to v0.98.0 (#2276) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 7d99e82c3..e337dddca 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.97.0 \ No newline at end of file +updatecli v0.98.0 \ No newline at end of file From 2bed72083d5e3ff92e6e16c70ca102863acc883a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 12:43:55 +0200 Subject: [PATCH 232/409] build(deps): bump docker/build-push-action (#2281) Bumps the github-actions group with 1 update in the / directory: [docker/build-push-action](https://github.com/docker/build-push-action). Updates `docker/build-push-action` from 6.15.0 to 6.16.0 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/471d1dc4e07e5cdedd4c2171150001c434f0b7a4...14487ce63c7a62a4a324b0bfb37086795e31c6c1) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-version: 6.16.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ac7e05c75..fff79082b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -146,7 +146,7 @@ jobs: - name: Build and push image id: docker-push - uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0 + uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0 with: context: . platforms: linux/amd64,linux/arm64 From 796880c3499c4e3c03a86bd9b9e2a2c847670aaf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 17:25:24 +0200 Subject: [PATCH 233/409] build(deps): bump wolfi/chainguard-base from `1c7f5aa` to `67d82bc` (#2280) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `1c7f5aa` to `67d82bc`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index e7f3d4502..e2a828bd7 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:1c7f5aa0e7997455b8500d095c7a90e617102d3941eb0757ac62cfea509e09b9 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:67d82bc56a9c34572abe331c14f5e4b23a284d94a5bc1ea3be64f991ced51892 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From a172025e4953f69d4609d28a46ae5e51f3c934c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 17:26:05 +0200 Subject: [PATCH 234/409] build(deps): bump certifi from 2025.1.31 to 2025.4.26 in /dev-utils (#2282) Bumps [certifi](https://github.com/certifi/python-certifi) from 2025.1.31 to 2025.4.26. - [Commits](https://github.com/certifi/python-certifi/compare/2025.01.31...2025.04.26) --- updated-dependencies: - dependency-name: certifi dependency-version: 2025.4.26 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dev-utils/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-utils/requirements.txt b/dev-utils/requirements.txt index de69d7314..e0727cdef 100644 --- a/dev-utils/requirements.txt +++ b/dev-utils/requirements.txt @@ -1,4 +1,4 @@ # These are the pinned requirements for the lambda layer/docker image -certifi==2025.1.31 +certifi==2025.4.26 urllib3==1.26.20 wrapt==1.14.1 From abe4bb3ee21520cf530c27b7db5873dece6bb7b5 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 5 May 2025 14:30:21 +0200 Subject: [PATCH 235/409] github-actions: use GitHub container registry (#2266) --- .ci/docker/README.md | 2 +- .ci/docker/util.sh | 1 + .github/workflows/build-images.yml | 35 ++++++++++++++++++++++++++++++ .github/workflows/run-matrix.yml | 4 ++++ tests/Dockerfile | 3 +++ tests/docker-compose.yml | 2 +- tests/scripts/docker/run_tests.sh | 17 ++++++++++----- 7 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/build-images.yml diff --git a/.ci/docker/README.md b/.ci/docker/README.md index 1241a7f05..9ca435c1c 100644 --- a/.ci/docker/README.md +++ b/.ci/docker/README.md @@ -2,7 +2,7 @@ Utility script for building and pushing the images based on `.ci/.matrix_python_full.yml`. -> :information_source: This script is mainly used in [publish-docker-images](https://github.com/elastic/apm-pipeline-library/actions/workflows/publish-docker-images.yml) workflow, +> :information_source: This script is mainly used in [publish-docker-images](https://github.com/elastic/apm-agent-python/actions/workflows/build-images.yml) workflow, which can be triggered safely at any time. ## Options diff --git a/.ci/docker/util.sh b/.ci/docker/util.sh index 9326a5773..865e4d884 100755 --- a/.ci/docker/util.sh +++ b/.ci/docker/util.sh @@ -44,6 +44,7 @@ for version in $versions; do case $ACTION in build) DOCKER_BUILDKIT=1 docker build \ + --progress=plain \ --cache-from="${full_image_name}" \ -f "${project_root}/tests/Dockerfile" \ --build-arg PYTHON_IMAGE="${version/-/:}" \ diff --git a/.github/workflows/build-images.yml b/.github/workflows/build-images.yml new file mode 100644 index 000000000..44dcb39be --- /dev/null +++ b/.github/workflows/build-images.yml @@ -0,0 +1,35 @@ +--- +name: build-images + +on: + workflow_dispatch: ~ + +permissions: + contents: read + +jobs: + + build-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }}/apm-agent-python-testing + steps: + + - uses: actions/checkout@v4 + + - name: Login to ghcr.io + uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - run: ./util.sh --action build --registry ${{ env.REGISTRY }} --name ${{ env.IMAGE_NAME }} + working-directory: .ci/docker + + - run: ./util.sh --action push --registry ${{ env.REGISTRY }} --name ${{ env.IMAGE_NAME }} + working-directory: .ci/docker diff --git a/.github/workflows/run-matrix.yml b/.github/workflows/run-matrix.yml index 0b31f4318..3dc3befb7 100644 --- a/.github/workflows/run-matrix.yml +++ b/.github/workflows/run-matrix.yml @@ -20,6 +20,10 @@ jobs: max-parallel: 10 matrix: include: ${{ fromJSON(inputs.include) }} + env: + # These env variables are used in the docker-compose.yml and the run_tests.sh script. + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }}/apm-agent-python-testing steps: - uses: actions/checkout@v4 - name: Run tests diff --git a/tests/Dockerfile b/tests/Dockerfile index cf1a8e30b..c5cd8050a 100644 --- a/tests/Dockerfile +++ b/tests/Dockerfile @@ -44,4 +44,7 @@ RUN chmod +x /usr/local/bin/entrypoint.sh WORKDIR /app +# configure the label to help with the GitHub container registry +LABEL org.opencontainers.image.source https://github.com/elastic/apm-agent-python + ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index c6598a969..b65a97e9e 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -185,7 +185,7 @@ services: - zookeeper run_tests: - image: elasticobservability/apm-agent-python-testing:${PYTHON_VERSION} + image: ${REGISTRY:-elasticobservability}/${IMAGE_NAME:-apm-agent-python-testing}:${PYTHON_VERSION} environment: ES_8_URL: 'http://elasticsearch8:9200' ES_7_URL: 'http://elasticsearch7:9200' diff --git a/tests/scripts/docker/run_tests.sh b/tests/scripts/docker/run_tests.sh index de518dc32..9de251f02 100755 --- a/tests/scripts/docker/run_tests.sh +++ b/tests/scripts/docker/run_tests.sh @@ -2,7 +2,7 @@ set -ex function cleanup { - PYTHON_VERSION=${1} docker compose down -v + PYTHON_VERSION=${1} REGISTRY=${REGISTRY} IMAGE_NAME=${IMAGE_NAME} docker compose down -v if [[ $CODECOV_TOKEN ]]; then cd .. @@ -21,6 +21,8 @@ docker_pip_cache="/tmp/cache/pip" TEST="${1}/${2}" LOCAL_USER_ID=${LOCAL_USER_ID:=$(id -u)} LOCAL_GROUP_ID=${LOCAL_GROUP_ID:=$(id -g)} +IMAGE_NAME=${IMAGE_NAME:-"apm-agent-python-testing"} +REGISTRY=${REGISTRY:-"elasticobservability"} cd tests @@ -38,26 +40,27 @@ else fi fi -echo "Running tests for ${1}/${2}" +echo "Running tests for ${TEST}" if [[ -n $DOCKER_DEPS ]] then - PYTHON_VERSION=${1} docker compose up -d ${DOCKER_DEPS} + PYTHON_VERSION=${1} REGISTRY=${REGISTRY} IMAGE_NAME=${IMAGE_NAME} docker compose up --quiet-pull -d ${DOCKER_DEPS} fi # CASS_DRIVER_NO_EXTENSIONS is set so we don't build the Cassandra C-extensions, # as this can take several minutes if ! ${CI}; then + full_image_name="${REGISTRY}/${IMAGE_NAME}:${1}" DOCKER_BUILDKIT=1 docker build \ --progress=plain \ - --cache-from="elasticobservability/apm-agent-python-testing:${1}" \ + --cache-from="${full_image_name}" \ --build-arg PYTHON_IMAGE="${1/-/:}" \ - --tag "elasticobservability/apm-agent-python-testing:${1}" \ + --tag "${full_image_name}" \ . fi -PYTHON_VERSION=${1} docker compose run \ +PYTHON_VERSION=${1} docker compose run --quiet-pull \ -e PYTHON_FULL_VERSION=${1} \ -e LOCAL_USER_ID=$LOCAL_USER_ID \ -e LOCAL_GROUP_ID=$LOCAL_GROUP_ID \ @@ -67,6 +70,8 @@ PYTHON_VERSION=${1} docker compose run \ -e WITH_COVERAGE=true \ -e CASS_DRIVER_NO_EXTENSIONS=1 \ -e PYTEST_JUNIT="--junitxml=/app/tests/docker-${1}-${2}-python-agent-junit.xml" \ + -e REGISTRY=${REGISTRY} \ + -e IMAGE_NAME=${IMAGE_NAME} \ -v ${pip_cache}:$(dirname ${docker_pip_cache}) \ -v "$(dirname $(pwd))":/app \ --rm run_tests \ From 2991a8e24b599ff82a6f7b3864463c40033f190b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 14:38:05 +0200 Subject: [PATCH 236/409] build(deps): bump actions/attest-build-provenance (#2284) Bumps the github-actions group with 1 update in the / directory: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance). Updates `actions/attest-build-provenance` from 2.2.3 to 2.3.0 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/c074443f1aee8d4aeeae555aebba3282517141b2...db473fddc028af60658334401dc6fa3ffd8669fd) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-version: 2.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fff79082b..422b9387c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/packages - name: generate build provenance - uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3 + uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0 with: subject-path: "${{ github.workspace }}/dist/*" @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/build-distribution - name: generate build provenance - uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3 + uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0 with: subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" @@ -158,7 +158,7 @@ jobs: AGENT_DIR=./build/dist/package/python - name: generate build provenance (containers) - uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v2.2.3 + uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0 with: subject-name: "${{ env.DOCKER_IMAGE_NAME }}" subject-digest: ${{ steps.docker-push.outputs.digest }} From 1ae1ec85166f8f06c99c4ceb19280981fe509a01 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 5 May 2025 16:14:28 +0200 Subject: [PATCH 237/409] ci: don't fail testing docker image build without a cached image (#2286) --- .ci/docker/util.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.ci/docker/util.sh b/.ci/docker/util.sh index 865e4d884..458f77f29 100755 --- a/.ci/docker/util.sh +++ b/.ci/docker/util.sh @@ -43,9 +43,13 @@ for version in $versions; do case $ACTION in build) + cache_image="${full_image_name}" + # check that we have an image before using it as a cache + docker manifest inspect "${full_image_name}" || cache_image= + DOCKER_BUILDKIT=1 docker build \ --progress=plain \ - --cache-from="${full_image_name}" \ + --cache-from="${cache_image}" \ -f "${project_root}/tests/Dockerfile" \ --build-arg PYTHON_IMAGE="${version/-/:}" \ -t "${full_image_name}" \ From 4ce0559cbc82084ac55b9c7c9da57a28f111d60f Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 5 May 2025 17:45:30 +0200 Subject: [PATCH 238/409] requirements: stick to Werkzeug < 3 for flask 2.1 and 2.2 (#2285) --- tests/requirements/reqs-flask-2.1.txt | 1 + tests/requirements/reqs-flask-2.2.txt | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/requirements/reqs-flask-2.1.txt b/tests/requirements/reqs-flask-2.1.txt index 84d89b8b9..6acf72eb6 100644 --- a/tests/requirements/reqs-flask-2.1.txt +++ b/tests/requirements/reqs-flask-2.1.txt @@ -1,4 +1,5 @@ Flask>=2.1,<2.2 +Werkzeug<3 blinker>=1.1 itsdangerous -r reqs-base.txt diff --git a/tests/requirements/reqs-flask-2.2.txt b/tests/requirements/reqs-flask-2.2.txt index 0b244a851..e6307fded 100644 --- a/tests/requirements/reqs-flask-2.2.txt +++ b/tests/requirements/reqs-flask-2.2.txt @@ -1,4 +1,5 @@ Flask>=2.2,<2.3 +Werkzeug<3 blinker>=1.1 itsdangerous -r reqs-base.txt From 6e79419d4f0d3390f69df35981715ddf0fe11786 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Tue, 6 May 2025 09:12:43 +0200 Subject: [PATCH 239/409] chore: deps(updatecli): Bump updatecli version to v0.99.0 (#2287) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index e337dddca..0b4ee7806 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.98.0 \ No newline at end of file +updatecli v0.99.0 \ No newline at end of file From 269cbc3c6ef20758931cb6c89ce4817cd1598b41 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 7 May 2025 18:40:32 +0200 Subject: [PATCH 240/409] Add official support for Python 3.13 (#2265) * Add official support for Python 3.13 And start running CI against it. * Mark aws lambda to support python 3.12 and 3.13 * More older django exclusion from matrix --- .ci/.matrix_exclude.yml | 54 ++++++++++++++++++++++-- .ci/.matrix_python.yml | 2 +- .ci/.matrix_python_full.yml | 1 + .ci/publish-aws.sh | 2 +- Makefile | 2 +- docs/reference/supported-technologies.md | 1 + setup.cfg | 1 + 7 files changed, 57 insertions(+), 6 deletions(-) diff --git a/.ci/.matrix_exclude.yml b/.ci/.matrix_exclude.yml index 0349959a1..c8477b6ca 100644 --- a/.ci/.matrix_exclude.yml +++ b/.ci/.matrix_exclude.yml @@ -69,6 +69,12 @@ exclude: FRAMEWORK: celery-5-django-3 - VERSION: python-3.12 # https://github.com/celery/billiard/issues/377 FRAMEWORK: celery-5-django-4 + - VERSION: python-3.13 # https://github.com/celery/billiard/issues/377 + FRAMEWORK: celery-5-flask-2 + - VERSION: python-3.13 # https://github.com/celery/billiard/issues/377 + FRAMEWORK: celery-5-django-3 + - VERSION: python-3.13 # https://github.com/celery/billiard/issues/377 + FRAMEWORK: celery-5-django-4 - VERSION: python-3.10 FRAMEWORK: graphene-2 - VERSION: python-3.10 @@ -101,6 +107,10 @@ exclude: FRAMEWORK: django-2.0 - VERSION: python-3.12 FRAMEWORK: django-2.1 + - VERSION: python-3.13 + FRAMEWORK: django-3.2 + - VERSION: python-3.13 + FRAMEWORK: django-4.0 - VERSION: python-3.12 FRAMEWORK: graphene-2 - VERSION: python-3.12 @@ -111,6 +121,22 @@ exclude: FRAMEWORK: cassandra-3.4 - VERSION: python-3.12 FRAMEWORK: pymongo-3.5 + - VERSION: python-3.13 + FRAMEWORK: django-1.11 + - VERSION: python-3.13 + FRAMEWORK: django-2.0 + - VERSION: python-3.13 + FRAMEWORK: django-2.1 + - VERSION: python-3.13 + FRAMEWORK: graphene-2 + - VERSION: python-3.13 + FRAMEWORK: aiohttp-3.0 + - VERSION: python-3.13 + FRAMEWORK: aiohttp-4.0 + - VERSION: python-3.13 + FRAMEWORK: cassandra-3.4 + - VERSION: python-3.13 + FRAMEWORK: pymongo-3.5 # pymongo - VERSION: python-3.10 FRAMEWORK: pymongo-3.1 @@ -118,18 +144,24 @@ exclude: FRAMEWORK: pymongo-3.1 - VERSION: python-3.12 FRAMEWORK: pymongo-3.1 + - VERSION: python-3.13 + FRAMEWORK: pymongo-3.1 - VERSION: python-3.10 FRAMEWORK: pymongo-3.2 - VERSION: python-3.11 FRAMEWORK: pymongo-3.2 - VERSION: python-3.12 FRAMEWORK: pymongo-3.2 + - VERSION: python-3.13 + FRAMEWORK: pymongo-3.2 - VERSION: python-3.10 FRAMEWORK: pymongo-3.3 - VERSION: python-3.11 FRAMEWORK: pymongo-3.3 - VERSION: python-3.12 FRAMEWORK: pymongo-3.3 + - VERSION: python-3.13 + FRAMEWORK: pymongo-3.3 - VERSION: python-3.8 FRAMEWORK: pymongo-3.4 - VERSION: python-3.9 @@ -140,6 +172,8 @@ exclude: FRAMEWORK: pymongo-3.4 - VERSION: python-3.12 FRAMEWORK: pymongo-3.4 + - VERSION: python-3.13 + FRAMEWORK: pymongo-3.4 - VERSION: pypy-3 FRAMEWORK: pymongo-3.0 # pymssql @@ -163,6 +197,10 @@ exclude: FRAMEWORK: boto3-1.5 - VERSION: python-3.12 FRAMEWORK: boto3-1.6 + - VERSION: python-3.13 + FRAMEWORK: boto3-1.5 + - VERSION: python-3.13 + FRAMEWORK: boto3-1.6 # aiohttp client, only supported in Python 3.7+ - VERSION: pypy-3 FRAMEWORK: aiohttp-3.0 @@ -254,11 +292,21 @@ exclude: FRAMEWORK: twisted-16 - VERSION: python-3.12 FRAMEWORK: twisted-15 + - VERSION: python-3.13 + FRAMEWORK: twisted-18 + - VERSION: python-3.13 + FRAMEWORK: twisted-17 + - VERSION: python-3.13 + FRAMEWORK: twisted-16 + - VERSION: python-3.13 + FRAMEWORK: twisted-15 # pylibmc - VERSION: python-3.11 FRAMEWORK: pylibmc-1.4 - VERSION: python-3.12 FRAMEWORK: pylibmc-1.4 + - VERSION: python-3.13 + FRAMEWORK: pylibmc-1.4 # grpc - VERSION: python-3.6 FRAMEWORK: grpc-newest @@ -274,6 +322,8 @@ exclude: FRAMEWORK: grpc-1.24 - VERSION: python-3.12 FRAMEWORK: grpc-1.24 + - VERSION: python-3.13 + FRAMEWORK: grpc-1.24 - VERSION: python-3.7 FRAMEWORK: flask-1.0 - VERSION: python-3.7 @@ -283,7 +333,5 @@ exclude: # TODO py3.12 - VERSION: python-3.12 FRAMEWORK: sanic-20.12 # no wheels available yet - - VERSION: python-3.12 - FRAMEWORK: kafka-python-newest # https://github.com/dpkp/kafka-python/pull/2376 - - VERSION: python-3.12 + - VERSION: python-3.13 FRAMEWORK: cassandra-newest # c extension issue diff --git a/.ci/.matrix_python.yml b/.ci/.matrix_python.yml index dbb9c7bf6..86c87ad88 100644 --- a/.ci/.matrix_python.yml +++ b/.ci/.matrix_python.yml @@ -1,3 +1,3 @@ VERSION: - python-3.6 - - python-3.12 + - python-3.13 diff --git a/.ci/.matrix_python_full.yml b/.ci/.matrix_python_full.yml index 03fead7ab..bb763b7ca 100644 --- a/.ci/.matrix_python_full.yml +++ b/.ci/.matrix_python_full.yml @@ -6,4 +6,5 @@ VERSION: - python-3.10 - python-3.11 - python-3.12 + - python-3.13 # - pypy-3 # excluded due to build issues with SQLite/Django diff --git a/.ci/publish-aws.sh b/.ci/publish-aws.sh index aac092bad..3bb7a554c 100755 --- a/.ci/publish-aws.sh +++ b/.ci/publish-aws.sh @@ -46,7 +46,7 @@ for region in $ALL_AWS_REGIONS; do --layer-name="${FULL_LAYER_NAME}" \ --description="AWS Lambda Extension Layer for the Elastic APM Python Agent" \ --license-info="BSD-3-Clause" \ - --compatible-runtimes python3.6 python3.7 python3.8 python3.9 python3.10 python3.11\ + --compatible-runtimes python3.6 python3.7 python3.8 python3.9 python3.10 python3.11 python3.12 python3.13\ --zip-file="fileb://${zip_file}") echo "${publish_output}" > "${AWS_FOLDER}/${region}" layer_version=$(echo "${publish_output}" | jq '.Version') diff --git a/Makefile b/Makefile index 51f7a4eb6..b2d00f400 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ test: # delete any __pycache__ folders to avoid hard-to-debug caching issues find . -type f -name '*.py[co]' -delete -o -type d -name __pycache__ -delete # pypy3 should be added to the first `if` once it supports py3.7 - if [[ "$$PYTHON_VERSION" =~ ^(3.7|3.8|3.9|3.10|3.11|3.12|nightly)$$ ]] ; then \ + if [[ "$$PYTHON_VERSION" =~ ^(3.7|3.8|3.9|3.10|3.11|3.12|3.13|nightly)$$ ]] ; then \ echo "Python 3.7+, with asyncio"; \ pytest -v $(PYTEST_ARGS) --showlocals $(PYTEST_MARKER) $(PYTEST_JUNIT); \ else \ diff --git a/docs/reference/supported-technologies.md b/docs/reference/supported-technologies.md index 715c6a76f..ad768b2cb 100644 --- a/docs/reference/supported-technologies.md +++ b/docs/reference/supported-technologies.md @@ -30,6 +30,7 @@ The following Python versions are supported: * 3.10 * 3.11 * 3.12 +* 3.13 ### Django [supported-django] diff --git a/setup.cfg b/setup.cfg index 2dca4283e..e9f766645 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,6 +22,7 @@ classifiers = Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 + Programming Language :: Python :: 3.13 Programming Language :: Python :: Implementation :: CPython Programming Language :: Python :: Implementation :: PyPy License :: OSI Approved :: BSD License From 96121f3fec0dde43bcda92dde23f80ca5653a56b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 May 2025 15:41:21 +0200 Subject: [PATCH 241/409] build(deps): bump wolfi/chainguard-base from `67d82bc` to `8998bae` (#2290) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `67d82bc` to `8998bae`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index e2a828bd7..2814f1c11 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:67d82bc56a9c34572abe331c14f5e4b23a284d94a5bc1ea3be64f991ced51892 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:8998baea90f5af3ff07e0f5c51816fcdc2e03cd49f90612026cb54bafc12335e ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 71ce3115e322f41497e209fd0f7ffef1b1d0d5c4 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 8 May 2025 18:18:13 +0200 Subject: [PATCH 242/409] ci: more exclusion for python 3.13 (#2289) Mostly old stuff that does not compile and web frameworks importing the removed cgi module. --- .ci/.matrix_exclude.yml | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/.ci/.matrix_exclude.yml b/.ci/.matrix_exclude.yml index c8477b6ca..db796ee34 100644 --- a/.ci/.matrix_exclude.yml +++ b/.ci/.matrix_exclude.yml @@ -107,10 +107,6 @@ exclude: FRAMEWORK: django-2.0 - VERSION: python-3.12 FRAMEWORK: django-2.1 - - VERSION: python-3.13 - FRAMEWORK: django-3.2 - - VERSION: python-3.13 - FRAMEWORK: django-4.0 - VERSION: python-3.12 FRAMEWORK: graphene-2 - VERSION: python-3.12 @@ -127,6 +123,16 @@ exclude: FRAMEWORK: django-2.0 - VERSION: python-3.13 FRAMEWORK: django-2.1 + - VERSION: python-3.13 + FRAMEWORK: django-2.2 + - VERSION: python-3.13 + FRAMEWORK: django-3.0 + - VERSION: python-3.13 + FRAMEWORK: django-3.1 + - VERSION: python-3.13 + FRAMEWORK: django-3.2 + - VERSION: python-3.13 + FRAMEWORK: django-4.0 - VERSION: python-3.13 FRAMEWORK: graphene-2 - VERSION: python-3.13 @@ -246,6 +252,8 @@ exclude: FRAMEWORK: asyncpg-newest - VERSION: python-3.6 FRAMEWORK: asyncpg-0.28 + - VERSION: python-3.13 + FRAMEWORK: asyncpg-0.28 # sanic - VERSION: pypy-3 FRAMEWORK: sanic-newest @@ -257,8 +265,10 @@ exclude: FRAMEWORK: sanic-newest - VERSION: python-3.8 FRAMEWORK: sanic-newest + - VERSION: python-3.13 + FRAMEWORK: sanic-20.12 + # aioredis - VERSION: pypy-3 - # aioredis FRAMEWORK: aioredis-newest - VERSION: python-3.6 FRAMEWORK: aioredis-newest @@ -335,3 +345,10 @@ exclude: FRAMEWORK: sanic-20.12 # no wheels available yet - VERSION: python-3.13 FRAMEWORK: cassandra-newest # c extension issue + # httpx + - VERSION: python-3.13 + FRAMEWORK: httpx-0.13 + - VERSION: python-3.13 + FRAMEWORK: httpx-0.14 + - VERSION: python-3.13 + FRAMEWORK: httpx-0.21 From d727ed54995431e5c3a38890f0502dd185cf65e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 May 2025 14:48:09 +0200 Subject: [PATCH 243/409] build(deps): bump wolfi/chainguard-base from `8998bae` to `4f102b1` (#2291) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `8998bae` to `4f102b1`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 2814f1c11..37b74cd28 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:8998baea90f5af3ff07e0f5c51816fcdc2e03cd49f90612026cb54bafc12335e +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:4f102b13319db859b8076e847abb15b90c6885a806c3dfae6fb146f3b33d5d0b ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From d68d7c154ae1ab39c084e83914d4cc2f2cd1d136 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 May 2025 14:40:35 +0200 Subject: [PATCH 244/409] build(deps): bump wolfi/chainguard-base from `4f102b1` to `4af6df9` (#2292) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `4f102b1` to `4af6df9`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 37b74cd28..1623ec05f 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:4f102b13319db859b8076e847abb15b90c6885a806c3dfae6fb146f3b33d5d0b +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:4af6df9cf5b7db760c361d8a9b41351b3ac1e97d0a21ac0a5b9c309567b7e90e ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 826b421af0afc97685fad5ae830d4182c9aafec8 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Thu, 15 May 2025 16:50:59 +0200 Subject: [PATCH 245/409] github-action: add supported GitHub commands (#2288) --- .github/workflows/github-commands-comment.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/github-commands-comment.yml diff --git a/.github/workflows/github-commands-comment.yml b/.github/workflows/github-commands-comment.yml new file mode 100644 index 000000000..8b5f48d34 --- /dev/null +++ b/.github/workflows/github-commands-comment.yml @@ -0,0 +1,18 @@ +--- +name: github-commands-comment + +on: + pull_request_target: + types: + - opened + +permissions: + contents: read + +jobs: + comment: + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - uses: elastic/oblt-actions/elastic/github-commands@v1 From 53cb254a5e5edfe28c4755ed419cc6632bd6243c Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Thu, 15 May 2025 13:04:42 -0500 Subject: [PATCH 246/409] add products to docset.yml (#2294) --- docs/docset.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/docset.yml b/docs/docset.yml index 0a3d71244..c11f471c4 100644 --- a/docs/docset.yml +++ b/docs/docset.yml @@ -1,4 +1,6 @@ project: 'APM Python agent docs' +products: + - id: apm-agent cross_links: - apm-agent-rum-js - apm-aws-lambda From 82c55855126f54f1559d3b2da3e92b941a6b4b69 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 10:52:21 +0200 Subject: [PATCH 247/409] build(deps): bump docker/build-push-action (#2296) Bumps the github-actions group with 1 update in the / directory: [docker/build-push-action](https://github.com/docker/build-push-action). Updates `docker/build-push-action` from 6.16.0 to 6.17.0 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/14487ce63c7a62a4a324b0bfb37086795e31c6c1...1dc73863535b631f98b2378be8619f83b136f4a0) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 422b9387c..8fc570f25 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -146,7 +146,7 @@ jobs: - name: Build and push image id: docker-push - uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0 + uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0 with: context: . platforms: linux/amd64,linux/arm64 From f78d6a9b81a7eb7e978bfcc9795fcadc553832d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 May 2025 14:34:45 +0200 Subject: [PATCH 248/409] build(deps): bump wolfi/chainguard-base from `4af6df9` to `55ee1dc` (#2297) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `4af6df9` to `55ee1dc`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 1623ec05f..78f0a334b 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:4af6df9cf5b7db760c361d8a9b41351b3ac1e97d0a21ac0a5b9c309567b7e90e +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:55ee1dca9780931b0929d6eb734f455790c06ddbb59f55008e0cddebfbfd1e2e ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 09a76b3a768cd895040fd09400c2051fc9208241 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Thu, 22 May 2025 10:48:12 +0200 Subject: [PATCH 249/409] chore: deps(updatecli): Bump updatecli version to v0.100.0 (#2298) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 0b4ee7806..aec57707c 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.99.0 \ No newline at end of file +updatecli v0.100.0 \ No newline at end of file From 7b6b5da1e2f79c22029deb989483bc657d327644 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 15:36:18 +0200 Subject: [PATCH 250/409] build(deps): bump wolfi/chainguard-base from `55ee1dc` to `3d19648` (#2299) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `55ee1dc` to `3d19648`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 78f0a334b..6ad1d3522 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:55ee1dca9780931b0929d6eb734f455790c06ddbb59f55008e0cddebfbfd1e2e +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:3d19648819612728a676ab4061edfb3283bd7117a22c6c4479ee1c1d51831832 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From f84285d70fb500ae80dcb2705f6cf51b6499d7c4 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 26 May 2025 09:23:49 +0200 Subject: [PATCH 251/409] ci: move from dependabot reviewers to CODEOWNERS (#2300) After https://github.blog/changelog/2025-04-29-dependabot-reviewers-configuration-option-being-replaced-by-code-owners/ --- .github/CODEOWNERS.yml | 3 +++ .github/dependabot.yml | 6 ------ 2 files changed, 3 insertions(+), 6 deletions(-) create mode 100644 .github/CODEOWNERS.yml diff --git a/.github/CODEOWNERS.yml b/.github/CODEOWNERS.yml new file mode 100644 index 000000000..245dd6855 --- /dev/null +++ b/.github/CODEOWNERS.yml @@ -0,0 +1,3 @@ +* @elastic/apm-agent-python +/.github/actions/ @elastic/apm-agent-python @elastic/observablt-ci +/.github/workflows/ @elastic/apm-agent-python @elastic/observablt-ci diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 384f44ee4..9abbe4339 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -17,8 +17,6 @@ updates: interval: "weekly" day: "sunday" time: "22:00" - reviewers: - - "elastic/apm-agent-python" ignore: - dependency-name: "urllib3" # ignore until lambda runtimes use OpenSSL 1.1.1+ versions: [">=2.0.0"] @@ -28,8 +26,6 @@ updates: directories: - '/' - '/.github/actions/*' - reviewers: - - "elastic/observablt-ci" schedule: interval: "weekly" day: "sunday" @@ -42,8 +38,6 @@ updates: - package-ecosystem: "docker" directories: - '/' - reviewers: - - "elastic/apm-agent-python" registries: "*" schedule: interval: "daily" From 6665dcd3225e1cd1cba0f9d9b2082a02a76b529e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 May 2025 14:04:04 +0200 Subject: [PATCH 252/409] build(deps): bump wolfi/chainguard-base from `3d19648` to `5671fdb` (#2301) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `3d19648` to `5671fdb`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 6ad1d3522..d07439bdf 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:3d19648819612728a676ab4061edfb3283bd7117a22c6c4479ee1c1d51831832 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:5671fdbd9cef7b86261f41eb9af4a19e19ce331a010cef141c1bd5eedb7c5c27 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From f50f9063e404f7a13d16f337d7bff1c04113c15f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 May 2025 17:04:16 +0200 Subject: [PATCH 253/409] build(deps): bump wolfi/chainguard-base from `5671fdb` to `9ccddc0` (#2303) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `5671fdb` to `9ccddc0`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index d07439bdf..6fcc25cc4 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:5671fdbd9cef7b86261f41eb9af4a19e19ce331a010cef141c1bd5eedb7c5c27 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:9ccddc0c64238add4f34c5014dbc3c52c18bd291359931174b91cd34b0c691b1 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From b3b71123f476cbbb9bfbc9b7000afb79e867aa80 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 09:32:16 +0200 Subject: [PATCH 254/409] chore: deps(updatecli): Bump updatecli version to v0.101.0 (#2307) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index aec57707c..30469333f 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.100.0 \ No newline at end of file +updatecli v0.101.0 \ No newline at end of file From f272522b95a3c8a41a5fb13bb878ba1633e51a99 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 31 May 2025 09:32:33 +0200 Subject: [PATCH 255/409] build(deps): bump wolfi/chainguard-base from `9ccddc0` to `d05de80` (#2306) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `9ccddc0` to `d05de80`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 6fcc25cc4..0f7f1ef5b 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:9ccddc0c64238add4f34c5014dbc3c52c18bd291359931174b91cd34b0c691b1 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:d05de806903139d26bd08aa7de04c6893c433f4b361674814f9a43dd74c4faee ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From da35e7ee014e284c1895caeeda3fa2dbf7513279 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Jun 2025 14:58:44 +0200 Subject: [PATCH 256/409] build(deps): bump alpine from `a8560b3` to `8a1f59f` (#2310) Bumps alpine from `a8560b3` to `8a1f59f`. --- updated-dependencies: - dependency-name: alpine dependency-version: 8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9293d3347..1185f4169 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,3 @@ -FROM alpine@sha256:a8560b36e8b8210634f77d9f7f9efd7ffa463e380b75e2e74aff4511df3ef88c +FROM alpine@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 166eee346558cc4721f0b6a858b73e9689bde777 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Jun 2025 14:59:06 +0200 Subject: [PATCH 257/409] build(deps): bump wolfi/chainguard-base from `d05de80` to `4a629c9` (#2309) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `d05de80` to `4a629c9`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 0f7f1ef5b..da3912190 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:d05de806903139d26bd08aa7de04c6893c433f4b361674814f9a43dd74c4faee +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:4a629c926ff6682fea43cb167bd1eee528667332dbe97afac7d4ae0f591fe4f8 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From eaab77f11986d63a972c35b7c55d79fb8ed11a34 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 4 Jun 2025 09:03:10 +0200 Subject: [PATCH 258/409] docs: update options to enable with uwsgi (#2311) We need to have py-call-uwsgi-fork-hooks set in order to have proper handling of our threads. While at it say that threads are enabled by default since a few releases. --- docs/reference/django-support.md | 2 +- docs/reference/flask-support.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/django-support.md b/docs/reference/django-support.md index e85f5a8a8..37b1fe3bd 100644 --- a/docs/reference/django-support.md +++ b/docs/reference/django-support.md @@ -24,7 +24,7 @@ For apm-server 6.2+, make sure you use version 2.0 or higher of `elastic-apm`. ::::{note} -If you use Django with uwsgi, make sure to [enable threads](http://uwsgi-docs.readthedocs.org/en/latest/Options.html#enable-threads). +If you use Django with uwsgi, make sure to [enable threads](http://uwsgi-docs.readthedocs.org/en/latest/Options.html#enable-threads) (enabled by default since 2.0.27) and [py-call-uwsgi-fork-hooks](https://uwsgi-docs.readthedocs.io/en/latest/Options.html#py-call-uwsgi-fork-hooks). :::: diff --git a/docs/reference/flask-support.md b/docs/reference/flask-support.md index e99e32994..3d9fa0ad4 100644 --- a/docs/reference/flask-support.md +++ b/docs/reference/flask-support.md @@ -24,7 +24,7 @@ For apm-server 6.2+, make sure you use version 2.0 or higher of `elastic-apm`. ::::{note} -If you use Flask with uwsgi, make sure to [enable threads](http://uwsgi-docs.readthedocs.org/en/latest/Options.html#enable-threads). +If you use Flask with uwsgi, make sure to [enable threads](http://uwsgi-docs.readthedocs.org/en/latest/Options.html#enable-threads) (enabled by default since 2.0.27) and [py-call-uwsgi-fork-hooks](https://uwsgi-docs.readthedocs.io/en/latest/Options.html#py-call-uwsgi-fork-hooks). :::: From 331f6867887fc4fb4c344cb470cf643739613183 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Jun 2025 13:01:48 +0000 Subject: [PATCH 259/409] build(deps): bump wolfi/chainguard-base from `4a629c9` to `fdfd7f3` (#2312) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `4a629c9` to `fdfd7f3`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index da3912190..2b18630c7 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:4a629c926ff6682fea43cb167bd1eee528667332dbe97afac7d4ae0f591fe4f8 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:fdfd7f357a09f933ab22143273849f8b247360f2f94f4dc2ea473001c59f9f0b ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From c3bbcecc496f7d6bce1b356900aa8c4ad3d051bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Jun 2025 09:58:41 +0200 Subject: [PATCH 260/409] build(deps): bump requests from 2.32.1 to 2.32.4 in /tests/requirements (#2314) Bumps [requests](https://github.com/psf/requests) from 2.32.1 to 2.32.4. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.32.1...v2.32.4) --- updated-dependencies: - dependency-name: requests dependency-version: 2.32.4 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tests/requirements/reqs-starlette-0.13.txt | 2 +- tests/requirements/reqs-starlette-0.14.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/requirements/reqs-starlette-0.13.txt b/tests/requirements/reqs-starlette-0.13.txt index 43d814c60..ef7617c74 100644 --- a/tests/requirements/reqs-starlette-0.13.txt +++ b/tests/requirements/reqs-starlette-0.13.txt @@ -1,5 +1,5 @@ starlette>=0.13,<0.14 aiofiles==0.7.0 -requests==2.32.1; python_version >= '3.8' +requests==2.32.4; python_version >= '3.8' requests==2.31.0; python_version < '3.8' -r reqs-base.txt diff --git a/tests/requirements/reqs-starlette-0.14.txt b/tests/requirements/reqs-starlette-0.14.txt index 52ea93114..263a67636 100644 --- a/tests/requirements/reqs-starlette-0.14.txt +++ b/tests/requirements/reqs-starlette-0.14.txt @@ -1,5 +1,5 @@ starlette>=0.14,<0.15 -requests==2.32.1; python_version >= '3.8' +requests==2.32.4; python_version >= '3.8' requests==2.31.0; python_version < '3.8' aiofiles -r reqs-base.txt From 07ecb93df6142cbef516db8b4f6fd61e7265f996 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Jun 2025 14:49:06 +0200 Subject: [PATCH 261/409] build(deps): bump wolfi/chainguard-base from `fdfd7f3` to `af25746` (#2316) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `fdfd7f3` to `af25746`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 2b18630c7..061bafbc4 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:fdfd7f357a09f933ab22143273849f8b247360f2f94f4dc2ea473001c59f9f0b +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:af257460ae20e9b5c72a20f11c4e523cf6df87c1931be4617fab5cf877790fc7 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 9a979f02d99e2baa09d7cbc31b8d19e060df2e4c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Jun 2025 16:36:54 +0200 Subject: [PATCH 262/409] build(deps): bump docker/build-push-action (#2308) Bumps the github-actions group with 1 update in the / directory: [docker/build-push-action](https://github.com/docker/build-push-action). Updates `docker/build-push-action` from 6.17.0 to 6.18.0 - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/1dc73863535b631f98b2378be8619f83b136f4a0...263435318d21b8e681c14492fe198d362a7d2c83) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8fc570f25..d14a2e882 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -146,7 +146,7 @@ jobs: - name: Build and push image id: docker-push - uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0 + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 with: context: . platforms: linux/amd64,linux/arm64 From 05dec7b4f46e5965ae985d4f214b19976551e8b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 09:50:21 +0200 Subject: [PATCH 263/409] build(deps): bump certifi from 2025.4.26 to 2025.6.15 in /dev-utils (#2320) Bumps [certifi](https://github.com/certifi/python-certifi) from 2025.4.26 to 2025.6.15. - [Commits](https://github.com/certifi/python-certifi/compare/2025.04.26...2025.06.15) --- updated-dependencies: - dependency-name: certifi dependency-version: 2025.6.15 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dev-utils/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-utils/requirements.txt b/dev-utils/requirements.txt index e0727cdef..d9280db70 100644 --- a/dev-utils/requirements.txt +++ b/dev-utils/requirements.txt @@ -1,4 +1,4 @@ # These are the pinned requirements for the lambda layer/docker image -certifi==2025.4.26 +certifi==2025.6.15 urllib3==1.26.20 wrapt==1.14.1 From 758e235d7a1e5502b1ffa50b481e721f215b1c71 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 09:51:59 +0200 Subject: [PATCH 264/409] chore: deps(updatecli): Bump updatecli version to v0.102.0 (#2318) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 30469333f..6f6d2a8e3 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.101.0 \ No newline at end of file +updatecli v0.102.0 \ No newline at end of file From a2ca3eb92e147f7083b5a0221685aa9f6b8d4be2 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 16 Jun 2025 10:05:37 +0200 Subject: [PATCH 265/409] ci: fix CODEOWNERS file name (#2321) --- .github/{CODEOWNERS.yml => CODEOWNERS} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{CODEOWNERS.yml => CODEOWNERS} (100%) diff --git a/.github/CODEOWNERS.yml b/.github/CODEOWNERS similarity index 100% rename from .github/CODEOWNERS.yml rename to .github/CODEOWNERS From b1889ddd404831d407ddaf0c794f12bd7881d804 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 13:48:28 +0200 Subject: [PATCH 266/409] build(deps): bump actions/attest-build-provenance (#2319) Bumps the github-actions group with 1 update in the / directory: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance). Updates `actions/attest-build-provenance` from 2.3.0 to 2.4.0 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/db473fddc028af60658334401dc6fa3ffd8669fd...e8998f949152b193b063cb0ec769d69d929409be) --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d14a2e882..fbdba3fd4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/packages - name: generate build provenance - uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0 + uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0 with: subject-path: "${{ github.workspace }}/dist/*" @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/build-distribution - name: generate build provenance - uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0 + uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0 with: subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" @@ -158,7 +158,7 @@ jobs: AGENT_DIR=./build/dist/package/python - name: generate build provenance (containers) - uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0 + uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0 with: subject-name: "${{ env.DOCKER_IMAGE_NAME }}" subject-digest: ${{ steps.docker-push.outputs.digest }} From 55f3b2ddbd575d04e8570eecc36b90674f9004db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 11:44:22 +0000 Subject: [PATCH 267/409] build(deps): bump wolfi/chainguard-base from `af25746` to `a7acf02` (#2322) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `af25746` to `a7acf02`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 061bafbc4..e96092129 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:af257460ae20e9b5c72a20f11c4e523cf6df87c1931be4617fab5cf877790fc7 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:a7acf020605f7b3faf04b180bf68d170e394d1f5b2249fce3805fd5d3f145c0e ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 83b26d0a2a5cc8e81b56e810bdfb95a42b98fd6d Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 25 Jun 2025 17:47:42 +0200 Subject: [PATCH 268/409] requirements/sanic: stick with older tracerite for older python versions (#2326) --- tests/requirements/reqs-sanic-newest.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/requirements/reqs-sanic-newest.txt b/tests/requirements/reqs-sanic-newest.txt index c30ea5e2b..7085f99e0 100644 --- a/tests/requirements/reqs-sanic-newest.txt +++ b/tests/requirements/reqs-sanic-newest.txt @@ -1,3 +1,4 @@ sanic sanic-testing +tracerite==1.1.1 ; python_version < '3.8' -r reqs-base.txt From 74415d6128b3475bba2e24ca8e853cc04356e072 Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Wed, 25 Jun 2025 17:48:31 +0200 Subject: [PATCH 269/409] docs-builder: add `pull-requests: write` permission to docs-build workflow (#2324) --- .github/workflows/docs-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs-build.yml b/.github/workflows/docs-build.yml index bb466166d..adf95da5d 100644 --- a/.github/workflows/docs-build.yml +++ b/.github/workflows/docs-build.yml @@ -16,4 +16,4 @@ jobs: deployments: write id-token: write contents: read - pull-requests: read + pull-requests: write From cd2f922c6a8c2a7b271f4a46f6d93a60ed6c6129 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 25 Jun 2025 15:55:02 +0000 Subject: [PATCH 270/409] build(deps): bump wolfi/chainguard-base from `a7acf02` to `c634d77` (#2323) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `a7acf02` to `c634d77`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Riccardo Magliocchetti --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index e96092129..2426f7ed2 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:a7acf020605f7b3faf04b180bf68d170e394d1f5b2249fce3805fd5d3f145c0e +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:c634d77ea251a2264a8f4009f53315408fb529101d2afcaeaed66f5b4257ccbb ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 191678850df5d3f6128a9f7f122d6efdfc0d8f38 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 15:44:02 +0200 Subject: [PATCH 271/409] build(deps): bump wolfi/chainguard-base from `c634d77` to `868f3eb` (#2327) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `c634d77` to `868f3eb`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 2426f7ed2..dcdfca749 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:c634d77ea251a2264a8f4009f53315408fb529101d2afcaeaed66f5b4257ccbb +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:868f3eb5022ea666a64037370ece2c08a9e00bf2c8e272d0db54232d30d460c2 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From c8cd131fd844ca34c6fafb288d7a34d619b7bf3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 14:08:38 +0200 Subject: [PATCH 272/409] build(deps): bump wolfi/chainguard-base from `868f3eb` to `5e3d0d5` (#2328) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `868f3eb` to `5e3d0d5`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index dcdfca749..7a2f6f828 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:868f3eb5022ea666a64037370ece2c08a9e00bf2c8e272d0db54232d30d460c2 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:5e3d0d5d6e3470b57d2f39e72418003f17027c98ee47bcf953225e6cc1be7ba2 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From a395c30d412aba79cedb9ef01a5f736ae8cdd1a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Jul 2025 21:15:59 +0200 Subject: [PATCH 273/409] build(deps): bump docker/setup-buildx-action (#2325) Bumps the github-actions group with 1 update in the / directory: [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action). Updates `docker/setup-buildx-action` from 3.10.0 to 3.11.1 - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2...e468171a9de216ec08956ac3ada2f0791b6bd435) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fbdba3fd4..7287312f5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -119,7 +119,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 + uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - name: Log in to the Elastic Container registry uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 From 8e160ca9d32935a275fbf9bc0c1903a7091684df Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 3 Jul 2025 15:03:22 +0200 Subject: [PATCH 274/409] ci: run tests on windows-2022 (#2330) Since windows-2019 images has been removed. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 36294b1f4..4fc7a275e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -109,7 +109,7 @@ jobs: windows: name: "windows (version: ${{ matrix.version }}, framework: ${{ matrix.framework }}, asyncio: ${{ matrix.asyncio }})" - runs-on: windows-2019 + runs-on: windows-2022 strategy: fail-fast: false matrix: From 7d3e12867f1fb43437e1bef59498411f80a15b54 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Jul 2025 15:25:04 +0200 Subject: [PATCH 275/409] build(deps): bump wolfi/chainguard-base from `5e3d0d5` to `c709f50` (#2329) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `5e3d0d5` to `c709f50`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 7a2f6f828..a72ad81fd 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:5e3d0d5d6e3470b57d2f39e72418003f17027c98ee47bcf953225e6cc1be7ba2 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:c709f502d7d35ffb3d9c6e51a4ef3110ec475102501789a4dc0da5a173df7688 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From ea902ae49df19d89352800bdeb8cea5dc0e3134f Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 08:57:29 +0200 Subject: [PATCH 276/409] chore: deps(updatecli): Bump updatecli version to v0.103.0 (#2336) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 6f6d2a8e3..3de462ab8 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.102.0 \ No newline at end of file +updatecli v0.103.0 \ No newline at end of file From 4534249a3895b3f7ac02e7dc1b633451f21cb748 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 7 Jul 2025 10:30:11 +0200 Subject: [PATCH 277/409] psycopg2: fix cursor execute and executemany signatures (#2331) * psycopg2: fix cursor execute and executemany signatures The sql parameter is called query and the parameters are called vars and vars_list. * Fix execute test --- .../instrumentation/packages/psycopg2.py | 6 ++++++ tests/instrumentation/psycopg2_tests.py | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/elasticapm/instrumentation/packages/psycopg2.py b/elasticapm/instrumentation/packages/psycopg2.py index a058597da..850d849e3 100644 --- a/elasticapm/instrumentation/packages/psycopg2.py +++ b/elasticapm/instrumentation/packages/psycopg2.py @@ -59,6 +59,12 @@ def _bake_sql(self, sql): def extract_signature(self, sql): return extract_signature(sql) + def execute(self, query, vars=None): + return self._trace_sql(self.__wrapped__.execute, query, vars) + + def executemany(self, query, vars_list): + return self._trace_sql(self.__wrapped__.executemany, query, vars_list) + def __enter__(self): return PGCursorProxy(self.__wrapped__.__enter__(), destination_info=self._self_destination_info) diff --git a/tests/instrumentation/psycopg2_tests.py b/tests/instrumentation/psycopg2_tests.py index 70c0d6329..1cee5b269 100644 --- a/tests/instrumentation/psycopg2_tests.py +++ b/tests/instrumentation/psycopg2_tests.py @@ -266,6 +266,27 @@ def test_fully_qualified_table_name(): assert "SELECT FROM db.schema.mytable" == actual +@pytest.mark.integrationtest +@pytest.mark.skipif(not has_postgres_configured, reason="PostgresSQL not configured") +def test_cursor_execute_signature(instrument, postgres_connection, elasticapm_client): + cursor = postgres_connection.cursor() + cursor.execute(query="SELECT 1", vars=None) + row = cursor.fetchone() + + assert row + + +@pytest.mark.integrationtest +@pytest.mark.skipif(not has_postgres_configured, reason="PostgresSQL not configured") +def test_cursor_executemany_signature(instrument, postgres_connection, elasticapm_client): + cursor = postgres_connection.cursor() + res = cursor.executemany( + query="INSERT INTO test VALUES (%s, %s)", + vars_list=((4, "four"),), + ) + assert res is None + + @pytest.mark.integrationtest @pytest.mark.skipif(not has_postgres_configured, reason="PostgresSQL not configured") def test_destination(instrument, postgres_connection, elasticapm_client): From c65649b07b4e7bb1fbe8f19bf1bdf0f992d41b4e Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 8 Jul 2025 09:21:11 +0200 Subject: [PATCH 278/409] docs/reference: create API key in Applications UI (#2333) Instead of asking to use the APM server CLI that has been removed in 9.0. While at it drop the technical preview warning. --- docs/reference/configuration.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 8da75ec6d..d95df439b 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -268,12 +268,7 @@ Secret tokens only provide any security if your APM Server uses TLS. | --- | --- | --- | --- | | `ELASTIC_APM_API_KEY` | `API_KEY` | `None` | A base64-encoded string | -::::{warning} -This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. -:::: - - -This base64-encoded string is used to ensure that only your agents can send data to your APM Server. The API key must be created using the [APM server command-line tool](docs-content://solutions/observability/apm/api-keys.md). +This base64-encoded string is used to ensure that only your agents can send data to your APM Server. The API key can be created in the [Applications UI](docs-content://solutions/observability/apm/api-keys.md#apm-create-an-api-key). ::::{warning} API keys only provide any real security if your APM Server uses TLS. From c3de9524771a805325e00473005b8c728c603705 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 8 Jul 2025 09:21:44 +0200 Subject: [PATCH 279/409] psycopg: fix cursor execute and executemany signatures (#2332) * psycopg: fix cursor execute and executemany signatures Adapt CursorProxy._trace_sql to take into account other keyword arguments passed to it. * asyncio/psycopg: fix cursor execute and executemany signatures Adapt AsyncCursorProxy._trace_sql to take into account other keyword arguments passed to it. * Fix compat with psycopg 3.0 * Fix tests with psycopg < 3.1.0 --- .../packages/asyncio/dbapi2_asyncio.py | 6 +- .../packages/asyncio/psycopg_async.py | 9 ++ elasticapm/instrumentation/packages/dbapi2.py | 6 +- .../instrumentation/packages/psycopg.py | 6 + .../asyncio_tests/psycopg_tests.py | 121 ++++++++++++++++++ tests/instrumentation/psycopg_tests.py | 27 ++++ 6 files changed, 169 insertions(+), 6 deletions(-) create mode 100644 tests/instrumentation/asyncio_tests/psycopg_tests.py diff --git a/elasticapm/instrumentation/packages/asyncio/dbapi2_asyncio.py b/elasticapm/instrumentation/packages/asyncio/dbapi2_asyncio.py index 4345731b7..078204bb0 100644 --- a/elasticapm/instrumentation/packages/asyncio/dbapi2_asyncio.py +++ b/elasticapm/instrumentation/packages/asyncio/dbapi2_asyncio.py @@ -65,7 +65,7 @@ def _bake_sql(self, sql): """ return sql - async def _trace_sql(self, method, sql, params, action=QUERY_ACTION): + async def _trace_sql(self, method, sql, params, action=QUERY_ACTION, **kwargs): sql_string = self._bake_sql(sql) if action == EXEC_ACTION: signature = sql_string + "()" @@ -89,9 +89,9 @@ async def _trace_sql(self, method, sql, params, action=QUERY_ACTION): leaf=True, ) as span: if params is None: - result = await method(sql) + result = await method(sql, **kwargs) else: - result = await method(sql, params) + result = await method(sql, params, **kwargs) # store "rows affected", but only for DML queries like insert/update/delete if span and self.rowcount not in (-1, None) and signature.startswith(self.DML_QUERIES): span.update_context("db", {"rows_affected": self.rowcount}) diff --git a/elasticapm/instrumentation/packages/asyncio/psycopg_async.py b/elasticapm/instrumentation/packages/asyncio/psycopg_async.py index 0ef565119..fbcfb93fc 100644 --- a/elasticapm/instrumentation/packages/asyncio/psycopg_async.py +++ b/elasticapm/instrumentation/packages/asyncio/psycopg_async.py @@ -55,12 +55,21 @@ def _bake_sql(self, sql): def extract_signature(self, sql): return extract_signature(sql) + async def execute(self, query, params=None, *, prepare=None, binary=None, **kwargs): + return await self._trace_sql(self.__wrapped__.execute, query, params, prepare=prepare, binary=binary, **kwargs) + + async def executemany(self, query, params_seq, **kwargs): + return await self._trace_sql(self.__wrapped__.executemany, query, params_seq, **kwargs) + async def __aenter__(self): return PGAsyncCursorProxy(await self.__wrapped__.__aenter__(), destination_info=self._self_destination_info) async def __aexit__(self, *args): return PGAsyncCursorProxy(await self.__wrapped__.__aexit__(*args), destination_info=self._self_destination_info) + def __aiter__(self): + return self.__wrapped__.__aiter__() + @property def _self_database(self): return self.connection.info.dbname or "" diff --git a/elasticapm/instrumentation/packages/dbapi2.py b/elasticapm/instrumentation/packages/dbapi2.py index fa1d0f31e..d903d41f8 100644 --- a/elasticapm/instrumentation/packages/dbapi2.py +++ b/elasticapm/instrumentation/packages/dbapi2.py @@ -243,7 +243,7 @@ def _bake_sql(self, sql): """ return sql - def _trace_sql(self, method, sql, params, action=QUERY_ACTION): + def _trace_sql(self, method, sql, params, action=QUERY_ACTION, **kwargs): sql_string = self._bake_sql(sql) if action == EXEC_ACTION: signature = sql_string + "()" @@ -268,9 +268,9 @@ def _trace_sql(self, method, sql, params, action=QUERY_ACTION): leaf=True, ) as span: if params is None: - result = method(sql) + result = method(sql, **kwargs) else: - result = method(sql, params) + result = method(sql, params, **kwargs) # store "rows affected", but only for DML queries like insert/update/delete if span and self.rowcount not in (-1, None) and signature.startswith(self.DML_QUERIES): span.update_context("db", {"rows_affected": self.rowcount}) diff --git a/elasticapm/instrumentation/packages/psycopg.py b/elasticapm/instrumentation/packages/psycopg.py index 3dbcf5a0a..3a0409c96 100644 --- a/elasticapm/instrumentation/packages/psycopg.py +++ b/elasticapm/instrumentation/packages/psycopg.py @@ -55,6 +55,12 @@ def _bake_sql(self, sql): def extract_signature(self, sql): return extract_signature(sql) + def execute(self, query, params=None, *, prepare=None, binary=None, **kwargs): + return self._trace_sql(self.__wrapped__.execute, query, params, prepare=prepare, binary=binary, **kwargs) + + def executemany(self, query, params_seq, **kwargs): + return self._trace_sql(self.__wrapped__.executemany, query, params_seq, **kwargs) + def __enter__(self): return PGCursorProxy(self.__wrapped__.__enter__(), destination_info=self._self_destination_info) diff --git a/tests/instrumentation/asyncio_tests/psycopg_tests.py b/tests/instrumentation/asyncio_tests/psycopg_tests.py new file mode 100644 index 000000000..5fbe07c48 --- /dev/null +++ b/tests/instrumentation/asyncio_tests/psycopg_tests.py @@ -0,0 +1,121 @@ +# BSD 3-Clause License +# +# Copyright (c) 2025, Elasticsearch BV +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import os + +import pytest +import pytest_asyncio + +from elasticapm.conf import constants + +psycopg = pytest.importorskip("psycopg") # isort:skip +pytestmark = [pytest.mark.psycopg, pytest.mark.asyncio] + +if "POSTGRES_DB" not in os.environ: + pytestmark.append(pytest.mark.skip("Skipping psycopg tests, no POSTGRES_DB environment variable set")) + + +def connect_kwargs(): + return { + "dbname": os.environ.get("POSTGRES_DB", "elasticapm_test"), + "user": os.environ.get("POSTGRES_USER", "postgres"), + "password": os.environ.get("POSTGRES_PASSWORD", "postgres"), + "host": os.environ.get("POSTGRES_HOST", None), + "port": os.environ.get("POSTGRES_PORT", None), + } + + +@pytest_asyncio.fixture(scope="function") +async def postgres_connection(request): + conn = await psycopg.AsyncConnection.connect(**connect_kwargs()) + cursor = conn.cursor() + await cursor.execute( + "CREATE TABLE test(id int, name VARCHAR(5) NOT NULL);" + "INSERT INTO test VALUES (1, 'one'), (2, 'two'), (3, 'three');" + ) + + yield conn + + # cleanup + await cursor.execute("ROLLBACK") + + +async def test_cursor_execute_signature(instrument, postgres_connection, elasticapm_client): + cursor = postgres_connection.cursor() + record = await cursor.execute(query="SELECT 1", params=None, prepare=None, binary=None) + assert record + + +async def test_cursor_executemany_signature(instrument, postgres_connection, elasticapm_client): + cursor = postgres_connection.cursor() + res = await cursor.executemany( + query="INSERT INTO test VALUES (%s, %s)", + params_seq=((4, "four"),), + returning=False, + ) + assert res is None + + +async def test_execute_with_sleep(instrument, postgres_connection, elasticapm_client): + elasticapm_client.begin_transaction("test") + cursor = postgres_connection.cursor() + await cursor.execute("SELECT pg_sleep(0.1);") + elasticapm_client.end_transaction("test", "OK") + + transaction = elasticapm_client.events[constants.TRANSACTION][0] + spans = elasticapm_client.spans_for_transaction(transaction) + + assert len(spans) == 1 + span = spans[0] + assert 100 < span["duration"] < 110 + assert transaction["id"] == span["transaction_id"] + assert span["type"] == "db" + assert span["subtype"] == "postgresql" + assert span["action"] == "query" + assert span["sync"] == False + assert span["name"] == "SELECT FROM" + + +async def test_executemany(instrument, postgres_connection, elasticapm_client): + elasticapm_client.begin_transaction("test") + cursor = postgres_connection.cursor() + await cursor.executemany("INSERT INTO test VALUES (%s, %s);", [(1, "uno"), (2, "due")]) + elasticapm_client.end_transaction("test", "OK") + + transaction = elasticapm_client.events[constants.TRANSACTION][0] + spans = elasticapm_client.spans_for_transaction(transaction) + + assert len(spans) == 1 + span = spans[0] + assert transaction["id"] == span["transaction_id"] + assert span["subtype"] == "postgresql" + assert span["action"] == "query" + assert span["sync"] == False + assert span["name"] == "INSERT INTO test" diff --git a/tests/instrumentation/psycopg_tests.py b/tests/instrumentation/psycopg_tests.py index 38768bcef..e744663f7 100644 --- a/tests/instrumentation/psycopg_tests.py +++ b/tests/instrumentation/psycopg_tests.py @@ -47,6 +47,8 @@ has_postgres_configured = "POSTGRES_DB" in os.environ +PSYCOPG_VERSION = tuple([int(x) for x in psycopg.version.__version__.split() if x.isdigit()]) + def connect_kwargs(): return { @@ -73,6 +75,31 @@ def postgres_connection(request): cursor.execute("ROLLBACK") +@pytest.mark.integrationtest +@pytest.mark.skipif(not has_postgres_configured, reason="PostgresSQL not configured") +def test_cursor_execute_signature(instrument, postgres_connection, elasticapm_client): + cursor = postgres_connection.cursor() + cursor.execute(query="SELECT 1", params=None, prepare=None, binary=None) + row = cursor.fetchone() + assert row + + +@pytest.mark.integrationtest +@pytest.mark.skipif(not has_postgres_configured, reason="PostgresSQL not configured") +def test_cursor_executemany_signature(instrument, postgres_connection, elasticapm_client): + cursor = postgres_connection.cursor() + if PSYCOPG_VERSION < (3, 1, 0): + kwargs = {} + else: + kwargs = {"returning": False} + res = cursor.executemany( + query="INSERT INTO test VALUES (%s, %s)", + params_seq=((4, "four"),), + **kwargs, + ) + assert res is None + + @pytest.mark.integrationtest @pytest.mark.skipif(not has_postgres_configured, reason="PostgresSQL not configured") def test_destination(instrument, postgres_connection, elasticapm_client): From 1f081dbdc6788bfadecf6eb969f4cb9cd6556393 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 8 Jul 2025 09:22:10 +0200 Subject: [PATCH 280/409] contrib/asgi: fix distributed tracing (#2334) We need to decode headers before looking for traceparent header. While at it rename a couple of tests with the same name. --- elasticapm/contrib/asgi.py | 5 ++--- tests/contrib/asgi/asgi_tests.py | 28 ++++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/elasticapm/contrib/asgi.py b/elasticapm/contrib/asgi.py index 92ee6c193..cb66e25eb 100644 --- a/elasticapm/contrib/asgi.py +++ b/elasticapm/contrib/asgi.py @@ -77,9 +77,8 @@ async def __call__(self, scope: "Scope", receive: "ASGIReceiveCallable", send: " url, url_dict = self.get_url(scope) body = None if not self.client.should_ignore_url(url): - self.client.begin_transaction( - transaction_type="request", trace_parent=TraceParent.from_headers(scope["headers"]) - ) + headers = self.get_headers(scope) + self.client.begin_transaction(transaction_type="request", trace_parent=TraceParent.from_headers(headers)) self.set_transaction_name(scope["method"], url) if scope["method"] in constants.HTTP_WITH_BODY and self.client.config.capture_body != "off": messages = [] diff --git a/tests/contrib/asgi/asgi_tests.py b/tests/contrib/asgi/asgi_tests.py index f2a096dcc..632875e3e 100644 --- a/tests/contrib/asgi/asgi_tests.py +++ b/tests/contrib/asgi/asgi_tests.py @@ -45,7 +45,7 @@ def instrumented_app(elasticapm_client): @pytest.mark.asyncio -async def test_transaction_span(instrumented_app, elasticapm_client): +async def test_transaction_span_success(instrumented_app, elasticapm_client): async with async_asgi_testclient.TestClient(instrumented_app) as client: resp = await client.get("/") assert resp.status_code == 200 @@ -67,7 +67,7 @@ async def test_transaction_span(instrumented_app, elasticapm_client): @pytest.mark.asyncio -async def test_transaction_span(instrumented_app, elasticapm_client): +async def test_transaction_span_failure(instrumented_app, elasticapm_client): async with async_asgi_testclient.TestClient(instrumented_app) as client: resp = await client.get("/500") assert resp.status_code == 500 @@ -83,6 +83,30 @@ async def test_transaction_span(instrumented_app, elasticapm_client): assert transaction["context"]["response"]["status_code"] == 500 +@pytest.mark.asyncio +async def test_transaction_traceparent(instrumented_app, elasticapm_client): + async with async_asgi_testclient.TestClient(instrumented_app) as client: + resp = await client.get("/", headers={"traceparent": "00-12345678901234567890123456789012-1234567890123456-01"}) + assert resp.status_code == 200 + assert resp.text == "OK" + + assert len(elasticapm_client.events[constants.TRANSACTION]) == 1 + assert len(elasticapm_client.events[constants.SPAN]) == 1 + transaction = elasticapm_client.events[constants.TRANSACTION][0] + span = elasticapm_client.events[constants.SPAN][0] + assert transaction["name"] == "GET unknown route" + assert transaction["result"] == "HTTP 2xx" + assert transaction["outcome"] == "success" + assert transaction["context"]["request"]["url"]["full"] == "/" + assert transaction["context"]["response"]["status_code"] == 200 + + assert transaction["trace_id"] == "12345678901234567890123456789012" + + assert span["name"] == "sleep" + assert span["outcome"] == "success" + assert span["sync"] == False + + @pytest.mark.asyncio async def test_transaction_ignore_url(instrumented_app, elasticapm_client): elasticapm_client.config.update("1", transaction_ignore_urls="/foo*") From f64a0e5352d1411c7ed1edcde97a23b11d705ee8 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 8 Jul 2025 09:22:33 +0200 Subject: [PATCH 281/409] elasticapm: change typing of start in Span / capture_span to float (#2335) This valued is passed to time_to_perf_counter that really requires a float. --- elasticapm/traces.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elasticapm/traces.py b/elasticapm/traces.py index 583505070..929458d7a 100644 --- a/elasticapm/traces.py +++ b/elasticapm/traces.py @@ -531,7 +531,7 @@ def __init__( span_subtype: Optional[str] = None, span_action: Optional[str] = None, sync: Optional[bool] = None, - start: Optional[int] = None, + start: Optional[float] = None, links: Optional[Sequence[TraceParent]] = None, ) -> None: """ @@ -1044,7 +1044,7 @@ def __init__( labels: Optional[dict] = None, span_subtype: Optional[str] = None, span_action: Optional[str] = None, - start: Optional[int] = None, + start: Optional[float] = None, duration: Optional[Union[float, timedelta]] = None, sync: Optional[bool] = None, links: Optional[Sequence[TraceParent]] = None, From b569f41d71aaf9a20b98c57a2bc8afdb8307b778 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 14:36:34 +0200 Subject: [PATCH 282/409] build(deps): bump wolfi/chainguard-base from `c709f50` to `bbc60f1` (#2339) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `c709f50` to `bbc60f1`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index a72ad81fd..31f43b24b 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:c709f502d7d35ffb3d9c6e51a4ef3110ec475102501789a4dc0da5a173df7688 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:bbc60f1a2dbdd8e6ae4fee4fdf83adbac275b9821b2ac05ca72b1d597babd51f ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 76cf5c8ba963b7a88543c80145f69640c6fafdcf Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 9 Jul 2025 09:11:36 +0200 Subject: [PATCH 283/409] Drop support for Python 3.6 (#2338) --- .ci/.matrix_exclude.yml | 54 -------------------------- .ci/.matrix_python.yml | 2 +- .ci/.matrix_python_full.yml | 1 - .ci/.matrix_windows.yml | 21 ---------- .ci/publish-aws.sh | 2 +- .github/workflows/test.yml | 3 -- Makefile | 10 +---- docs/reference/run-tests-locally.md | 2 +- elasticapm/__init__.py | 4 +- elasticapm/base.py | 4 +- elasticapm/instrumentation/register.py | 50 ++++++++++-------------- setup.cfg | 3 +- tests/requirements/reqs-base.txt | 23 ++++------- tests/scripts/run_tests.sh | 2 +- 14 files changed, 40 insertions(+), 141 deletions(-) delete mode 100644 .ci/.matrix_windows.yml diff --git a/.ci/.matrix_exclude.yml b/.ci/.matrix_exclude.yml index db796ee34..524291f20 100644 --- a/.ci/.matrix_exclude.yml +++ b/.ci/.matrix_exclude.yml @@ -5,18 +5,12 @@ exclude: # Django 4.0 requires Python 3.8+ - VERSION: pypy-3 # current pypy-3 is compatible with Python 3.7 FRAMEWORK: django-4.0 - - VERSION: python-3.6 - FRAMEWORK: django-4.0 - VERSION: python-3.7 FRAMEWORK: django-4.0 # Django 4.2 requires Python 3.8+ - - VERSION: python-3.6 - FRAMEWORK: django-4.2 - VERSION: python-3.7 FRAMEWORK: django-4.2 # Django 5.0 requires Python 3.10+ - - VERSION: python-3.6 - FRAMEWORK: django-5.0 - VERSION: python-3.7 FRAMEWORK: django-5.0 - VERSION: python-3.8 @@ -25,12 +19,8 @@ exclude: FRAMEWORK: django-5.0 - VERSION: pypy-3 # current pypy-3 is compatible with Python 3.7 FRAMEWORK: celery-5-django-4 - - VERSION: python-3.6 - FRAMEWORK: celery-5-django-4 - VERSION: python-3.7 FRAMEWORK: celery-5-django-4 - - VERSION: python-3.6 - FRAMEWORK: celery-5-django-5 - VERSION: python-3.7 FRAMEWORK: celery-5-django-5 - VERSION: python-3.8 @@ -40,14 +30,6 @@ exclude: # Flask - VERSION: pypy-3 FRAMEWORK: flask-0.11 # see https://github.com/pallets/flask/commit/6e46d0cd, 0.11.2 was never released - - VERSION: python-3.6 - FRAMEWORK: flask-2.1 - - VERSION: python-3.6 - FRAMEWORK: flask-2.2 - - VERSION: python-3.6 - FRAMEWORK: flask-2.3 - - VERSION: python-3.6 - FRAMEWORK: flask-3.0 - VERSION: python-3.7 FRAMEWORK: flask-2.3 - VERSION: python-3.7 @@ -185,8 +167,6 @@ exclude: # pymssql - VERSION: pypy-3 # currently fails with error on pypy3 FRAMEWORK: pymssql-newest - - VERSION: python-3.6 # dropped support for py3.6 - FRAMEWORK: pymssql-newest # pyodbc - VERSION: pypy-3 FRAMEWORK: pyodbc-newest @@ -210,48 +190,28 @@ exclude: # aiohttp client, only supported in Python 3.7+ - VERSION: pypy-3 FRAMEWORK: aiohttp-3.0 - - VERSION: python-3.6 - FRAMEWORK: aiohttp-3.0 - VERSION: pypy-3 FRAMEWORK: aiohttp-4.0 - - VERSION: python-3.6 - FRAMEWORK: aiohttp-4.0 - VERSION: pypy-3 FRAMEWORK: aiohttp-newest - - VERSION: python-3.6 - FRAMEWORK: aiohttp-newest # tornado, only supported in Python 3.7+ - VERSION: pypy-3 FRAMEWORK: tornado-newest - - VERSION: python-3.6 - FRAMEWORK: tornado-newest # Starlette, only supported in python 3.7+ - VERSION: pypy-3 FRAMEWORK: starlette-0.13 - - VERSION: python-3.6 - FRAMEWORK: starlette-0.13 - VERSION: pypy-3 FRAMEWORK: starlette-0.14 - - VERSION: python-3.6 - FRAMEWORK: starlette-0.14 - VERSION: pypy-3 FRAMEWORK: starlette-newest - - VERSION: python-3.6 - FRAMEWORK: starlette-newest # aiopg - VERSION: pypy-3 FRAMEWORK: aiopg-newest - - VERSION: python-3.6 - FRAMEWORK: aiopg-newest # asyncpg - VERSION: pypy-3 FRAMEWORK: asyncpg-newest - VERSION: pypy-3 FRAMEWORK: asyncpg-0.28 - - VERSION: python-3.6 - FRAMEWORK: asyncpg-newest - - VERSION: python-3.6 - FRAMEWORK: asyncpg-0.28 - VERSION: python-3.13 FRAMEWORK: asyncpg-0.28 # sanic @@ -259,10 +219,6 @@ exclude: FRAMEWORK: sanic-newest - VERSION: pypy-3 FRAMEWORK: sanic-20.12 - - VERSION: python-3.6 - FRAMEWORK: sanic-20.12 - - VERSION: python-3.6 - FRAMEWORK: sanic-newest - VERSION: python-3.8 FRAMEWORK: sanic-newest - VERSION: python-3.13 @@ -270,21 +226,13 @@ exclude: # aioredis - VERSION: pypy-3 FRAMEWORK: aioredis-newest - - VERSION: python-3.6 - FRAMEWORK: aioredis-newest # aiomysql - VERSION: pypy-3 FRAMEWORK: aiomysql-newest - - VERSION: python-3.6 - FRAMEWORK: aiomysql-newest # aiobotocore - VERSION: pypy-3 FRAMEWORK: aiobotocore-newest - - VERSION: python-3.6 - FRAMEWORK: aiobotocore-newest # mysql-connector-python - - VERSION: python-3.6 - FRAMEWORK: mysql_connector-newest # twisted - VERSION: python-3.11 FRAMEWORK: twisted-18 @@ -318,8 +266,6 @@ exclude: - VERSION: python-3.13 FRAMEWORK: pylibmc-1.4 # grpc - - VERSION: python-3.6 - FRAMEWORK: grpc-newest - VERSION: python-3.7 FRAMEWORK: grpc-1.24 - VERSION: python-3.8 diff --git a/.ci/.matrix_python.yml b/.ci/.matrix_python.yml index 86c87ad88..1649823b1 100644 --- a/.ci/.matrix_python.yml +++ b/.ci/.matrix_python.yml @@ -1,3 +1,3 @@ VERSION: - - python-3.6 + - python-3.7 - python-3.13 diff --git a/.ci/.matrix_python_full.yml b/.ci/.matrix_python_full.yml index bb763b7ca..98e228991 100644 --- a/.ci/.matrix_python_full.yml +++ b/.ci/.matrix_python_full.yml @@ -1,5 +1,4 @@ VERSION: - - python-3.6 - python-3.7 - python-3.8 - python-3.9 diff --git a/.ci/.matrix_windows.yml b/.ci/.matrix_windows.yml deleted file mode 100644 index 0f12b9422..000000000 --- a/.ci/.matrix_windows.yml +++ /dev/null @@ -1,21 +0,0 @@ -# This is the limited list of matrix builds in Windows, to be triggered on a PR basis -# The format is: -# VERSION: Major.Minor python version. -# FRAMEWORK: What framework to be tested. String format. -# ASYNCIO: Whether it's enabled or disabled. Boolean format. -# -# TODO: Remove this file when fully migrated to GH Actions - -windows: -# - VERSION: "3.6" -# FRAMEWORK: "none" -# ASYNCIO: "true" -# - VERSION: "3.7" -# FRAMEWORK: "none" -# ASYNCIO: "true" - - VERSION: "3.8" - FRAMEWORK: "none" - ASYNCIO: "true" - - VERSION: "3.9" # waiting for greenlet to have binary wheels for 3.9 - FRAMEWORK: "none" - ASYNCIO: "true" diff --git a/.ci/publish-aws.sh b/.ci/publish-aws.sh index 3bb7a554c..137b82ef6 100755 --- a/.ci/publish-aws.sh +++ b/.ci/publish-aws.sh @@ -46,7 +46,7 @@ for region in $ALL_AWS_REGIONS; do --layer-name="${FULL_LAYER_NAME}" \ --description="AWS Lambda Extension Layer for the Elastic APM Python Agent" \ --license-info="BSD-3-Clause" \ - --compatible-runtimes python3.6 python3.7 python3.8 python3.9 python3.10 python3.11 python3.12 python3.13\ + --compatible-runtimes python3.7 python3.8 python3.9 python3.10 python3.11 python3.12 python3.13\ --zip-file="fileb://${zip_file}") echo "${publish_output}" > "${AWS_FOLDER}/${region}" layer_version=$(echo "${publish_output}" | jq '.Version') diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4fc7a275e..bd3f79c76 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -114,9 +114,6 @@ jobs: fail-fast: false matrix: include: - # - version: "3.6" - # framework: "none" - # asyncio: "true" # - version: "3.7" # framework: none # asyncio: true diff --git a/Makefile b/Makefile index b2d00f400..82e4d2fb4 100644 --- a/Makefile +++ b/Makefile @@ -10,14 +10,8 @@ flake8: test: # delete any __pycache__ folders to avoid hard-to-debug caching issues find . -type f -name '*.py[co]' -delete -o -type d -name __pycache__ -delete - # pypy3 should be added to the first `if` once it supports py3.7 - if [[ "$$PYTHON_VERSION" =~ ^(3.7|3.8|3.9|3.10|3.11|3.12|3.13|nightly)$$ ]] ; then \ - echo "Python 3.7+, with asyncio"; \ - pytest -v $(PYTEST_ARGS) --showlocals $(PYTEST_MARKER) $(PYTEST_JUNIT); \ - else \ - echo "Python < 3.7, without asyncio"; \ - pytest -v $(PYTEST_ARGS) --showlocals $(PYTEST_MARKER) $(PYTEST_JUNIT) --ignore-glob='*/asyncio*/*'; \ - fi + echo "Python 3.7+, with asyncio"; \ + pytest -v $(PYTEST_ARGS) --showlocals $(PYTEST_MARKER) $(PYTEST_JUNIT); \ coverage: PYTEST_ARGS=--cov --cov-context=test --cov-config=setup.cfg --cov-branch coverage: export COVERAGE_FILE=.coverage.docker.$(PYTHON_FULL_VERSION).$(FRAMEWORK) diff --git a/docs/reference/run-tests-locally.md b/docs/reference/run-tests-locally.md index f72432d7e..689b08524 100644 --- a/docs/reference/run-tests-locally.md +++ b/docs/reference/run-tests-locally.md @@ -53,7 +53,7 @@ $ ./tests/scripts/docker/run_tests.sh python-version framework-version bool: def check_python_version(self) -> None: v = tuple(map(int, platform.python_version_tuple()[:2])) - if v < (3, 6): - warnings.warn("The Elastic APM agent only supports Python 3.6+", DeprecationWarning) + if v < (3, 7): + warnings.warn("The Elastic APM agent only supports Python 3.7+", DeprecationWarning) def check_server_version( self, gte: Optional[Tuple[int, ...]] = None, lte: Optional[Tuple[int, ...]] = None diff --git a/elasticapm/instrumentation/register.py b/elasticapm/instrumentation/register.py index b37aff1e9..3e5d82230 100644 --- a/elasticapm/instrumentation/register.py +++ b/elasticapm/instrumentation/register.py @@ -28,8 +28,6 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import sys - from elasticapm.utils.module_import import import_string _cls_register = { @@ -70,35 +68,29 @@ "elasticapm.instrumentation.packages.kafka.KafkaInstrumentation", "elasticapm.instrumentation.packages.grpc.GRPCClientInstrumentation", "elasticapm.instrumentation.packages.grpc.GRPCServerInstrumentation", + "elasticapm.instrumentation.packages.asyncio.sleep.AsyncIOSleepInstrumentation", + "elasticapm.instrumentation.packages.asyncio.aiohttp_client.AioHttpClientInstrumentation", + "elasticapm.instrumentation.packages.httpx.async.httpx.HttpxAsyncClientInstrumentation", + "elasticapm.instrumentation.packages.asyncio.elasticsearch.ElasticSearchAsyncConnection", + "elasticapm.instrumentation.packages.asyncio.elasticsearch.ElasticsearchAsyncTransportInstrumentation", + "elasticapm.instrumentation.packages.asyncio.aiopg.AioPGInstrumentation", + "elasticapm.instrumentation.packages.asyncio.asyncpg.AsyncPGInstrumentation", + "elasticapm.instrumentation.packages.tornado.TornadoRequestExecuteInstrumentation", + "elasticapm.instrumentation.packages.tornado.TornadoHandleRequestExceptionInstrumentation", + "elasticapm.instrumentation.packages.tornado.TornadoRenderInstrumentation", + "elasticapm.instrumentation.packages.httpx.async.httpcore.HTTPCoreAsyncInstrumentation", + "elasticapm.instrumentation.packages.asyncio.aioredis.RedisConnectionPoolInstrumentation", + "elasticapm.instrumentation.packages.asyncio.aioredis.RedisPipelineInstrumentation", + "elasticapm.instrumentation.packages.asyncio.aioredis.RedisConnectionInstrumentation", + "elasticapm.instrumentation.packages.asyncio.aiomysql.AioMySQLInstrumentation", + "elasticapm.instrumentation.packages.asyncio.aiobotocore.AioBotocoreInstrumentation", + "elasticapm.instrumentation.packages.asyncio.starlette.StarletteServerErrorMiddlewareInstrumentation", + "elasticapm.instrumentation.packages.asyncio.redis_asyncio.RedisAsyncioInstrumentation", + "elasticapm.instrumentation.packages.asyncio.redis_asyncio.RedisPipelineInstrumentation", + "elasticapm.instrumentation.packages.asyncio.psycopg_async.AsyncPsycopgInstrumentation", + "elasticapm.instrumentation.packages.grpc.GRPCAsyncServerInstrumentation", } -if sys.version_info >= (3, 7): - _cls_register.update( - [ - "elasticapm.instrumentation.packages.asyncio.sleep.AsyncIOSleepInstrumentation", - "elasticapm.instrumentation.packages.asyncio.aiohttp_client.AioHttpClientInstrumentation", - "elasticapm.instrumentation.packages.httpx.async.httpx.HttpxAsyncClientInstrumentation", - "elasticapm.instrumentation.packages.asyncio.elasticsearch.ElasticSearchAsyncConnection", - "elasticapm.instrumentation.packages.asyncio.elasticsearch.ElasticsearchAsyncTransportInstrumentation", - "elasticapm.instrumentation.packages.asyncio.aiopg.AioPGInstrumentation", - "elasticapm.instrumentation.packages.asyncio.asyncpg.AsyncPGInstrumentation", - "elasticapm.instrumentation.packages.tornado.TornadoRequestExecuteInstrumentation", - "elasticapm.instrumentation.packages.tornado.TornadoHandleRequestExceptionInstrumentation", - "elasticapm.instrumentation.packages.tornado.TornadoRenderInstrumentation", - "elasticapm.instrumentation.packages.httpx.async.httpcore.HTTPCoreAsyncInstrumentation", - "elasticapm.instrumentation.packages.asyncio.aioredis.RedisConnectionPoolInstrumentation", - "elasticapm.instrumentation.packages.asyncio.aioredis.RedisPipelineInstrumentation", - "elasticapm.instrumentation.packages.asyncio.aioredis.RedisConnectionInstrumentation", - "elasticapm.instrumentation.packages.asyncio.aiomysql.AioMySQLInstrumentation", - "elasticapm.instrumentation.packages.asyncio.aiobotocore.AioBotocoreInstrumentation", - "elasticapm.instrumentation.packages.asyncio.starlette.StarletteServerErrorMiddlewareInstrumentation", - "elasticapm.instrumentation.packages.asyncio.redis_asyncio.RedisAsyncioInstrumentation", - "elasticapm.instrumentation.packages.asyncio.redis_asyncio.RedisPipelineInstrumentation", - "elasticapm.instrumentation.packages.asyncio.psycopg_async.AsyncPsycopgInstrumentation", - "elasticapm.instrumentation.packages.grpc.GRPCAsyncServerInstrumentation", - ] - ) - # These instrumentations should only be enabled if we're instrumenting via the # wrapper script, which calls register_wrapper_instrumentations() below. _wrapper_register = { diff --git a/setup.cfg b/setup.cfg index e9f766645..5a29c245f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,7 +15,6 @@ classifiers = Operating System :: OS Independent Topic :: Software Development Programming Language :: Python - Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 @@ -33,7 +32,7 @@ project_urls = Tracker = https://github.com/elastic/apm-agent-python/issues [options] -python_requires = >=3.6, <4 +python_requires = >=3.7, <4 packages = find: include_package_data = true zip_safe = false diff --git a/tests/requirements/reqs-base.txt b/tests/requirements/reqs-base.txt index d1105586a..9fb503dd3 100644 --- a/tests/requirements/reqs-base.txt +++ b/tests/requirements/reqs-base.txt @@ -1,8 +1,6 @@ -pytest==7.0.1 ; python_version == '3.6' -pytest==7.4.0 ; python_version > '3.6' +pytest==7.4.0 pytest-random-order==1.1.0 pytest-django==4.4.0 -coverage==6.2 ; python_version == '3.6' coverage==6.3 ; python_version == '3.7' coverage[toml]==6.3 ; python_version == '3.7' coverage==7.3.1 ; python_version > '3.7' @@ -10,16 +8,11 @@ pytest-cov==4.0.0 ; python_version < '3.8' pytest-cov==4.1.0 ; python_version > '3.7' jinja2==3.1.5 ; python_version == '3.7' pytest-localserver==0.9.0 -pytest-mock==3.6.1 ; python_version == '3.6' -pytest-mock==3.10.0 ; python_version > '3.6' -pytest-benchmark==3.4.1 ; python_version == '3.6' -pytest-benchmark==4.0.0 ; python_version > '3.6' -pytest-bdd==5.0.0 ; python_version == '3.6' -pytest-bdd==6.1.1 ; python_version > '3.6' -pytest-rerunfailures==10.2 ; python_version == '3.6' -pytest-rerunfailures==11.1.2 ; python_version > '3.6' -jsonschema==3.2.0 ; python_version == '3.6' -jsonschema==4.17.3 ; python_version > '3.6' +pytest-mock==3.10.0 +pytest-benchmark==4.0.0 +pytest-bdd==6.1.1 +pytest-rerunfailures==11.1.2 +jsonschema==4.17.3 urllib3!=2.0.0,<3.0.0 @@ -32,6 +25,6 @@ structlog wrapt>=1.14.1,!=1.15.0 simplejson -pytest-asyncio==0.21.0 ; python_version >= '3.7' -asynctest==0.13.0 ; python_version >= '3.7' +pytest-asyncio==0.21.0 +asynctest==0.13.0 typing_extensions!=3.10.0.1 ; python_version >= '3.10' # see https://github.com/python/typing/issues/865 diff --git a/tests/scripts/run_tests.sh b/tests/scripts/run_tests.sh index 7fcc85010..414a09885 100755 --- a/tests/scripts/run_tests.sh +++ b/tests/scripts/run_tests.sh @@ -6,7 +6,7 @@ export PATH=${HOME}/.local/bin:${PATH} python -m pip install --user -U pip setuptools --cache-dir "${PIP_CACHE}" python -m pip install --user -r "tests/requirements/reqs-${FRAMEWORK}.txt" --cache-dir "${PIP_CACHE}" -export PYTHON_VERSION=$(python -c "import platform; pv=platform.python_version_tuple(); print('pypy' + ('' if pv[0] == 2 else str(pv[0])) if platform.python_implementation() == 'PyPy' else '.'.join(map(str, platform.python_version_tuple()[:2])))") +export PYTHON_VERSION=$(python -c "import platform; pv=platform.python_version_tuple(); print('pypy' + (str(pv[0])) if platform.python_implementation() == 'PyPy' else '.'.join(map(str, platform.python_version_tuple()[:2])))") # check if the full FRAMEWORK name is in scripts/envs if [[ -e "./tests/scripts/envs/${FRAMEWORK}.sh" ]] From ee7652ee7a4fd96352636136447956f0ec57414e Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 09:11:57 +0200 Subject: [PATCH 284/409] chore: deps(updatecli): Bump updatecli version to v0.103.1 (#2343) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 3de462ab8..8a1126234 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.103.0 \ No newline at end of file +updatecli v0.103.1 \ No newline at end of file From e74865c0bac01794cd628e6ce7b10671407b6121 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 9 Jul 2025 09:12:17 +0200 Subject: [PATCH 285/409] contrib/serverless/azure: fix client_class and metrics sets invocation (#2337) Fix extension handling of client_class that was ignored and typo in metrics_sets arg that was mistyped. While at it fix also a broken tests and add testing in the matrix. --- .ci/.matrix_framework.yml | 1 + .ci/.matrix_framework_full.yml | 1 + elasticapm/contrib/serverless/azure.py | 4 +-- .../azurefunctions/azure_functions_tests.py | 26 +++++++++++++++++-- .../reqs-azurefunctions-newest.txt | 2 ++ 5 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 tests/requirements/reqs-azurefunctions-newest.txt diff --git a/.ci/.matrix_framework.yml b/.ci/.matrix_framework.yml index 679064a72..b86e51d01 100644 --- a/.ci/.matrix_framework.yml +++ b/.ci/.matrix_framework.yml @@ -56,3 +56,4 @@ FRAMEWORK: - aiobotocore-newest - kafka-python-newest - grpc-newest + - azurefunctions-newest diff --git a/.ci/.matrix_framework_full.yml b/.ci/.matrix_framework_full.yml index 8cd63d48d..54fc7f19a 100644 --- a/.ci/.matrix_framework_full.yml +++ b/.ci/.matrix_framework_full.yml @@ -92,3 +92,4 @@ FRAMEWORK: - kafka-python-newest - grpc-newest #- grpc-1.24 # This appears to have problems with python>3.6? + - azurefunctions-newest diff --git a/elasticapm/contrib/serverless/azure.py b/elasticapm/contrib/serverless/azure.py index c5df4882a..33d406934 100644 --- a/elasticapm/contrib/serverless/azure.py +++ b/elasticapm/contrib/serverless/azure.py @@ -96,7 +96,7 @@ def configure(cls, client_class=AzureFunctionsClient, **kwargs) -> None: if not client: kwargs["metrics_interval"] = "0ms" kwargs["breakdown_metrics"] = "false" - if "metric_sets" not in kwargs and "ELASTIC_APM_METRICS_SETS" not in os.environ: + if "metrics_sets" not in kwargs and "ELASTIC_APM_METRICS_SETS" not in os.environ: # Allow users to override metrics sets kwargs["metrics_sets"] = [] kwargs["central_config"] = "false" @@ -114,7 +114,7 @@ def configure(cls, client_class=AzureFunctionsClient, **kwargs) -> None: and "AZURE_FUNCTIONS_ENVIRONMENT" in os.environ ): kwargs["environment"] = os.environ["AZURE_FUNCTIONS_ENVIRONMENT"] - client = AzureFunctionsClient(**kwargs) + client = client_class(**kwargs) cls.client = client @classmethod diff --git a/tests/contrib/serverless/azurefunctions/azure_functions_tests.py b/tests/contrib/serverless/azurefunctions/azure_functions_tests.py index 1db33758a..c274b92ef 100644 --- a/tests/contrib/serverless/azurefunctions/azure_functions_tests.py +++ b/tests/contrib/serverless/azurefunctions/azure_functions_tests.py @@ -37,6 +37,7 @@ import azure.functions as func import mock +import elasticapm from elasticapm.conf import constants from elasticapm.contrib.serverless.azure import AzureFunctionsClient, ElasticAPMExtension, get_faas_data from tests.fixtures import TempStoreClient @@ -95,6 +96,7 @@ def test_extension_configure(): ElasticAPMExtension.configure(client_class=AzureFunctionsTestClient) client = ElasticAPMExtension.client assert client.config.metrics_interval == datetime.timedelta(0) + assert client.config.breakdown_metrics is False assert client.config.central_config is False assert client.config.cloud_provider == "none" assert client.config.framework_name == "Azure Functions" @@ -106,6 +108,27 @@ def test_extension_configure(): ElasticAPMExtension.client = None +def test_extension_configure_with_kwargs(): + try: + ElasticAPMExtension.configure( + client_class=AzureFunctionsTestClient, metrics_sets=["foo"], service_name="foo", environment="bar" + ) + client = ElasticAPMExtension.client + + assert client.config.metrics_interval == datetime.timedelta(0) + assert client.config.breakdown_metrics is False + assert client.config.central_config is False + assert client.config.cloud_provider == "none" + assert client.config.framework_name == "Azure Functions" + assert client.config.service_name == "foo" + assert client.config.environment == "bar" + assert client.config.metrics_sets == ["foo"] + finally: + if ElasticAPMExtension.client: + ElasticAPMExtension.client.close() + ElasticAPMExtension.client = None + + @pytest.mark.parametrize( "elasticapm_client", [{"client_class": AzureFunctionsTestClient}], indirect=["elasticapm_client"] ) @@ -122,8 +145,7 @@ def test_pre_post_invocation_app_level_request(elasticapm_client): body=b"", ) response = func.HttpResponse("", status_code=200, headers={}, mimetype="text/html") - context = mock.Mock(function_name="foo") - context.function_name = "foo_function" + context = mock.Mock(function_name="foo_function", invocation_id="fooid") ElasticAPMExtension.pre_invocation_app_level(None, context, {"request": request}) ElasticAPMExtension.post_invocation_app_level(None, context, func_ret=response) transaction = elasticapm_client.events[constants.TRANSACTION][0] diff --git a/tests/requirements/reqs-azurefunctions-newest.txt b/tests/requirements/reqs-azurefunctions-newest.txt new file mode 100644 index 000000000..76dc50b48 --- /dev/null +++ b/tests/requirements/reqs-azurefunctions-newest.txt @@ -0,0 +1,2 @@ +azure-functions +-r reqs-base.txt From e4dff95d2176a152f385d431848afa71355b3a44 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 9 Jul 2025 10:14:07 +0200 Subject: [PATCH 286/409] Drop support for opentracing (#2342) --- .ci/.matrix_framework.yml | 1 - .ci/.matrix_framework_fips.yml | 1 - .ci/.matrix_framework_full.yml | 2 - elasticapm/contrib/opentracing/__init__.py | 43 --- elasticapm/contrib/opentracing/span.py | 136 -------- elasticapm/contrib/opentracing/tracer.py | 131 -------- setup.cfg | 3 - tests/contrib/opentracing/__init__.py | 29 -- tests/contrib/opentracing/tests.py | 313 ------------------ tests/requirements/reqs-opentracing-2.0.txt | 2 - .../requirements/reqs-opentracing-newest.txt | 2 - tests/scripts/envs/opentracing.sh | 1 - 12 files changed, 664 deletions(-) delete mode 100644 elasticapm/contrib/opentracing/__init__.py delete mode 100644 elasticapm/contrib/opentracing/span.py delete mode 100644 elasticapm/contrib/opentracing/tracer.py delete mode 100644 tests/contrib/opentracing/__init__.py delete mode 100644 tests/contrib/opentracing/tests.py delete mode 100644 tests/requirements/reqs-opentracing-2.0.txt delete mode 100644 tests/requirements/reqs-opentracing-newest.txt delete mode 100644 tests/scripts/envs/opentracing.sh diff --git a/.ci/.matrix_framework.yml b/.ci/.matrix_framework.yml index b86e51d01..1ece9a68a 100644 --- a/.ci/.matrix_framework.yml +++ b/.ci/.matrix_framework.yml @@ -12,7 +12,6 @@ FRAMEWORK: - flask-3.0 - jinja2-3 - opentelemetry-newest - - opentracing-newest - twisted-newest - celery-5-flask-2 - celery-5-django-4 diff --git a/.ci/.matrix_framework_fips.yml b/.ci/.matrix_framework_fips.yml index 6bbc9cd3e..0c733de80 100644 --- a/.ci/.matrix_framework_fips.yml +++ b/.ci/.matrix_framework_fips.yml @@ -6,7 +6,6 @@ FRAMEWORK: - flask-3.0 - jinja2-3 - opentelemetry-newest - - opentracing-newest - twisted-newest - celery-5-flask-2 - celery-5-django-5 diff --git a/.ci/.matrix_framework_full.yml b/.ci/.matrix_framework_full.yml index 54fc7f19a..b1fbeaa58 100644 --- a/.ci/.matrix_framework_full.yml +++ b/.ci/.matrix_framework_full.yml @@ -30,8 +30,6 @@ FRAMEWORK: - celery-5-django-4 - celery-5-django-5 - opentelemetry-newest - - opentracing-newest - - opentracing-2.0 - twisted-newest - twisted-18 - twisted-17 diff --git a/elasticapm/contrib/opentracing/__init__.py b/elasticapm/contrib/opentracing/__init__.py deleted file mode 100644 index 71619ea20..000000000 --- a/elasticapm/contrib/opentracing/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -# BSD 3-Clause License -# -# Copyright (c) 2019, Elasticsearch BV -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -import warnings - -from .span import OTSpan # noqa: F401 -from .tracer import Tracer # noqa: F401 - -warnings.warn( - ( - "The OpenTracing bridge is deprecated and will be removed in the next major release. " - "Please migrate to the OpenTelemetry bridge." - ), - DeprecationWarning, -) diff --git a/elasticapm/contrib/opentracing/span.py b/elasticapm/contrib/opentracing/span.py deleted file mode 100644 index 6bc00fec5..000000000 --- a/elasticapm/contrib/opentracing/span.py +++ /dev/null @@ -1,136 +0,0 @@ -# BSD 3-Clause License -# -# Copyright (c) 2019, Elasticsearch BV -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from opentracing.span import Span as OTSpanBase -from opentracing.span import SpanContext as OTSpanContextBase - -from elasticapm import traces -from elasticapm.utils import get_url_dict -from elasticapm.utils.logging import get_logger - -try: - # opentracing-python 2.1+ - from opentracing import logs as ot_logs - from opentracing import tags -except ImportError: - # opentracing-python <2.1 - from opentracing.ext import tags - - ot_logs = None - - -logger = get_logger("elasticapm.contrib.opentracing") - - -class OTSpan(OTSpanBase): - def __init__(self, tracer, context, elastic_apm_ref) -> None: - super(OTSpan, self).__init__(tracer, context) - self.elastic_apm_ref = elastic_apm_ref - self.is_transaction = isinstance(elastic_apm_ref, traces.Transaction) - self.is_dropped = isinstance(elastic_apm_ref, traces.DroppedSpan) - if not context.span: - context.span = self - - def log_kv(self, key_values, timestamp=None): - exc_type, exc_val, exc_tb = None, None, None - if "python.exception.type" in key_values: - exc_type = key_values["python.exception.type"] - exc_val = key_values.get("python.exception.val") - exc_tb = key_values.get("python.exception.tb") - elif ot_logs and key_values.get(ot_logs.EVENT) == tags.ERROR: - exc_type = key_values[ot_logs.ERROR_KIND] - exc_val = key_values.get(ot_logs.ERROR_OBJECT) - exc_tb = key_values.get(ot_logs.STACK) - else: - logger.debug("Can't handle non-exception type opentracing logs") - if exc_type: - agent = self.tracer._agent - agent.capture_exception(exc_info=(exc_type, exc_val, exc_tb)) - return self - - def set_operation_name(self, operation_name): - self.elastic_apm_ref.name = operation_name - return self - - def set_tag(self, key, value): - if self.is_transaction: - if key == "type": - self.elastic_apm_ref.transaction_type = value - elif key == "result": - self.elastic_apm_ref.result = value - elif key == tags.HTTP_STATUS_CODE: - self.elastic_apm_ref.result = "HTTP {}xx".format(str(value)[0]) - traces.set_context({"status_code": value}, "response") - elif key == "user.id": - traces.set_user_context(user_id=value) - elif key == "user.username": - traces.set_user_context(username=value) - elif key == "user.email": - traces.set_user_context(email=value) - elif key == tags.HTTP_URL: - traces.set_context({"url": get_url_dict(value)}, "request") - elif key == tags.HTTP_METHOD: - traces.set_context({"method": value}, "request") - elif key == tags.COMPONENT: - traces.set_context({"framework": {"name": value}}, "service") - else: - self.elastic_apm_ref.label(**{key: value}) - elif not self.is_dropped: - if key.startswith("db."): - span_context = self.elastic_apm_ref.context or {} - if "db" not in span_context: - span_context["db"] = {} - if key == tags.DATABASE_STATEMENT: - span_context["db"]["statement"] = value - elif key == tags.DATABASE_USER: - span_context["db"]["user"] = value - elif key == tags.DATABASE_TYPE: - span_context["db"]["type"] = value - self.elastic_apm_ref.type = "db." + value - else: - self.elastic_apm_ref.label(**{key: value}) - self.elastic_apm_ref.context = span_context - elif key == tags.SPAN_KIND: - self.elastic_apm_ref.type = value - else: - self.elastic_apm_ref.label(**{key: value}) - return self - - def finish(self, finish_time=None) -> None: - if self.is_transaction: - self.tracer._agent.end_transaction() - elif not self.is_dropped: - self.elastic_apm_ref.transaction.end_span() - - -class OTSpanContext(OTSpanContextBase): - def __init__(self, trace_parent, span=None) -> None: - self.trace_parent = trace_parent - self.span = span diff --git a/elasticapm/contrib/opentracing/tracer.py b/elasticapm/contrib/opentracing/tracer.py deleted file mode 100644 index d331735f6..000000000 --- a/elasticapm/contrib/opentracing/tracer.py +++ /dev/null @@ -1,131 +0,0 @@ -# BSD 3-Clause License -# -# Copyright (c) 2019, Elasticsearch BV -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -import warnings - -from opentracing import Format, InvalidCarrierException, SpanContextCorruptedException, UnsupportedFormatException -from opentracing.scope_managers import ThreadLocalScopeManager -from opentracing.tracer import ReferenceType -from opentracing.tracer import Tracer as TracerBase - -import elasticapm -from elasticapm import get_client, instrument, traces -from elasticapm.conf import constants -from elasticapm.contrib.opentracing.span import OTSpan, OTSpanContext -from elasticapm.utils import disttracing - - -class Tracer(TracerBase): - def __init__(self, client_instance=None, config=None, scope_manager=None) -> None: - self._agent = client_instance or get_client() or elasticapm.Client(config=config) - if scope_manager and not isinstance(scope_manager, ThreadLocalScopeManager): - warnings.warn( - "Currently, the Elastic APM opentracing bridge only supports the ThreadLocalScopeManager. " - "Usage of other scope managers will lead to unpredictable results." - ) - self._scope_manager = scope_manager or ThreadLocalScopeManager() - if self._agent.config.instrument and self._agent.config.enabled: - instrument() - - def start_active_span( - self, - operation_name, - child_of=None, - references=None, - tags=None, - start_time=None, - ignore_active_span=False, - finish_on_close=True, - ): - ot_span = self.start_span( - operation_name, - child_of=child_of, - references=references, - tags=tags, - start_time=start_time, - ignore_active_span=ignore_active_span, - ) - scope = self._scope_manager.activate(ot_span, finish_on_close) - return scope - - def start_span( - self, operation_name=None, child_of=None, references=None, tags=None, start_time=None, ignore_active_span=False - ): - if isinstance(child_of, OTSpanContext): - parent_context = child_of - elif isinstance(child_of, OTSpan): - parent_context = child_of.context - elif references and references[0].type == ReferenceType.CHILD_OF: - parent_context = references[0].referenced_context - else: - parent_context = None - transaction = traces.execution_context.get_transaction() - if not transaction: - trace_parent = parent_context.trace_parent if parent_context else None - transaction = self._agent.begin_transaction("custom", trace_parent=trace_parent) - transaction.name = operation_name - span_context = OTSpanContext(trace_parent=transaction.trace_parent) - ot_span = OTSpan(self, span_context, transaction) - else: - # to allow setting an explicit parent span, we check if the parent_context is set - # and if it is a span. In all other cases, the parent is found implicitly through the - # execution context. - parent_span_id = ( - parent_context.span.elastic_apm_ref.id - if parent_context and parent_context.span and not parent_context.span.is_transaction - else None - ) - span = transaction._begin_span(operation_name, None, parent_span_id=parent_span_id) - trace_parent = parent_context.trace_parent if parent_context else transaction.trace_parent - span_context = OTSpanContext(trace_parent=trace_parent.copy_from(span_id=span.id)) - ot_span = OTSpan(self, span_context, span) - if tags: - for k, v in tags.items(): - ot_span.set_tag(k, v) - return ot_span - - def extract(self, format, carrier): - if format in (Format.HTTP_HEADERS, Format.TEXT_MAP): - trace_parent = disttracing.TraceParent.from_headers(carrier) - if not trace_parent: - raise SpanContextCorruptedException("could not extract span context from carrier") - return OTSpanContext(trace_parent=trace_parent) - raise UnsupportedFormatException - - def inject(self, span_context, format, carrier): - if format in (Format.HTTP_HEADERS, Format.TEXT_MAP): - if not isinstance(carrier, dict): - raise InvalidCarrierException("carrier for {} format should be dict-like".format(format)) - val = span_context.trace_parent.to_ascii() - carrier[constants.TRACEPARENT_HEADER_NAME] = val - if self._agent.config.use_elastic_traceparent_header: - carrier[constants.TRACEPARENT_LEGACY_HEADER_NAME] = val - return - raise UnsupportedFormatException diff --git a/setup.cfg b/setup.cfg index 5a29c245f..fc4d8be79 100644 --- a/setup.cfg +++ b/setup.cfg @@ -56,8 +56,6 @@ tornado = tornado starlette = starlette -opentracing = - opentracing>=2.0.0 sanic = sanic opentelemetry = @@ -82,7 +80,6 @@ markers = gevent eventlet celery - opentracing cassandra psycopg2 mongodb diff --git a/tests/contrib/opentracing/__init__.py b/tests/contrib/opentracing/__init__.py deleted file mode 100644 index 7e2b340e6..000000000 --- a/tests/contrib/opentracing/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# BSD 3-Clause License -# -# Copyright (c) 2019, Elasticsearch BV -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/tests/contrib/opentracing/tests.py b/tests/contrib/opentracing/tests.py deleted file mode 100644 index 50970c269..000000000 --- a/tests/contrib/opentracing/tests.py +++ /dev/null @@ -1,313 +0,0 @@ -# BSD 3-Clause License -# -# Copyright (c) 2019, Elasticsearch BV -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from datetime import timedelta - -import pytest # isort:skip - -opentracing = pytest.importorskip("opentracing") # isort:skip - -import sys - -import mock -from opentracing import Format - -import elasticapm -from elasticapm.conf import constants -from elasticapm.contrib.opentracing import Tracer -from elasticapm.contrib.opentracing.span import OTSpanContext -from elasticapm.utils.disttracing import TraceParent - -pytestmark = pytest.mark.opentracing - - -try: - from opentracing import logs as ot_logs - from opentracing import tags -except ImportError: - ot_logs = None - - -@pytest.fixture() -def tracer(elasticapm_client): - yield Tracer(client_instance=elasticapm_client) - elasticapm.uninstrument() - - -def test_tracer_with_instantiated_client(elasticapm_client): - tracer = Tracer(client_instance=elasticapm_client) - assert tracer._agent is elasticapm_client - - -def test_tracer_with_config(): - config = {"METRICS_INTERVAL": "0s", "SERVER_URL": "https://example.com/test"} - tracer = Tracer(config=config) - try: - assert tracer._agent.config.metrics_interval == timedelta(seconds=0) - assert tracer._agent.config.server_url == "https://example.com/test" - finally: - tracer._agent.close() - - -def test_tracer_instrument(elasticapm_client): - with mock.patch("elasticapm.contrib.opentracing.tracer.instrument") as mock_instrument: - elasticapm_client.config.instrument = False - Tracer(client_instance=elasticapm_client) - assert mock_instrument.call_count == 0 - - elasticapm_client.config.instrument = True - Tracer(client_instance=elasticapm_client) - assert mock_instrument.call_count == 1 - - -def test_ot_transaction_started(tracer): - with tracer.start_active_span("test") as ot_scope: - ot_scope.span.set_tag("result", "OK") - client = tracer._agent - transaction = client.events[constants.TRANSACTION][0] - assert transaction["type"] == "custom" - assert transaction["name"] == "test" - assert transaction["result"] == "OK" - - -def test_ot_span(tracer): - with tracer.start_active_span("test") as ot_scope_transaction: - with tracer.start_active_span("testspan") as ot_scope_span: - ot_scope_span.span.set_tag("span.kind", "custom") - with tracer.start_active_span("testspan2") as ot_scope_span2: - with tracer.start_active_span("testspan3", child_of=ot_scope_span.span) as ot_scope_span3: - pass - client = tracer._agent - transaction = client.events[constants.TRANSACTION][0] - span1 = client.events[constants.SPAN][2] - span2 = client.events[constants.SPAN][1] - span3 = client.events[constants.SPAN][0] - assert span1["transaction_id"] == span1["parent_id"] == transaction["id"] - assert span1["name"] == "testspan" - - assert span2["transaction_id"] == transaction["id"] - assert span2["parent_id"] == span1["id"] - assert span2["name"] == "testspan2" - - # check that span3 has span1 as parent - assert span3["transaction_id"] == transaction["id"] - assert span3["parent_id"] == span1["id"] - assert span3["name"] == "testspan3" - - -def test_transaction_tags(tracer): - with tracer.start_active_span("test") as ot_scope: - ot_scope.span.set_tag("type", "foo") - ot_scope.span.set_tag("http.status_code", 200) - ot_scope.span.set_tag("http.url", "http://example.com/foo") - ot_scope.span.set_tag("http.method", "GET") - ot_scope.span.set_tag("user.id", 1) - ot_scope.span.set_tag("user.email", "foo@example.com") - ot_scope.span.set_tag("user.username", "foo") - ot_scope.span.set_tag("component", "Django") - ot_scope.span.set_tag("something.else", "foo") - client = tracer._agent - transaction = client.events[constants.TRANSACTION][0] - - assert transaction["type"] == "foo" - assert transaction["result"] == "HTTP 2xx" - assert transaction["context"]["response"]["status_code"] == 200 - assert transaction["context"]["request"]["url"]["full"] == "http://example.com/foo" - assert transaction["context"]["request"]["method"] == "GET" - assert transaction["context"]["user"] == {"id": 1, "email": "foo@example.com", "username": "foo"} - assert transaction["context"]["service"]["framework"]["name"] == "Django" - assert transaction["context"]["tags"] == {"something_else": "foo"} - - -def test_span_tags(tracer): - with tracer.start_active_span("transaction") as ot_scope_t: - with tracer.start_active_span("span") as ot_scope_s: - s = ot_scope_s.span - s.set_tag("db.type", "sql") - s.set_tag("db.statement", "SELECT * FROM foo") - s.set_tag("db.user", "bar") - s.set_tag("db.instance", "baz") - with tracer.start_active_span("span") as ot_scope_s: - s = ot_scope_s.span - s.set_tag("span.kind", "foo") - s.set_tag("something.else", "bar") - client = tracer._agent - span1 = client.events[constants.SPAN][0] - span2 = client.events[constants.SPAN][1] - - assert span1["context"]["db"] == {"type": "sql", "user": "bar", "statement": "SELECT * FROM foo"} - assert span1["type"] == "db.sql" - assert span1["context"]["tags"] == {"db_instance": "baz"} - - assert span2["type"] == "foo" - assert span2["context"]["tags"] == {"something_else": "bar"} - - -@pytest.mark.parametrize("elasticapm_client", [{"transaction_max_spans": 1}], indirect=True) -def test_dropped_spans(tracer): - assert tracer._agent.config.transaction_max_spans == 1 - with tracer.start_active_span("transaction") as ot_scope_t: - with tracer.start_active_span("span") as ot_scope_s: - s = ot_scope_s.span - s.set_tag("db.type", "sql") - with tracer.start_active_span("span") as ot_scope_s: - s = ot_scope_s.span - s.set_tag("db.type", "sql") - client = tracer._agent - spans = client.events[constants.SPAN] - assert len(spans) == 1 - - -def test_error_log(tracer): - with tracer.start_active_span("transaction") as tx_scope: - try: - raise ValueError("oops") - except ValueError: - exc_type, exc_val, exc_tb = sys.exc_info()[:3] - tx_scope.span.log_kv( - {"python.exception.type": exc_type, "python.exception.val": exc_val, "python.exception.tb": exc_tb} - ) - client = tracer._agent - error = client.events[constants.ERROR][0] - - assert error["exception"]["message"] == "ValueError: oops" - - -@pytest.mark.skipif(ot_logs is None, reason="New key names in opentracing-python 2.1") -def test_error_log_ot_21(tracer): - with tracer.start_active_span("transaction") as tx_scope: - try: - raise ValueError("oops") - except ValueError: - exc_type, exc_val, exc_tb = sys.exc_info()[:3] - tx_scope.span.log_kv( - { - ot_logs.EVENT: tags.ERROR, - ot_logs.ERROR_KIND: exc_type, - ot_logs.ERROR_OBJECT: exc_val, - ot_logs.STACK: exc_tb, - } - ) - client = tracer._agent - error = client.events[constants.ERROR][0] - - assert error["exception"]["message"] == "ValueError: oops" - - -def test_error_log_automatic_in_span_context_manager(tracer): - scope = tracer.start_active_span("transaction") - with pytest.raises(ValueError): - with scope.span: - raise ValueError("oops") - - client = tracer._agent - error = client.events[constants.ERROR][0] - - assert error["exception"]["message"] == "ValueError: oops" - - -def test_span_set_bagge_item_noop(tracer): - scope = tracer.start_active_span("transaction") - assert scope.span.set_baggage_item("key", "val") == scope.span - - -def test_tracer_extract_http(tracer): - span_context = tracer.extract( - Format.HTTP_HEADERS, {"elastic-apm-traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"} - ) - - assert span_context.trace_parent.version == 0 - assert span_context.trace_parent.trace_id == "0af7651916cd43dd8448eb211c80319c" - assert span_context.trace_parent.span_id == "b7ad6b7169203331" - - -def test_tracer_extract_map(tracer): - span_context = tracer.extract( - Format.TEXT_MAP, {"elastic-apm-traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"} - ) - - assert span_context.trace_parent.version == 0 - assert span_context.trace_parent.trace_id == "0af7651916cd43dd8448eb211c80319c" - assert span_context.trace_parent.span_id == "b7ad6b7169203331" - - -def test_tracer_extract_binary(tracer): - with pytest.raises(opentracing.UnsupportedFormatException): - tracer.extract(Format.BINARY, b"foo") - - -def test_tracer_extract_corrupted(tracer): - with pytest.raises(opentracing.SpanContextCorruptedException): - tracer.extract(Format.HTTP_HEADERS, {"nothing-to": "see-here"}) - - -@pytest.mark.parametrize( - "elasticapm_client", - [ - pytest.param({"use_elastic_traceparent_header": True}, id="use_elastic_traceparent_header-True"), - pytest.param({"use_elastic_traceparent_header": False}, id="use_elastic_traceparent_header-False"), - ], - indirect=True, -) -def test_tracer_inject_http(tracer): - span_context = OTSpanContext( - trace_parent=TraceParent.from_string("00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01") - ) - carrier = {} - tracer.inject(span_context, Format.HTTP_HEADERS, carrier) - assert carrier[constants.TRACEPARENT_HEADER_NAME] == b"00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" - if tracer._agent.config.use_elastic_traceparent_header: - assert carrier[constants.TRACEPARENT_LEGACY_HEADER_NAME] == carrier[constants.TRACEPARENT_HEADER_NAME] - - -@pytest.mark.parametrize( - "elasticapm_client", - [ - pytest.param({"use_elastic_traceparent_header": True}, id="use_elastic_traceparent_header-True"), - pytest.param({"use_elastic_traceparent_header": False}, id="use_elastic_traceparent_header-False"), - ], - indirect=True, -) -def test_tracer_inject_map(tracer): - span_context = OTSpanContext( - trace_parent=TraceParent.from_string("00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01") - ) - carrier = {} - tracer.inject(span_context, Format.TEXT_MAP, carrier) - assert carrier[constants.TRACEPARENT_HEADER_NAME] == b"00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" - if tracer._agent.config.use_elastic_traceparent_header: - assert carrier[constants.TRACEPARENT_LEGACY_HEADER_NAME] == carrier[constants.TRACEPARENT_HEADER_NAME] - - -def test_tracer_inject_binary(tracer): - span_context = OTSpanContext( - trace_parent=TraceParent.from_string("00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01") - ) - with pytest.raises(opentracing.UnsupportedFormatException): - tracer.inject(span_context, Format.BINARY, {}) diff --git a/tests/requirements/reqs-opentracing-2.0.txt b/tests/requirements/reqs-opentracing-2.0.txt deleted file mode 100644 index de859ccbb..000000000 --- a/tests/requirements/reqs-opentracing-2.0.txt +++ /dev/null @@ -1,2 +0,0 @@ -opentracing>=2.0.0,<2.1.0 --r reqs-base.txt diff --git a/tests/requirements/reqs-opentracing-newest.txt b/tests/requirements/reqs-opentracing-newest.txt deleted file mode 100644 index b82c2d976..000000000 --- a/tests/requirements/reqs-opentracing-newest.txt +++ /dev/null @@ -1,2 +0,0 @@ -opentracing>=2.1.0 --r reqs-base.txt diff --git a/tests/scripts/envs/opentracing.sh b/tests/scripts/envs/opentracing.sh deleted file mode 100644 index 243c0ee96..000000000 --- a/tests/scripts/envs/opentracing.sh +++ /dev/null @@ -1 +0,0 @@ -export PYTEST_MARKER="-m opentracing" From 1fde4255e221b787d92a10f18bba2261c1bc4c3e Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 10 Jul 2025 10:58:09 +0200 Subject: [PATCH 287/409] instrumentation/mysql_connector: fix connection retrieval (#2344) The code was not working becase there was no default on getattr calls. --- elasticapm/instrumentation/packages/mysql_connector.py | 5 ++--- tests/instrumentation/mysql_connector_tests.py | 3 --- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/elasticapm/instrumentation/packages/mysql_connector.py b/elasticapm/instrumentation/packages/mysql_connector.py index a411af1c0..6b82ce4a8 100644 --- a/elasticapm/instrumentation/packages/mysql_connector.py +++ b/elasticapm/instrumentation/packages/mysql_connector.py @@ -46,9 +46,8 @@ def extract_signature(self, sql): @property def _self_database(self) -> str: - # for unknown reasons, the connection is available as the `_connection` attribute on Python 3.6, - # and as `_cnx` on later Python versions - connection = getattr(self, "_cnx") or getattr(self, "_connection") + # it looks like the connection is available as the `_connection` or as `_cnx` depending on Python versions + connection = getattr(self, "_connection", None) or getattr(self, "_cnx", None) return connection.database if connection else "" diff --git a/tests/instrumentation/mysql_connector_tests.py b/tests/instrumentation/mysql_connector_tests.py index 89e2df812..3b6cb47bd 100644 --- a/tests/instrumentation/mysql_connector_tests.py +++ b/tests/instrumentation/mysql_connector_tests.py @@ -63,9 +63,6 @@ def mysql_connector_connection(request): cursor.execute("DROP TABLE `test`") -@pytest.mark.skipif( - sys.version_info >= (3, 12), reason="Perhaps related to changes in weakref in py3.12?" -) # TODO py3.12 @pytest.mark.integrationtest def test_mysql_connector_select(instrument, mysql_connector_connection, elasticapm_client): cursor = mysql_connector_connection.cursor() From 378ed312d3eb107658441d4aa2ac63900bfbf632 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 10 Jul 2025 11:01:10 +0200 Subject: [PATCH 288/409] instrumentations/azure: fixes to work with azure-data-tables (#2187) * Check if body exists before trying to convert it to JSON * Update in azure-data-tables uses PATCH instead of PUT * Tests and requirements file for Azure Storage * Fixed typo in Azure Functions tests * Apply suggestions from code review * Update tests/instrumentation/azure_tests.py * Fixup tests * Fix requirements --------- Co-authored-by: cpiment <10828255+cpiment@users.noreply.github.com> --- .ci/.matrix_framework.yml | 1 + .ci/.matrix_framework_full.yml | 1 + elasticapm/instrumentation/packages/azure.py | 11 +-- .../azurefunctions/azure_functions_tests.py | 2 +- tests/instrumentation/azure_tests.py | 70 +++++++++++++++++++ tests/requirements/reqs-azure-newest.txt | 6 ++ 6 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 tests/requirements/reqs-azure-newest.txt diff --git a/.ci/.matrix_framework.yml b/.ci/.matrix_framework.yml index 1ece9a68a..1cd690c39 100644 --- a/.ci/.matrix_framework.yml +++ b/.ci/.matrix_framework.yml @@ -56,3 +56,4 @@ FRAMEWORK: - kafka-python-newest - grpc-newest - azurefunctions-newest + - azure-newest diff --git a/.ci/.matrix_framework_full.yml b/.ci/.matrix_framework_full.yml index b1fbeaa58..cdabff496 100644 --- a/.ci/.matrix_framework_full.yml +++ b/.ci/.matrix_framework_full.yml @@ -91,3 +91,4 @@ FRAMEWORK: - grpc-newest #- grpc-1.24 # This appears to have problems with python>3.6? - azurefunctions-newest + - azure-newest diff --git a/elasticapm/instrumentation/packages/azure.py b/elasticapm/instrumentation/packages/azure.py index 4200bb42b..934dbb17b 100644 --- a/elasticapm/instrumentation/packages/azure.py +++ b/elasticapm/instrumentation/packages/azure.py @@ -300,9 +300,12 @@ def handle_azuretable(request, hostname, path, query_params, service, service_ty account_name = hostname.split(".")[0] method = request.method body = request.body - try: - body = json.loads(body) - except json.decoder.JSONDecodeError: # str not bytes + if body: + try: + body = json.loads(body) + except json.decoder.JSONDecodeError: # str not bytes + body = {} + else: body = {} # /tablename(PartitionKey='',RowKey='') resource_name = path.split("/", 1)[1] if "/" in path else path @@ -313,7 +316,7 @@ def handle_azuretable(request, hostname, path, query_params, service, service_ty } operation_name = "Unknown" - if method.lower() == "put": + if method.lower() == "put" or method.lower() == "patch": operation_name = "Update" if "properties" in query_params.get("comp", []): operation_name = "SetProperties" diff --git a/tests/contrib/serverless/azurefunctions/azure_functions_tests.py b/tests/contrib/serverless/azurefunctions/azure_functions_tests.py index c274b92ef..e2abbdcd3 100644 --- a/tests/contrib/serverless/azurefunctions/azure_functions_tests.py +++ b/tests/contrib/serverless/azurefunctions/azure_functions_tests.py @@ -29,7 +29,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import pytest -azure = pytest.importorskip("azure") +azure = pytest.importorskip("azure.functions") import datetime import os diff --git a/tests/instrumentation/azure_tests.py b/tests/instrumentation/azure_tests.py index aeaab03c0..5662bedfc 100644 --- a/tests/instrumentation/azure_tests.py +++ b/tests/instrumentation/azure_tests.py @@ -39,10 +39,13 @@ azureblob = pytest.importorskip("azure.storage.blob") azurequeue = pytest.importorskip("azure.storage.queue") azuretable = pytest.importorskip("azure.cosmosdb.table") +azuredatatable = pytest.importorskip("azure.data.tables") azurefile = pytest.importorskip("azure.storage.fileshare") pytestmark = [pytest.mark.azurestorage] + from azure.cosmosdb.table.tableservice import TableService +from azure.data.tables import TableServiceClient as DataTableServiceClient from azure.storage.blob import BlobServiceClient from azure.storage.fileshare import ShareClient from azure.storage.queue import QueueClient @@ -82,6 +85,19 @@ def queue_client(): queue_client.delete_queue() +@pytest.fixture() +def data_table_service(): + table_name = "apmagentpythonci" + str(uuid.uuid4().hex) + data_table_service_client = DataTableServiceClient.from_connection_string(conn_str=CONNECTION_STRING) + data_table_service = data_table_service_client.get_table_client(table_name) + data_table_service.create_table() + data_table_service.table_name = table_name + + yield data_table_service + + data_table_service.delete_table() + + @pytest.fixture() def table_service(): table_name = "apmagentpythonci" + str(uuid.uuid4().hex) @@ -182,6 +198,24 @@ def test_queue(instrument, elasticapm_client, queue_client): assert span["action"] == "delete" +def test_data_table_create(instrument, elasticapm_client): + table_name = "apmagentpythonci" + str(uuid.uuid4().hex) + data_table_service_client = DataTableServiceClient.from_connection_string(conn_str=CONNECTION_STRING) + data_table_service = data_table_service_client.get_table_client(table_name) + + elasticapm_client.begin_transaction("transaction.test") + data_table_service.create_table() + data_table_service.delete_table() + elasticapm_client.end_transaction("MyView") + + span = elasticapm_client.events[constants.SPAN][0] + + assert span["name"] == "AzureTable Create {}".format(table_name) + assert span["type"] == "storage" + assert span["subtype"] == "azuretable" + assert span["action"] == "Create" + + def test_table_create(instrument, elasticapm_client): table_name = "apmagentpythonci" + str(uuid.uuid4().hex) table_service = TableService(connection_string=CONNECTION_STRING) @@ -199,6 +233,42 @@ def test_table_create(instrument, elasticapm_client): assert span["action"] == "Create" +def test_data_table(instrument, elasticapm_client, data_table_service): + table_name = data_table_service.table_name + elasticapm_client.begin_transaction("transaction.test") + task = {"PartitionKey": "tasksSeattle", "RowKey": "001", "description": "Take out the trash", "priority": 200} + data_table_service.create_entity(task) + task = {"PartitionKey": "tasksSeattle", "RowKey": "001", "description": "Take out the garbage", "priority": 250} + data_table_service.update_entity(task) + task = data_table_service.get_entity("tasksSeattle", "001") + data_table_service.delete_entity("tasksSeattle", "001") + elasticapm_client.end_transaction("MyView") + + span = elasticapm_client.events[constants.SPAN][0] + assert span["name"] == "AzureTable Insert {}".format(table_name) + assert span["type"] == "storage" + assert span["subtype"] == "azuretable" + assert span["action"] == "Insert" + + span = elasticapm_client.events[constants.SPAN][1] + assert span["name"] == "AzureTable Update {}(PartitionKey='tasksSeattle',RowKey='001')".format(table_name) + assert span["type"] == "storage" + assert span["subtype"] == "azuretable" + assert span["action"] == "Update" + + span = elasticapm_client.events[constants.SPAN][2] + assert span["name"] == "AzureTable Query {}(PartitionKey='tasksSeattle',RowKey='001')".format(table_name) + assert span["type"] == "storage" + assert span["subtype"] == "azuretable" + assert span["action"] == "Query" + + span = elasticapm_client.events[constants.SPAN][3] + assert span["name"] == "AzureTable Delete {}(PartitionKey='tasksSeattle',RowKey='001')".format(table_name) + assert span["type"] == "storage" + assert span["subtype"] == "azuretable" + assert span["action"] == "Delete" + + def test_table(instrument, elasticapm_client, table_service): table_name = table_service.table_name elasticapm_client.begin_transaction("transaction.test") diff --git a/tests/requirements/reqs-azure-newest.txt b/tests/requirements/reqs-azure-newest.txt new file mode 100644 index 000000000..d2bf7d1f0 --- /dev/null +++ b/tests/requirements/reqs-azure-newest.txt @@ -0,0 +1,6 @@ +azure-storage-blob +azure-storage-queue +azure-data-tables +azure-storage-file-share +azure-cosmosdb-table +-r reqs-base.txt From 8ac1781d091ca609212897d83adb35926edef90d Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 10 Jul 2025 11:03:46 +0200 Subject: [PATCH 289/409] Drop Python 3.7 support (#2340) --- .ci/.matrix_exclude.yml | 23 ----------------------- .ci/.matrix_python.yml | 2 +- .ci/.matrix_python_full.yml | 1 - .ci/publish-aws.sh | 2 +- .github/workflows/test.yml | 3 --- elasticapm/__init__.py | 4 ++-- elasticapm/base.py | 4 ++-- setup.cfg | 3 +-- tests/context/test_context.py | 16 ++-------------- tests/requirements/reqs-base.txt | 8 ++------ 10 files changed, 11 insertions(+), 55 deletions(-) diff --git a/.ci/.matrix_exclude.yml b/.ci/.matrix_exclude.yml index 524291f20..d4b1416b2 100644 --- a/.ci/.matrix_exclude.yml +++ b/.ci/.matrix_exclude.yml @@ -5,24 +5,13 @@ exclude: # Django 4.0 requires Python 3.8+ - VERSION: pypy-3 # current pypy-3 is compatible with Python 3.7 FRAMEWORK: django-4.0 - - VERSION: python-3.7 - FRAMEWORK: django-4.0 - # Django 4.2 requires Python 3.8+ - - VERSION: python-3.7 - FRAMEWORK: django-4.2 # Django 5.0 requires Python 3.10+ - - VERSION: python-3.7 - FRAMEWORK: django-5.0 - VERSION: python-3.8 FRAMEWORK: django-5.0 - VERSION: python-3.9 FRAMEWORK: django-5.0 - VERSION: pypy-3 # current pypy-3 is compatible with Python 3.7 FRAMEWORK: celery-5-django-4 - - VERSION: python-3.7 - FRAMEWORK: celery-5-django-4 - - VERSION: python-3.7 - FRAMEWORK: celery-5-django-5 - VERSION: python-3.8 FRAMEWORK: celery-5-django-5 - VERSION: python-3.9 @@ -30,10 +19,6 @@ exclude: # Flask - VERSION: pypy-3 FRAMEWORK: flask-0.11 # see https://github.com/pallets/flask/commit/6e46d0cd, 0.11.2 was never released - - VERSION: python-3.7 - FRAMEWORK: flask-2.3 - - VERSION: python-3.7 - FRAMEWORK: flask-3.0 # Python 3.10 removed a bunch of classes from collections, now in collections.abc - VERSION: python-3.10 FRAMEWORK: django-1.11 @@ -266,8 +251,6 @@ exclude: - VERSION: python-3.13 FRAMEWORK: pylibmc-1.4 # grpc - - VERSION: python-3.7 - FRAMEWORK: grpc-1.24 - VERSION: python-3.8 FRAMEWORK: grpc-1.24 - VERSION: python-3.9 @@ -280,12 +263,6 @@ exclude: FRAMEWORK: grpc-1.24 - VERSION: python-3.13 FRAMEWORK: grpc-1.24 - - VERSION: python-3.7 - FRAMEWORK: flask-1.0 - - VERSION: python-3.7 - FRAMEWORK: flask-1.1 - - VERSION: python-3.7 - FRAMEWORK: jinja2-2 # TODO py3.12 - VERSION: python-3.12 FRAMEWORK: sanic-20.12 # no wheels available yet diff --git a/.ci/.matrix_python.yml b/.ci/.matrix_python.yml index 1649823b1..a6c1e6948 100644 --- a/.ci/.matrix_python.yml +++ b/.ci/.matrix_python.yml @@ -1,3 +1,3 @@ VERSION: - - python-3.7 + - python-3.8 - python-3.13 diff --git a/.ci/.matrix_python_full.yml b/.ci/.matrix_python_full.yml index 98e228991..1c8ee413a 100644 --- a/.ci/.matrix_python_full.yml +++ b/.ci/.matrix_python_full.yml @@ -1,5 +1,4 @@ VERSION: - - python-3.7 - python-3.8 - python-3.9 - python-3.10 diff --git a/.ci/publish-aws.sh b/.ci/publish-aws.sh index 137b82ef6..39ef88425 100755 --- a/.ci/publish-aws.sh +++ b/.ci/publish-aws.sh @@ -46,7 +46,7 @@ for region in $ALL_AWS_REGIONS; do --layer-name="${FULL_LAYER_NAME}" \ --description="AWS Lambda Extension Layer for the Elastic APM Python Agent" \ --license-info="BSD-3-Clause" \ - --compatible-runtimes python3.7 python3.8 python3.9 python3.10 python3.11 python3.12 python3.13\ + --compatible-runtimes python3.8 python3.9 python3.10 python3.11 python3.12 python3.13\ --zip-file="fileb://${zip_file}") echo "${publish_output}" > "${AWS_FOLDER}/${region}" layer_version=$(echo "${publish_output}" | jq '.Version') diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bd3f79c76..3e68d173b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -114,9 +114,6 @@ jobs: fail-fast: false matrix: include: - # - version: "3.7" - # framework: none - # asyncio: true - version: "3.8" framework: none asyncio: true diff --git a/elasticapm/__init__.py b/elasticapm/__init__.py index bbfe43f0f..df6eee9e1 100644 --- a/elasticapm/__init__.py +++ b/elasticapm/__init__.py @@ -62,7 +62,7 @@ VERSION = "unknown" -if sys.version_info < (3, 7): - raise DeprecationWarning("The Elastic APM agent requires Python 3.7+") +if sys.version_info < (3, 8): + raise DeprecationWarning("The Elastic APM agent requires Python 3.8+") from elasticapm.contrib.asyncio.traces import async_capture_span # noqa: F401 E402 diff --git a/elasticapm/base.py b/elasticapm/base.py index 9140f98be..bd02afc1e 100644 --- a/elasticapm/base.py +++ b/elasticapm/base.py @@ -704,8 +704,8 @@ def should_ignore_topic(self, topic: str) -> bool: def check_python_version(self) -> None: v = tuple(map(int, platform.python_version_tuple()[:2])) - if v < (3, 7): - warnings.warn("The Elastic APM agent only supports Python 3.7+", DeprecationWarning) + if v < (3, 8): + warnings.warn("The Elastic APM agent only supports Python 3.8+", DeprecationWarning) def check_server_version( self, gte: Optional[Tuple[int, ...]] = None, lte: Optional[Tuple[int, ...]] = None diff --git a/setup.cfg b/setup.cfg index fc4d8be79..7532a5ad6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,7 +15,6 @@ classifiers = Operating System :: OS Independent Topic :: Software Development Programming Language :: Python - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 @@ -32,7 +31,7 @@ project_urls = Tracker = https://github.com/elastic/apm-agent-python/issues [options] -python_requires = >=3.7, <4 +python_requires = >=3.8, <4 packages = find: include_package_data = true zip_safe = false diff --git a/tests/context/test_context.py b/tests/context/test_context.py index 058d60bab..1bae6cb87 100644 --- a/tests/context/test_context.py +++ b/tests/context/test_context.py @@ -39,21 +39,9 @@ def test_execution_context_backing(): execution_context = elasticapm.context.init_execution_context() - if sys.version_info[0] == 3 and sys.version_info[1] >= 7: - from elasticapm.context.contextvars import ContextVarsContext + from elasticapm.context.contextvars import ContextVarsContext - assert isinstance(execution_context, ContextVarsContext) - else: - try: - import opentelemetry - - pytest.skip( - "opentelemetry installs contextvars backport, so this test isn't valid for the opentelemetry matrix" - ) - except ImportError: - pass - - assert isinstance(execution_context, ThreadLocalContext) + assert isinstance(execution_context, ContextVarsContext) def test_execution_context_monkeypatched(monkeypatch): diff --git a/tests/requirements/reqs-base.txt b/tests/requirements/reqs-base.txt index 9fb503dd3..0ce35a889 100644 --- a/tests/requirements/reqs-base.txt +++ b/tests/requirements/reqs-base.txt @@ -1,12 +1,8 @@ pytest==7.4.0 pytest-random-order==1.1.0 pytest-django==4.4.0 -coverage==6.3 ; python_version == '3.7' -coverage[toml]==6.3 ; python_version == '3.7' -coverage==7.3.1 ; python_version > '3.7' -pytest-cov==4.0.0 ; python_version < '3.8' -pytest-cov==4.1.0 ; python_version > '3.7' -jinja2==3.1.5 ; python_version == '3.7' +coverage==7.3.1 +pytest-cov==4.1.0 pytest-localserver==0.9.0 pytest-mock==3.10.0 pytest-benchmark==4.0.0 From eb34e89a6b34a8d80067c138bec1e9d82546558c Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 10 Jul 2025 11:04:23 +0200 Subject: [PATCH 290/409] contrib/django: remove deprecated LoggingHandler (#2345) --- elasticapm/conf/__init__.py | 5 -- elasticapm/contrib/django/handlers.py | 33 ------------- tests/contrib/django/django_tests.py | 68 --------------------------- 3 files changed, 106 deletions(-) diff --git a/elasticapm/conf/__init__.py b/elasticapm/conf/__init__.py index 6d19eb96c..45b4db5a2 100644 --- a/elasticapm/conf/__init__.py +++ b/elasticapm/conf/__init__.py @@ -887,11 +887,6 @@ def setup_logging(handler): >>> client = ElasticAPM(...) >>> setup_logging(LoggingHandler(client)) - Within Django: - - >>> from elasticapm.contrib.django.handlers import LoggingHandler - >>> setup_logging(LoggingHandler()) - Returns a boolean based on if logging was configured or not. """ # TODO We should probably revisit this. Does it make more sense as diff --git a/elasticapm/contrib/django/handlers.py b/elasticapm/contrib/django/handlers.py index c980acc4f..550cfae87 100644 --- a/elasticapm/contrib/django/handlers.py +++ b/elasticapm/contrib/django/handlers.py @@ -31,44 +31,11 @@ from __future__ import absolute_import -import logging import sys import warnings from django.conf import settings as django_settings -from elasticapm import get_client -from elasticapm.handlers.logging import LoggingHandler as BaseLoggingHandler -from elasticapm.utils.logging import get_logger - -logger = get_logger("elasticapm.logging") - - -class LoggingHandler(BaseLoggingHandler): - def __init__(self, level=logging.NOTSET) -> None: - warnings.warn( - "The LoggingHandler is deprecated and will be removed in v7.0 of the agent. " - "Please use `log_ecs_reformatting` and ship the logs with Elastic " - "Agent or Filebeat instead. " - "https://www.elastic.co/guide/en/apm/agent/python/current/logs.html", - DeprecationWarning, - ) - # skip initialization of BaseLoggingHandler - logging.Handler.__init__(self, level=level) - - @property - def client(self): - return get_client() - - def _emit(self, record, **kwargs): - from elasticapm.contrib.django.middleware import LogMiddleware - - # Fetch the request from a threadlocal variable, if available - request = getattr(LogMiddleware.thread, "request", None) - request = getattr(record, "request", request) - - return super(LoggingHandler, self)._emit(record, request=request, **kwargs) - def exception_handler(client, request=None, **kwargs): def actually_do_stuff(request=None, **kwargs) -> None: diff --git a/tests/contrib/django/django_tests.py b/tests/contrib/django/django_tests.py index 535729bcf..ad88e462e 100644 --- a/tests/contrib/django/django_tests.py +++ b/tests/contrib/django/django_tests.py @@ -62,7 +62,6 @@ from elasticapm.conf.constants import ERROR, SPAN, TRANSACTION from elasticapm.contrib.django.apps import ElasticAPMConfig from elasticapm.contrib.django.client import client, get_client -from elasticapm.contrib.django.handlers import LoggingHandler from elasticapm.contrib.django.middleware.wsgi import ElasticAPM from elasticapm.utils.disttracing import TraceParent from tests.contrib.django.conftest import BASE_TEMPLATE_DIR @@ -410,25 +409,6 @@ def test_ignored_exception_is_ignored(django_elasticapm_client, client): assert len(django_elasticapm_client.events[ERROR]) == 0 -def test_record_none_exc_info(django_elasticapm_client): - # sys.exc_info can return (None, None, None) if no exception is being - # handled anywhere on the stack. See: - # http://docs.python.org/library/sys.html#sys.exc_info - record = logging.LogRecord( - "foo", logging.INFO, pathname=None, lineno=None, msg="test", args=(), exc_info=(None, None, None) - ) - handler = LoggingHandler() - handler.emit(record) - - assert len(django_elasticapm_client.events[ERROR]) == 1 - event = django_elasticapm_client.events[ERROR][0] - - assert event["log"]["param_message"] == "test" - assert event["log"]["logger_name"] == "foo" - assert event["log"]["level"] == "info" - assert "exception" not in event - - def test_404_middleware(django_elasticapm_client, client): with override_settings( **middleware_setting(django.VERSION, ["elasticapm.contrib.django.middleware.Catch404Middleware"]) @@ -1032,54 +1012,6 @@ def test_filter_matches_module_only(django_sending_elasticapm_client): assert len(django_sending_elasticapm_client.httpserver.requests) == 1 -def test_django_logging_request_kwarg(django_elasticapm_client): - handler = LoggingHandler() - - logger = logging.getLogger(__name__) - logger.handlers = [] - logger.addHandler(handler) - - logger.error( - "This is a test error", - extra={ - "request": WSGIRequest( - environ={ - "wsgi.input": io.StringIO(), - "REQUEST_METHOD": "POST", - "SERVER_NAME": "testserver", - "SERVER_PORT": "80", - "CONTENT_TYPE": "application/json", - "ACCEPT": "application/json", - } - ) - }, - ) - - assert len(django_elasticapm_client.events[ERROR]) == 1 - event = django_elasticapm_client.events[ERROR][0] - assert "request" in event["context"] - request = event["context"]["request"] - assert request["method"] == "POST" - - -def test_django_logging_middleware(django_elasticapm_client, client): - handler = LoggingHandler() - - logger = logging.getLogger("logmiddleware") - logger.handlers = [] - logger.addHandler(handler) - logger.level = logging.INFO - - with override_settings( - **middleware_setting(django.VERSION, ["elasticapm.contrib.django.middleware.LogMiddleware"]) - ): - client.get(reverse("elasticapm-logging")) - assert len(django_elasticapm_client.events[ERROR]) == 1 - event = django_elasticapm_client.events[ERROR][0] - assert "request" in event["context"] - assert event["context"]["request"]["url"]["pathname"] == reverse("elasticapm-logging") - - def client_get(client, url): return client.get(url) From 39f4191315e8b2a94adcd220aaeaa816bd7725d2 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 10 Jul 2025 11:04:49 +0200 Subject: [PATCH 291/409] contrib/flask: remove deprecated log shipping integration (#2346) * contrib/flask: remove deprecated log shipping integration * Fix tests --- elasticapm/contrib/flask/__init__.py | 22 ++-------------------- tests/contrib/flask/flask_tests.py | 26 -------------------------- 2 files changed, 2 insertions(+), 46 deletions(-) diff --git a/elasticapm/contrib/flask/__init__.py b/elasticapm/contrib/flask/__init__.py index fdb6906dd..b6e4e312b 100644 --- a/elasticapm/contrib/flask/__init__.py +++ b/elasticapm/contrib/flask/__init__.py @@ -31,9 +31,6 @@ from __future__ import absolute_import -import logging -import warnings - import flask from flask import request, signals @@ -41,9 +38,8 @@ import elasticapm.instrumentation.control from elasticapm import get_client from elasticapm.base import Client -from elasticapm.conf import constants, setup_logging +from elasticapm.conf import constants from elasticapm.contrib.flask.utils import get_data_from_request, get_data_from_response -from elasticapm.handlers.logging import LoggingHandler from elasticapm.traces import execution_context from elasticapm.utils import build_name_with_http_method_prefix from elasticapm.utils.disttracing import TraceParent @@ -81,14 +77,8 @@ class ElasticAPM(object): >>> elasticapm.capture_message('hello, world!') """ - def __init__(self, app=None, client=None, client_cls=Client, logging=False, **defaults) -> None: + def __init__(self, app=None, client=None, client_cls=Client, **defaults) -> None: self.app = app - self.logging = logging - if self.logging: - warnings.warn( - "Flask log shipping is deprecated. See the Flask docs for more info and alternatives.", - DeprecationWarning, - ) self.client = client or get_client() self.client_cls = client_cls @@ -127,14 +117,6 @@ def init_app(self, app, **defaults) -> None: self.client = self.client_cls(config, **defaults) - # 0 is a valid log level (NOTSET), so we need to check explicitly for it - if self.logging or self.logging is logging.NOTSET: - if self.logging is not True: - kwargs = {"level": self.logging} - else: - kwargs = {} - setup_logging(LoggingHandler(self.client, **kwargs)) - signals.got_request_exception.connect(self.handle_exception, sender=app, weak=False) try: diff --git a/tests/contrib/flask/flask_tests.py b/tests/contrib/flask/flask_tests.py index 7ffce68cf..4a38dc3b4 100644 --- a/tests/contrib/flask/flask_tests.py +++ b/tests/contrib/flask/flask_tests.py @@ -441,32 +441,6 @@ def test_rum_tracing_context_processor(flask_apm_client): assert callable(context["apm"]["span_id"]) -@pytest.mark.parametrize("flask_apm_client", [{"logging": True}], indirect=True) -def test_logging_enabled(flask_apm_client): - logger = logging.getLogger() - logger.error("test") - error = flask_apm_client.client.events[ERROR][0] - assert error["log"]["level"] == "error" - assert error["log"]["message"] == "test" - - -@pytest.mark.parametrize("flask_apm_client", [{"logging": False}], indirect=True) -def test_logging_disabled(flask_apm_client): - logger = logging.getLogger() - logger.error("test") - assert len(flask_apm_client.client.events[ERROR]) == 0 - - -@pytest.mark.parametrize("flask_apm_client", [{"logging": logging.ERROR}], indirect=True) -def test_logging_by_level(flask_apm_client): - logger = logging.getLogger() - logger.warning("test") - logger.error("test") - assert len(flask_apm_client.client.events[ERROR]) == 1 - error = flask_apm_client.client.events[ERROR][0] - assert error["log"]["level"] == "error" - - def test_flask_transaction_ignore_urls(flask_apm_client): resp = flask_apm_client.app.test_client().get("/users/") resp.close() From 4b0c8e98543da42a0bf3b923335b088b22dfdf5e Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 10 Jul 2025 11:43:52 +0200 Subject: [PATCH 292/409] Drop deprecated logging handler (#2348) * elastic/handlers: drop deprecated LoggingHandler * contrib/flask: raise an exception if logging parameter is passed to ElasticAPM --- elasticapm/conf/__init__.py | 4 - elasticapm/contrib/flask/__init__.py | 3 + elasticapm/handlers/logging.py | 170 --------------- tests/contrib/flask/flask_tests.py | 5 + tests/handlers/logging/logging_tests.py | 270 +----------------------- 5 files changed, 9 insertions(+), 443 deletions(-) diff --git a/elasticapm/conf/__init__.py b/elasticapm/conf/__init__.py index 45b4db5a2..d787491ab 100644 --- a/elasticapm/conf/__init__.py +++ b/elasticapm/conf/__init__.py @@ -883,10 +883,6 @@ def setup_logging(handler): For a typical Python install: - >>> from elasticapm.handlers.logging import LoggingHandler - >>> client = ElasticAPM(...) - >>> setup_logging(LoggingHandler(client)) - Returns a boolean based on if logging was configured or not. """ # TODO We should probably revisit this. Does it make more sense as diff --git a/elasticapm/contrib/flask/__init__.py b/elasticapm/contrib/flask/__init__.py index b6e4e312b..4be9fe7ae 100644 --- a/elasticapm/contrib/flask/__init__.py +++ b/elasticapm/contrib/flask/__init__.py @@ -82,6 +82,9 @@ def __init__(self, app=None, client=None, client_cls=Client, **defaults) -> None self.client = client or get_client() self.client_cls = client_cls + if "logging" in defaults: + raise ValueError("Flask log shipping has been removed, drop the ElasticAPM logging parameter") + if app: self.init_app(app, **defaults) diff --git a/elasticapm/handlers/logging.py b/elasticapm/handlers/logging.py index 96718d2db..bcdd15bb0 100644 --- a/elasticapm/handlers/logging.py +++ b/elasticapm/handlers/logging.py @@ -32,181 +32,11 @@ from __future__ import absolute_import import logging -import sys -import traceback -import warnings import wrapt from elasticapm import get_client -from elasticapm.base import Client from elasticapm.traces import execution_context -from elasticapm.utils.stacks import iter_stack_frames - - -class LoggingHandler(logging.Handler): - def __init__(self, *args, **kwargs) -> None: - warnings.warn( - "The LoggingHandler is deprecated and will be removed in v7.0 of " - "the agent. Please use `log_ecs_reformatting` and ship the logs " - "with Elastic Agent or Filebeat instead. " - "https://www.elastic.co/guide/en/apm/agent/python/current/logs.html", - DeprecationWarning, - ) - self.client = None - if "client" in kwargs: - self.client = kwargs.pop("client") - elif len(args) > 0: - arg = args[0] - if isinstance(arg, Client): - self.client = arg - - if not self.client: - client_cls = kwargs.pop("client_cls", None) - if client_cls: - self.client = client_cls(*args, **kwargs) - else: - warnings.warn( - "LoggingHandler requires a Client instance. No Client was received.", - DeprecationWarning, - ) - self.client = Client(*args, **kwargs) - logging.Handler.__init__(self, level=kwargs.get("level", logging.NOTSET)) - - def emit(self, record): - self.format(record) - - # Avoid typical config issues by overriding loggers behavior - if record.name.startswith(("elasticapm.errors",)): - sys.stderr.write(record.getMessage() + "\n") - return - - try: - return self._emit(record) - except Exception: - sys.stderr.write("Top level ElasticAPM exception caught - failed creating log record.\n") - sys.stderr.write(record.getMessage() + "\n") - sys.stderr.write(traceback.format_exc() + "\n") - - try: - self.client.capture("Exception") - except Exception: - pass - - def _emit(self, record, **kwargs): - data = {} - - for k, v in record.__dict__.items(): - if "." not in k and k not in ("culprit",): - continue - data[k] = v - - stack = getattr(record, "stack", None) - if stack is True: - stack = iter_stack_frames(config=self.client.config) - - if stack: - frames = [] - started = False - last_mod = "" - for item in stack: - if isinstance(item, (list, tuple)): - frame, lineno = item - else: - frame, lineno = item, item.f_lineno - - if not started: - f_globals = getattr(frame, "f_globals", {}) - module_name = f_globals.get("__name__", "") - if last_mod.startswith("logging") and not module_name.startswith("logging"): - started = True - else: - last_mod = module_name - continue - frames.append((frame, lineno)) - stack = frames - - custom = getattr(record, "data", {}) - # Add in all of the data from the record that we aren't already capturing - for k in record.__dict__.keys(): - if k in ( - "stack", - "name", - "args", - "msg", - "levelno", - "exc_text", - "exc_info", - "data", - "created", - "levelname", - "msecs", - "relativeCreated", - ): - continue - if k.startswith("_"): - continue - custom[k] = record.__dict__[k] - - # If there's no exception being processed, - # exc_info may be a 3-tuple of None - # http://docs.python.org/library/sys.html#sys.exc_info - if record.exc_info and all(record.exc_info): - handler = self.client.get_handler("elasticapm.events.Exception") - exception = handler.capture(self.client, exc_info=record.exc_info) - else: - exception = None - - return self.client.capture( - "Message", - param_message={"message": str(record.msg), "params": record.args}, - stack=stack, - custom=custom, - exception=exception, - level=record.levelno, - logger_name=record.name, - **kwargs, - ) - - -class LoggingFilter(logging.Filter): - """ - This filter doesn't actually do any "filtering" -- rather, it just adds - three new attributes to any "filtered" LogRecord objects: - - * elasticapm_transaction_id - * elasticapm_trace_id - * elasticapm_span_id - * elasticapm_service_name - - These attributes can then be incorporated into your handlers and formatters, - so that you can tie log messages to transactions in elasticsearch. - - This filter also adds these fields to a dictionary attribute, - `elasticapm_labels`, using the official tracing fields names as documented - here: https://www.elastic.co/guide/en/ecs/current/ecs-tracing.html - - Note that if you're using Python 3.2+, by default we will add a - LogRecordFactory to your root logger which will add these attributes - automatically. - """ - - def __init__(self, name=""): - super().__init__(name=name) - warnings.warn( - "The LoggingFilter is deprecated and will be removed in v7.0 of " - "the agent. On Python 3.2+, by default we add a LogRecordFactory to " - "your root logger automatically" - "https://www.elastic.co/guide/en/apm/agent/python/current/logs.html", - DeprecationWarning, - ) - - def filter(self, record): - """ - Add elasticapm attributes to `record`. - """ - _add_attributes_to_log_record(record) - return True @wrapt.decorator diff --git a/tests/contrib/flask/flask_tests.py b/tests/contrib/flask/flask_tests.py index 4a38dc3b4..8d893533b 100644 --- a/tests/contrib/flask/flask_tests.py +++ b/tests/contrib/flask/flask_tests.py @@ -50,6 +50,11 @@ pytestmark = pytest.mark.flask +def test_logging_parameter_raises_exception(): + with pytest.raises(ValueError, match="Flask log shipping has been removed, drop the ElasticAPM logging parameter"): + ElasticAPM(config=None, logging=True) + + def test_error_handler(flask_apm_client): client = flask_apm_client.app.test_client() response = client.get("/an-error/") diff --git a/tests/handlers/logging/logging_tests.py b/tests/handlers/logging/logging_tests.py index 8cc8fc4f1..00a1a4ab6 100644 --- a/tests/handlers/logging/logging_tests.py +++ b/tests/handlers/logging/logging_tests.py @@ -40,238 +40,13 @@ from elasticapm.conf import Config from elasticapm.conf.constants import ERROR -from elasticapm.handlers.logging import Formatter, LoggingFilter, LoggingHandler +from elasticapm.handlers.logging import Formatter from elasticapm.handlers.structlog import structlog_processor from elasticapm.traces import capture_span from elasticapm.utils.stacks import iter_stack_frames from tests.fixtures import TempStoreClient -@pytest.fixture() -def logger(elasticapm_client): - elasticapm_client.config.include_paths = ["tests", "elasticapm"] - handler = LoggingHandler(elasticapm_client) - logger = logging.getLogger(__name__) - logger.handlers = [] - logger.addHandler(handler) - logger.client = elasticapm_client - logger.level = logging.INFO - return logger - - -def test_logger_basic(logger): - logger.error("This is a test error") - - assert len(logger.client.events) == 1 - event = logger.client.events[ERROR][0] - assert event["log"]["logger_name"] == __name__ - assert event["log"]["level"] == "error" - assert event["log"]["message"] == "This is a test error" - assert "stacktrace" in event["log"] - assert "exception" not in event - assert "param_message" in event["log"] - assert event["log"]["param_message"] == "This is a test error" - - -def test_logger_warning(logger): - logger.warning("This is a test warning") - assert len(logger.client.events) == 1 - event = logger.client.events[ERROR][0] - assert event["log"]["logger_name"] == __name__ - assert event["log"]["level"] == "warning" - assert "exception" not in event - assert "param_message" in event["log"] - assert event["log"]["param_message"] == "This is a test warning" - - -def test_logger_extra_data(logger): - logger.info("This is a test info with a url", extra=dict(data=dict(url="http://example.com"))) - assert len(logger.client.events) == 1 - event = logger.client.events[ERROR][0] - assert event["context"]["custom"]["url"] == "http://example.com" - assert "stacktrace" in event["log"] - assert "exception" not in event - assert "param_message" in event["log"] - assert event["log"]["param_message"] == "This is a test info with a url" - - -def test_logger_exc_info(logger): - try: - raise ValueError("This is a test ValueError") - except ValueError: - logger.info("This is a test info with an exception", exc_info=True) - - assert len(logger.client.events) == 1 - event = logger.client.events[ERROR][0] - - # assert event['message'] == 'This is a test info with an exception' - assert "exception" in event - assert "stacktrace" in event["exception"] - exc = event["exception"] - assert exc["type"] == "ValueError" - assert exc["message"] == "ValueError: This is a test ValueError" - assert "param_message" in event["log"] - assert event["log"]["message"] == "This is a test info with an exception" - - -def test_message_params(logger): - logger.info("This is a test of %s", "args") - assert len(logger.client.events) == 1 - event = logger.client.events[ERROR][0] - assert "exception" not in event - assert "param_message" in event["log"] - assert event["log"]["message"] == "This is a test of args" - assert event["log"]["param_message"] == "This is a test of %s" - - -def test_record_stack(logger): - logger.info("This is a test of stacks", extra={"stack": True}) - assert len(logger.client.events) == 1 - event = logger.client.events[ERROR][0] - frames = event["log"]["stacktrace"] - assert len(frames) != 1 - frame = frames[0] - assert frame["module"] == __name__ - assert "exception" not in event - assert "param_message" in event["log"] - assert event["log"]["param_message"] == "This is a test of stacks" - assert event["culprit"] == "tests.handlers.logging.logging_tests.test_record_stack" - assert event["log"]["message"] == "This is a test of stacks" - - -def test_no_record_stack(logger): - logger.info("This is a test of no stacks", extra={"stack": False}) - assert len(logger.client.events) == 1 - event = logger.client.events[ERROR][0] - assert event.get("culprit") == None - assert event["log"]["message"] == "This is a test of no stacks" - assert "stacktrace" not in event["log"] - assert "exception" not in event - assert "param_message" in event["log"] - assert event["log"]["param_message"] == "This is a test of no stacks" - - -def test_no_record_stack_via_config(logger): - logger.client.config.auto_log_stacks = False - logger.info("This is a test of no stacks") - assert len(logger.client.events) == 1 - event = logger.client.events[ERROR][0] - assert event.get("culprit") == None - assert event["log"]["message"] == "This is a test of no stacks" - assert "stacktrace" not in event["log"] - assert "exception" not in event - assert "param_message" in event["log"] - assert event["log"]["param_message"] == "This is a test of no stacks" - - -def test_explicit_stack(logger): - logger.info("This is a test of stacks", extra={"stack": iter_stack_frames()}) - assert len(logger.client.events) == 1 - event = logger.client.events[ERROR][0] - assert "culprit" in event, event - assert event["culprit"] == "tests.handlers.logging.logging_tests.test_explicit_stack" - assert "message" in event["log"], event - assert event["log"]["message"] == "This is a test of stacks" - assert "exception" not in event - assert "param_message" in event["log"] - assert event["log"]["param_message"] == "This is a test of stacks" - assert "stacktrace" in event["log"] - - -def test_extra_culprit(logger): - logger.info("This is a test of stacks", extra={"culprit": "foo.bar"}) - assert len(logger.client.events) == 1 - event = logger.client.events[ERROR][0] - assert event["culprit"] == "foo.bar" - assert "culprit" not in event["context"]["custom"] - - -def test_logger_exception(logger): - try: - raise ValueError("This is a test ValueError") - except ValueError: - logger.exception("This is a test with an exception", extra={"stack": True}) - - assert len(logger.client.events) == 1 - event = logger.client.events[ERROR][0] - - assert event["log"]["message"] == "This is a test with an exception" - assert "stacktrace" in event["log"] - assert "exception" in event - exc = event["exception"] - assert exc["type"] == "ValueError" - assert exc["message"] == "ValueError: This is a test ValueError" - assert "param_message" in event["log"] - assert event["log"]["message"] == "This is a test with an exception" - - -def test_client_arg(elasticapm_client): - handler = LoggingHandler(elasticapm_client) - assert handler.client == elasticapm_client - - -def test_client_kwarg(elasticapm_client): - handler = LoggingHandler(client=elasticapm_client) - assert handler.client == elasticapm_client - - -def test_logger_setup(): - handler = LoggingHandler( - server_url="foo", service_name="bar", secret_token="baz", metrics_interval="0ms", client_cls=TempStoreClient - ) - client = handler.client - assert client.config.server_url == "foo" - assert client.config.service_name == "bar" - assert client.config.secret_token == "baz" - assert handler.level == logging.NOTSET - - -def test_logging_handler_emit_error(capsys, elasticapm_client): - handler = LoggingHandler(elasticapm_client) - handler._emit = lambda: 1 / 0 - handler.emit(LogRecord("x", 1, "/ab/c/", 10, "Oops", (), None)) - out, err = capsys.readouterr() - assert "Top level ElasticAPM exception caught" in err - assert "Oops" in err - - -def test_logging_handler_dont_emit_elasticapm(capsys, elasticapm_client): - handler = LoggingHandler(elasticapm_client) - handler.emit(LogRecord("elasticapm.errors", 1, "/ab/c/", 10, "Oops", (), None)) - out, err = capsys.readouterr() - assert "Oops" in err - - -def test_logging_handler_emit_error_non_str_message(capsys, elasticapm_client): - handler = LoggingHandler(elasticapm_client) - handler._emit = lambda: 1 / 0 - handler.emit(LogRecord("x", 1, "/ab/c/", 10, ValueError("oh no"), (), None)) - out, err = capsys.readouterr() - assert "Top level ElasticAPM exception caught" in err - assert "oh no" in err - - -def test_arbitrary_object(logger): - logger.error(["a", "list", "of", "strings"]) - assert len(logger.client.events) == 1 - event = logger.client.events[ERROR][0] - assert "param_message" in event["log"] - assert event["log"]["param_message"] == "['a', 'list', 'of', 'strings']" - - -def test_logging_filter_no_span(elasticapm_client): - transaction = elasticapm_client.begin_transaction("test") - f = LoggingFilter() - record = logging.LogRecord(__name__, logging.DEBUG, __file__, 252, "dummy_msg", [], None) - f.filter(record) - assert record.elasticapm_transaction_id == transaction.id - assert record.elasticapm_service_name == transaction.tracer.config.service_name - assert record.elasticapm_service_environment == transaction.tracer.config.environment - assert record.elasticapm_trace_id == transaction.trace_parent.trace_id - assert record.elasticapm_span_id is None - assert record.elasticapm_labels - - def test_structlog_processor_no_span(elasticapm_client): transaction = elasticapm_client.begin_transaction("test") event_dict = {} @@ -281,37 +56,6 @@ def test_structlog_processor_no_span(elasticapm_client): assert "span.id" not in new_dict -@pytest.mark.parametrize("elasticapm_client", [{"transaction_max_spans": 5}], indirect=True) -def test_logging_filter_span(elasticapm_client): - transaction = elasticapm_client.begin_transaction("test") - with capture_span("test") as span: - f = LoggingFilter() - record = logging.LogRecord(__name__, logging.DEBUG, __file__, 252, "dummy_msg", [], None) - f.filter(record) - assert record.elasticapm_transaction_id == transaction.id - assert record.elasticapm_service_name == transaction.tracer.config.service_name - assert record.elasticapm_service_environment == transaction.tracer.config.environment - assert record.elasticapm_trace_id == transaction.trace_parent.trace_id - assert record.elasticapm_span_id == span.id - assert record.elasticapm_labels - - # Capture too many spans so we start dropping - for i in range(10): - with capture_span("drop"): - pass - - # Test logging with DroppedSpan - with capture_span("drop") as span: - record = logging.LogRecord(__name__, logging.DEBUG, __file__, 252, "dummy_msg2", [], None) - f.filter(record) - assert record.elasticapm_transaction_id == transaction.id - assert record.elasticapm_service_name == transaction.tracer.config.service_name - assert record.elasticapm_service_environment == transaction.tracer.config.environment - assert record.elasticapm_trace_id == transaction.trace_parent.trace_id - assert record.elasticapm_span_id is None - assert record.elasticapm_labels - - @pytest.mark.parametrize("elasticapm_client", [{"transaction_max_spans": 5}], indirect=True) def test_structlog_processor_span(elasticapm_client): transaction = elasticapm_client.begin_transaction("test") @@ -373,18 +117,6 @@ def test_formatter(): assert hasattr(record, "elasticapm_service_environment") -def test_logging_handler_no_client(recwarn): - # In 6.0, this should be changed to expect a ValueError instead of a log - warnings.simplefilter("always") - LoggingHandler(transport_class="tests.fixtures.DummyTransport") - while True: - # If we never find our desired warning this will eventually throw an - # AssertionError - w = recwarn.pop(DeprecationWarning) - if "LoggingHandler requires a Client instance" in w.message.args[0]: - return True - - @pytest.mark.parametrize( "elasticapm_client,expected", [ From d521f3035217e07ff84b027e0b49c7b59612ca14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= <64706471+Pokapiec@users.noreply.github.com> Date: Thu, 10 Jul 2025 14:38:06 +0200 Subject: [PATCH 293/409] #2114 repeating query in django view (#2158) * avoid making extensive queries by overriding queryset cache * move variable transformation to transform encoding util * handle django not installed, clearer failed query code * Cleanup pr * Run pre-commit * Update elasticapm/utils/encoding.py --------- Co-authored-by: p.okapiec Co-authored-by: Riccardo Magliocchetti --- elasticapm/utils/encoding.py | 13 +++++++++++++ tests/contrib/django/django_tests.py | 17 +++++++++++++++++ tests/contrib/django/testapp/urls.py | 1 + tests/contrib/django/testapp/views.py | 16 ++++++++++++++++ 4 files changed, 47 insertions(+) diff --git a/elasticapm/utils/encoding.py b/elasticapm/utils/encoding.py index 4455f2685..fedaa8d63 100644 --- a/elasticapm/utils/encoding.py +++ b/elasticapm/utils/encoding.py @@ -36,6 +36,11 @@ import uuid from decimal import Decimal +try: + from django.db.models import QuerySet as DjangoQuerySet +except ImportError: + DjangoQuerySet = None + from elasticapm.conf.constants import KEYWORD_MAX_LENGTH, LABEL_RE, LABEL_TYPES, LONG_FIELD_MAX_LENGTH PROTECTED_TYPES = (int, type(None), float, Decimal, datetime.datetime, datetime.date, datetime.time) @@ -144,6 +149,14 @@ class value_type(list): ret = float(value) elif isinstance(value, int): ret = int(value) + elif ( + DjangoQuerySet is not None + and isinstance(value, DjangoQuerySet) + and getattr(value, "_result_cache", True) is None + ): + # if we have a Django QuerySet a None result cache it may mean that the underlying query failed + # so represent it as unevaluated instead of retrying the query again + ret = "<%s `unevaluated`>" % (value.__class__.__name__) elif value is not None: try: ret = transform(repr(value)) diff --git a/tests/contrib/django/django_tests.py b/tests/contrib/django/django_tests.py index ad88e462e..529d56a69 100644 --- a/tests/contrib/django/django_tests.py +++ b/tests/contrib/django/django_tests.py @@ -1250,6 +1250,23 @@ def test_capture_post_errors_dict(client, django_elasticapm_client): assert error["context"]["request"]["body"] == "[REDACTED]" +@pytest.mark.parametrize( + "django_elasticapm_client", + [{"capture_body": "errors"}, {"capture_body": "transactions"}, {"capture_body": "all"}, {"capture_body": "off"}], + indirect=True, +) +def test_capture_django_orm_timeout_error(client, django_elasticapm_client): + with pytest.raises(DatabaseError): + client.get(reverse("elasticapm-django-orm-exc")) + + errors = django_elasticapm_client.events[ERROR] + if django_elasticapm_client.config.capture_body in (constants.ERROR, "all"): + stacktrace = errors[0]["exception"]["stacktrace"] + frames = [frame for frame in stacktrace if frame["function"] == "django_queryset_error"] + qs_var = frames[0]["vars"]["qs"] + assert qs_var == "" + + def test_capture_body_config_is_dynamic_for_errors(client, django_elasticapm_client): django_elasticapm_client.config.update(version="1", capture_body="all") with pytest.raises(MyException): diff --git a/tests/contrib/django/testapp/urls.py b/tests/contrib/django/testapp/urls.py index 857215280..92302e313 100644 --- a/tests/contrib/django/testapp/urls.py +++ b/tests/contrib/django/testapp/urls.py @@ -62,6 +62,7 @@ def handler500(request): re_path(r"^trigger-500-ioerror$", views.raise_ioerror, name="elasticapm-raise-ioerror"), re_path(r"^trigger-500-decorated$", views.decorated_raise_exc, name="elasticapm-raise-exc-decor"), re_path(r"^trigger-500-django$", views.django_exc, name="elasticapm-django-exc"), + re_path(r"^trigger-500-django-orm-exc$", views.django_queryset_error, name="elasticapm-django-orm-exc"), re_path(r"^trigger-500-template$", views.template_exc, name="elasticapm-template-exc"), re_path(r"^trigger-500-log-request$", views.logging_request_exc, name="elasticapm-log-request-exc"), re_path(r"^streaming$", views.streaming_view, name="elasticapm-streaming-view"), diff --git a/tests/contrib/django/testapp/views.py b/tests/contrib/django/testapp/views.py index 5a11b0961..906a8c2df 100644 --- a/tests/contrib/django/testapp/views.py +++ b/tests/contrib/django/testapp/views.py @@ -34,6 +34,8 @@ import time from django.contrib.auth.models import User +from django.db import DatabaseError +from django.db.models import QuerySet from django.http import HttpResponse, StreamingHttpResponse from django.shortcuts import get_object_or_404, render from django.views import View @@ -70,6 +72,20 @@ def django_exc(request): return get_object_or_404(MyException, pk=1) +def django_queryset_error(request): + """Simulation of django ORM timeout""" + + class CustomQuerySet(QuerySet): + def all(self): + raise DatabaseError() + + def __repr__(self) -> str: + return str(self._result_cache) + + qs = CustomQuerySet() + list(qs.all()) + + def raise_exc(request): raise MyException(request.GET.get("message", "view exception")) From 2a483aa27822ce8a905902c897bf766bb1b7b776 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 11 Jul 2025 12:01:28 +0200 Subject: [PATCH 294/409] Remove usage of deprecated pkg_resource in setup.py (#2349) * Remove usage of deprecated pkg_resource In setup.py just remove a check for a 2018+ release of setuptools. --- setup.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.py b/setup.py index 5dbb8f643..a4e976bfc 100644 --- a/setup.py +++ b/setup.py @@ -44,11 +44,8 @@ import codecs import os -import pkg_resources from setuptools import setup -pkg_resources.require("setuptools>=39.2") - def get_version(): """ From d45894534e4277d48bdd589b421b483aadeeef04 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 09:53:17 +0200 Subject: [PATCH 295/409] build(deps): bump certifi from 2025.6.15 to 2025.7.9 in /dev-utils (#2352) Bumps [certifi](https://github.com/certifi/python-certifi) from 2025.6.15 to 2025.7.9. - [Commits](https://github.com/certifi/python-certifi/compare/2025.06.15...2025.07.09) --- updated-dependencies: - dependency-name: certifi dependency-version: 2025.7.9 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dev-utils/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-utils/requirements.txt b/dev-utils/requirements.txt index d9280db70..832f8f33e 100644 --- a/dev-utils/requirements.txt +++ b/dev-utils/requirements.txt @@ -1,4 +1,4 @@ # These are the pinned requirements for the lambda layer/docker image -certifi==2025.6.15 +certifi==2025.7.9 urllib3==1.26.20 wrapt==1.14.1 From 395e6c6044a01b1e0abf49f2f3639c3ff614f095 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 09:53:38 +0200 Subject: [PATCH 296/409] chore: deps(updatecli): Bump updatecli version to v0.104.0 (#2353) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 8a1126234..46c433f64 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.103.1 \ No newline at end of file +updatecli v0.104.0 \ No newline at end of file From a4c7c4b07507f430e9e9aa4cdf8020d7fb4163e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:37:54 +0200 Subject: [PATCH 297/409] build(deps): bump alpine from `8a1f59f` to `4bcff63` (#2355) Bumps alpine from `8a1f59f` to `4bcff63`. --- updated-dependencies: - dependency-name: alpine dependency-version: 4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1185f4169..83bca8724 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,3 @@ -FROM alpine@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715 +FROM alpine@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From f08331adae033aa56d6c5c5d235a26afb1afc546 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Jul 2025 12:40:03 +0200 Subject: [PATCH 298/409] build(deps): bump wolfi/chainguard-base from `bbc60f1` to `bc3e1ff` (#2358) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `bbc60f1` to `bc3e1ff`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 31f43b24b..29bc6d5bf 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:bbc60f1a2dbdd8e6ae4fee4fdf83adbac275b9821b2ac05ca72b1d597babd51f +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:bc3e1ff1495c06129cf27631a5a29d1f0d086cd1ab4fdf5363b5bc11518745c4 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 07246d6f54e289642f4751f746a83a28518b5768 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 09:20:14 +0200 Subject: [PATCH 299/409] build(deps): bump certifi from 2025.7.9 to 2025.7.14 in /dev-utils (#2361) --- updated-dependencies: - dependency-name: certifi dependency-version: 2025.7.14 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dev-utils/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-utils/requirements.txt b/dev-utils/requirements.txt index 832f8f33e..4aaf41f2c 100644 --- a/dev-utils/requirements.txt +++ b/dev-utils/requirements.txt @@ -1,4 +1,4 @@ # These are the pinned requirements for the lambda layer/docker image -certifi==2025.7.9 +certifi==2025.7.14 urllib3==1.26.20 wrapt==1.14.1 From 4551dc0fc207d660f1b9bf4662b243a01a6045b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:00:03 +0200 Subject: [PATCH 300/409] build(deps): bump wolfi/chainguard-base from `bc3e1ff` to `3dce013` (#2359) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `bc3e1ff` to `3dce013`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 29bc6d5bf..7b30ef67a 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:bc3e1ff1495c06129cf27631a5a29d1f0d086cd1ab4fdf5363b5bc11518745c4 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:3dce013a042044ce4c9ad897ab939088ecb65a8402ed22371363286b270b6393 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From dd6d77965c83e09abf3a79df6786a7a1e8769510 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 14:13:42 +0200 Subject: [PATCH 301/409] build(deps): bump wolfi/chainguard-base from `3dce013` to `2a601e3` (#2362) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `3dce013` to `2a601e3`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 7b30ef67a..5e4cbfb0a 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:3dce013a042044ce4c9ad897ab939088ecb65a8402ed22371363286b270b6393 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:2a601e3db11b58d6d5bfedb651c513e46556e231a56f437990ec4f0248f2207b ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 5a50c5088efecb650911e43b62b02146f9927b77 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Jul 2025 14:56:29 +0200 Subject: [PATCH 302/409] build(deps): bump wolfi/chainguard-base from `2a601e3` to `b9d4f53` (#2365) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `2a601e3` to `b9d4f53`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 5e4cbfb0a..e4b5d8b1c 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:2a601e3db11b58d6d5bfedb651c513e46556e231a56f437990ec4f0248f2207b +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:b9d4f5310ebccf219efb74aaa7921d07bffdca8655e9878fccf633448e38d654 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 84782821ee64ae234f8c14dbf97df5ba56b27cfc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Jul 2025 14:25:12 +0200 Subject: [PATCH 303/409] build(deps): bump wolfi/chainguard-base from `b9d4f53` to `427e74c` (#2368) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `b9d4f53` to `427e74c`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index e4b5d8b1c..e2673a3d7 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:b9d4f5310ebccf219efb74aaa7921d07bffdca8655e9878fccf633448e38d654 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:427e74c0176b8f1f59a0d78537792764e5420069ee5df1f0fea39799d24e4137 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 18aba5d3e2e60c0c46dd24401745af3017d8fcaf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 14:33:54 +0200 Subject: [PATCH 304/409] build(deps): bump wolfi/chainguard-base from `427e74c` to `9ded4d2` (#2371) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `427e74c` to `9ded4d2`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index e2673a3d7..f42f99218 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:427e74c0176b8f1f59a0d78537792764e5420069ee5df1f0fea39799d24e4137 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:9ded4d2364e7f263cada56b0b9ca3ef643e8dac958a79df3d18c2a9f0a33fbc7 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 7241b27ba9f190167c70abc728f629061396d0fb Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 30 Jul 2025 17:58:05 +0200 Subject: [PATCH 305/409] Use unittest.mock instead of the mock backport package (#2370) * setup: Add missing azurestorage pytest marker * tests: use unittest.mock instead of mock Use the standard library module instead of the backported package. Should fix mocks failures with Python 3.13. --- setup.cfg | 1 + tests/client/client_tests.py | 2 +- tests/client/exception_tests.py | 2 +- tests/client/py3_exception_tests.py | 2 +- tests/config/central_config_tests.py | 3 ++- tests/config/tests.py | 2 +- tests/contrib/asyncio/aiohttp_web_tests.py | 3 ++- tests/contrib/asyncio/starlette_tests.py | 2 +- tests/contrib/asyncio/tornado/tornado_tests.py | 2 +- tests/contrib/celery/flask_tests.py | 2 +- tests/contrib/django/django_tests.py | 2 +- tests/contrib/django/wrapper_tests.py | 2 +- tests/contrib/flask/flask_tests.py | 2 +- .../serverless/azurefunctions/azure_functions_tests.py | 2 +- tests/events/tests.py | 3 ++- tests/fixtures.py | 2 +- tests/instrumentation/base_tests.py | 2 +- tests/instrumentation/transactions_store_tests.py | 2 +- tests/instrumentation/urllib_tests.py | 2 +- tests/metrics/base_tests.py | 2 +- tests/metrics/breakdown_tests.py | 3 ++- tests/processors/tests.py | 6 ++++-- tests/requirements/lint-isort.txt | 1 - tests/requirements/reqs-base.txt | 1 - tests/scripts/envs/azure.sh | 1 + tests/transports/test_base.py | 2 +- tests/transports/test_urllib3.py | 2 +- tests/utils/cloud_tests.py | 2 +- tests/utils/compat_tests.py | 3 ++- tests/utils/stacks/tests.py | 2 +- tests/utils/threading_tests.py | 2 +- 31 files changed, 37 insertions(+), 30 deletions(-) create mode 100644 tests/scripts/envs/azure.sh diff --git a/setup.cfg b/setup.cfg index 7532a5ad6..d3611a899 100644 --- a/setup.cfg +++ b/setup.cfg @@ -107,6 +107,7 @@ markers = aiobotocore kafka grpc + azurestorage addopts=--random-order [isort] diff --git a/tests/client/client_tests.py b/tests/client/client_tests.py index 62e10d301..05388921f 100644 --- a/tests/client/client_tests.py +++ b/tests/client/client_tests.py @@ -39,8 +39,8 @@ import time import warnings from collections import defaultdict +from unittest import mock -import mock import pytest from pytest_localserver.http import ContentServer from pytest_localserver.https import DEFAULT_CERTIFICATE diff --git a/tests/client/exception_tests.py b/tests/client/exception_tests.py index 082835be3..056f23548 100644 --- a/tests/client/exception_tests.py +++ b/tests/client/exception_tests.py @@ -29,8 +29,8 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import os +from unittest import mock -import mock import pytest import elasticapm diff --git a/tests/client/py3_exception_tests.py b/tests/client/py3_exception_tests.py index ad8bb10ca..e1ff057eb 100644 --- a/tests/client/py3_exception_tests.py +++ b/tests/client/py3_exception_tests.py @@ -38,7 +38,7 @@ # # -import mock +from unittest import mock from elasticapm.conf.constants import ERROR diff --git a/tests/config/central_config_tests.py b/tests/config/central_config_tests.py index 568cf180a..d3aaf4fe7 100644 --- a/tests/config/central_config_tests.py +++ b/tests/config/central_config_tests.py @@ -28,7 +28,8 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import mock +from unittest import mock + import pytest diff --git a/tests/config/tests.py b/tests/config/tests.py index 284f5694a..ba07d7795 100644 --- a/tests/config/tests.py +++ b/tests/config/tests.py @@ -35,8 +35,8 @@ import platform import stat from datetime import timedelta +from unittest import mock -import mock import pytest import elasticapm.conf diff --git a/tests/contrib/asyncio/aiohttp_web_tests.py b/tests/contrib/asyncio/aiohttp_web_tests.py index 929a8e1a7..4a63d8714 100644 --- a/tests/contrib/asyncio/aiohttp_web_tests.py +++ b/tests/contrib/asyncio/aiohttp_web_tests.py @@ -32,7 +32,8 @@ aiohttp = pytest.importorskip("aiohttp") # isort:skip -import mock +from unittest import mock + from multidict import MultiDict import elasticapm diff --git a/tests/contrib/asyncio/starlette_tests.py b/tests/contrib/asyncio/starlette_tests.py index 38c51fa08..4053b5d5b 100644 --- a/tests/contrib/asyncio/starlette_tests.py +++ b/tests/contrib/asyncio/starlette_tests.py @@ -38,8 +38,8 @@ starlette = pytest.importorskip("starlette") # isort:skip import os +from unittest import mock -import mock import urllib3 import wrapt from starlette.applications import Starlette diff --git a/tests/contrib/asyncio/tornado/tornado_tests.py b/tests/contrib/asyncio/tornado/tornado_tests.py index 3ce3bafed..337d54942 100644 --- a/tests/contrib/asyncio/tornado/tornado_tests.py +++ b/tests/contrib/asyncio/tornado/tornado_tests.py @@ -33,8 +33,8 @@ tornado = pytest.importorskip("tornado") # isort:skip import os +from unittest import mock -import mock from wrapt import BoundFunctionWrapper import elasticapm diff --git a/tests/contrib/celery/flask_tests.py b/tests/contrib/celery/flask_tests.py index 29dd61fdb..cb7c53ed5 100644 --- a/tests/contrib/celery/flask_tests.py +++ b/tests/contrib/celery/flask_tests.py @@ -33,7 +33,7 @@ flask = pytest.importorskip("flask") # isort:skip celery = pytest.importorskip("celery") # isort:skip -import mock +from unittest import mock from elasticapm.conf.constants import ERROR, TRANSACTION diff --git a/tests/contrib/django/django_tests.py b/tests/contrib/django/django_tests.py index 529d56a69..312583ac1 100644 --- a/tests/contrib/django/django_tests.py +++ b/tests/contrib/django/django_tests.py @@ -41,8 +41,8 @@ import logging import os from copy import deepcopy +from unittest import mock -import mock from django.conf import settings from django.contrib.auth.models import User from django.contrib.redirects.models import Redirect diff --git a/tests/contrib/django/wrapper_tests.py b/tests/contrib/django/wrapper_tests.py index 4c3f186fc..6464f17b7 100644 --- a/tests/contrib/django/wrapper_tests.py +++ b/tests/contrib/django/wrapper_tests.py @@ -32,7 +32,7 @@ # Installing an app is not reversible, so using this instrumentation "for real" would # pollute the Django instance used by pytest. -import mock +from unittest import mock from elasticapm.instrumentation.packages.django import DjangoAutoInstrumentation diff --git a/tests/contrib/flask/flask_tests.py b/tests/contrib/flask/flask_tests.py index 8d893533b..38657a6f6 100644 --- a/tests/contrib/flask/flask_tests.py +++ b/tests/contrib/flask/flask_tests.py @@ -35,9 +35,9 @@ import io import logging import os +from unittest import mock from urllib.request import urlopen -import mock from flask import signals import elasticapm diff --git a/tests/contrib/serverless/azurefunctions/azure_functions_tests.py b/tests/contrib/serverless/azurefunctions/azure_functions_tests.py index e2abbdcd3..91550a59f 100644 --- a/tests/contrib/serverless/azurefunctions/azure_functions_tests.py +++ b/tests/contrib/serverless/azurefunctions/azure_functions_tests.py @@ -33,9 +33,9 @@ import datetime import os +from unittest import mock import azure.functions as func -import mock import elasticapm from elasticapm.conf import constants diff --git a/tests/events/tests.py b/tests/events/tests.py index cd6cc6d3c..89d33ca3b 100644 --- a/tests/events/tests.py +++ b/tests/events/tests.py @@ -32,8 +32,9 @@ from __future__ import absolute_import +from unittest.mock import Mock + import pytest -from mock import Mock from elasticapm.events import Exception, Message diff --git a/tests/fixtures.py b/tests/fixtures.py index ddeaa1f5b..9d69d80cb 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -46,10 +46,10 @@ import zlib from collections import defaultdict from typing import Optional +from unittest import mock from urllib.request import pathname2url import jsonschema -import mock import pytest from pytest_localserver.http import ContentServer from werkzeug.wrappers import Request, Response diff --git a/tests/instrumentation/base_tests.py b/tests/instrumentation/base_tests.py index da2e265ed..9e92aa29c 100644 --- a/tests/instrumentation/base_tests.py +++ b/tests/instrumentation/base_tests.py @@ -31,8 +31,8 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import logging +from unittest import mock -import mock import pytest import wrapt diff --git a/tests/instrumentation/transactions_store_tests.py b/tests/instrumentation/transactions_store_tests.py index 23a8d0b2a..2d1dcefcc 100644 --- a/tests/instrumentation/transactions_store_tests.py +++ b/tests/instrumentation/transactions_store_tests.py @@ -32,8 +32,8 @@ import logging import time from collections import defaultdict +from unittest import mock -import mock import pytest import elasticapm diff --git a/tests/instrumentation/urllib_tests.py b/tests/instrumentation/urllib_tests.py index fbf5fa44f..62dda0402 100644 --- a/tests/instrumentation/urllib_tests.py +++ b/tests/instrumentation/urllib_tests.py @@ -28,10 +28,10 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import urllib.parse +from unittest import mock from urllib.error import HTTPError, URLError from urllib.request import urlopen -import mock import pytest from elasticapm.conf import constants diff --git a/tests/metrics/base_tests.py b/tests/metrics/base_tests.py index 526e5079c..a9c51fb34 100644 --- a/tests/metrics/base_tests.py +++ b/tests/metrics/base_tests.py @@ -31,8 +31,8 @@ import logging import time from multiprocessing.dummy import Pool +from unittest import mock -import mock import pytest from elasticapm.conf import constants diff --git a/tests/metrics/breakdown_tests.py b/tests/metrics/breakdown_tests.py index 3e6b7ed9e..8572bcf6a 100644 --- a/tests/metrics/breakdown_tests.py +++ b/tests/metrics/breakdown_tests.py @@ -28,7 +28,8 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import mock +from unittest import mock + import pytest import elasticapm diff --git a/tests/processors/tests.py b/tests/processors/tests.py index 772eccf72..84fdfb2b9 100644 --- a/tests/processors/tests.py +++ b/tests/processors/tests.py @@ -34,8 +34,8 @@ import logging import os +from unittest import mock -import mock import pytest import elasticapm @@ -464,7 +464,9 @@ def test_drop_events_in_processor(elasticapm_client, caplog): assert shouldnt_be_called_processor.call_count == 0 assert elasticapm_client._transport.events[TRANSACTION][0] is None assert_any_record_contains( - caplog.records, "Dropped event of type transaction due to processor mock.mock.dropper", "elasticapm.transport" + caplog.records, + "Dropped event of type transaction due to processor unittest.mock.dropper", + "elasticapm.transport", ) diff --git a/tests/requirements/lint-isort.txt b/tests/requirements/lint-isort.txt index 16ad5274c..2a7924352 100644 --- a/tests/requirements/lint-isort.txt +++ b/tests/requirements/lint-isort.txt @@ -1,2 +1 @@ isort -mock diff --git a/tests/requirements/reqs-base.txt b/tests/requirements/reqs-base.txt index 0ce35a889..ff6e4d24c 100644 --- a/tests/requirements/reqs-base.txt +++ b/tests/requirements/reqs-base.txt @@ -14,7 +14,6 @@ jsonschema==4.17.3 urllib3!=2.0.0,<3.0.0 certifi Logbook -mock pytz ecs_logging structlog diff --git a/tests/scripts/envs/azure.sh b/tests/scripts/envs/azure.sh new file mode 100644 index 000000000..d190b5882 --- /dev/null +++ b/tests/scripts/envs/azure.sh @@ -0,0 +1 @@ +export PYTEST_MARKER="-m azurestorage" diff --git a/tests/transports/test_base.py b/tests/transports/test_base.py index 2f77c3e95..37250cf7c 100644 --- a/tests/transports/test_base.py +++ b/tests/transports/test_base.py @@ -35,8 +35,8 @@ import sys import time import timeit +from unittest import mock -import mock import pytest from elasticapm.transport.base import Transport, TransportState diff --git a/tests/transports/test_urllib3.py b/tests/transports/test_urllib3.py index 32a5b7384..e53cb91e8 100644 --- a/tests/transports/test_urllib3.py +++ b/tests/transports/test_urllib3.py @@ -31,9 +31,9 @@ import os import time +from unittest import mock import certifi -import mock import pytest import urllib3.poolmanager from urllib3.exceptions import MaxRetryError, TimeoutError diff --git a/tests/utils/cloud_tests.py b/tests/utils/cloud_tests.py index 07c1f82e0..a365c2c93 100644 --- a/tests/utils/cloud_tests.py +++ b/tests/utils/cloud_tests.py @@ -29,8 +29,8 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import os +from unittest import mock -import mock import urllib3 import elasticapm.utils.cloud diff --git a/tests/utils/compat_tests.py b/tests/utils/compat_tests.py index 9da7ac2f8..352d0bb48 100644 --- a/tests/utils/compat_tests.py +++ b/tests/utils/compat_tests.py @@ -28,7 +28,8 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import mock +from unittest import mock + import pytest from elasticapm.utils import compat diff --git a/tests/utils/stacks/tests.py b/tests/utils/stacks/tests.py index c4be88ff2..6b3ba0f89 100644 --- a/tests/utils/stacks/tests.py +++ b/tests/utils/stacks/tests.py @@ -34,9 +34,9 @@ import os import pkgutil +from unittest.mock import Mock import pytest -from mock import Mock import elasticapm from elasticapm.conf import constants diff --git a/tests/utils/threading_tests.py b/tests/utils/threading_tests.py index 1c7329cd9..d9f2bf651 100644 --- a/tests/utils/threading_tests.py +++ b/tests/utils/threading_tests.py @@ -29,8 +29,8 @@ import platform import time +from unittest import mock -import mock import pytest from elasticapm.utils.threading import IntervalTimer From 234cf82078d1a89851b24c251889da7cf8152158 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 30 Jul 2025 18:06:11 +0200 Subject: [PATCH 306/409] setup: keep checking for setuptools (#2367) Use importlib.metadata if available otherwise fallback to pkg_resources. This partly reverts 2a483aa27822ce8a905902c897bf766bb1b7b776. --- setup.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/setup.py b/setup.py index a4e976bfc..b41dcaca9 100644 --- a/setup.py +++ b/setup.py @@ -46,6 +46,15 @@ from setuptools import setup +try: + import importlib.metadata + + importlib.metadata.requires("setuptools") +except ImportError: + import pkg_resources + + pkg_resources.require("setuptools") + def get_version(): """ From 090e841f89756bccee30474070faf889314137b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 31 Jul 2025 14:32:55 +0200 Subject: [PATCH 307/409] build(deps): bump wolfi/chainguard-base from `9ded4d2` to `66d7835` (#2373) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `9ded4d2` to `66d7835`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index f42f99218..f7f452362 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:9ded4d2364e7f263cada56b0b9ca3ef643e8dac958a79df3d18c2a9f0a33fbc7 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:66d78357f294fef975f0286c04f963249b5c6a835a53c26a4d1c8dd5ecfbd57d ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 8fb2039d734f4b7b39e2fb6b23821d6b83e3606a Mon Sep 17 00:00:00 2001 From: Jacky Lam Date: Fri, 1 Aug 2025 15:24:09 +0100 Subject: [PATCH 308/409] feat(tracing): Add span links from SNS messages (#2363) * feat(tracing): Add span links from SNS messages * fix: Update SNS capture_serverless test --------- Co-authored-by: Riccardo Magliocchetti --- elasticapm/contrib/serverless/aws.py | 9 +++++++++ tests/contrib/serverless/aws_sns_test_data.json | 4 ++++ tests/contrib/serverless/aws_tests.py | 2 ++ 3 files changed, 15 insertions(+) diff --git a/elasticapm/contrib/serverless/aws.py b/elasticapm/contrib/serverless/aws.py index 1717d57cc..e2af1a735 100644 --- a/elasticapm/contrib/serverless/aws.py +++ b/elasticapm/contrib/serverless/aws.py @@ -235,11 +235,20 @@ def __enter__(self): transaction_name = "RECEIVE {}".format(record["eventSourceARN"].split(":")[5]) if "Records" in self.event: + # SQS links = [ TraceParent.from_string(record["messageAttributes"]["traceparent"]["stringValue"]) for record in self.event["Records"][:1000] if "messageAttributes" in record and "traceparent" in record["messageAttributes"] ] + # SNS + links += [ + TraceParent.from_string(record["Sns"]["MessageAttributes"]["traceparent"]["Value"]) + for record in self.event["Records"][:1000] + if "Sns" in record + and "MessageAttributes" in record["Sns"] + and "traceparent" in record["Sns"]["MessageAttributes"] + ] else: links = [] diff --git a/tests/contrib/serverless/aws_sns_test_data.json b/tests/contrib/serverless/aws_sns_test_data.json index e6c7a89ef..a1900c54b 100644 --- a/tests/contrib/serverless/aws_sns_test_data.json +++ b/tests/contrib/serverless/aws_sns_test_data.json @@ -27,6 +27,10 @@ "City": { "Type": "String", "Value": "Any City" + }, + "traceparent": { + "Type": "String", + "Value": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-00" } } } diff --git a/tests/contrib/serverless/aws_tests.py b/tests/contrib/serverless/aws_tests.py index df062a378..f52636dcf 100644 --- a/tests/contrib/serverless/aws_tests.py +++ b/tests/contrib/serverless/aws_tests.py @@ -344,6 +344,8 @@ def test_func(event, context): assert transaction["span_count"]["started"] == 1 assert transaction["context"]["message"]["headers"]["Population"] == "1250800" assert transaction["context"]["message"]["headers"]["City"] == "Any City" + assert len(transaction["links"]) == 1 + assert transaction["links"][0] == {"trace_id": "0af7651916cd43dd8448eb211c80319c", "span_id": "b7ad6b7169203331"} def test_capture_serverless_sqs(event_sqs, context, elasticapm_client): From 98351c1eb7dc30e8796e6354ceb8fa6157575419 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Sat, 2 Aug 2025 10:02:01 +0200 Subject: [PATCH 309/409] tests: rremove some flakyness from client tests (#2375) Update test_send_remote_failover_sync_non_transport_exception_error to remove some flakyness: - use a custom transport class instead of mocking it because *sometime* the send method won't get mocked :O - then before asserting the transport state give it a bit more slack time to the queue to process and flush the data - Finally retry the check of the stat for the send without error to give the queue enough time to process it --- tests/client/client_tests.py | 33 ++++++++++++++++++++++----------- tests/fixtures.py | 13 +++++++++++++ 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/tests/client/client_tests.py b/tests/client/client_tests.py index 05388921f..e73e726ab 100644 --- a/tests/client/client_tests.py +++ b/tests/client/client_tests.py @@ -274,33 +274,44 @@ def test_send_remote_failover_sync(should_try, sending_elasticapm_client, caplog assert not sending_elasticapm_client._transport.state.did_fail() -@mock.patch("elasticapm.transport.http.Transport.send") -@mock.patch("elasticapm.transport.base.TransportState.should_try") -def test_send_remote_failover_sync_non_transport_exception_error(should_try, http_send, caplog): - should_try.return_value = True - +@mock.patch("elasticapm.transport.base.TransportState.should_try", return_value=True) +def test_send_remote_failover_sync_non_transport_exception_error(should_try, caplog): client = Client( server_url="http://example.com", service_name="app_name", secret_token="secret", - transport_class="elasticapm.transport.http.Transport", + transport_class="tests.fixtures.MockSendHTTPTransport", metrics_interval="0ms", metrics_sets=[], ) + # test error - http_send.side_effect = ValueError("oopsie") + client._transport.send_mock.side_effect = ValueError("oopsie") with caplog.at_level("ERROR", "elasticapm.transport"): client.capture_message("foo", handled=False) - client._transport.flush() + try: + client._transport.flush() + except ValueError: + # give flush a bit more room because we may take a bit more than the max timeout to flush + client._transport._flushed.wait(timeout=1) assert client._transport.state.did_fail() assert_any_record_contains(caplog.records, "oopsie", "elasticapm.transport") # test recovery - http_send.side_effect = None + client._transport.send_mock.side_effect = None client.capture_message("foo", handled=False) - client.close() + try: + client._transport.flush() + except ValueError: + # give flush a bit more room because we may take a bit more than the max timeout to flush + client._transport._flushed.wait(timeout=1) + # We have a race here with the queue where we would end up checking for did_fail before the message + # is being handled by the queue, so sleep a bit and retry to give it enough time + retries = 0 + while client._transport.state.did_fail() and retries < 3: + time.sleep(0.1) + retries += 1 assert not client._transport.state.did_fail() - client.close() @pytest.mark.parametrize("validating_httpserver", [{"skip_validate": True}], indirect=True) diff --git a/tests/fixtures.py b/tests/fixtures.py index 9d69d80cb..1b9119b99 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -59,6 +59,7 @@ from elasticapm.conf.constants import SPAN from elasticapm.instrumentation import register from elasticapm.traces import execution_context +from elasticapm.transport.http import Transport from elasticapm.transport.http_base import HTTPTransportBase from elasticapm.utils.threading import ThreadManager @@ -396,6 +397,18 @@ def get_config(self, current_version=None, keys=None): return False, None, 30 +class MockSendHTTPTransport(Transport): + """Mocking the send method of the Transport class sometimes fails silently in client tests. + After spending some time trying to understand this with no luck just use this class instead.""" + + def __init__(self, url, *args, **kwargs): + self.send_mock = mock.Mock() + super().__init__(url, *args, **kwargs) + + def send(self, data, forced_flush=False, custom_url=None, custom_headers=None): + return self.send_mock(data, forced_flush, custom_url, custom_headers) + + class TempStoreClient(Client): def __init__(self, config=None, **inline) -> None: inline.setdefault("transport_class", "tests.fixtures.DummyTransport") From b533c964c8f62489001b2797ceeb2b1c84db1d7b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 09:28:02 +0200 Subject: [PATCH 310/409] build(deps): bump certifi from 2025.7.14 to 2025.8.3 in /dev-utils (#2381) Bumps [certifi](https://github.com/certifi/python-certifi) from 2025.7.14 to 2025.8.3. - [Commits](https://github.com/certifi/python-certifi/compare/2025.07.14...2025.08.03) --- updated-dependencies: - dependency-name: certifi dependency-version: 2025.8.3 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dev-utils/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-utils/requirements.txt b/dev-utils/requirements.txt index 4aaf41f2c..bab366f4e 100644 --- a/dev-utils/requirements.txt +++ b/dev-utils/requirements.txt @@ -1,4 +1,4 @@ # These are the pinned requirements for the lambda layer/docker image -certifi==2025.7.14 +certifi==2025.8.3 urllib3==1.26.20 wrapt==1.14.1 From f5c1a924003408d3ee0790961c2a9b326c92a763 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 12:26:55 +0000 Subject: [PATCH 311/409] build(deps): bump docker/metadata-action (#2380) Bumps the github-actions group with 1 update in the / directory: [docker/metadata-action](https://github.com/docker/metadata-action). Updates `docker/metadata-action` from 5.7.0 to 5.8.0 - [Release notes](https://github.com/docker/metadata-action/releases) - [Commits](https://github.com/docker/metadata-action/compare/902fa8ec7d6ecbf8d84d538b9b233a880e428804...c1e51972afc2121e065aed6d45c65596fe445f3f) --- updated-dependencies: - dependency-name: docker/metadata-action dependency-version: 5.8.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Francisco Ramon --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7287312f5..1ea9f0a7f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -135,7 +135,7 @@ jobs: - name: Extract metadata (tags, labels) id: docker-meta - uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 + uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0 with: images: ${{ env.DOCKER_IMAGE_NAME }} tags: | From 6d15aac5f7dd91297baa8dc3ffa22b3b33094c0c Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 4 Aug 2025 17:25:40 +0200 Subject: [PATCH 312/409] Revert log shipping removals (#2383) * Revert "Drop deprecated logging handler (#2348)" This reverts commit 4b0c8e98543da42a0bf3b923335b088b22dfdf5e. * Revert "contrib/flask: remove deprecated log shipping integration (#2346)" This reverts commit 39f4191315e8b2a94adcd220aaeaa816bd7725d2. * Revert "contrib/django: remove deprecated LoggingHandler (#2345)" This reverts commit eb34e89a6b34a8d80067c138bec1e9d82546558c. --- elasticapm/conf/__init__.py | 9 + elasticapm/contrib/django/handlers.py | 33 +++ elasticapm/contrib/flask/__init__.py | 25 ++- elasticapm/handlers/logging.py | 170 +++++++++++++++ tests/contrib/django/django_tests.py | 68 ++++++ tests/contrib/flask/flask_tests.py | 31 ++- tests/handlers/logging/logging_tests.py | 270 +++++++++++++++++++++++- 7 files changed, 595 insertions(+), 11 deletions(-) diff --git a/elasticapm/conf/__init__.py b/elasticapm/conf/__init__.py index d787491ab..6d19eb96c 100644 --- a/elasticapm/conf/__init__.py +++ b/elasticapm/conf/__init__.py @@ -883,6 +883,15 @@ def setup_logging(handler): For a typical Python install: + >>> from elasticapm.handlers.logging import LoggingHandler + >>> client = ElasticAPM(...) + >>> setup_logging(LoggingHandler(client)) + + Within Django: + + >>> from elasticapm.contrib.django.handlers import LoggingHandler + >>> setup_logging(LoggingHandler()) + Returns a boolean based on if logging was configured or not. """ # TODO We should probably revisit this. Does it make more sense as diff --git a/elasticapm/contrib/django/handlers.py b/elasticapm/contrib/django/handlers.py index 550cfae87..c980acc4f 100644 --- a/elasticapm/contrib/django/handlers.py +++ b/elasticapm/contrib/django/handlers.py @@ -31,11 +31,44 @@ from __future__ import absolute_import +import logging import sys import warnings from django.conf import settings as django_settings +from elasticapm import get_client +from elasticapm.handlers.logging import LoggingHandler as BaseLoggingHandler +from elasticapm.utils.logging import get_logger + +logger = get_logger("elasticapm.logging") + + +class LoggingHandler(BaseLoggingHandler): + def __init__(self, level=logging.NOTSET) -> None: + warnings.warn( + "The LoggingHandler is deprecated and will be removed in v7.0 of the agent. " + "Please use `log_ecs_reformatting` and ship the logs with Elastic " + "Agent or Filebeat instead. " + "https://www.elastic.co/guide/en/apm/agent/python/current/logs.html", + DeprecationWarning, + ) + # skip initialization of BaseLoggingHandler + logging.Handler.__init__(self, level=level) + + @property + def client(self): + return get_client() + + def _emit(self, record, **kwargs): + from elasticapm.contrib.django.middleware import LogMiddleware + + # Fetch the request from a threadlocal variable, if available + request = getattr(LogMiddleware.thread, "request", None) + request = getattr(record, "request", request) + + return super(LoggingHandler, self)._emit(record, request=request, **kwargs) + def exception_handler(client, request=None, **kwargs): def actually_do_stuff(request=None, **kwargs) -> None: diff --git a/elasticapm/contrib/flask/__init__.py b/elasticapm/contrib/flask/__init__.py index 4be9fe7ae..fdb6906dd 100644 --- a/elasticapm/contrib/flask/__init__.py +++ b/elasticapm/contrib/flask/__init__.py @@ -31,6 +31,9 @@ from __future__ import absolute_import +import logging +import warnings + import flask from flask import request, signals @@ -38,8 +41,9 @@ import elasticapm.instrumentation.control from elasticapm import get_client from elasticapm.base import Client -from elasticapm.conf import constants +from elasticapm.conf import constants, setup_logging from elasticapm.contrib.flask.utils import get_data_from_request, get_data_from_response +from elasticapm.handlers.logging import LoggingHandler from elasticapm.traces import execution_context from elasticapm.utils import build_name_with_http_method_prefix from elasticapm.utils.disttracing import TraceParent @@ -77,14 +81,17 @@ class ElasticAPM(object): >>> elasticapm.capture_message('hello, world!') """ - def __init__(self, app=None, client=None, client_cls=Client, **defaults) -> None: + def __init__(self, app=None, client=None, client_cls=Client, logging=False, **defaults) -> None: self.app = app + self.logging = logging + if self.logging: + warnings.warn( + "Flask log shipping is deprecated. See the Flask docs for more info and alternatives.", + DeprecationWarning, + ) self.client = client or get_client() self.client_cls = client_cls - if "logging" in defaults: - raise ValueError("Flask log shipping has been removed, drop the ElasticAPM logging parameter") - if app: self.init_app(app, **defaults) @@ -120,6 +127,14 @@ def init_app(self, app, **defaults) -> None: self.client = self.client_cls(config, **defaults) + # 0 is a valid log level (NOTSET), so we need to check explicitly for it + if self.logging or self.logging is logging.NOTSET: + if self.logging is not True: + kwargs = {"level": self.logging} + else: + kwargs = {} + setup_logging(LoggingHandler(self.client, **kwargs)) + signals.got_request_exception.connect(self.handle_exception, sender=app, weak=False) try: diff --git a/elasticapm/handlers/logging.py b/elasticapm/handlers/logging.py index bcdd15bb0..96718d2db 100644 --- a/elasticapm/handlers/logging.py +++ b/elasticapm/handlers/logging.py @@ -32,11 +32,181 @@ from __future__ import absolute_import import logging +import sys +import traceback +import warnings import wrapt from elasticapm import get_client +from elasticapm.base import Client from elasticapm.traces import execution_context +from elasticapm.utils.stacks import iter_stack_frames + + +class LoggingHandler(logging.Handler): + def __init__(self, *args, **kwargs) -> None: + warnings.warn( + "The LoggingHandler is deprecated and will be removed in v7.0 of " + "the agent. Please use `log_ecs_reformatting` and ship the logs " + "with Elastic Agent or Filebeat instead. " + "https://www.elastic.co/guide/en/apm/agent/python/current/logs.html", + DeprecationWarning, + ) + self.client = None + if "client" in kwargs: + self.client = kwargs.pop("client") + elif len(args) > 0: + arg = args[0] + if isinstance(arg, Client): + self.client = arg + + if not self.client: + client_cls = kwargs.pop("client_cls", None) + if client_cls: + self.client = client_cls(*args, **kwargs) + else: + warnings.warn( + "LoggingHandler requires a Client instance. No Client was received.", + DeprecationWarning, + ) + self.client = Client(*args, **kwargs) + logging.Handler.__init__(self, level=kwargs.get("level", logging.NOTSET)) + + def emit(self, record): + self.format(record) + + # Avoid typical config issues by overriding loggers behavior + if record.name.startswith(("elasticapm.errors",)): + sys.stderr.write(record.getMessage() + "\n") + return + + try: + return self._emit(record) + except Exception: + sys.stderr.write("Top level ElasticAPM exception caught - failed creating log record.\n") + sys.stderr.write(record.getMessage() + "\n") + sys.stderr.write(traceback.format_exc() + "\n") + + try: + self.client.capture("Exception") + except Exception: + pass + + def _emit(self, record, **kwargs): + data = {} + + for k, v in record.__dict__.items(): + if "." not in k and k not in ("culprit",): + continue + data[k] = v + + stack = getattr(record, "stack", None) + if stack is True: + stack = iter_stack_frames(config=self.client.config) + + if stack: + frames = [] + started = False + last_mod = "" + for item in stack: + if isinstance(item, (list, tuple)): + frame, lineno = item + else: + frame, lineno = item, item.f_lineno + + if not started: + f_globals = getattr(frame, "f_globals", {}) + module_name = f_globals.get("__name__", "") + if last_mod.startswith("logging") and not module_name.startswith("logging"): + started = True + else: + last_mod = module_name + continue + frames.append((frame, lineno)) + stack = frames + + custom = getattr(record, "data", {}) + # Add in all of the data from the record that we aren't already capturing + for k in record.__dict__.keys(): + if k in ( + "stack", + "name", + "args", + "msg", + "levelno", + "exc_text", + "exc_info", + "data", + "created", + "levelname", + "msecs", + "relativeCreated", + ): + continue + if k.startswith("_"): + continue + custom[k] = record.__dict__[k] + + # If there's no exception being processed, + # exc_info may be a 3-tuple of None + # http://docs.python.org/library/sys.html#sys.exc_info + if record.exc_info and all(record.exc_info): + handler = self.client.get_handler("elasticapm.events.Exception") + exception = handler.capture(self.client, exc_info=record.exc_info) + else: + exception = None + + return self.client.capture( + "Message", + param_message={"message": str(record.msg), "params": record.args}, + stack=stack, + custom=custom, + exception=exception, + level=record.levelno, + logger_name=record.name, + **kwargs, + ) + + +class LoggingFilter(logging.Filter): + """ + This filter doesn't actually do any "filtering" -- rather, it just adds + three new attributes to any "filtered" LogRecord objects: + + * elasticapm_transaction_id + * elasticapm_trace_id + * elasticapm_span_id + * elasticapm_service_name + + These attributes can then be incorporated into your handlers and formatters, + so that you can tie log messages to transactions in elasticsearch. + + This filter also adds these fields to a dictionary attribute, + `elasticapm_labels`, using the official tracing fields names as documented + here: https://www.elastic.co/guide/en/ecs/current/ecs-tracing.html + + Note that if you're using Python 3.2+, by default we will add a + LogRecordFactory to your root logger which will add these attributes + automatically. + """ + + def __init__(self, name=""): + super().__init__(name=name) + warnings.warn( + "The LoggingFilter is deprecated and will be removed in v7.0 of " + "the agent. On Python 3.2+, by default we add a LogRecordFactory to " + "your root logger automatically" + "https://www.elastic.co/guide/en/apm/agent/python/current/logs.html", + DeprecationWarning, + ) + + def filter(self, record): + """ + Add elasticapm attributes to `record`. + """ + _add_attributes_to_log_record(record) + return True @wrapt.decorator diff --git a/tests/contrib/django/django_tests.py b/tests/contrib/django/django_tests.py index 312583ac1..94843a83f 100644 --- a/tests/contrib/django/django_tests.py +++ b/tests/contrib/django/django_tests.py @@ -62,6 +62,7 @@ from elasticapm.conf.constants import ERROR, SPAN, TRANSACTION from elasticapm.contrib.django.apps import ElasticAPMConfig from elasticapm.contrib.django.client import client, get_client +from elasticapm.contrib.django.handlers import LoggingHandler from elasticapm.contrib.django.middleware.wsgi import ElasticAPM from elasticapm.utils.disttracing import TraceParent from tests.contrib.django.conftest import BASE_TEMPLATE_DIR @@ -409,6 +410,25 @@ def test_ignored_exception_is_ignored(django_elasticapm_client, client): assert len(django_elasticapm_client.events[ERROR]) == 0 +def test_record_none_exc_info(django_elasticapm_client): + # sys.exc_info can return (None, None, None) if no exception is being + # handled anywhere on the stack. See: + # http://docs.python.org/library/sys.html#sys.exc_info + record = logging.LogRecord( + "foo", logging.INFO, pathname=None, lineno=None, msg="test", args=(), exc_info=(None, None, None) + ) + handler = LoggingHandler() + handler.emit(record) + + assert len(django_elasticapm_client.events[ERROR]) == 1 + event = django_elasticapm_client.events[ERROR][0] + + assert event["log"]["param_message"] == "test" + assert event["log"]["logger_name"] == "foo" + assert event["log"]["level"] == "info" + assert "exception" not in event + + def test_404_middleware(django_elasticapm_client, client): with override_settings( **middleware_setting(django.VERSION, ["elasticapm.contrib.django.middleware.Catch404Middleware"]) @@ -1012,6 +1032,54 @@ def test_filter_matches_module_only(django_sending_elasticapm_client): assert len(django_sending_elasticapm_client.httpserver.requests) == 1 +def test_django_logging_request_kwarg(django_elasticapm_client): + handler = LoggingHandler() + + logger = logging.getLogger(__name__) + logger.handlers = [] + logger.addHandler(handler) + + logger.error( + "This is a test error", + extra={ + "request": WSGIRequest( + environ={ + "wsgi.input": io.StringIO(), + "REQUEST_METHOD": "POST", + "SERVER_NAME": "testserver", + "SERVER_PORT": "80", + "CONTENT_TYPE": "application/json", + "ACCEPT": "application/json", + } + ) + }, + ) + + assert len(django_elasticapm_client.events[ERROR]) == 1 + event = django_elasticapm_client.events[ERROR][0] + assert "request" in event["context"] + request = event["context"]["request"] + assert request["method"] == "POST" + + +def test_django_logging_middleware(django_elasticapm_client, client): + handler = LoggingHandler() + + logger = logging.getLogger("logmiddleware") + logger.handlers = [] + logger.addHandler(handler) + logger.level = logging.INFO + + with override_settings( + **middleware_setting(django.VERSION, ["elasticapm.contrib.django.middleware.LogMiddleware"]) + ): + client.get(reverse("elasticapm-logging")) + assert len(django_elasticapm_client.events[ERROR]) == 1 + event = django_elasticapm_client.events[ERROR][0] + assert "request" in event["context"] + assert event["context"]["request"]["url"]["pathname"] == reverse("elasticapm-logging") + + def client_get(client, url): return client.get(url) diff --git a/tests/contrib/flask/flask_tests.py b/tests/contrib/flask/flask_tests.py index 38657a6f6..a54cfe75a 100644 --- a/tests/contrib/flask/flask_tests.py +++ b/tests/contrib/flask/flask_tests.py @@ -50,11 +50,6 @@ pytestmark = pytest.mark.flask -def test_logging_parameter_raises_exception(): - with pytest.raises(ValueError, match="Flask log shipping has been removed, drop the ElasticAPM logging parameter"): - ElasticAPM(config=None, logging=True) - - def test_error_handler(flask_apm_client): client = flask_apm_client.app.test_client() response = client.get("/an-error/") @@ -446,6 +441,32 @@ def test_rum_tracing_context_processor(flask_apm_client): assert callable(context["apm"]["span_id"]) +@pytest.mark.parametrize("flask_apm_client", [{"logging": True}], indirect=True) +def test_logging_enabled(flask_apm_client): + logger = logging.getLogger() + logger.error("test") + error = flask_apm_client.client.events[ERROR][0] + assert error["log"]["level"] == "error" + assert error["log"]["message"] == "test" + + +@pytest.mark.parametrize("flask_apm_client", [{"logging": False}], indirect=True) +def test_logging_disabled(flask_apm_client): + logger = logging.getLogger() + logger.error("test") + assert len(flask_apm_client.client.events[ERROR]) == 0 + + +@pytest.mark.parametrize("flask_apm_client", [{"logging": logging.ERROR}], indirect=True) +def test_logging_by_level(flask_apm_client): + logger = logging.getLogger() + logger.warning("test") + logger.error("test") + assert len(flask_apm_client.client.events[ERROR]) == 1 + error = flask_apm_client.client.events[ERROR][0] + assert error["log"]["level"] == "error" + + def test_flask_transaction_ignore_urls(flask_apm_client): resp = flask_apm_client.app.test_client().get("/users/") resp.close() diff --git a/tests/handlers/logging/logging_tests.py b/tests/handlers/logging/logging_tests.py index 00a1a4ab6..8cc8fc4f1 100644 --- a/tests/handlers/logging/logging_tests.py +++ b/tests/handlers/logging/logging_tests.py @@ -40,13 +40,238 @@ from elasticapm.conf import Config from elasticapm.conf.constants import ERROR -from elasticapm.handlers.logging import Formatter +from elasticapm.handlers.logging import Formatter, LoggingFilter, LoggingHandler from elasticapm.handlers.structlog import structlog_processor from elasticapm.traces import capture_span from elasticapm.utils.stacks import iter_stack_frames from tests.fixtures import TempStoreClient +@pytest.fixture() +def logger(elasticapm_client): + elasticapm_client.config.include_paths = ["tests", "elasticapm"] + handler = LoggingHandler(elasticapm_client) + logger = logging.getLogger(__name__) + logger.handlers = [] + logger.addHandler(handler) + logger.client = elasticapm_client + logger.level = logging.INFO + return logger + + +def test_logger_basic(logger): + logger.error("This is a test error") + + assert len(logger.client.events) == 1 + event = logger.client.events[ERROR][0] + assert event["log"]["logger_name"] == __name__ + assert event["log"]["level"] == "error" + assert event["log"]["message"] == "This is a test error" + assert "stacktrace" in event["log"] + assert "exception" not in event + assert "param_message" in event["log"] + assert event["log"]["param_message"] == "This is a test error" + + +def test_logger_warning(logger): + logger.warning("This is a test warning") + assert len(logger.client.events) == 1 + event = logger.client.events[ERROR][0] + assert event["log"]["logger_name"] == __name__ + assert event["log"]["level"] == "warning" + assert "exception" not in event + assert "param_message" in event["log"] + assert event["log"]["param_message"] == "This is a test warning" + + +def test_logger_extra_data(logger): + logger.info("This is a test info with a url", extra=dict(data=dict(url="http://example.com"))) + assert len(logger.client.events) == 1 + event = logger.client.events[ERROR][0] + assert event["context"]["custom"]["url"] == "http://example.com" + assert "stacktrace" in event["log"] + assert "exception" not in event + assert "param_message" in event["log"] + assert event["log"]["param_message"] == "This is a test info with a url" + + +def test_logger_exc_info(logger): + try: + raise ValueError("This is a test ValueError") + except ValueError: + logger.info("This is a test info with an exception", exc_info=True) + + assert len(logger.client.events) == 1 + event = logger.client.events[ERROR][0] + + # assert event['message'] == 'This is a test info with an exception' + assert "exception" in event + assert "stacktrace" in event["exception"] + exc = event["exception"] + assert exc["type"] == "ValueError" + assert exc["message"] == "ValueError: This is a test ValueError" + assert "param_message" in event["log"] + assert event["log"]["message"] == "This is a test info with an exception" + + +def test_message_params(logger): + logger.info("This is a test of %s", "args") + assert len(logger.client.events) == 1 + event = logger.client.events[ERROR][0] + assert "exception" not in event + assert "param_message" in event["log"] + assert event["log"]["message"] == "This is a test of args" + assert event["log"]["param_message"] == "This is a test of %s" + + +def test_record_stack(logger): + logger.info("This is a test of stacks", extra={"stack": True}) + assert len(logger.client.events) == 1 + event = logger.client.events[ERROR][0] + frames = event["log"]["stacktrace"] + assert len(frames) != 1 + frame = frames[0] + assert frame["module"] == __name__ + assert "exception" not in event + assert "param_message" in event["log"] + assert event["log"]["param_message"] == "This is a test of stacks" + assert event["culprit"] == "tests.handlers.logging.logging_tests.test_record_stack" + assert event["log"]["message"] == "This is a test of stacks" + + +def test_no_record_stack(logger): + logger.info("This is a test of no stacks", extra={"stack": False}) + assert len(logger.client.events) == 1 + event = logger.client.events[ERROR][0] + assert event.get("culprit") == None + assert event["log"]["message"] == "This is a test of no stacks" + assert "stacktrace" not in event["log"] + assert "exception" not in event + assert "param_message" in event["log"] + assert event["log"]["param_message"] == "This is a test of no stacks" + + +def test_no_record_stack_via_config(logger): + logger.client.config.auto_log_stacks = False + logger.info("This is a test of no stacks") + assert len(logger.client.events) == 1 + event = logger.client.events[ERROR][0] + assert event.get("culprit") == None + assert event["log"]["message"] == "This is a test of no stacks" + assert "stacktrace" not in event["log"] + assert "exception" not in event + assert "param_message" in event["log"] + assert event["log"]["param_message"] == "This is a test of no stacks" + + +def test_explicit_stack(logger): + logger.info("This is a test of stacks", extra={"stack": iter_stack_frames()}) + assert len(logger.client.events) == 1 + event = logger.client.events[ERROR][0] + assert "culprit" in event, event + assert event["culprit"] == "tests.handlers.logging.logging_tests.test_explicit_stack" + assert "message" in event["log"], event + assert event["log"]["message"] == "This is a test of stacks" + assert "exception" not in event + assert "param_message" in event["log"] + assert event["log"]["param_message"] == "This is a test of stacks" + assert "stacktrace" in event["log"] + + +def test_extra_culprit(logger): + logger.info("This is a test of stacks", extra={"culprit": "foo.bar"}) + assert len(logger.client.events) == 1 + event = logger.client.events[ERROR][0] + assert event["culprit"] == "foo.bar" + assert "culprit" not in event["context"]["custom"] + + +def test_logger_exception(logger): + try: + raise ValueError("This is a test ValueError") + except ValueError: + logger.exception("This is a test with an exception", extra={"stack": True}) + + assert len(logger.client.events) == 1 + event = logger.client.events[ERROR][0] + + assert event["log"]["message"] == "This is a test with an exception" + assert "stacktrace" in event["log"] + assert "exception" in event + exc = event["exception"] + assert exc["type"] == "ValueError" + assert exc["message"] == "ValueError: This is a test ValueError" + assert "param_message" in event["log"] + assert event["log"]["message"] == "This is a test with an exception" + + +def test_client_arg(elasticapm_client): + handler = LoggingHandler(elasticapm_client) + assert handler.client == elasticapm_client + + +def test_client_kwarg(elasticapm_client): + handler = LoggingHandler(client=elasticapm_client) + assert handler.client == elasticapm_client + + +def test_logger_setup(): + handler = LoggingHandler( + server_url="foo", service_name="bar", secret_token="baz", metrics_interval="0ms", client_cls=TempStoreClient + ) + client = handler.client + assert client.config.server_url == "foo" + assert client.config.service_name == "bar" + assert client.config.secret_token == "baz" + assert handler.level == logging.NOTSET + + +def test_logging_handler_emit_error(capsys, elasticapm_client): + handler = LoggingHandler(elasticapm_client) + handler._emit = lambda: 1 / 0 + handler.emit(LogRecord("x", 1, "/ab/c/", 10, "Oops", (), None)) + out, err = capsys.readouterr() + assert "Top level ElasticAPM exception caught" in err + assert "Oops" in err + + +def test_logging_handler_dont_emit_elasticapm(capsys, elasticapm_client): + handler = LoggingHandler(elasticapm_client) + handler.emit(LogRecord("elasticapm.errors", 1, "/ab/c/", 10, "Oops", (), None)) + out, err = capsys.readouterr() + assert "Oops" in err + + +def test_logging_handler_emit_error_non_str_message(capsys, elasticapm_client): + handler = LoggingHandler(elasticapm_client) + handler._emit = lambda: 1 / 0 + handler.emit(LogRecord("x", 1, "/ab/c/", 10, ValueError("oh no"), (), None)) + out, err = capsys.readouterr() + assert "Top level ElasticAPM exception caught" in err + assert "oh no" in err + + +def test_arbitrary_object(logger): + logger.error(["a", "list", "of", "strings"]) + assert len(logger.client.events) == 1 + event = logger.client.events[ERROR][0] + assert "param_message" in event["log"] + assert event["log"]["param_message"] == "['a', 'list', 'of', 'strings']" + + +def test_logging_filter_no_span(elasticapm_client): + transaction = elasticapm_client.begin_transaction("test") + f = LoggingFilter() + record = logging.LogRecord(__name__, logging.DEBUG, __file__, 252, "dummy_msg", [], None) + f.filter(record) + assert record.elasticapm_transaction_id == transaction.id + assert record.elasticapm_service_name == transaction.tracer.config.service_name + assert record.elasticapm_service_environment == transaction.tracer.config.environment + assert record.elasticapm_trace_id == transaction.trace_parent.trace_id + assert record.elasticapm_span_id is None + assert record.elasticapm_labels + + def test_structlog_processor_no_span(elasticapm_client): transaction = elasticapm_client.begin_transaction("test") event_dict = {} @@ -56,6 +281,37 @@ def test_structlog_processor_no_span(elasticapm_client): assert "span.id" not in new_dict +@pytest.mark.parametrize("elasticapm_client", [{"transaction_max_spans": 5}], indirect=True) +def test_logging_filter_span(elasticapm_client): + transaction = elasticapm_client.begin_transaction("test") + with capture_span("test") as span: + f = LoggingFilter() + record = logging.LogRecord(__name__, logging.DEBUG, __file__, 252, "dummy_msg", [], None) + f.filter(record) + assert record.elasticapm_transaction_id == transaction.id + assert record.elasticapm_service_name == transaction.tracer.config.service_name + assert record.elasticapm_service_environment == transaction.tracer.config.environment + assert record.elasticapm_trace_id == transaction.trace_parent.trace_id + assert record.elasticapm_span_id == span.id + assert record.elasticapm_labels + + # Capture too many spans so we start dropping + for i in range(10): + with capture_span("drop"): + pass + + # Test logging with DroppedSpan + with capture_span("drop") as span: + record = logging.LogRecord(__name__, logging.DEBUG, __file__, 252, "dummy_msg2", [], None) + f.filter(record) + assert record.elasticapm_transaction_id == transaction.id + assert record.elasticapm_service_name == transaction.tracer.config.service_name + assert record.elasticapm_service_environment == transaction.tracer.config.environment + assert record.elasticapm_trace_id == transaction.trace_parent.trace_id + assert record.elasticapm_span_id is None + assert record.elasticapm_labels + + @pytest.mark.parametrize("elasticapm_client", [{"transaction_max_spans": 5}], indirect=True) def test_structlog_processor_span(elasticapm_client): transaction = elasticapm_client.begin_transaction("test") @@ -117,6 +373,18 @@ def test_formatter(): assert hasattr(record, "elasticapm_service_environment") +def test_logging_handler_no_client(recwarn): + # In 6.0, this should be changed to expect a ValueError instead of a log + warnings.simplefilter("always") + LoggingHandler(transport_class="tests.fixtures.DummyTransport") + while True: + # If we never find our desired warning this will eventually throw an + # AssertionError + w = recwarn.pop(DeprecationWarning) + if "LoggingHandler requires a Client instance" in w.message.args[0]: + return True + + @pytest.mark.parametrize( "elasticapm_client,expected", [ From dee49af2665c966599ee1775525c7c7789b3ee5f Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 4 Aug 2025 17:29:53 +0200 Subject: [PATCH 313/409] Revert "Drop support for opentracing (#2342)" (#2384) This reverts commit e4dff95d2176a152f385d431848afa71355b3a44. --- .ci/.matrix_framework.yml | 1 + .ci/.matrix_framework_fips.yml | 1 + .ci/.matrix_framework_full.yml | 2 + elasticapm/contrib/opentracing/__init__.py | 43 +++ elasticapm/contrib/opentracing/span.py | 136 ++++++++ elasticapm/contrib/opentracing/tracer.py | 131 ++++++++ setup.cfg | 3 + tests/contrib/opentracing/__init__.py | 29 ++ tests/contrib/opentracing/tests.py | 313 ++++++++++++++++++ tests/requirements/reqs-opentracing-2.0.txt | 2 + .../requirements/reqs-opentracing-newest.txt | 2 + tests/scripts/envs/opentracing.sh | 1 + 12 files changed, 664 insertions(+) create mode 100644 elasticapm/contrib/opentracing/__init__.py create mode 100644 elasticapm/contrib/opentracing/span.py create mode 100644 elasticapm/contrib/opentracing/tracer.py create mode 100644 tests/contrib/opentracing/__init__.py create mode 100644 tests/contrib/opentracing/tests.py create mode 100644 tests/requirements/reqs-opentracing-2.0.txt create mode 100644 tests/requirements/reqs-opentracing-newest.txt create mode 100644 tests/scripts/envs/opentracing.sh diff --git a/.ci/.matrix_framework.yml b/.ci/.matrix_framework.yml index 1cd690c39..8c73d0810 100644 --- a/.ci/.matrix_framework.yml +++ b/.ci/.matrix_framework.yml @@ -12,6 +12,7 @@ FRAMEWORK: - flask-3.0 - jinja2-3 - opentelemetry-newest + - opentracing-newest - twisted-newest - celery-5-flask-2 - celery-5-django-4 diff --git a/.ci/.matrix_framework_fips.yml b/.ci/.matrix_framework_fips.yml index 0c733de80..6bbc9cd3e 100644 --- a/.ci/.matrix_framework_fips.yml +++ b/.ci/.matrix_framework_fips.yml @@ -6,6 +6,7 @@ FRAMEWORK: - flask-3.0 - jinja2-3 - opentelemetry-newest + - opentracing-newest - twisted-newest - celery-5-flask-2 - celery-5-django-5 diff --git a/.ci/.matrix_framework_full.yml b/.ci/.matrix_framework_full.yml index cdabff496..4adb9b25e 100644 --- a/.ci/.matrix_framework_full.yml +++ b/.ci/.matrix_framework_full.yml @@ -30,6 +30,8 @@ FRAMEWORK: - celery-5-django-4 - celery-5-django-5 - opentelemetry-newest + - opentracing-newest + - opentracing-2.0 - twisted-newest - twisted-18 - twisted-17 diff --git a/elasticapm/contrib/opentracing/__init__.py b/elasticapm/contrib/opentracing/__init__.py new file mode 100644 index 000000000..71619ea20 --- /dev/null +++ b/elasticapm/contrib/opentracing/__init__.py @@ -0,0 +1,43 @@ +# BSD 3-Clause License +# +# Copyright (c) 2019, Elasticsearch BV +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +import warnings + +from .span import OTSpan # noqa: F401 +from .tracer import Tracer # noqa: F401 + +warnings.warn( + ( + "The OpenTracing bridge is deprecated and will be removed in the next major release. " + "Please migrate to the OpenTelemetry bridge." + ), + DeprecationWarning, +) diff --git a/elasticapm/contrib/opentracing/span.py b/elasticapm/contrib/opentracing/span.py new file mode 100644 index 000000000..6bc00fec5 --- /dev/null +++ b/elasticapm/contrib/opentracing/span.py @@ -0,0 +1,136 @@ +# BSD 3-Clause License +# +# Copyright (c) 2019, Elasticsearch BV +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from opentracing.span import Span as OTSpanBase +from opentracing.span import SpanContext as OTSpanContextBase + +from elasticapm import traces +from elasticapm.utils import get_url_dict +from elasticapm.utils.logging import get_logger + +try: + # opentracing-python 2.1+ + from opentracing import logs as ot_logs + from opentracing import tags +except ImportError: + # opentracing-python <2.1 + from opentracing.ext import tags + + ot_logs = None + + +logger = get_logger("elasticapm.contrib.opentracing") + + +class OTSpan(OTSpanBase): + def __init__(self, tracer, context, elastic_apm_ref) -> None: + super(OTSpan, self).__init__(tracer, context) + self.elastic_apm_ref = elastic_apm_ref + self.is_transaction = isinstance(elastic_apm_ref, traces.Transaction) + self.is_dropped = isinstance(elastic_apm_ref, traces.DroppedSpan) + if not context.span: + context.span = self + + def log_kv(self, key_values, timestamp=None): + exc_type, exc_val, exc_tb = None, None, None + if "python.exception.type" in key_values: + exc_type = key_values["python.exception.type"] + exc_val = key_values.get("python.exception.val") + exc_tb = key_values.get("python.exception.tb") + elif ot_logs and key_values.get(ot_logs.EVENT) == tags.ERROR: + exc_type = key_values[ot_logs.ERROR_KIND] + exc_val = key_values.get(ot_logs.ERROR_OBJECT) + exc_tb = key_values.get(ot_logs.STACK) + else: + logger.debug("Can't handle non-exception type opentracing logs") + if exc_type: + agent = self.tracer._agent + agent.capture_exception(exc_info=(exc_type, exc_val, exc_tb)) + return self + + def set_operation_name(self, operation_name): + self.elastic_apm_ref.name = operation_name + return self + + def set_tag(self, key, value): + if self.is_transaction: + if key == "type": + self.elastic_apm_ref.transaction_type = value + elif key == "result": + self.elastic_apm_ref.result = value + elif key == tags.HTTP_STATUS_CODE: + self.elastic_apm_ref.result = "HTTP {}xx".format(str(value)[0]) + traces.set_context({"status_code": value}, "response") + elif key == "user.id": + traces.set_user_context(user_id=value) + elif key == "user.username": + traces.set_user_context(username=value) + elif key == "user.email": + traces.set_user_context(email=value) + elif key == tags.HTTP_URL: + traces.set_context({"url": get_url_dict(value)}, "request") + elif key == tags.HTTP_METHOD: + traces.set_context({"method": value}, "request") + elif key == tags.COMPONENT: + traces.set_context({"framework": {"name": value}}, "service") + else: + self.elastic_apm_ref.label(**{key: value}) + elif not self.is_dropped: + if key.startswith("db."): + span_context = self.elastic_apm_ref.context or {} + if "db" not in span_context: + span_context["db"] = {} + if key == tags.DATABASE_STATEMENT: + span_context["db"]["statement"] = value + elif key == tags.DATABASE_USER: + span_context["db"]["user"] = value + elif key == tags.DATABASE_TYPE: + span_context["db"]["type"] = value + self.elastic_apm_ref.type = "db." + value + else: + self.elastic_apm_ref.label(**{key: value}) + self.elastic_apm_ref.context = span_context + elif key == tags.SPAN_KIND: + self.elastic_apm_ref.type = value + else: + self.elastic_apm_ref.label(**{key: value}) + return self + + def finish(self, finish_time=None) -> None: + if self.is_transaction: + self.tracer._agent.end_transaction() + elif not self.is_dropped: + self.elastic_apm_ref.transaction.end_span() + + +class OTSpanContext(OTSpanContextBase): + def __init__(self, trace_parent, span=None) -> None: + self.trace_parent = trace_parent + self.span = span diff --git a/elasticapm/contrib/opentracing/tracer.py b/elasticapm/contrib/opentracing/tracer.py new file mode 100644 index 000000000..d331735f6 --- /dev/null +++ b/elasticapm/contrib/opentracing/tracer.py @@ -0,0 +1,131 @@ +# BSD 3-Clause License +# +# Copyright (c) 2019, Elasticsearch BV +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import warnings + +from opentracing import Format, InvalidCarrierException, SpanContextCorruptedException, UnsupportedFormatException +from opentracing.scope_managers import ThreadLocalScopeManager +from opentracing.tracer import ReferenceType +from opentracing.tracer import Tracer as TracerBase + +import elasticapm +from elasticapm import get_client, instrument, traces +from elasticapm.conf import constants +from elasticapm.contrib.opentracing.span import OTSpan, OTSpanContext +from elasticapm.utils import disttracing + + +class Tracer(TracerBase): + def __init__(self, client_instance=None, config=None, scope_manager=None) -> None: + self._agent = client_instance or get_client() or elasticapm.Client(config=config) + if scope_manager and not isinstance(scope_manager, ThreadLocalScopeManager): + warnings.warn( + "Currently, the Elastic APM opentracing bridge only supports the ThreadLocalScopeManager. " + "Usage of other scope managers will lead to unpredictable results." + ) + self._scope_manager = scope_manager or ThreadLocalScopeManager() + if self._agent.config.instrument and self._agent.config.enabled: + instrument() + + def start_active_span( + self, + operation_name, + child_of=None, + references=None, + tags=None, + start_time=None, + ignore_active_span=False, + finish_on_close=True, + ): + ot_span = self.start_span( + operation_name, + child_of=child_of, + references=references, + tags=tags, + start_time=start_time, + ignore_active_span=ignore_active_span, + ) + scope = self._scope_manager.activate(ot_span, finish_on_close) + return scope + + def start_span( + self, operation_name=None, child_of=None, references=None, tags=None, start_time=None, ignore_active_span=False + ): + if isinstance(child_of, OTSpanContext): + parent_context = child_of + elif isinstance(child_of, OTSpan): + parent_context = child_of.context + elif references and references[0].type == ReferenceType.CHILD_OF: + parent_context = references[0].referenced_context + else: + parent_context = None + transaction = traces.execution_context.get_transaction() + if not transaction: + trace_parent = parent_context.trace_parent if parent_context else None + transaction = self._agent.begin_transaction("custom", trace_parent=trace_parent) + transaction.name = operation_name + span_context = OTSpanContext(trace_parent=transaction.trace_parent) + ot_span = OTSpan(self, span_context, transaction) + else: + # to allow setting an explicit parent span, we check if the parent_context is set + # and if it is a span. In all other cases, the parent is found implicitly through the + # execution context. + parent_span_id = ( + parent_context.span.elastic_apm_ref.id + if parent_context and parent_context.span and not parent_context.span.is_transaction + else None + ) + span = transaction._begin_span(operation_name, None, parent_span_id=parent_span_id) + trace_parent = parent_context.trace_parent if parent_context else transaction.trace_parent + span_context = OTSpanContext(trace_parent=trace_parent.copy_from(span_id=span.id)) + ot_span = OTSpan(self, span_context, span) + if tags: + for k, v in tags.items(): + ot_span.set_tag(k, v) + return ot_span + + def extract(self, format, carrier): + if format in (Format.HTTP_HEADERS, Format.TEXT_MAP): + trace_parent = disttracing.TraceParent.from_headers(carrier) + if not trace_parent: + raise SpanContextCorruptedException("could not extract span context from carrier") + return OTSpanContext(trace_parent=trace_parent) + raise UnsupportedFormatException + + def inject(self, span_context, format, carrier): + if format in (Format.HTTP_HEADERS, Format.TEXT_MAP): + if not isinstance(carrier, dict): + raise InvalidCarrierException("carrier for {} format should be dict-like".format(format)) + val = span_context.trace_parent.to_ascii() + carrier[constants.TRACEPARENT_HEADER_NAME] = val + if self._agent.config.use_elastic_traceparent_header: + carrier[constants.TRACEPARENT_LEGACY_HEADER_NAME] = val + return + raise UnsupportedFormatException diff --git a/setup.cfg b/setup.cfg index d3611a899..9ad206732 100644 --- a/setup.cfg +++ b/setup.cfg @@ -55,6 +55,8 @@ tornado = tornado starlette = starlette +opentracing = + opentracing>=2.0.0 sanic = sanic opentelemetry = @@ -79,6 +81,7 @@ markers = gevent eventlet celery + opentracing cassandra psycopg2 mongodb diff --git a/tests/contrib/opentracing/__init__.py b/tests/contrib/opentracing/__init__.py new file mode 100644 index 000000000..7e2b340e6 --- /dev/null +++ b/tests/contrib/opentracing/__init__.py @@ -0,0 +1,29 @@ +# BSD 3-Clause License +# +# Copyright (c) 2019, Elasticsearch BV +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/tests/contrib/opentracing/tests.py b/tests/contrib/opentracing/tests.py new file mode 100644 index 000000000..50970c269 --- /dev/null +++ b/tests/contrib/opentracing/tests.py @@ -0,0 +1,313 @@ +# BSD 3-Clause License +# +# Copyright (c) 2019, Elasticsearch BV +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +from datetime import timedelta + +import pytest # isort:skip + +opentracing = pytest.importorskip("opentracing") # isort:skip + +import sys + +import mock +from opentracing import Format + +import elasticapm +from elasticapm.conf import constants +from elasticapm.contrib.opentracing import Tracer +from elasticapm.contrib.opentracing.span import OTSpanContext +from elasticapm.utils.disttracing import TraceParent + +pytestmark = pytest.mark.opentracing + + +try: + from opentracing import logs as ot_logs + from opentracing import tags +except ImportError: + ot_logs = None + + +@pytest.fixture() +def tracer(elasticapm_client): + yield Tracer(client_instance=elasticapm_client) + elasticapm.uninstrument() + + +def test_tracer_with_instantiated_client(elasticapm_client): + tracer = Tracer(client_instance=elasticapm_client) + assert tracer._agent is elasticapm_client + + +def test_tracer_with_config(): + config = {"METRICS_INTERVAL": "0s", "SERVER_URL": "https://example.com/test"} + tracer = Tracer(config=config) + try: + assert tracer._agent.config.metrics_interval == timedelta(seconds=0) + assert tracer._agent.config.server_url == "https://example.com/test" + finally: + tracer._agent.close() + + +def test_tracer_instrument(elasticapm_client): + with mock.patch("elasticapm.contrib.opentracing.tracer.instrument") as mock_instrument: + elasticapm_client.config.instrument = False + Tracer(client_instance=elasticapm_client) + assert mock_instrument.call_count == 0 + + elasticapm_client.config.instrument = True + Tracer(client_instance=elasticapm_client) + assert mock_instrument.call_count == 1 + + +def test_ot_transaction_started(tracer): + with tracer.start_active_span("test") as ot_scope: + ot_scope.span.set_tag("result", "OK") + client = tracer._agent + transaction = client.events[constants.TRANSACTION][0] + assert transaction["type"] == "custom" + assert transaction["name"] == "test" + assert transaction["result"] == "OK" + + +def test_ot_span(tracer): + with tracer.start_active_span("test") as ot_scope_transaction: + with tracer.start_active_span("testspan") as ot_scope_span: + ot_scope_span.span.set_tag("span.kind", "custom") + with tracer.start_active_span("testspan2") as ot_scope_span2: + with tracer.start_active_span("testspan3", child_of=ot_scope_span.span) as ot_scope_span3: + pass + client = tracer._agent + transaction = client.events[constants.TRANSACTION][0] + span1 = client.events[constants.SPAN][2] + span2 = client.events[constants.SPAN][1] + span3 = client.events[constants.SPAN][0] + assert span1["transaction_id"] == span1["parent_id"] == transaction["id"] + assert span1["name"] == "testspan" + + assert span2["transaction_id"] == transaction["id"] + assert span2["parent_id"] == span1["id"] + assert span2["name"] == "testspan2" + + # check that span3 has span1 as parent + assert span3["transaction_id"] == transaction["id"] + assert span3["parent_id"] == span1["id"] + assert span3["name"] == "testspan3" + + +def test_transaction_tags(tracer): + with tracer.start_active_span("test") as ot_scope: + ot_scope.span.set_tag("type", "foo") + ot_scope.span.set_tag("http.status_code", 200) + ot_scope.span.set_tag("http.url", "http://example.com/foo") + ot_scope.span.set_tag("http.method", "GET") + ot_scope.span.set_tag("user.id", 1) + ot_scope.span.set_tag("user.email", "foo@example.com") + ot_scope.span.set_tag("user.username", "foo") + ot_scope.span.set_tag("component", "Django") + ot_scope.span.set_tag("something.else", "foo") + client = tracer._agent + transaction = client.events[constants.TRANSACTION][0] + + assert transaction["type"] == "foo" + assert transaction["result"] == "HTTP 2xx" + assert transaction["context"]["response"]["status_code"] == 200 + assert transaction["context"]["request"]["url"]["full"] == "http://example.com/foo" + assert transaction["context"]["request"]["method"] == "GET" + assert transaction["context"]["user"] == {"id": 1, "email": "foo@example.com", "username": "foo"} + assert transaction["context"]["service"]["framework"]["name"] == "Django" + assert transaction["context"]["tags"] == {"something_else": "foo"} + + +def test_span_tags(tracer): + with tracer.start_active_span("transaction") as ot_scope_t: + with tracer.start_active_span("span") as ot_scope_s: + s = ot_scope_s.span + s.set_tag("db.type", "sql") + s.set_tag("db.statement", "SELECT * FROM foo") + s.set_tag("db.user", "bar") + s.set_tag("db.instance", "baz") + with tracer.start_active_span("span") as ot_scope_s: + s = ot_scope_s.span + s.set_tag("span.kind", "foo") + s.set_tag("something.else", "bar") + client = tracer._agent + span1 = client.events[constants.SPAN][0] + span2 = client.events[constants.SPAN][1] + + assert span1["context"]["db"] == {"type": "sql", "user": "bar", "statement": "SELECT * FROM foo"} + assert span1["type"] == "db.sql" + assert span1["context"]["tags"] == {"db_instance": "baz"} + + assert span2["type"] == "foo" + assert span2["context"]["tags"] == {"something_else": "bar"} + + +@pytest.mark.parametrize("elasticapm_client", [{"transaction_max_spans": 1}], indirect=True) +def test_dropped_spans(tracer): + assert tracer._agent.config.transaction_max_spans == 1 + with tracer.start_active_span("transaction") as ot_scope_t: + with tracer.start_active_span("span") as ot_scope_s: + s = ot_scope_s.span + s.set_tag("db.type", "sql") + with tracer.start_active_span("span") as ot_scope_s: + s = ot_scope_s.span + s.set_tag("db.type", "sql") + client = tracer._agent + spans = client.events[constants.SPAN] + assert len(spans) == 1 + + +def test_error_log(tracer): + with tracer.start_active_span("transaction") as tx_scope: + try: + raise ValueError("oops") + except ValueError: + exc_type, exc_val, exc_tb = sys.exc_info()[:3] + tx_scope.span.log_kv( + {"python.exception.type": exc_type, "python.exception.val": exc_val, "python.exception.tb": exc_tb} + ) + client = tracer._agent + error = client.events[constants.ERROR][0] + + assert error["exception"]["message"] == "ValueError: oops" + + +@pytest.mark.skipif(ot_logs is None, reason="New key names in opentracing-python 2.1") +def test_error_log_ot_21(tracer): + with tracer.start_active_span("transaction") as tx_scope: + try: + raise ValueError("oops") + except ValueError: + exc_type, exc_val, exc_tb = sys.exc_info()[:3] + tx_scope.span.log_kv( + { + ot_logs.EVENT: tags.ERROR, + ot_logs.ERROR_KIND: exc_type, + ot_logs.ERROR_OBJECT: exc_val, + ot_logs.STACK: exc_tb, + } + ) + client = tracer._agent + error = client.events[constants.ERROR][0] + + assert error["exception"]["message"] == "ValueError: oops" + + +def test_error_log_automatic_in_span_context_manager(tracer): + scope = tracer.start_active_span("transaction") + with pytest.raises(ValueError): + with scope.span: + raise ValueError("oops") + + client = tracer._agent + error = client.events[constants.ERROR][0] + + assert error["exception"]["message"] == "ValueError: oops" + + +def test_span_set_bagge_item_noop(tracer): + scope = tracer.start_active_span("transaction") + assert scope.span.set_baggage_item("key", "val") == scope.span + + +def test_tracer_extract_http(tracer): + span_context = tracer.extract( + Format.HTTP_HEADERS, {"elastic-apm-traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"} + ) + + assert span_context.trace_parent.version == 0 + assert span_context.trace_parent.trace_id == "0af7651916cd43dd8448eb211c80319c" + assert span_context.trace_parent.span_id == "b7ad6b7169203331" + + +def test_tracer_extract_map(tracer): + span_context = tracer.extract( + Format.TEXT_MAP, {"elastic-apm-traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"} + ) + + assert span_context.trace_parent.version == 0 + assert span_context.trace_parent.trace_id == "0af7651916cd43dd8448eb211c80319c" + assert span_context.trace_parent.span_id == "b7ad6b7169203331" + + +def test_tracer_extract_binary(tracer): + with pytest.raises(opentracing.UnsupportedFormatException): + tracer.extract(Format.BINARY, b"foo") + + +def test_tracer_extract_corrupted(tracer): + with pytest.raises(opentracing.SpanContextCorruptedException): + tracer.extract(Format.HTTP_HEADERS, {"nothing-to": "see-here"}) + + +@pytest.mark.parametrize( + "elasticapm_client", + [ + pytest.param({"use_elastic_traceparent_header": True}, id="use_elastic_traceparent_header-True"), + pytest.param({"use_elastic_traceparent_header": False}, id="use_elastic_traceparent_header-False"), + ], + indirect=True, +) +def test_tracer_inject_http(tracer): + span_context = OTSpanContext( + trace_parent=TraceParent.from_string("00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01") + ) + carrier = {} + tracer.inject(span_context, Format.HTTP_HEADERS, carrier) + assert carrier[constants.TRACEPARENT_HEADER_NAME] == b"00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" + if tracer._agent.config.use_elastic_traceparent_header: + assert carrier[constants.TRACEPARENT_LEGACY_HEADER_NAME] == carrier[constants.TRACEPARENT_HEADER_NAME] + + +@pytest.mark.parametrize( + "elasticapm_client", + [ + pytest.param({"use_elastic_traceparent_header": True}, id="use_elastic_traceparent_header-True"), + pytest.param({"use_elastic_traceparent_header": False}, id="use_elastic_traceparent_header-False"), + ], + indirect=True, +) +def test_tracer_inject_map(tracer): + span_context = OTSpanContext( + trace_parent=TraceParent.from_string("00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01") + ) + carrier = {} + tracer.inject(span_context, Format.TEXT_MAP, carrier) + assert carrier[constants.TRACEPARENT_HEADER_NAME] == b"00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01" + if tracer._agent.config.use_elastic_traceparent_header: + assert carrier[constants.TRACEPARENT_LEGACY_HEADER_NAME] == carrier[constants.TRACEPARENT_HEADER_NAME] + + +def test_tracer_inject_binary(tracer): + span_context = OTSpanContext( + trace_parent=TraceParent.from_string("00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01") + ) + with pytest.raises(opentracing.UnsupportedFormatException): + tracer.inject(span_context, Format.BINARY, {}) diff --git a/tests/requirements/reqs-opentracing-2.0.txt b/tests/requirements/reqs-opentracing-2.0.txt new file mode 100644 index 000000000..de859ccbb --- /dev/null +++ b/tests/requirements/reqs-opentracing-2.0.txt @@ -0,0 +1,2 @@ +opentracing>=2.0.0,<2.1.0 +-r reqs-base.txt diff --git a/tests/requirements/reqs-opentracing-newest.txt b/tests/requirements/reqs-opentracing-newest.txt new file mode 100644 index 000000000..b82c2d976 --- /dev/null +++ b/tests/requirements/reqs-opentracing-newest.txt @@ -0,0 +1,2 @@ +opentracing>=2.1.0 +-r reqs-base.txt diff --git a/tests/scripts/envs/opentracing.sh b/tests/scripts/envs/opentracing.sh new file mode 100644 index 000000000..243c0ee96 --- /dev/null +++ b/tests/scripts/envs/opentracing.sh @@ -0,0 +1 @@ +export PYTEST_MARKER="-m opentracing" From 35dc65bd3a243dae9a183f41da92195250f2b7a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:01:18 +0200 Subject: [PATCH 314/409] build(deps): bump wolfi/chainguard-base from `66d7835` to `442a566` (#2389) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `66d7835` to `442a566`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index f7f452362..eca385f37 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:66d78357f294fef975f0286c04f963249b5c6a835a53c26a4d1c8dd5ecfbd57d +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:442a5663000b3d66d565e61d400b30a4638383a72d90494cfc3104b34dfb3211 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From f0f12bebe8de628fa5eed6e703e618fce5b65d7c Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:02:33 +0200 Subject: [PATCH 315/409] chore: deps(updatecli): Bump updatecli version to v0.105.0 (#2387) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 46c433f64..13a67464c 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.104.0 \ No newline at end of file +updatecli v0.105.0 \ No newline at end of file From abbb037f48527359bfcb115433cbca3c4ebe36f7 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 11 Aug 2025 17:06:18 +0200 Subject: [PATCH 316/409] tests: bump MongoDB latest to 4.2 (#2391) Because latest client uses a newer wire protocol. --- tests/docker-compose.yml | 8 ++++---- tests/scripts/envs/pymongo-newest.sh | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index b65a97e9e..09010f1bf 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -46,12 +46,12 @@ services: volumes: - pymongodata36:/data/db - mongodb40: - image: mongo:4.0 + mongodb42: + image: mongo:4.2 ports: - "27017:27017" volumes: - - pymongodata40:/data/db + - pymongodata42:/data/db memcached: image: memcached @@ -205,7 +205,7 @@ volumes: driver: local pymongodata36: driver: local - pymongodata40: + pymongodata42: driver: local pyesdata7: driver: local diff --git a/tests/scripts/envs/pymongo-newest.sh b/tests/scripts/envs/pymongo-newest.sh index d1766b69f..8b496971b 100644 --- a/tests/scripts/envs/pymongo-newest.sh +++ b/tests/scripts/envs/pymongo-newest.sh @@ -1,3 +1,3 @@ export PYTEST_MARKER="-m mongodb" -export DOCKER_DEPS="mongodb40" -export MONGODB_HOST="mongodb40" +export DOCKER_DEPS="mongodb42" +export MONGODB_HOST="mongodb42" From 8446aa14f9e8c02bdc8ac959da11636828dc24c6 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 11 Aug 2025 17:08:23 +0200 Subject: [PATCH 317/409] Revert old python support removal (#2385) * Revert "Drop Python 3.7 support (#2340)" This reverts commit 8ac1781d091ca609212897d83adb35926edef90d. * Revert "Drop support for Python 3.6 (#2338)" This reverts commit 76cf5c8ba963b7a88543c80145f69640c6fafdcf. * Revert "Use unittest.mock instead of the mock backport package (#2370)" This reverts commit 7241b27ba9f190167c70abc728f629061396d0fb. [xrmx: keep the azurestorage marker addition] --- .ci/.matrix_exclude.yml | 77 +++++++++++++++++++ .ci/.matrix_python.yml | 2 +- .ci/.matrix_python_full.yml | 2 + .ci/publish-aws.sh | 2 +- .github/workflows/test.yml | 6 ++ Makefile | 10 ++- docs/reference/run-tests-locally.md | 2 +- elasticapm/__init__.py | 4 +- elasticapm/base.py | 4 +- elasticapm/instrumentation/register.py | 50 +++++++----- setup.cfg | 4 +- tests/client/client_tests.py | 2 +- tests/client/exception_tests.py | 2 +- tests/client/py3_exception_tests.py | 2 +- tests/config/central_config_tests.py | 3 +- tests/config/tests.py | 2 +- tests/context/test_context.py | 16 +++- tests/contrib/asyncio/aiohttp_web_tests.py | 3 +- tests/contrib/asyncio/starlette_tests.py | 2 +- .../contrib/asyncio/tornado/tornado_tests.py | 2 +- tests/contrib/celery/flask_tests.py | 2 +- tests/contrib/django/django_tests.py | 2 +- tests/contrib/django/wrapper_tests.py | 2 +- tests/contrib/flask/flask_tests.py | 2 +- .../azurefunctions/azure_functions_tests.py | 2 +- tests/events/tests.py | 3 +- tests/fixtures.py | 2 +- tests/instrumentation/base_tests.py | 2 +- .../transactions_store_tests.py | 2 +- tests/instrumentation/urllib_tests.py | 2 +- tests/metrics/base_tests.py | 2 +- tests/metrics/breakdown_tests.py | 3 +- tests/processors/tests.py | 6 +- tests/requirements/lint-isort.txt | 1 + tests/requirements/reqs-base.txt | 32 +++++--- tests/scripts/envs/azure.sh | 1 - tests/scripts/run_tests.sh | 2 +- tests/transports/test_base.py | 2 +- tests/transports/test_urllib3.py | 2 +- tests/utils/cloud_tests.py | 2 +- tests/utils/compat_tests.py | 3 +- tests/utils/stacks/tests.py | 2 +- tests/utils/threading_tests.py | 2 +- 43 files changed, 198 insertions(+), 80 deletions(-) delete mode 100644 tests/scripts/envs/azure.sh diff --git a/.ci/.matrix_exclude.yml b/.ci/.matrix_exclude.yml index d4b1416b2..db796ee34 100644 --- a/.ci/.matrix_exclude.yml +++ b/.ci/.matrix_exclude.yml @@ -5,13 +5,34 @@ exclude: # Django 4.0 requires Python 3.8+ - VERSION: pypy-3 # current pypy-3 is compatible with Python 3.7 FRAMEWORK: django-4.0 + - VERSION: python-3.6 + FRAMEWORK: django-4.0 + - VERSION: python-3.7 + FRAMEWORK: django-4.0 + # Django 4.2 requires Python 3.8+ + - VERSION: python-3.6 + FRAMEWORK: django-4.2 + - VERSION: python-3.7 + FRAMEWORK: django-4.2 # Django 5.0 requires Python 3.10+ + - VERSION: python-3.6 + FRAMEWORK: django-5.0 + - VERSION: python-3.7 + FRAMEWORK: django-5.0 - VERSION: python-3.8 FRAMEWORK: django-5.0 - VERSION: python-3.9 FRAMEWORK: django-5.0 - VERSION: pypy-3 # current pypy-3 is compatible with Python 3.7 FRAMEWORK: celery-5-django-4 + - VERSION: python-3.6 + FRAMEWORK: celery-5-django-4 + - VERSION: python-3.7 + FRAMEWORK: celery-5-django-4 + - VERSION: python-3.6 + FRAMEWORK: celery-5-django-5 + - VERSION: python-3.7 + FRAMEWORK: celery-5-django-5 - VERSION: python-3.8 FRAMEWORK: celery-5-django-5 - VERSION: python-3.9 @@ -19,6 +40,18 @@ exclude: # Flask - VERSION: pypy-3 FRAMEWORK: flask-0.11 # see https://github.com/pallets/flask/commit/6e46d0cd, 0.11.2 was never released + - VERSION: python-3.6 + FRAMEWORK: flask-2.1 + - VERSION: python-3.6 + FRAMEWORK: flask-2.2 + - VERSION: python-3.6 + FRAMEWORK: flask-2.3 + - VERSION: python-3.6 + FRAMEWORK: flask-3.0 + - VERSION: python-3.7 + FRAMEWORK: flask-2.3 + - VERSION: python-3.7 + FRAMEWORK: flask-3.0 # Python 3.10 removed a bunch of classes from collections, now in collections.abc - VERSION: python-3.10 FRAMEWORK: django-1.11 @@ -152,6 +185,8 @@ exclude: # pymssql - VERSION: pypy-3 # currently fails with error on pypy3 FRAMEWORK: pymssql-newest + - VERSION: python-3.6 # dropped support for py3.6 + FRAMEWORK: pymssql-newest # pyodbc - VERSION: pypy-3 FRAMEWORK: pyodbc-newest @@ -175,28 +210,48 @@ exclude: # aiohttp client, only supported in Python 3.7+ - VERSION: pypy-3 FRAMEWORK: aiohttp-3.0 + - VERSION: python-3.6 + FRAMEWORK: aiohttp-3.0 - VERSION: pypy-3 FRAMEWORK: aiohttp-4.0 + - VERSION: python-3.6 + FRAMEWORK: aiohttp-4.0 - VERSION: pypy-3 FRAMEWORK: aiohttp-newest + - VERSION: python-3.6 + FRAMEWORK: aiohttp-newest # tornado, only supported in Python 3.7+ - VERSION: pypy-3 FRAMEWORK: tornado-newest + - VERSION: python-3.6 + FRAMEWORK: tornado-newest # Starlette, only supported in python 3.7+ - VERSION: pypy-3 FRAMEWORK: starlette-0.13 + - VERSION: python-3.6 + FRAMEWORK: starlette-0.13 - VERSION: pypy-3 FRAMEWORK: starlette-0.14 + - VERSION: python-3.6 + FRAMEWORK: starlette-0.14 - VERSION: pypy-3 FRAMEWORK: starlette-newest + - VERSION: python-3.6 + FRAMEWORK: starlette-newest # aiopg - VERSION: pypy-3 FRAMEWORK: aiopg-newest + - VERSION: python-3.6 + FRAMEWORK: aiopg-newest # asyncpg - VERSION: pypy-3 FRAMEWORK: asyncpg-newest - VERSION: pypy-3 FRAMEWORK: asyncpg-0.28 + - VERSION: python-3.6 + FRAMEWORK: asyncpg-newest + - VERSION: python-3.6 + FRAMEWORK: asyncpg-0.28 - VERSION: python-3.13 FRAMEWORK: asyncpg-0.28 # sanic @@ -204,6 +259,10 @@ exclude: FRAMEWORK: sanic-newest - VERSION: pypy-3 FRAMEWORK: sanic-20.12 + - VERSION: python-3.6 + FRAMEWORK: sanic-20.12 + - VERSION: python-3.6 + FRAMEWORK: sanic-newest - VERSION: python-3.8 FRAMEWORK: sanic-newest - VERSION: python-3.13 @@ -211,13 +270,21 @@ exclude: # aioredis - VERSION: pypy-3 FRAMEWORK: aioredis-newest + - VERSION: python-3.6 + FRAMEWORK: aioredis-newest # aiomysql - VERSION: pypy-3 FRAMEWORK: aiomysql-newest + - VERSION: python-3.6 + FRAMEWORK: aiomysql-newest # aiobotocore - VERSION: pypy-3 FRAMEWORK: aiobotocore-newest + - VERSION: python-3.6 + FRAMEWORK: aiobotocore-newest # mysql-connector-python + - VERSION: python-3.6 + FRAMEWORK: mysql_connector-newest # twisted - VERSION: python-3.11 FRAMEWORK: twisted-18 @@ -251,6 +318,10 @@ exclude: - VERSION: python-3.13 FRAMEWORK: pylibmc-1.4 # grpc + - VERSION: python-3.6 + FRAMEWORK: grpc-newest + - VERSION: python-3.7 + FRAMEWORK: grpc-1.24 - VERSION: python-3.8 FRAMEWORK: grpc-1.24 - VERSION: python-3.9 @@ -263,6 +334,12 @@ exclude: FRAMEWORK: grpc-1.24 - VERSION: python-3.13 FRAMEWORK: grpc-1.24 + - VERSION: python-3.7 + FRAMEWORK: flask-1.0 + - VERSION: python-3.7 + FRAMEWORK: flask-1.1 + - VERSION: python-3.7 + FRAMEWORK: jinja2-2 # TODO py3.12 - VERSION: python-3.12 FRAMEWORK: sanic-20.12 # no wheels available yet diff --git a/.ci/.matrix_python.yml b/.ci/.matrix_python.yml index a6c1e6948..86c87ad88 100644 --- a/.ci/.matrix_python.yml +++ b/.ci/.matrix_python.yml @@ -1,3 +1,3 @@ VERSION: - - python-3.8 + - python-3.6 - python-3.13 diff --git a/.ci/.matrix_python_full.yml b/.ci/.matrix_python_full.yml index 1c8ee413a..bb763b7ca 100644 --- a/.ci/.matrix_python_full.yml +++ b/.ci/.matrix_python_full.yml @@ -1,4 +1,6 @@ VERSION: + - python-3.6 + - python-3.7 - python-3.8 - python-3.9 - python-3.10 diff --git a/.ci/publish-aws.sh b/.ci/publish-aws.sh index 39ef88425..3bb7a554c 100755 --- a/.ci/publish-aws.sh +++ b/.ci/publish-aws.sh @@ -46,7 +46,7 @@ for region in $ALL_AWS_REGIONS; do --layer-name="${FULL_LAYER_NAME}" \ --description="AWS Lambda Extension Layer for the Elastic APM Python Agent" \ --license-info="BSD-3-Clause" \ - --compatible-runtimes python3.8 python3.9 python3.10 python3.11 python3.12 python3.13\ + --compatible-runtimes python3.6 python3.7 python3.8 python3.9 python3.10 python3.11 python3.12 python3.13\ --zip-file="fileb://${zip_file}") echo "${publish_output}" > "${AWS_FOLDER}/${region}" layer_version=$(echo "${publish_output}" | jq '.Version') diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3e68d173b..4fc7a275e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -114,6 +114,12 @@ jobs: fail-fast: false matrix: include: + # - version: "3.6" + # framework: "none" + # asyncio: "true" + # - version: "3.7" + # framework: none + # asyncio: true - version: "3.8" framework: none asyncio: true diff --git a/Makefile b/Makefile index 82e4d2fb4..b2d00f400 100644 --- a/Makefile +++ b/Makefile @@ -10,8 +10,14 @@ flake8: test: # delete any __pycache__ folders to avoid hard-to-debug caching issues find . -type f -name '*.py[co]' -delete -o -type d -name __pycache__ -delete - echo "Python 3.7+, with asyncio"; \ - pytest -v $(PYTEST_ARGS) --showlocals $(PYTEST_MARKER) $(PYTEST_JUNIT); \ + # pypy3 should be added to the first `if` once it supports py3.7 + if [[ "$$PYTHON_VERSION" =~ ^(3.7|3.8|3.9|3.10|3.11|3.12|3.13|nightly)$$ ]] ; then \ + echo "Python 3.7+, with asyncio"; \ + pytest -v $(PYTEST_ARGS) --showlocals $(PYTEST_MARKER) $(PYTEST_JUNIT); \ + else \ + echo "Python < 3.7, without asyncio"; \ + pytest -v $(PYTEST_ARGS) --showlocals $(PYTEST_MARKER) $(PYTEST_JUNIT) --ignore-glob='*/asyncio*/*'; \ + fi coverage: PYTEST_ARGS=--cov --cov-context=test --cov-config=setup.cfg --cov-branch coverage: export COVERAGE_FILE=.coverage.docker.$(PYTHON_FULL_VERSION).$(FRAMEWORK) diff --git a/docs/reference/run-tests-locally.md b/docs/reference/run-tests-locally.md index 689b08524..f72432d7e 100644 --- a/docs/reference/run-tests-locally.md +++ b/docs/reference/run-tests-locally.md @@ -53,7 +53,7 @@ $ ./tests/scripts/docker/run_tests.sh python-version framework-version bool: def check_python_version(self) -> None: v = tuple(map(int, platform.python_version_tuple()[:2])) - if v < (3, 8): - warnings.warn("The Elastic APM agent only supports Python 3.8+", DeprecationWarning) + if v < (3, 6): + warnings.warn("The Elastic APM agent only supports Python 3.6+", DeprecationWarning) def check_server_version( self, gte: Optional[Tuple[int, ...]] = None, lte: Optional[Tuple[int, ...]] = None diff --git a/elasticapm/instrumentation/register.py b/elasticapm/instrumentation/register.py index 3e5d82230..b37aff1e9 100644 --- a/elasticapm/instrumentation/register.py +++ b/elasticapm/instrumentation/register.py @@ -28,6 +28,8 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import sys + from elasticapm.utils.module_import import import_string _cls_register = { @@ -68,29 +70,35 @@ "elasticapm.instrumentation.packages.kafka.KafkaInstrumentation", "elasticapm.instrumentation.packages.grpc.GRPCClientInstrumentation", "elasticapm.instrumentation.packages.grpc.GRPCServerInstrumentation", - "elasticapm.instrumentation.packages.asyncio.sleep.AsyncIOSleepInstrumentation", - "elasticapm.instrumentation.packages.asyncio.aiohttp_client.AioHttpClientInstrumentation", - "elasticapm.instrumentation.packages.httpx.async.httpx.HttpxAsyncClientInstrumentation", - "elasticapm.instrumentation.packages.asyncio.elasticsearch.ElasticSearchAsyncConnection", - "elasticapm.instrumentation.packages.asyncio.elasticsearch.ElasticsearchAsyncTransportInstrumentation", - "elasticapm.instrumentation.packages.asyncio.aiopg.AioPGInstrumentation", - "elasticapm.instrumentation.packages.asyncio.asyncpg.AsyncPGInstrumentation", - "elasticapm.instrumentation.packages.tornado.TornadoRequestExecuteInstrumentation", - "elasticapm.instrumentation.packages.tornado.TornadoHandleRequestExceptionInstrumentation", - "elasticapm.instrumentation.packages.tornado.TornadoRenderInstrumentation", - "elasticapm.instrumentation.packages.httpx.async.httpcore.HTTPCoreAsyncInstrumentation", - "elasticapm.instrumentation.packages.asyncio.aioredis.RedisConnectionPoolInstrumentation", - "elasticapm.instrumentation.packages.asyncio.aioredis.RedisPipelineInstrumentation", - "elasticapm.instrumentation.packages.asyncio.aioredis.RedisConnectionInstrumentation", - "elasticapm.instrumentation.packages.asyncio.aiomysql.AioMySQLInstrumentation", - "elasticapm.instrumentation.packages.asyncio.aiobotocore.AioBotocoreInstrumentation", - "elasticapm.instrumentation.packages.asyncio.starlette.StarletteServerErrorMiddlewareInstrumentation", - "elasticapm.instrumentation.packages.asyncio.redis_asyncio.RedisAsyncioInstrumentation", - "elasticapm.instrumentation.packages.asyncio.redis_asyncio.RedisPipelineInstrumentation", - "elasticapm.instrumentation.packages.asyncio.psycopg_async.AsyncPsycopgInstrumentation", - "elasticapm.instrumentation.packages.grpc.GRPCAsyncServerInstrumentation", } +if sys.version_info >= (3, 7): + _cls_register.update( + [ + "elasticapm.instrumentation.packages.asyncio.sleep.AsyncIOSleepInstrumentation", + "elasticapm.instrumentation.packages.asyncio.aiohttp_client.AioHttpClientInstrumentation", + "elasticapm.instrumentation.packages.httpx.async.httpx.HttpxAsyncClientInstrumentation", + "elasticapm.instrumentation.packages.asyncio.elasticsearch.ElasticSearchAsyncConnection", + "elasticapm.instrumentation.packages.asyncio.elasticsearch.ElasticsearchAsyncTransportInstrumentation", + "elasticapm.instrumentation.packages.asyncio.aiopg.AioPGInstrumentation", + "elasticapm.instrumentation.packages.asyncio.asyncpg.AsyncPGInstrumentation", + "elasticapm.instrumentation.packages.tornado.TornadoRequestExecuteInstrumentation", + "elasticapm.instrumentation.packages.tornado.TornadoHandleRequestExceptionInstrumentation", + "elasticapm.instrumentation.packages.tornado.TornadoRenderInstrumentation", + "elasticapm.instrumentation.packages.httpx.async.httpcore.HTTPCoreAsyncInstrumentation", + "elasticapm.instrumentation.packages.asyncio.aioredis.RedisConnectionPoolInstrumentation", + "elasticapm.instrumentation.packages.asyncio.aioredis.RedisPipelineInstrumentation", + "elasticapm.instrumentation.packages.asyncio.aioredis.RedisConnectionInstrumentation", + "elasticapm.instrumentation.packages.asyncio.aiomysql.AioMySQLInstrumentation", + "elasticapm.instrumentation.packages.asyncio.aiobotocore.AioBotocoreInstrumentation", + "elasticapm.instrumentation.packages.asyncio.starlette.StarletteServerErrorMiddlewareInstrumentation", + "elasticapm.instrumentation.packages.asyncio.redis_asyncio.RedisAsyncioInstrumentation", + "elasticapm.instrumentation.packages.asyncio.redis_asyncio.RedisPipelineInstrumentation", + "elasticapm.instrumentation.packages.asyncio.psycopg_async.AsyncPsycopgInstrumentation", + "elasticapm.instrumentation.packages.grpc.GRPCAsyncServerInstrumentation", + ] + ) + # These instrumentations should only be enabled if we're instrumenting via the # wrapper script, which calls register_wrapper_instrumentations() below. _wrapper_register = { diff --git a/setup.cfg b/setup.cfg index 9ad206732..0a56a9b01 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,6 +15,8 @@ classifiers = Operating System :: OS Independent Topic :: Software Development Programming Language :: Python + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 @@ -31,7 +33,7 @@ project_urls = Tracker = https://github.com/elastic/apm-agent-python/issues [options] -python_requires = >=3.8, <4 +python_requires = >=3.6, <4 packages = find: include_package_data = true zip_safe = false diff --git a/tests/client/client_tests.py b/tests/client/client_tests.py index e73e726ab..e42ada12c 100644 --- a/tests/client/client_tests.py +++ b/tests/client/client_tests.py @@ -39,8 +39,8 @@ import time import warnings from collections import defaultdict -from unittest import mock +import mock import pytest from pytest_localserver.http import ContentServer from pytest_localserver.https import DEFAULT_CERTIFICATE diff --git a/tests/client/exception_tests.py b/tests/client/exception_tests.py index 056f23548..082835be3 100644 --- a/tests/client/exception_tests.py +++ b/tests/client/exception_tests.py @@ -29,8 +29,8 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import os -from unittest import mock +import mock import pytest import elasticapm diff --git a/tests/client/py3_exception_tests.py b/tests/client/py3_exception_tests.py index e1ff057eb..ad8bb10ca 100644 --- a/tests/client/py3_exception_tests.py +++ b/tests/client/py3_exception_tests.py @@ -38,7 +38,7 @@ # # -from unittest import mock +import mock from elasticapm.conf.constants import ERROR diff --git a/tests/config/central_config_tests.py b/tests/config/central_config_tests.py index d3aaf4fe7..568cf180a 100644 --- a/tests/config/central_config_tests.py +++ b/tests/config/central_config_tests.py @@ -28,8 +28,7 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from unittest import mock - +import mock import pytest diff --git a/tests/config/tests.py b/tests/config/tests.py index ba07d7795..284f5694a 100644 --- a/tests/config/tests.py +++ b/tests/config/tests.py @@ -35,8 +35,8 @@ import platform import stat from datetime import timedelta -from unittest import mock +import mock import pytest import elasticapm.conf diff --git a/tests/context/test_context.py b/tests/context/test_context.py index 1bae6cb87..058d60bab 100644 --- a/tests/context/test_context.py +++ b/tests/context/test_context.py @@ -39,9 +39,21 @@ def test_execution_context_backing(): execution_context = elasticapm.context.init_execution_context() - from elasticapm.context.contextvars import ContextVarsContext + if sys.version_info[0] == 3 and sys.version_info[1] >= 7: + from elasticapm.context.contextvars import ContextVarsContext - assert isinstance(execution_context, ContextVarsContext) + assert isinstance(execution_context, ContextVarsContext) + else: + try: + import opentelemetry + + pytest.skip( + "opentelemetry installs contextvars backport, so this test isn't valid for the opentelemetry matrix" + ) + except ImportError: + pass + + assert isinstance(execution_context, ThreadLocalContext) def test_execution_context_monkeypatched(monkeypatch): diff --git a/tests/contrib/asyncio/aiohttp_web_tests.py b/tests/contrib/asyncio/aiohttp_web_tests.py index 4a63d8714..929a8e1a7 100644 --- a/tests/contrib/asyncio/aiohttp_web_tests.py +++ b/tests/contrib/asyncio/aiohttp_web_tests.py @@ -32,8 +32,7 @@ aiohttp = pytest.importorskip("aiohttp") # isort:skip -from unittest import mock - +import mock from multidict import MultiDict import elasticapm diff --git a/tests/contrib/asyncio/starlette_tests.py b/tests/contrib/asyncio/starlette_tests.py index 4053b5d5b..38c51fa08 100644 --- a/tests/contrib/asyncio/starlette_tests.py +++ b/tests/contrib/asyncio/starlette_tests.py @@ -38,8 +38,8 @@ starlette = pytest.importorskip("starlette") # isort:skip import os -from unittest import mock +import mock import urllib3 import wrapt from starlette.applications import Starlette diff --git a/tests/contrib/asyncio/tornado/tornado_tests.py b/tests/contrib/asyncio/tornado/tornado_tests.py index 337d54942..3ce3bafed 100644 --- a/tests/contrib/asyncio/tornado/tornado_tests.py +++ b/tests/contrib/asyncio/tornado/tornado_tests.py @@ -33,8 +33,8 @@ tornado = pytest.importorskip("tornado") # isort:skip import os -from unittest import mock +import mock from wrapt import BoundFunctionWrapper import elasticapm diff --git a/tests/contrib/celery/flask_tests.py b/tests/contrib/celery/flask_tests.py index cb7c53ed5..29dd61fdb 100644 --- a/tests/contrib/celery/flask_tests.py +++ b/tests/contrib/celery/flask_tests.py @@ -33,7 +33,7 @@ flask = pytest.importorskip("flask") # isort:skip celery = pytest.importorskip("celery") # isort:skip -from unittest import mock +import mock from elasticapm.conf.constants import ERROR, TRANSACTION diff --git a/tests/contrib/django/django_tests.py b/tests/contrib/django/django_tests.py index 94843a83f..72c791280 100644 --- a/tests/contrib/django/django_tests.py +++ b/tests/contrib/django/django_tests.py @@ -41,8 +41,8 @@ import logging import os from copy import deepcopy -from unittest import mock +import mock from django.conf import settings from django.contrib.auth.models import User from django.contrib.redirects.models import Redirect diff --git a/tests/contrib/django/wrapper_tests.py b/tests/contrib/django/wrapper_tests.py index 6464f17b7..4c3f186fc 100644 --- a/tests/contrib/django/wrapper_tests.py +++ b/tests/contrib/django/wrapper_tests.py @@ -32,7 +32,7 @@ # Installing an app is not reversible, so using this instrumentation "for real" would # pollute the Django instance used by pytest. -from unittest import mock +import mock from elasticapm.instrumentation.packages.django import DjangoAutoInstrumentation diff --git a/tests/contrib/flask/flask_tests.py b/tests/contrib/flask/flask_tests.py index a54cfe75a..7ffce68cf 100644 --- a/tests/contrib/flask/flask_tests.py +++ b/tests/contrib/flask/flask_tests.py @@ -35,9 +35,9 @@ import io import logging import os -from unittest import mock from urllib.request import urlopen +import mock from flask import signals import elasticapm diff --git a/tests/contrib/serverless/azurefunctions/azure_functions_tests.py b/tests/contrib/serverless/azurefunctions/azure_functions_tests.py index 91550a59f..e2abbdcd3 100644 --- a/tests/contrib/serverless/azurefunctions/azure_functions_tests.py +++ b/tests/contrib/serverless/azurefunctions/azure_functions_tests.py @@ -33,9 +33,9 @@ import datetime import os -from unittest import mock import azure.functions as func +import mock import elasticapm from elasticapm.conf import constants diff --git a/tests/events/tests.py b/tests/events/tests.py index 89d33ca3b..cd6cc6d3c 100644 --- a/tests/events/tests.py +++ b/tests/events/tests.py @@ -32,9 +32,8 @@ from __future__ import absolute_import -from unittest.mock import Mock - import pytest +from mock import Mock from elasticapm.events import Exception, Message diff --git a/tests/fixtures.py b/tests/fixtures.py index 1b9119b99..a559791bf 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -46,10 +46,10 @@ import zlib from collections import defaultdict from typing import Optional -from unittest import mock from urllib.request import pathname2url import jsonschema +import mock import pytest from pytest_localserver.http import ContentServer from werkzeug.wrappers import Request, Response diff --git a/tests/instrumentation/base_tests.py b/tests/instrumentation/base_tests.py index 9e92aa29c..da2e265ed 100644 --- a/tests/instrumentation/base_tests.py +++ b/tests/instrumentation/base_tests.py @@ -31,8 +31,8 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import logging -from unittest import mock +import mock import pytest import wrapt diff --git a/tests/instrumentation/transactions_store_tests.py b/tests/instrumentation/transactions_store_tests.py index 2d1dcefcc..23a8d0b2a 100644 --- a/tests/instrumentation/transactions_store_tests.py +++ b/tests/instrumentation/transactions_store_tests.py @@ -32,8 +32,8 @@ import logging import time from collections import defaultdict -from unittest import mock +import mock import pytest import elasticapm diff --git a/tests/instrumentation/urllib_tests.py b/tests/instrumentation/urllib_tests.py index 62dda0402..fbf5fa44f 100644 --- a/tests/instrumentation/urllib_tests.py +++ b/tests/instrumentation/urllib_tests.py @@ -28,10 +28,10 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import urllib.parse -from unittest import mock from urllib.error import HTTPError, URLError from urllib.request import urlopen +import mock import pytest from elasticapm.conf import constants diff --git a/tests/metrics/base_tests.py b/tests/metrics/base_tests.py index a9c51fb34..526e5079c 100644 --- a/tests/metrics/base_tests.py +++ b/tests/metrics/base_tests.py @@ -31,8 +31,8 @@ import logging import time from multiprocessing.dummy import Pool -from unittest import mock +import mock import pytest from elasticapm.conf import constants diff --git a/tests/metrics/breakdown_tests.py b/tests/metrics/breakdown_tests.py index 8572bcf6a..3e6b7ed9e 100644 --- a/tests/metrics/breakdown_tests.py +++ b/tests/metrics/breakdown_tests.py @@ -28,8 +28,7 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from unittest import mock - +import mock import pytest import elasticapm diff --git a/tests/processors/tests.py b/tests/processors/tests.py index 84fdfb2b9..772eccf72 100644 --- a/tests/processors/tests.py +++ b/tests/processors/tests.py @@ -34,8 +34,8 @@ import logging import os -from unittest import mock +import mock import pytest import elasticapm @@ -464,9 +464,7 @@ def test_drop_events_in_processor(elasticapm_client, caplog): assert shouldnt_be_called_processor.call_count == 0 assert elasticapm_client._transport.events[TRANSACTION][0] is None assert_any_record_contains( - caplog.records, - "Dropped event of type transaction due to processor unittest.mock.dropper", - "elasticapm.transport", + caplog.records, "Dropped event of type transaction due to processor mock.mock.dropper", "elasticapm.transport" ) diff --git a/tests/requirements/lint-isort.txt b/tests/requirements/lint-isort.txt index 2a7924352..16ad5274c 100644 --- a/tests/requirements/lint-isort.txt +++ b/tests/requirements/lint-isort.txt @@ -1 +1,2 @@ isort +mock diff --git a/tests/requirements/reqs-base.txt b/tests/requirements/reqs-base.txt index ff6e4d24c..d1105586a 100644 --- a/tests/requirements/reqs-base.txt +++ b/tests/requirements/reqs-base.txt @@ -1,25 +1,37 @@ -pytest==7.4.0 +pytest==7.0.1 ; python_version == '3.6' +pytest==7.4.0 ; python_version > '3.6' pytest-random-order==1.1.0 pytest-django==4.4.0 -coverage==7.3.1 -pytest-cov==4.1.0 +coverage==6.2 ; python_version == '3.6' +coverage==6.3 ; python_version == '3.7' +coverage[toml]==6.3 ; python_version == '3.7' +coverage==7.3.1 ; python_version > '3.7' +pytest-cov==4.0.0 ; python_version < '3.8' +pytest-cov==4.1.0 ; python_version > '3.7' +jinja2==3.1.5 ; python_version == '3.7' pytest-localserver==0.9.0 -pytest-mock==3.10.0 -pytest-benchmark==4.0.0 -pytest-bdd==6.1.1 -pytest-rerunfailures==11.1.2 -jsonschema==4.17.3 +pytest-mock==3.6.1 ; python_version == '3.6' +pytest-mock==3.10.0 ; python_version > '3.6' +pytest-benchmark==3.4.1 ; python_version == '3.6' +pytest-benchmark==4.0.0 ; python_version > '3.6' +pytest-bdd==5.0.0 ; python_version == '3.6' +pytest-bdd==6.1.1 ; python_version > '3.6' +pytest-rerunfailures==10.2 ; python_version == '3.6' +pytest-rerunfailures==11.1.2 ; python_version > '3.6' +jsonschema==3.2.0 ; python_version == '3.6' +jsonschema==4.17.3 ; python_version > '3.6' urllib3!=2.0.0,<3.0.0 certifi Logbook +mock pytz ecs_logging structlog wrapt>=1.14.1,!=1.15.0 simplejson -pytest-asyncio==0.21.0 -asynctest==0.13.0 +pytest-asyncio==0.21.0 ; python_version >= '3.7' +asynctest==0.13.0 ; python_version >= '3.7' typing_extensions!=3.10.0.1 ; python_version >= '3.10' # see https://github.com/python/typing/issues/865 diff --git a/tests/scripts/envs/azure.sh b/tests/scripts/envs/azure.sh deleted file mode 100644 index d190b5882..000000000 --- a/tests/scripts/envs/azure.sh +++ /dev/null @@ -1 +0,0 @@ -export PYTEST_MARKER="-m azurestorage" diff --git a/tests/scripts/run_tests.sh b/tests/scripts/run_tests.sh index 414a09885..7fcc85010 100755 --- a/tests/scripts/run_tests.sh +++ b/tests/scripts/run_tests.sh @@ -6,7 +6,7 @@ export PATH=${HOME}/.local/bin:${PATH} python -m pip install --user -U pip setuptools --cache-dir "${PIP_CACHE}" python -m pip install --user -r "tests/requirements/reqs-${FRAMEWORK}.txt" --cache-dir "${PIP_CACHE}" -export PYTHON_VERSION=$(python -c "import platform; pv=platform.python_version_tuple(); print('pypy' + (str(pv[0])) if platform.python_implementation() == 'PyPy' else '.'.join(map(str, platform.python_version_tuple()[:2])))") +export PYTHON_VERSION=$(python -c "import platform; pv=platform.python_version_tuple(); print('pypy' + ('' if pv[0] == 2 else str(pv[0])) if platform.python_implementation() == 'PyPy' else '.'.join(map(str, platform.python_version_tuple()[:2])))") # check if the full FRAMEWORK name is in scripts/envs if [[ -e "./tests/scripts/envs/${FRAMEWORK}.sh" ]] diff --git a/tests/transports/test_base.py b/tests/transports/test_base.py index 37250cf7c..2f77c3e95 100644 --- a/tests/transports/test_base.py +++ b/tests/transports/test_base.py @@ -35,8 +35,8 @@ import sys import time import timeit -from unittest import mock +import mock import pytest from elasticapm.transport.base import Transport, TransportState diff --git a/tests/transports/test_urllib3.py b/tests/transports/test_urllib3.py index e53cb91e8..32a5b7384 100644 --- a/tests/transports/test_urllib3.py +++ b/tests/transports/test_urllib3.py @@ -31,9 +31,9 @@ import os import time -from unittest import mock import certifi +import mock import pytest import urllib3.poolmanager from urllib3.exceptions import MaxRetryError, TimeoutError diff --git a/tests/utils/cloud_tests.py b/tests/utils/cloud_tests.py index a365c2c93..07c1f82e0 100644 --- a/tests/utils/cloud_tests.py +++ b/tests/utils/cloud_tests.py @@ -29,8 +29,8 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import os -from unittest import mock +import mock import urllib3 import elasticapm.utils.cloud diff --git a/tests/utils/compat_tests.py b/tests/utils/compat_tests.py index 352d0bb48..9da7ac2f8 100644 --- a/tests/utils/compat_tests.py +++ b/tests/utils/compat_tests.py @@ -28,8 +28,7 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from unittest import mock - +import mock import pytest from elasticapm.utils import compat diff --git a/tests/utils/stacks/tests.py b/tests/utils/stacks/tests.py index 6b3ba0f89..c4be88ff2 100644 --- a/tests/utils/stacks/tests.py +++ b/tests/utils/stacks/tests.py @@ -34,9 +34,9 @@ import os import pkgutil -from unittest.mock import Mock import pytest +from mock import Mock import elasticapm from elasticapm.conf import constants diff --git a/tests/utils/threading_tests.py b/tests/utils/threading_tests.py index d9f2bf651..1c7329cd9 100644 --- a/tests/utils/threading_tests.py +++ b/tests/utils/threading_tests.py @@ -29,8 +29,8 @@ import platform import time -from unittest import mock +import mock import pytest from elasticapm.utils.threading import IntervalTimer From 2567c5beb907cd8da7ae79b90b83effc51205518 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 09:10:14 +0200 Subject: [PATCH 318/409] chore: deps(updatecli): Bump updatecli version to v0.105.1 (#2394) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 13a67464c..a4187dc33 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.105.0 \ No newline at end of file +updatecli v0.105.1 \ No newline at end of file From a260ca9cfcafa6d030c327e834de0847db0bcede Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 12 Aug 2025 09:13:36 +0200 Subject: [PATCH 319/409] update CHANGELOG and bump version to 6.24.0 (#2392) --- CHANGELOG.asciidoc | 23 +++++++++++++++++++++++ CONTRIBUTING.md | 1 + docs/release-notes/breaking-changes.md | 2 +- docs/release-notes/index.md | 19 +++++++++++++++++++ elasticapm/version.py | 2 +- 5 files changed, 45 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 7550c42d6..63817c5cd 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -32,6 +32,29 @@ endif::[] [[release-notes-6.x]] === Python Agent version 6.x +[[release-notes-6.24.0]] +==== 6.24.0 - 2025-08-12 + +[float] +===== Features + +* Add support for recent sanic versions {pull}2190[#2190], {pull}2194[#2194] +* Make server certificate verification mandatory in fips mode {pull}2227[#2227] +* Add support Python 3.13 {pull}2216[#2216] +* Add support for azure-data-tables package for azure instrumentation {pull}2187[#2187] +* Add span links from SNS messages {pull}2363[#2363] + +[float] +===== Bug fixes + +* Fix psycopg2 cursor execute and executemany signatures {pull}2331[#2331] +* Fix psycopg cursor execute and executemany signatures {pull}2332[#2332] +* Fix asgi middleware distributed tracing {pull}2334[#2334] +* Fix typing of start in Span / capture_span to float {pull}2335[#2335] +* Fix azure instrumentation client_class and metrics sets invocation {pull}2337[#2337] +* Fix mysql_connector instrumentation connection retrieval {pull}2334[#2334] +* Remove spurious Django QuerySet evaluation in case of database errors {pull}2158[#2158] + [[release-notes-6.23.0]] ==== 6.23.0 - 2024-07-30 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 687ac5efb..348735fd8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -183,6 +183,7 @@ If you have commit access, the process is as follows: 1. Update the version in `elasticapm/version.py` according to the scale of the change. (major, minor or patch) 1. Update `CHANGELOG.asciidoc`. Rename the `Unreleased` section to the correct version (`vX.X.X`), and nest under the appropriate sub-heading, e.g., `Python Agent version 5.x`. +1. Update `docs/release-notes/`. 1. For Majors: [Create an issue](https://github.com/elastic/website-requests/issues/new) to request an update of the [EOL table](https://www.elastic.co/support/eol). 1. For Majors: Add the new major version to `conf.yaml` in the [elastic/docs](https://github.com/elastic/docs) repo. 1. Commit changes with message `update CHANGELOG and bump version to X.Y.Z` diff --git a/docs/release-notes/breaking-changes.md b/docs/release-notes/breaking-changes.md index 0b7fa989a..26a31362b 100644 --- a/docs/release-notes/breaking-changes.md +++ b/docs/release-notes/breaking-changes.md @@ -25,4 +25,4 @@ Before you upgrade, carefully review the Elastic APM RPython Agent breaking chan * Align `sanitize_field_names` config with the [cross-agent spec](https://github.com/elastic/apm/blob/3fa78e2a1eeea81c73c2e16e96dbf6b2e79f3c64/specs/agents/sanitization.md). If you are using a non-default `sanitize_field_names`, surrounding each of your entries with stars (e.g. `*secret*`) will retain the old behavior. For more information, check [#982](https://github.com/elastic/apm-agent-python/pull/982). * Remove credit card sanitization for field values. This improves performance, and the security value of this check was dubious anyway. For more information, check [#982](https://github.com/elastic/apm-agent-python/pull/982). * Remove HTTP querystring sanitization. This improves performance, and is meant to standardize behavior across the agents, as defined in [#334](https://github.com/elastic/apm/pull/334). For more information, check [#982](https://github.com/elastic/apm-agent-python/pull/982). -* Remove `elasticapm.tag()` (deprecated since 5.0.0). For more information, check [#1034](https://github.com/elastic/apm-agent-python/pull/1034). \ No newline at end of file +* Remove `elasticapm.tag()` (deprecated since 5.0.0). For more information, check [#1034](https://github.com/elastic/apm-agent-python/pull/1034). diff --git a/docs/release-notes/index.md b/docs/release-notes/index.md index 1d40b1596..edbe75561 100644 --- a/docs/release-notes/index.md +++ b/docs/release-notes/index.md @@ -20,6 +20,25 @@ To check for security updates, go to [Security announcements for the Elastic sta % ### Fixes [elastic-apm-python-agent-versionext-fixes] +## 6.24.0 [elastic-apm-python-agent-6240-release-notes] +**Release date:** August 12, 2025 + +### Features and enhancements [elastic-apm-python-agent-6240-features-enhancements] +* Add support for recent sanic versions [#2190](https://github.com/elastic/apm-agent-python/pull/2190), [#2194](https://github.com/elastic/apm-agent-python/pull/2194) +* Make server certificate verification mandatory in fips mode [#2227](https://github.com/elastic/apm-agent-python/pull/2227) +* Add support Python 3.13 [#2216](https://github.com/elastic/apm-agent-python/pull/2216) +* Add support for azure-data-tables package for azure instrumentation [#2187](https://github.com/elastic/apm-agent-python/pull/2187) +* Add span links from SNS messages [#2363](https://github.com/elastic/apm-agent-python/pull/2363) + +### Fixes [elastic-apm-python-agent-6240-fixes] +* Fix psycopg2 cursor execute and executemany signatures [#2331](https://github.com/elastic/apm-agent-python/pull/2331) +* Fix psycopg cursor execute and executemany signatures [#2332](https://github.com/elastic/apm-agent-python/pull/2332) +* Fix asgi middleware distributed tracing [#2334](https://github.com/elastic/apm-agent-python/pull/2334) +* Fix typing of start in Span / capture_span to float [#2335](https://github.com/elastic/apm-agent-python/pull/2335) +* Fix azure instrumentation client_class and metrics sets invocation [#2337](https://github.com/elastic/apm-agent-python/pull/2337) +* Fix mysql_connector instrumentation connection retrieval [#2334](https://github.com/elastic/apm-agent-python/pull/2334) +* Remove spurious Django QuerySet evaluation in case of database errors [#2158](https://github.com/elastic/apm-agent-python/pull/2158) + ## 6.23.0 [elastic-apm-python-agent-6230-release-notes] **Release date:** July 30, 2024 diff --git a/elasticapm/version.py b/elasticapm/version.py index e9eff8543..a0a2626df 100644 --- a/elasticapm/version.py +++ b/elasticapm/version.py @@ -28,5 +28,5 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = (6, 23, 0) +__version__ = (6, 24, 0) VERSION = ".".join(map(str, __version__)) From c40b30218b7179f888fd38e4c408c8ae62f5ccf5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 11:09:59 +0200 Subject: [PATCH 320/409] build(deps): bump the github-actions group across 1 directory with 2 updates (#2390) * build(deps): bump the github-actions group across 1 directory with 2 updates Bumps the github-actions group with 2 updates in the / directory: [docker/login-action](https://github.com/docker/login-action) and [actions/download-artifact](https://github.com/actions/download-artifact). Updates `docker/login-action` from 3.4.0 to 3.5.0 - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/74a5d142397b4f367a81961eba4e8cd7edddf772...184bdaa0721073962dff0199f1fb9940f07167d1) Updates `actions/download-artifact` from 4 to 5 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: 3.5.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: actions/download-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] * Upgrade MongoDB version from 4.0 to 4.2 for pymongo-newest environment * Set right pymongodata var for mongoDB 4.2 * Set right pymongodata var for mongoDB 4.2 * Undo upgrade changes because are done here https://github.com/elastic/apm-agent-python/pull/2391 --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: fr4nc1sc0.r4m0n --- .github/workflows/build-images.yml | 2 +- .github/workflows/release.yml | 10 +++++----- .github/workflows/test.yml | 2 +- .github/workflows/updatecli.yml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-images.yml b/.github/workflows/build-images.yml index 44dcb39be..c359d4e39 100644 --- a/.github/workflows/build-images.yml +++ b/.github/workflows/build-images.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/checkout@v4 - name: Login to ghcr.io - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1ea9f0a7f..9eb0358a9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -41,7 +41,7 @@ jobs: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: name: packages path: dist @@ -79,7 +79,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: name: build-distribution path: ./build @@ -122,13 +122,13 @@ jobs: uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - name: Log in to the Elastic Container registry - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 with: registry: ${{ secrets.ELASTIC_DOCKER_REGISTRY }} username: ${{ secrets.ELASTIC_DOCKER_USERNAME }} password: ${{ secrets.ELASTIC_DOCKER_PASSWORD }} - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: name: build-distribution path: ./build @@ -173,7 +173,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: name: arn-file - name: Create GitHub Draft Release diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4fc7a275e..8de3c2414 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -199,7 +199,7 @@ jobs: - run: python -Im pip install --upgrade coverage[toml] - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v5 with: pattern: coverage-reports-* merge-multiple: true diff --git a/.github/workflows/updatecli.yml b/.github/workflows/updatecli.yml index a1109f743..1b5a66d0c 100644 --- a/.github/workflows/updatecli.yml +++ b/.github/workflows/updatecli.yml @@ -29,7 +29,7 @@ jobs: "pull_requests": "write" } - - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 + - uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 with: registry: ghcr.io username: ${{ github.actor }} From 1210b84d87779b2a222f290f971b3f0478814e13 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 13 Aug 2025 11:02:18 +0200 Subject: [PATCH 321/409] docs: Fix typo in PR number for 6.24.0 changelog (#2396) --- CHANGELOG.asciidoc | 2 +- docs/release-notes/index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 63817c5cd..484269c89 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -52,7 +52,7 @@ endif::[] * Fix asgi middleware distributed tracing {pull}2334[#2334] * Fix typing of start in Span / capture_span to float {pull}2335[#2335] * Fix azure instrumentation client_class and metrics sets invocation {pull}2337[#2337] -* Fix mysql_connector instrumentation connection retrieval {pull}2334[#2334] +* Fix mysql_connector instrumentation connection retrieval {pull}2344[#2344] * Remove spurious Django QuerySet evaluation in case of database errors {pull}2158[#2158] [[release-notes-6.23.0]] diff --git a/docs/release-notes/index.md b/docs/release-notes/index.md index edbe75561..42c84cb88 100644 --- a/docs/release-notes/index.md +++ b/docs/release-notes/index.md @@ -36,7 +36,7 @@ To check for security updates, go to [Security announcements for the Elastic sta * Fix asgi middleware distributed tracing [#2334](https://github.com/elastic/apm-agent-python/pull/2334) * Fix typing of start in Span / capture_span to float [#2335](https://github.com/elastic/apm-agent-python/pull/2335) * Fix azure instrumentation client_class and metrics sets invocation [#2337](https://github.com/elastic/apm-agent-python/pull/2337) -* Fix mysql_connector instrumentation connection retrieval [#2334](https://github.com/elastic/apm-agent-python/pull/2334) +* Fix mysql_connector instrumentation connection retrieval [#2344](https://github.com/elastic/apm-agent-python/pull/2344) * Remove spurious Django QuerySet evaluation in case of database errors [#2158](https://github.com/elastic/apm-agent-python/pull/2158) ## 6.23.0 [elastic-apm-python-agent-6230-release-notes] From 54116d0be19546a21a015f490a50137f50756e79 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 13 Aug 2025 11:02:45 +0200 Subject: [PATCH 322/409] docs: silence warnings for thin spaces around emdashes (#2393) --- docs/reference/instrumenting-custom-code.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/instrumenting-custom-code.md b/docs/reference/instrumenting-custom-code.md index ad6afc8fb..851dee965 100644 --- a/docs/reference/instrumenting-custom-code.md +++ b/docs/reference/instrumenting-custom-code.md @@ -62,7 +62,7 @@ See [the API docs](/reference/api-reference.md#api-capture-span) for more inform It’s important to note that `elasticapm.capture_span` only works if there is an existing transaction. If you’re not using one of our [supported frameworks](/reference/set-up-apm-python-agent.md), you need to create a `Client` object and begin and end the transactions yourself. You can even utilize the agent’s [automatic instrumentation](/reference/supported-technologies.md#automatic-instrumentation)! -To collect the spans generated by the supported libraries, you need to invoke `elasticapm.instrument()` (just once, at the initialization stage of your application) and create at least one transaction. It is up to you to determine what you consider a transaction within your application — it can be the whole execution of the script or a part of it. +To collect the spans generated by the supported libraries, you need to invoke `elasticapm.instrument()` (just once, at the initialization stage of your application) and create at least one transaction. It is up to you to determine what you consider a transaction within your application — it can be the whole execution of the script or a part of it. The example below will consider the whole execution as a single transaction with two HTTP request spans in it. The config for `elasticapm.Client` can be passed in programmatically, and it will also utilize any config environment variables available to it automatically. @@ -85,7 +85,7 @@ if __name__ == '__main__': client.end_transaction(name=__name__, result="success") ``` -Note that you don’t need to do anything to send the data — the `Client` object will handle that before the script exits. Additionally, the `Client` object should be treated as a singleton — you should only create one instance and store/pass around that instance for all transaction handling. +Note that you don’t need to do anything to send the data — the `Client` object will handle that before the script exits. Additionally, the `Client` object should be treated as a singleton — you should only create one instance and store/pass around that instance for all transaction handling. ## Distributed Tracing [instrumenting-custom-code-distributed-tracing] From f65e1c60e9af5739bd396227d29499450b72a923 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 09:42:45 +0200 Subject: [PATCH 323/409] build(deps): bump wolfi/chainguard-base from `442a566` to `6613553` (#2398) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `442a566` to `6613553`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index eca385f37..9e5ac8abf 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:442a5663000b3d66d565e61d400b30a4638383a72d90494cfc3104b34dfb3211 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:661355310ab9ecc86082d247341881eb272853eb8fe4c91e53e02f0f2e30fb2c ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From c29336cd10c86e72c3a1f5c8c2a47e208e0d4a92 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 14:16:17 +0200 Subject: [PATCH 324/409] build(deps): bump wolfi/chainguard-base from `6613553` to `9161f40` (#2404) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `6613553` to `9161f40`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 9e5ac8abf..80682ddf4 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:661355310ab9ecc86082d247341881eb272853eb8fe4c91e53e02f0f2e30fb2c +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:9161f40d9690ad840d262521328452691c342da8f9aa6573c07a87dd59950bc9 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 6dc41aed28e34bc4280d683e544218627bc6075c Mon Sep 17 00:00:00 2001 From: Francisco Ramon Date: Mon, 25 Aug 2025 11:50:48 +0200 Subject: [PATCH 325/409] Add flag to skip build-distrubution execution step when release (#2397) * Add flag to skip build-distrubution execution step when triggered from a release tag event * Remove previous build-distribution step from release.yml --- .github/workflows/release.yml | 30 ++++++++++++++++-------------- .github/workflows/test-release.yml | 11 +++++++++++ .github/workflows/test.yml | 11 +++++++++++ 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9eb0358a9..2f2f4d2f8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,11 +11,27 @@ permissions: contents: read jobs: + build-distribution: + permissions: + attestations: write + id-token: write + contents: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/build-distribution + - name: generate build provenance + uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0 + with: + subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" + test: uses: ./.github/workflows/test-release.yml + needs: build-distribution with: full-matrix: true enabled: ${{ startsWith(github.ref, 'refs/tags') }} + skip-build: true packages: permissions: @@ -56,20 +72,6 @@ jobs: with: repository-url: https://test.pypi.org/legacy/ - build-distribution: - permissions: - attestations: write - id-token: write - contents: write - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/build-distribution - - name: generate build provenance - uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0 - with: - subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" - publish-lambda-layers: permissions: contents: read diff --git a/.github/workflows/test-release.yml b/.github/workflows/test-release.yml index c873f9eb7..43812eb94 100644 --- a/.github/workflows/test-release.yml +++ b/.github/workflows/test-release.yml @@ -15,6 +15,11 @@ on: description: "Whether to run the workfow" required: true type: boolean + skip-build: + description: "Skip the build distribution step" + required: false + type: boolean + default: false workflow_dispatch: inputs: full-matrix: @@ -25,6 +30,11 @@ on: description: "Whether to run the workfow" required: true type: boolean + skip-build: + description: "Skip the build distribution step" + required: false + type: boolean + default: false jobs: test: @@ -32,6 +42,7 @@ jobs: uses: ./.github/workflows/test.yml with: full-matrix: ${{ inputs.full-matrix }} + skip-build: ${{ inputs.skip-build }} run-if-disabled: if: ${{ ! inputs.enabled }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8de3c2414..a7d25aa1d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,6 +11,11 @@ on: description: "The git ref of elastic/apm-agent-python to run test workflow from." required: false type: string + skip-build: + description: "Skip the build distribution step" + required: false + type: boolean + default: false pull_request: paths-ignore: - "**/*.md" @@ -29,12 +34,18 @@ on: description: "Run the full matrix" required: true type: boolean + skip-build: + description: "Skip the build distribution step" + required: false + type: boolean + default: false permissions: contents: read jobs: build-distribution: + if: ${{ !inputs.skip-build }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 92c4b600daf294e35f7524f7cb9e52012981426b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 09:37:39 +0200 Subject: [PATCH 326/409] build(deps): bump wolfi/chainguard-base from `9161f40` to `ec4dd3c` (#2408) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `9161f40` to `ec4dd3c`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 80682ddf4..efa6c689d 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:9161f40d9690ad840d262521328452691c342da8f9aa6573c07a87dd59950bc9 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:ec4dd3c3f9c1a1174e3705f841f8fd8e31e3749c727493cabe24f549d1dcb121 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From e14b178fa898762b889af82e6e8375267fd84479 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 18:29:47 +0200 Subject: [PATCH 327/409] build(deps): bump actions/checkout (#2401) Bumps the github-actions group with 1 update in the / directory: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 4 to 5 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-images.yml | 2 +- .github/workflows/packages.yml | 2 +- .github/workflows/release.yml | 12 ++++++------ .github/workflows/run-matrix.yml | 2 +- .github/workflows/test-fips.yml | 4 ++-- .github/workflows/test.yml | 8 ++++---- .github/workflows/updatecli.yml | 2 +- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-images.yml b/.github/workflows/build-images.yml index c359d4e39..100904eb2 100644 --- a/.github/workflows/build-images.yml +++ b/.github/workflows/build-images.yml @@ -19,7 +19,7 @@ jobs: IMAGE_NAME: ${{ github.repository }}/apm-agent-python-testing steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Login to ghcr.io uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 496107508..974ca3d8f 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -20,5 +20,5 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: ./.github/actions/packages diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2f2f4d2f8..0e661ed9b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: contents: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: ./.github/actions/build-distribution - name: generate build provenance uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0 @@ -40,7 +40,7 @@ jobs: contents: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: ./.github/actions/packages - name: generate build provenance uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0 @@ -56,7 +56,7 @@ jobs: permissions: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/download-artifact@v5 with: name: packages @@ -80,7 +80,7 @@ jobs: - build-distribution runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/download-artifact@v5 with: name: build-distribution @@ -118,7 +118,7 @@ jobs: env: DOCKER_IMAGE_NAME: docker.elastic.co/observability/apm-agent-python steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Set up Docker Buildx uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 @@ -174,7 +174,7 @@ jobs: if: startsWith(github.ref, 'refs/tags') runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/download-artifact@v5 with: name: arn-file diff --git a/.github/workflows/run-matrix.yml b/.github/workflows/run-matrix.yml index 3dc3befb7..adf148f03 100644 --- a/.github/workflows/run-matrix.yml +++ b/.github/workflows/run-matrix.yml @@ -25,7 +25,7 @@ jobs: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }}/apm-agent-python-testing steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Run tests run: ./tests/scripts/docker/run_tests.sh ${{ matrix.version }} ${{ matrix.framework }} env: diff --git a/.github/workflows/test-fips.yml b/.github/workflows/test-fips.yml index 3712f00d0..ca387979a 100644 --- a/.github/workflows/test-fips.yml +++ b/.github/workflows/test-fips.yml @@ -16,7 +16,7 @@ jobs: outputs: matrix: ${{ steps.generate.outputs.matrix }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - id: generate uses: elastic/oblt-actions/version-framework@v1 with: @@ -40,7 +40,7 @@ jobs: max-parallel: 10 matrix: ${{ fromJSON(needs.create-matrix.outputs.matrix) }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: check that python has fips mode enabled run: | python3 -c 'import _hashlib; assert _hashlib.get_fips_mode() == 1' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a7d25aa1d..2e3daf093 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,7 +48,7 @@ jobs: if: ${{ !inputs.skip-build }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: ./.github/actions/build-distribution @@ -59,7 +59,7 @@ jobs: data: ${{ steps.split.outputs.data }} chunks: ${{ steps.split.outputs.chunks }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: ref: ${{ inputs.ref || github.ref }} - id: generate @@ -142,7 +142,7 @@ jobs: FRAMEWORK: ${{ matrix.framework }} ASYNCIO: ${{ matrix.asyncio }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: ref: ${{ inputs.ref || github.ref }} - uses: actions/setup-python@v5 @@ -199,7 +199,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: ref: ${{ inputs.ref || github.ref }} diff --git a/.github/workflows/updatecli.yml b/.github/workflows/updatecli.yml index 1b5a66d0c..6f9b0c15d 100644 --- a/.github/workflows/updatecli.yml +++ b/.github/workflows/updatecli.yml @@ -15,7 +15,7 @@ jobs: contents: read packages: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Get token id: get_token From adf18bf9229b94173bdf373e0cf5093e2ae90adf Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 09:52:50 +0200 Subject: [PATCH 328/409] chore: deps(updatecli): Bump updatecli version to v0.106.0 (#2409) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index a4187dc33..22835bce3 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.105.1 \ No newline at end of file +updatecli v0.106.0 \ No newline at end of file From 741c689c46dd36f952790f6294fa6777e45b8b0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 13:27:55 +0000 Subject: [PATCH 329/409] build(deps): bump wolfi/chainguard-base from `ec4dd3c` to `ed432bb` (#2411) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `ec4dd3c` to `ed432bb`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index efa6c689d..2cf67abe2 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:ec4dd3c3f9c1a1174e3705f841f8fd8e31e3749c727493cabe24f549d1dcb121 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:ed432bb5128cbeccfdcc6cbe204658d099e798cb4fb669e6b01d408c4d88f297 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From df251febc86da300a3a45c0cea9b4c1457ee5b29 Mon Sep 17 00:00:00 2001 From: Colleen McGinnis Date: Fri, 29 Aug 2025 08:31:43 -0500 Subject: [PATCH 330/409] replace placeholder URLs (#2407) --- docs/reference/sanic-support.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/sanic-support.md b/docs/reference/sanic-support.md index 515bf6837..7b0ea5b3c 100644 --- a/docs/reference/sanic-support.md +++ b/docs/reference/sanic-support.md @@ -42,7 +42,7 @@ To configure the agent using initialization arguments and Sanic’s Configuratio ```python # Create a file named external_config.py in your application # If you want this module based configuration to be used for APM, prefix them with ELASTIC_APM_ -ELASTIC_APM_SERVER_URL = "https://serverurl.apm.com:443" +ELASTIC_APM_SERVER_URL = "https://serverurl.example.com:443" ELASTIC_APM_SECRET_TOKEN = "sometoken" ``` From 7288a75d813efc32e3c07c753de67dcce5d05dee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 10:37:00 +0200 Subject: [PATCH 331/409] build(deps): bump actions/attest-build-provenance from 2.4.0 to 3.0.0 in the github-actions group across 1 directory (#2416) * build(deps): bump actions/attest-build-provenance Bumps the github-actions group with 1 update: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance). Updates `actions/attest-build-provenance` from 2.4.0 to 3.0.0 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/e8998f949152b193b063cb0ec769d69d929409be...977bb373ede98d70efdf65b84cb5f73e068dcc2a) --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0e661ed9b..9d7617267 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/checkout@v5 - uses: ./.github/actions/build-distribution - name: generate build provenance - uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0 + uses: actions/attest-build-provenance@v3 with: subject-path: "${{ github.workspace }}/build/dist/elastic-apm-python-lambda-layer.zip" @@ -43,7 +43,7 @@ jobs: - uses: actions/checkout@v5 - uses: ./.github/actions/packages - name: generate build provenance - uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0 + uses: actions/attest-build-provenance@v3 with: subject-path: "${{ github.workspace }}/dist/*" @@ -160,7 +160,7 @@ jobs: AGENT_DIR=./build/dist/package/python - name: generate build provenance (containers) - uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0 + uses: actions/attest-build-provenance@v3 with: subject-name: "${{ env.DOCKER_IMAGE_NAME }}" subject-digest: ${{ steps.docker-push.outputs.digest }} From c44a073b799e3fbd04f2d9ef3a8f90d82e4ea475 Mon Sep 17 00:00:00 2001 From: Fabrizio Ferri-Benedetti Date: Fri, 5 Sep 2025 19:08:07 +0200 Subject: [PATCH 332/409] Add applies_to metadata (#2414) * Add applies_to metadata * Remove products * Update docs/reference/wrapper-support.md Co-authored-by: Colleen McGinnis * Update docs/reference/asgi-middleware.md Co-authored-by: Colleen McGinnis * Update docs/reference/lambda-support.md Co-authored-by: Colleen McGinnis * Update docs/release-notes/known-issues.md Co-authored-by: Colleen McGinnis --------- Co-authored-by: Colleen McGinnis --- docs/reference/advanced-topics.md | 6 ++ docs/reference/aiohttp-server-support.md | 6 ++ docs/reference/api-reference.md | 94 ++++++++++++++++----- docs/reference/asgi-middleware.md | 6 ++ docs/reference/azure-functions-support.md | 6 ++ docs/reference/configuration.md | 6 ++ docs/reference/django-support.md | 6 ++ docs/reference/flask-support.md | 6 ++ docs/reference/how-agent-works.md | 6 ++ docs/reference/index.md | 6 ++ docs/reference/instrumenting-custom-code.md | 6 ++ docs/reference/lambda-support.md | 6 ++ docs/reference/logs.md | 6 ++ docs/reference/metrics.md | 6 ++ docs/reference/opentelemetry-api-bridge.md | 6 ++ docs/reference/performance-tuning.md | 6 ++ docs/reference/run-tests-locally.md | 6 ++ docs/reference/sanic-support.md | 6 ++ docs/reference/sanitizing-data.md | 6 ++ docs/reference/set-up-apm-python-agent.md | 6 ++ docs/reference/starlette-support.md | 6 ++ docs/reference/supported-technologies.md | 6 ++ docs/reference/tornado-support.md | 6 ++ docs/reference/upgrading-4-x.md | 6 ++ docs/reference/upgrading-5-x.md | 6 ++ docs/reference/upgrading-6-x.md | 6 ++ docs/reference/upgrading.md | 6 ++ docs/reference/wrapper-support.md | 6 ++ docs/release-notes/breaking-changes.md | 7 ++ docs/release-notes/deprecations.md | 6 ++ docs/release-notes/index.md | 6 ++ docs/release-notes/known-issues.md | 6 ++ 32 files changed, 260 insertions(+), 21 deletions(-) diff --git a/docs/reference/advanced-topics.md b/docs/reference/advanced-topics.md index aade7f2df..1f251c886 100644 --- a/docs/reference/advanced-topics.md +++ b/docs/reference/advanced-topics.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/advanced-topics.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Advanced topics [advanced-topics] diff --git a/docs/reference/aiohttp-server-support.md b/docs/reference/aiohttp-server-support.md index fcde2cdab..77e8c25e3 100644 --- a/docs/reference/aiohttp-server-support.md +++ b/docs/reference/aiohttp-server-support.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/aiohttp-server-support.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Aiohttp Server support [aiohttp-server-support] diff --git a/docs/reference/api-reference.md b/docs/reference/api-reference.md index 63ddbb58f..aba676e80 100644 --- a/docs/reference/api-reference.md +++ b/docs/reference/api-reference.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/api.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # API reference [api] @@ -15,7 +21,9 @@ The public Client API consists of several methods on the `Client` class. This AP ### Instantiation [client-api-init] -Added in v1.0.0. +```{applies_to} +apm_agent_python: ga 1.0.0 +``` To create a `Client` instance, import it and call its constructor: @@ -36,7 +44,9 @@ framework integrations like [Django](/reference/django-support.md) and [Flask](/ #### `elasticapm.get_client()` [api-get-client] -[small]#Added in v6.1.0. +```{applies_to} +apm_agent_python: ga 6.1.0 +``` Retrieves the `Client` singleton. This is useful for many framework integrations, where the client is instantiated automatically. @@ -51,7 +61,11 @@ client.capture_message('foo') #### `Client.capture_exception()` [client-api-capture-exception] -Added in v1.0.0. `handled` added in v2.0.0. +```{applies_to} +apm_agent_python: ga 1.0.0 +``` + +`handled` added in v2.0.0. Captures an exception object: @@ -73,7 +87,9 @@ Returns the id of the error as a string. #### `Client.capture_message()` [client-api-capture-message] -Added in v1.0.0. +```{applies_to} +apm_agent_python: ga 1.0.0 +``` Captures a message with optional added contextual data. Example: @@ -110,7 +126,11 @@ Either the `message` or the `param_message` argument is required. #### `Client.begin_transaction()` [client-api-begin-transaction] -Added in v1.0.0. `trace_parent` support added in v5.6.0. +```{applies_to} +apm_agent_python: ga 1.0.0 +``` + +`trace_parent` support added in v5.6.0. Begin tracking a transaction. Should be called e.g. at the beginning of a request or when starting a background task. Example: @@ -125,7 +145,9 @@ client.begin_transaction('processors') #### `Client.end_transaction()` [client-api-end-transaction] -Added in v1.0.0. +```{applies_to} +apm_agent_python: ga 1.0.0 +``` End tracking the transaction. Should be called e.g. at the end of a request or when ending a background task. Example: @@ -149,7 +171,9 @@ Transactions can be started with a `TraceParent` object. This creates a transact #### `elasticapm.trace_parent_from_string()` [api-traceparent-from-string] -Added in v5.6.0. +```{applies_to} +apm_agent_python: ga 5.6.0 +``` Create a `TraceParent` object from the string representation generated by `TraceParent.to_string()`: @@ -163,7 +187,9 @@ client.begin_transaction('processors', trace_parent=parent) #### `elasticapm.trace_parent_from_headers()` [api-traceparent-from-headers] -Added in v5.6.0. +```{applies_to} +apm_agent_python: ga 5.6.0 +``` Create a `TraceParent` object from HTTP headers (usually generated by another Elastic APM agent): @@ -177,7 +203,9 @@ client.begin_transaction('processors', trace_parent=parent) #### `elasticapm.get_trace_parent_header()` [api-traceparent-get-header] -Added in v5.10.0. +```{applies_to} +apm_agent_python: ga 5.10.0 +``` Return the string representation of the current transaction `TraceParent` object: @@ -191,7 +219,9 @@ elasticapm.get_trace_parent_header() ### `elasticapm.instrument()` [api-elasticapm-instrument] -Added in v1.0.0. +```{applies_to} +apm_agent_python: ga 1.0.0 +``` Instruments libraries automatically. This includes a wide range of standard library and 3rd party modules. A list of instrumented modules can be found in `elasticapm.instrumentation.register`. This function should be called as early as possibly in the startup of your application. For [supported frameworks](/reference/supported-technologies.md#framework-support), this is called automatically. Example: @@ -204,7 +234,9 @@ elasticapm.instrument() ### `elasticapm.set_transaction_name()` [api-set-transaction-name] -Added in v1.0.0. +```{applies_to} +apm_agent_python: ga 1.0.0 +``` Set the name of the current transaction. For supported frameworks, the transaction name is determined automatically, and can be overridden using this function. Example: @@ -220,7 +252,9 @@ elasticapm.set_transaction_name('myapp.billing_process') ### `elasticapm.set_transaction_result()` [api-set-transaction-result] -Added in v2.2.0. +```{applies_to} +apm_agent_python: ga 2.2.0 +``` Set the result of the current transaction. For supported frameworks, the transaction result is determined automatically, and can be overridden using this function. Example: @@ -236,7 +270,9 @@ elasticapm.set_transaction_result('SUCCESS') ### `elasticapm.set_transaction_outcome()` [api-set-transaction-outcome] -Added in v5.9.0. +```{applies_to} +apm_agent_python: ga 5.9.0 +``` Sets the outcome of the transaction. The value can either be `"success"`, `"failure"` or `"unknown"`. This should only be called at the end of a transaction after the outcome is determined. @@ -277,7 +313,9 @@ elasticapm.set_transaction_outcome(OUTCOME.UNKNOWN) ### `elasticapm.get_transaction_id()` [api-get-transaction-id] -Added in v5.2.0. +```{applies_to} +apm_agent_python: ga 5.2.0 +``` Get the id of the current transaction. Example: @@ -290,7 +328,9 @@ transaction_id = elasticapm.get_transaction_id() ### `elasticapm.get_trace_id()` [api-get-trace-id] -Added in v5.2.0. +```{applies_to} +apm_agent_python: ga 5.2.0 +``` Get the `trace_id` of the current transaction’s trace. Example: @@ -303,7 +343,9 @@ trace_id = elasticapm.get_trace_id() ### `elasticapm.get_span_id()` [api-get-span-id] -Added in v5.2.0. +```{applies_to} +apm_agent_python: ga 5.2.0 +``` Get the id of the current span. Example: @@ -316,7 +358,9 @@ span_id = elasticapm.get_span_id() ### `elasticapm.set_custom_context()` [api-set-custom-context] -Added in v2.0.0. +```{applies_to} +apm_agent_python: ga 2.0.0 +``` Attach custom contextual data to the current transaction and errors. Supported frameworks will automatically attach information about the HTTP request and the logged in user. You can attach further data using this function. @@ -345,7 +389,9 @@ Errors that happen after this call will also have the custom context attached to ### `elasticapm.set_user_context()` [api-set-user-context] -Added in v2.0.0. +```{applies_to} +apm_agent_python: ga 2.0.0 +``` Attach information about the currently logged in user to the current transaction and errors. Example: @@ -364,7 +410,9 @@ Errors that happen after this call will also have the user context attached to t ### `elasticapm.capture_span` [api-capture-span] -Added in v4.1.0. +```{applies_to} +apm_agent_python: ga 4.1.0 +``` Capture a custom span. This can be used either as a function decorator or as a context manager (in a `with` statement). When used as a decorator, the name of the span will be set to the name of the function. When used as a context manager, a name has to be provided. @@ -397,7 +445,9 @@ def coffee_maker(strength): ### `elasticapm.async_capture_span` [api-async-capture-span] -Added in v5.4.0. +```{applies_to} +apm_agent_python: ga 5.4.0 +``` Capture a custom async-aware span. This can be used either as a function decorator or as a context manager (in an `async with` statement). When used as a decorator, the name of the span will be set to the name of the function. When used as a context manager, a name has to be provided. @@ -435,7 +485,9 @@ async def coffee_maker(strength): ### `elasticapm.label()` [api-label] -Added in v5.0.0. +```{applies_to} +apm_agent_python: ga 5.0.0 +``` Attach labels to the the current transaction and errors. diff --git a/docs/reference/asgi-middleware.md b/docs/reference/asgi-middleware.md index 852f12565..ef33234ec 100644 --- a/docs/reference/asgi-middleware.md +++ b/docs/reference/asgi-middleware.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/asgi-middleware.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: preview --- # ASGI Middleware [asgi-middleware] diff --git a/docs/reference/azure-functions-support.md b/docs/reference/azure-functions-support.md index 44a62416f..a2571bb6e 100644 --- a/docs/reference/azure-functions-support.md +++ b/docs/reference/azure-functions-support.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/azure-functions-support.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Monitoring Azure Functions [azure-functions-support] diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index d95df439b..5934f3b6e 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/configuration.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Configuration [configuration] diff --git a/docs/reference/django-support.md b/docs/reference/django-support.md index 37b1fe3bd..61e5991ae 100644 --- a/docs/reference/django-support.md +++ b/docs/reference/django-support.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/django-support.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Django support [django-support] diff --git a/docs/reference/flask-support.md b/docs/reference/flask-support.md index 3d9fa0ad4..06f29712c 100644 --- a/docs/reference/flask-support.md +++ b/docs/reference/flask-support.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/flask-support.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Flask support [flask-support] diff --git a/docs/reference/how-agent-works.md b/docs/reference/how-agent-works.md index 6c0f597ca..d6d6f447c 100644 --- a/docs/reference/how-agent-works.md +++ b/docs/reference/how-agent-works.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/how-the-agent-works.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # How the Agent works [how-the-agent-works] diff --git a/docs/reference/index.md b/docs/reference/index.md index 9e7eed840..e92d5fff1 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -2,6 +2,12 @@ mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/getting-started.html - https://www.elastic.co/guide/en/apm/agent/python/current/index.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # APM Python agent [getting-started] diff --git a/docs/reference/instrumenting-custom-code.md b/docs/reference/instrumenting-custom-code.md index 851dee965..43b7ff14b 100644 --- a/docs/reference/instrumenting-custom-code.md +++ b/docs/reference/instrumenting-custom-code.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/instrumenting-custom-code.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Instrumenting custom code [instrumenting-custom-code] diff --git a/docs/reference/lambda-support.md b/docs/reference/lambda-support.md index 6d6799287..ada302cd6 100644 --- a/docs/reference/lambda-support.md +++ b/docs/reference/lambda-support.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/lambda-support.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga sub: apm-lambda-ext-v: ver-1-5-7 apm-python-v: ver-6-23-0 diff --git a/docs/reference/logs.md b/docs/reference/logs.md index 18a4f0803..22fbde3a3 100644 --- a/docs/reference/logs.md +++ b/docs/reference/logs.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/logs.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Logs [logs] diff --git a/docs/reference/metrics.md b/docs/reference/metrics.md index 4f1ff2b27..af4222c0b 100644 --- a/docs/reference/metrics.md +++ b/docs/reference/metrics.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/metrics.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Metrics [metrics] diff --git a/docs/reference/opentelemetry-api-bridge.md b/docs/reference/opentelemetry-api-bridge.md index 1d7c3e6f9..704d86ab5 100644 --- a/docs/reference/opentelemetry-api-bridge.md +++ b/docs/reference/opentelemetry-api-bridge.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/opentelemetry-bridge.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # OpenTelemetry API Bridge [opentelemetry-bridge] diff --git a/docs/reference/performance-tuning.md b/docs/reference/performance-tuning.md index 04ce10e64..6ff228baf 100644 --- a/docs/reference/performance-tuning.md +++ b/docs/reference/performance-tuning.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/tuning-and-overhead.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Performance tuning [tuning-and-overhead] diff --git a/docs/reference/run-tests-locally.md b/docs/reference/run-tests-locally.md index f72432d7e..758209217 100644 --- a/docs/reference/run-tests-locally.md +++ b/docs/reference/run-tests-locally.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/run-tests-locally.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Run Tests Locally [run-tests-locally] diff --git a/docs/reference/sanic-support.md b/docs/reference/sanic-support.md index 7b0ea5b3c..953527a22 100644 --- a/docs/reference/sanic-support.md +++ b/docs/reference/sanic-support.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/sanic-support.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Sanic Support [sanic-support] diff --git a/docs/reference/sanitizing-data.md b/docs/reference/sanitizing-data.md index b41d89148..5797e427c 100644 --- a/docs/reference/sanitizing-data.md +++ b/docs/reference/sanitizing-data.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/sanitizing-data.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Sanitizing data [sanitizing-data] diff --git a/docs/reference/set-up-apm-python-agent.md b/docs/reference/set-up-apm-python-agent.md index a74e45208..8ed82bc59 100644 --- a/docs/reference/set-up-apm-python-agent.md +++ b/docs/reference/set-up-apm-python-agent.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/set-up.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Set up the APM Python Agent [set-up] diff --git a/docs/reference/starlette-support.md b/docs/reference/starlette-support.md index 673ab79db..fcffa08f3 100644 --- a/docs/reference/starlette-support.md +++ b/docs/reference/starlette-support.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/starlette-support.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Starlette/FastAPI Support [starlette-support] diff --git a/docs/reference/supported-technologies.md b/docs/reference/supported-technologies.md index ad768b2cb..437928d6f 100644 --- a/docs/reference/supported-technologies.md +++ b/docs/reference/supported-technologies.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/supported-technologies.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Supported technologies [supported-technologies] diff --git a/docs/reference/tornado-support.md b/docs/reference/tornado-support.md index bae66762b..6a7530b95 100644 --- a/docs/reference/tornado-support.md +++ b/docs/reference/tornado-support.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/tornado-support.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Tornado Support [tornado-support] diff --git a/docs/reference/upgrading-4-x.md b/docs/reference/upgrading-4-x.md index 9a1cbdbd6..40ddc547b 100644 --- a/docs/reference/upgrading-4-x.md +++ b/docs/reference/upgrading-4-x.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/upgrading-4.x.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Upgrading to version 4 of the agent [upgrading-4-x] diff --git a/docs/reference/upgrading-5-x.md b/docs/reference/upgrading-5-x.md index b124841dd..cabf9616a 100644 --- a/docs/reference/upgrading-5-x.md +++ b/docs/reference/upgrading-5-x.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/upgrading-5.x.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Upgrading to version 5 of the agent [upgrading-5-x] diff --git a/docs/reference/upgrading-6-x.md b/docs/reference/upgrading-6-x.md index 36b8a3393..df1c59acd 100644 --- a/docs/reference/upgrading-6-x.md +++ b/docs/reference/upgrading-6-x.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/upgrading-6.x.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Upgrading to version 6 of the agent [upgrading-6-x] diff --git a/docs/reference/upgrading.md b/docs/reference/upgrading.md index 83ae39902..28f424b99 100644 --- a/docs/reference/upgrading.md +++ b/docs/reference/upgrading.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/upgrading.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Upgrading [upgrading] diff --git a/docs/reference/wrapper-support.md b/docs/reference/wrapper-support.md index a8f01bbbc..db054a598 100644 --- a/docs/reference/wrapper-support.md +++ b/docs/reference/wrapper-support.md @@ -1,6 +1,12 @@ --- mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/wrapper-support.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: preview --- # Wrapper Support [wrapper-support] diff --git a/docs/release-notes/breaking-changes.md b/docs/release-notes/breaking-changes.md index 26a31362b..bcf8c5410 100644 --- a/docs/release-notes/breaking-changes.md +++ b/docs/release-notes/breaking-changes.md @@ -1,8 +1,15 @@ --- navigation_title: "Breaking changes" +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Elastic APM Python Agent breaking changes [elastic-apm-python-agent-breaking-changes] + Before you upgrade, carefully review the Elastic APM RPython Agent breaking changes and take the necessary steps to mitigate any issues. % To learn how to upgrade, check out . diff --git a/docs/release-notes/deprecations.md b/docs/release-notes/deprecations.md index 6efab6863..b2c14f742 100644 --- a/docs/release-notes/deprecations.md +++ b/docs/release-notes/deprecations.md @@ -1,5 +1,11 @@ --- navigation_title: "Deprecations" +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Elastic APM Python Agent deprecations [elastic-apm-python-agent-deprecations] diff --git a/docs/release-notes/index.md b/docs/release-notes/index.md index 42c84cb88..0424a4b5c 100644 --- a/docs/release-notes/index.md +++ b/docs/release-notes/index.md @@ -3,6 +3,12 @@ navigation_title: "Elastic APM Python Agent" mapped_pages: - https://www.elastic.co/guide/en/apm/agent/python/current/release-notes-6.x.html - https://www.elastic.co/guide/en/apm/agent/python/current/release-notes.html +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Elastic APM Python Agent release notes [elastic-apm-python-agent-release-notes] diff --git a/docs/release-notes/known-issues.md b/docs/release-notes/known-issues.md index cc6f71b04..4972fdbc9 100644 --- a/docs/release-notes/known-issues.md +++ b/docs/release-notes/known-issues.md @@ -1,5 +1,11 @@ --- navigation_title: "Known issues" +applies_to: + stack: + serverless: + observability: + product: + apm_agent_python: ga --- # Elastic APM Python Agent known issues [elastic-apm-python-agent-known-issues] From e0acba24143bc0f83f90091fc4a6161537ee21ad Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 15:50:22 +0200 Subject: [PATCH 333/409] build(deps): bump wolfi/chainguard-base from `ed432bb` to `bb3bb94` (#2418) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `ed432bb` to `bb3bb94`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 2cf67abe2..33afbaaaf 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:ed432bb5128cbeccfdcc6cbe204658d099e798cb4fb669e6b01d408c4d88f297 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:bb3bb9491f564296956a879033da287961829c431fc8dc68222c2b60f9bfa052 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 43f82599bbec327b9f400e9cd7fc61b04677ac44 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 15:50:40 +0200 Subject: [PATCH 334/409] chore: deps(updatecli): Bump updatecli version to v0.107.0 (#2419) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 22835bce3..ca67f6862 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.106.0 \ No newline at end of file +updatecli v0.107.0 \ No newline at end of file From 4c54cbad910cd30968dd7947fce1049299be3d35 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 10 Sep 2025 12:10:41 +0200 Subject: [PATCH 335/409] tests/docker-compose.yml: move off bitnami kafka image (#2412) * tests/docker-compose.yml: move off bitnami kafka image Bitnami images will be gone shortly. This lets us also drop the zookeeper service since newer kafka works in KRaft mode. * Fix kafka volume mapped data dir * remove unneeded environment variable for kafka * Try using 3.9.1 * try 4.1.0 * Steal kafka configuration from apm-nodejs --- tests/docker-compose.yml | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index 09010f1bf..9b8e06da4 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -164,25 +164,23 @@ services: - "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack" - "/var/run/docker.sock:/var/run/docker.sock" - zookeeper: - image: docker.io/bitnami/zookeeper:3.8 - ports: - - "2181:2181" - volumes: - - "zookeeper_data:/bitnami" - environment: - - ALLOW_ANONYMOUS_LOGIN=yes kafka: - image: docker.io/bitnami/kafka:3.1 + image: apache/kafka:4.1.0 ports: - "9092:9092" - volumes: - - "kafka_data:/bitnami" environment: - - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 - - ALLOW_PLAINTEXT_LISTENER=yes - depends_on: - - zookeeper + - "KAFKA_NODE_ID=1" + - "KAFKA_PROCESS_ROLES=broker,controller" + - "KAFKA_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093" + - "KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092" + - "KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT" + - "KAFKA_CONTROLLER_QUORUM_VOTERS=1@localhost:9093" + - "KAFKA_CONTROLLER_LISTENER_NAMES=CONTROLLER" + - "KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1" + - "KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR=1" + - "KAFKA_TRANSACTION_STATE_LOG_MIN_ISR=1" + volumes: + - "kafka_data:/var/lib/kafka/data" run_tests: image: ${REGISTRY:-elasticobservability}/${IMAGE_NAME:-apm-agent-python-testing}:${PYTHON_VERSION} @@ -223,8 +221,6 @@ volumes: driver: local mysqldata: driver: local - zookeeper_data: - driver: local kafka_data: driver: local localstack_data: From 64a4eb40bd2beaf54f39d1adc102621f4658e668 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 14:00:26 +0200 Subject: [PATCH 336/409] build(deps): bump wolfi/chainguard-base from `bb3bb94` to `1bb3aa7` (#2423) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `bb3bb94` to `1bb3aa7`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 33afbaaaf..7f2ba4956 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:bb3bb9491f564296956a879033da287961829c431fc8dc68222c2b60f9bfa052 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:1bb3aa73b5c4040c76fff79735aa8183dea2a03358956295e766e7c4989c59c1 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 30ed6480fe2fe905675e8acfd2968b30b28b705f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Sep 2025 14:01:34 +0200 Subject: [PATCH 337/409] build(deps): bump wolfi/chainguard-base from `1bb3aa7` to `ec17277` (#2425) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `1bb3aa7` to `ec17277`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 7f2ba4956..cdf0e606a 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:1bb3aa73b5c4040c76fff79735aa8183dea2a03358956295e766e7c4989c59c1 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:ec172778d432abfcd18641ec25270b2fff7c22ff9956d44c495860cdb0477357 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 117bc3a03debc2c62125e9e856e49e6a121404ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 09:36:47 +0200 Subject: [PATCH 338/409] build(deps): bump the github-actions group across 3 directories with 3 updates (#2421) Bumps the github-actions group with 3 updates in the / directory: [actions/github-script](https://github.com/actions/github-script), [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) and [actions/setup-python](https://github.com/actions/setup-python). Bumps the github-actions group with 1 update in the /.github/actions/build-distribution directory: [actions/setup-python](https://github.com/actions/setup-python). Bumps the github-actions group with 1 update in the /.github/actions/packages directory: [actions/setup-python](https://github.com/actions/setup-python). Updates `actions/github-script` from 7 to 8 - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v7...v8) Updates `pypa/gh-action-pypi-publish` from 1.12.4 to 1.13.0 - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/76f52bc884231f62b9a034ebfe128415bbaabdfc...ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e) Updates `actions/setup-python` from 5 to 6 - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) Updates `actions/setup-python` from 5 to 6 - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) Updates `actions/setup-python` from 5 to 6 - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: pypa/gh-action-pypi-publish dependency-version: 1.13.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/build-distribution/action.yml | 2 +- .github/actions/packages/action.yml | 2 +- .github/workflows/matrix-command.yml | 2 +- .github/workflows/release.yml | 4 ++-- .github/workflows/test.yml | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/actions/build-distribution/action.yml b/.github/actions/build-distribution/action.yml index bc0d55c29..41d3dea01 100644 --- a/.github/actions/build-distribution/action.yml +++ b/.github/actions/build-distribution/action.yml @@ -6,7 +6,7 @@ description: Run the build distribution runs: using: "composite" steps: - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: "3.10" diff --git a/.github/actions/packages/action.yml b/.github/actions/packages/action.yml index 871f49c32..e2daa0882 100644 --- a/.github/actions/packages/action.yml +++ b/.github/actions/packages/action.yml @@ -6,7 +6,7 @@ description: Run the packages runs: using: "composite" steps: - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: "3.10" - name: Override the version if there is no tag release. diff --git a/.github/workflows/matrix-command.yml b/.github/workflows/matrix-command.yml index f2c32658f..9692b8cea 100644 --- a/.github/workflows/matrix-command.yml +++ b/.github/workflows/matrix-command.yml @@ -21,7 +21,7 @@ jobs: pull-requests: write steps: - name: Is comment allowed? - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const actorPermission = (await github.rest.repos.getCollaboratorPermissionLevel({ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9d7617267..d111c6c1e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -63,12 +63,12 @@ jobs: path: dist - name: Upload pypi.org if: startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 + uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 with: repository-url: https://upload.pypi.org/legacy/ - name: Upload test.pypi.org if: ${{ ! startsWith(github.ref, 'refs/tags') }} - uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4 + uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 with: repository-url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2e3daf093..af7403d11 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -145,7 +145,7 @@ jobs: - uses: actions/checkout@v5 with: ref: ${{ inputs.ref || github.ref }} - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.version }} cache: pip @@ -203,7 +203,7 @@ jobs: with: ref: ${{ inputs.ref || github.ref }} - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: # Use latest Python, so it understands all syntax. python-version: 3.11 From 183ebc5d364c3ed9e23d50ffd54b9e0ed633fa7c Mon Sep 17 00:00:00 2001 From: Fabrizio Ferri-Benedetti Date: Wed, 17 Sep 2025 23:33:39 +0200 Subject: [PATCH 339/409] Add crosslinks to troubleshooting (#2428) * Add crosslinks to troubleshooting * Remove excess slashes * Change link text --- docs/reference/index.md | 3 +++ docs/reference/toc.yml | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/reference/index.md b/docs/reference/index.md index e92d5fff1..df1c5bf30 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -32,3 +32,6 @@ More detailed information on how the Agent works can be found in the [advanced t APM Agents work in conjunction with the [APM Server](docs-content://solutions/observability/apm/index.md), [Elasticsearch](docs-content://get-started/introduction.md#what-is-es), and [Kibana](docs-content://get-started/introduction.md#what-is-kib). The [APM documentation](docs-content://solutions/observability/apm/index.md) provides details on how these components work together, and provides a matrix outlining [Agent and Server compatibility](docs-content://solutions/observability/apm/apm-agent-compatibility.md). +## Troubleshooting + +If you're experiencing issues with the APM Python agent, refer to [Troubleshoot APM Python Agent](docs-content://troubleshoot/observability/apm-agent-python/apm-python-agent.md). \ No newline at end of file diff --git a/docs/reference/toc.yml b/docs/reference/toc.yml index 9d4df720a..1cd287df6 100644 --- a/docs/reference/toc.yml +++ b/docs/reference/toc.yml @@ -30,4 +30,6 @@ toc: children: - file: upgrading-6-x.md - file: upgrading-5-x.md - - file: upgrading-4-x.md \ No newline at end of file + - file: upgrading-4-x.md + - title: Troubleshooting + crosslink: docs-content://troubleshoot/observability/apm-agent-python/apm-python-agent.md \ No newline at end of file From 4cfcd247ece1a9bbf4616fb5be21fc840c7ae982 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 22 Sep 2025 09:02:47 +0200 Subject: [PATCH 340/409] docs: lambda layer version may not be 1 (#2430) Don't hardcode the apm layer version as 1, in 6.24.0 it's 3. While at it fix vars substitution. --- docs/reference/lambda-support.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/reference/lambda-support.md b/docs/reference/lambda-support.md index ada302cd6..354556870 100644 --- a/docs/reference/lambda-support.md +++ b/docs/reference/lambda-support.md @@ -8,8 +8,9 @@ applies_to: product: apm_agent_python: ga sub: - apm-lambda-ext-v: ver-1-5-7 - apm-python-v: ver-6-23-0 + apm-lambda-ext-v: ver-1-6-0 + apm-python-v: ver-6-24-0 + apm-python-layer-v: 3 --- # Monitoring AWS Lambda Python Functions [lambda-support] @@ -39,14 +40,14 @@ To add the layers to your Lambda function through the AWS Management Console: 3. Choose the *Specify an ARN* radio button 4. Copy and paste the following ARNs of the {{apm-lambda-ext}} layer and the APM agent layer in the *Specify an ARN* text input: * APM Extension layer: - ``` + ```bash subs=true arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-extension-{{apm-lambda-ext-v}}-{ARCHITECTURE}:1 <1> ``` 1. Replace `{AWS_REGION}` with the AWS region of your Lambda function and `{ARCHITECTURE}` with its architecture. * APM agent layer: - ``` - arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-python-{{apm-python-v}}:1 <1> + ```bash subs=true + arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-python-{{apm-python-v}}:{{apm-python-layer-v}} <1> ``` 1. Replace `{AWS_REGION}` with the AWS region of your Lambda function. @@ -57,10 +58,10 @@ To add the layers to your Lambda function through the AWS Management Console: ::::::{tab-item} AWS CLI To add the Layer ARNs of the {{apm-lambda-ext}} and the APM agent through the AWS command line interface execute the following command: -```bash +```bash subs=true aws lambda update-function-configuration --function-name yourLambdaFunctionName \ --layers arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-extension-{{apm-lambda-ext-v}}-{ARCHITECTURE}:1 \ <1> -arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-python-{{apm-python-v}}:1 <2> +arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-python-{{apm-python-v}}:{{apm-python-layer-v}} <2> ``` 1. Replace `{AWS_REGION}` with the AWS region of your Lambda function and `{ARCHITECTURE}` with its architecture. 2. Replace `{AWS_REGION}` with the AWS region of your Lambda function. @@ -69,7 +70,7 @@ arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-python-{{apm-python-v ::::::{tab-item} SAM In your SAM `template.yml` file add the Layer ARNs of the {{apm-lambda-ext}} and the APM agent as follows: -```yaml +```yaml subs=true ... Resources: yourLambdaFunction: @@ -78,7 +79,7 @@ Resources: ... Layers: - arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-extension-{{apm-lambda-ext-v}}-{ARCHITECTURE}:1 <1> - - arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-python-{{apm-python-v}}:1 <2> + - arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-python-{{apm-python-v}}:{{apm-python-layer-v}} <2> ... ``` 1. Replace `{AWS_REGION}` with the AWS region of your Lambda function and `{ARCHITECTURE}` with its architecture. @@ -88,14 +89,14 @@ Resources: ::::::{tab-item} Serverless In your `serverless.yml` file add the Layer ARNs of the {{apm-lambda-ext}} and the APM agent to your function as follows: -```yaml +```yaml subs=true ... functions: yourLambdaFunction: handler: ... layers: - arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-extension-{{apm-lambda-ext-v}}-{ARCHITECTURE}:1 <1> - - arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-python-{{apm-python-v}}:1 <2> + - arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-python-{{apm-python-v}}:{{apm-python-layer-v}} <2> ... ``` 1. Replace `{AWS_REGION}` with the AWS region of your Lambda function and `{ARCHITECTURE}` with its architecture. @@ -105,11 +106,11 @@ functions: ::::::{tab-item} Terraform To add the{{apm-lambda-ext}} and the APM agent to your function add the ARNs to the `layers` property in your Terraform file: -```yaml +```yaml subs=true ... resource "aws_lambda_function" "your_lambda_function" { ... - layers = ["arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-extension-{{apm-lambda-ext-v}}-{ARCHITECTURE}:1", "arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-python-{{apm-python-v}}:1"] <1> + layers = ["arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-extension-{{apm-lambda-ext-v}}-{ARCHITECTURE}:1", "arn:aws:lambda:{AWS_REGION}:267093732750:layer:elastic-apm-python-{{apm-python-v}}:{{apm-python-layer-v}}"] <1> } ... ``` From 54715a95e74982121d1576472b3645256bcf08c4 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Sep 2025 09:11:12 +0200 Subject: [PATCH 341/409] Replace tibdex/github-app-token with actions/create-github-app-token (#2432) * Initial plan * Replace tibdex/github-app-token with actions/create-github-app-token@v2 Co-authored-by: v1v <2871786+v1v@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: v1v <2871786+v1v@users.noreply.github.com> --- .github/workflows/labeler.yml | 11 ++++------- .github/workflows/updatecli.yml | 13 +++++-------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index b2e83005c..f1a14d24f 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -20,14 +20,11 @@ jobs: steps: - name: Get token id: get_token - uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0 + uses: actions/create-github-app-token@v2 with: - app_id: ${{ secrets.OBS_AUTOMATION_APP_ID }} - private_key: ${{ secrets.OBS_AUTOMATION_APP_PEM }} - permissions: >- - { - "members": "read" - } + app-id: ${{ secrets.OBS_AUTOMATION_APP_ID }} + private-key: ${{ secrets.OBS_AUTOMATION_APP_PEM }} + permission-members: read - name: Add agent-python label run: gh issue edit "$NUMBER" --add-label "agent-python" --repo "${{ github.repository }}" - id: is_elastic_member diff --git a/.github/workflows/updatecli.yml b/.github/workflows/updatecli.yml index 6f9b0c15d..88a11774b 100644 --- a/.github/workflows/updatecli.yml +++ b/.github/workflows/updatecli.yml @@ -19,15 +19,12 @@ jobs: - name: Get token id: get_token - uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0 + uses: actions/create-github-app-token@v2 with: - app_id: ${{ secrets.OBS_AUTOMATION_APP_ID }} - private_key: ${{ secrets.OBS_AUTOMATION_APP_PEM }} - permissions: >- - { - "contents": "write", - "pull_requests": "write" - } + app-id: ${{ secrets.OBS_AUTOMATION_APP_ID }} + private-key: ${{ secrets.OBS_AUTOMATION_APP_PEM }} + permission-contents: write + permission-pull-requests: write - uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 with: From 9bab21678c1b6d1ea1c47055ad21b672ce9ad824 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Wed, 24 Sep 2025 10:43:30 +0200 Subject: [PATCH 342/409] Add an item about bumping versions in lambda doc to release checklist (#2434) --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 348735fd8..a7f6d0703 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -200,3 +200,4 @@ If you have commit access, the process is as follows: 1. Edit and publish the [draft Github release](https://github.com/elastic/apm-agent-python/releases) created by Github Actions. Substitute the generated changelog with one hand written into the body of the release. +1. Update substitutions variables in `docs/reference/lambda-support.md`. From ac65e37beadb7a2a191969c2f460ca785209a152 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 09:54:24 +0200 Subject: [PATCH 343/409] chore: deps(updatecli): Bump updatecli version to v0.108.0 (#2436) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index ca67f6862..54f4a9dfa 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.107.0 \ No newline at end of file +updatecli v0.108.0 \ No newline at end of file From 3801b2794a5722f923943241b3dc5b9d36cf5630 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 13:44:14 +0200 Subject: [PATCH 344/409] build(deps): bump wolfi/chainguard-base from `ec17277` to `00fa283` (#2438) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `ec17277` to `00fa283`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index cdf0e606a..e62a7b825 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:ec172778d432abfcd18641ec25270b2fff7c22ff9956d44c495860cdb0477357 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:00fa283d3647165f65fa64324d60f09d29f0ad1ade717af349131b2fc65f8f8f ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From a4e1991b559291a3721c757b0e6662d9c8722b9b Mon Sep 17 00:00:00 2001 From: Imad Saddik <79410781+ImadSaddik@users.noreply.github.com> Date: Thu, 2 Oct 2025 14:52:22 +0100 Subject: [PATCH 345/409] Fixed a small typo in sanitizing-data.md (#2440) --- docs/reference/sanitizing-data.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/sanitizing-data.md b/docs/reference/sanitizing-data.md index 5797e427c..51694b88d 100644 --- a/docs/reference/sanitizing-data.md +++ b/docs/reference/sanitizing-data.md @@ -56,7 +56,7 @@ We recommend using the above list of processors that sanitize passwords and secr :::: -The default set of processors sanitize fields based on a set of defaults defined in `elasticapm.conf.constants`. This set can be configured with the `SANITIZE_FIELD_NAMES` configuration option. For example, if your application produces a sensitive field called `My-Sensitive-Field`, the default processors can be used to automatically sanitize this field. You can specify what fields to santize within default processors like this: +The default set of processors sanitize fields based on a set of defaults defined in `elasticapm.conf.constants`. This set can be configured with the `SANITIZE_FIELD_NAMES` configuration option. For example, if your application produces a sensitive field called `My-Sensitive-Field`, the default processors can be used to automatically sanitize this field. You can specify what fields to sanitize within default processors like this: ```python ELASTIC_APM = { From 77ad44f06eae5a31ce14acb699656b910a3c259e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Oct 2025 13:47:04 +0200 Subject: [PATCH 346/409] build(deps): bump wolfi/chainguard-base from `00fa283` to `5d0f094` (#2442) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `00fa283` to `5d0f094`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index e62a7b825..69bbc7e3a 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:00fa283d3647165f65fa64324d60f09d29f0ad1ade717af349131b2fc65f8f8f +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:5d0f094bdbe4fcc12416da5d48fdcafef1aecc101088038b6392231c697d9038 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From e008bf3b0521496a5f1bab1e6c288766d8c922f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 09:33:38 +0200 Subject: [PATCH 347/409] build(deps): bump docker/login-action (#2445) Bumps the github-actions group with 1 update in the / directory: [docker/login-action](https://github.com/docker/login-action). Updates `docker/login-action` from 3.5.0 to 3.6.0 - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/184bdaa0721073962dff0199f1fb9940f07167d1...5e57cd118135c172c3672efd75eb46360885c0ef) --- .github/workflows/build-images.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/updatecli.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-images.yml b/.github/workflows/build-images.yml index 100904eb2..7d5f1c78b 100644 --- a/.github/workflows/build-images.yml +++ b/.github/workflows/build-images.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/checkout@v5 - name: Login to ghcr.io - uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d111c6c1e..740943749 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -124,7 +124,7 @@ jobs: uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 - name: Log in to the Elastic Container registry - uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: registry: ${{ secrets.ELASTIC_DOCKER_REGISTRY }} username: ${{ secrets.ELASTIC_DOCKER_USERNAME }} diff --git a/.github/workflows/updatecli.yml b/.github/workflows/updatecli.yml index 88a11774b..42a76d272 100644 --- a/.github/workflows/updatecli.yml +++ b/.github/workflows/updatecli.yml @@ -26,7 +26,7 @@ jobs: permission-contents: write permission-pull-requests: write - - uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0 + - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 with: registry: ghcr.io username: ${{ github.actor }} From 694c0bf75122ff6bb683c0307daa19b30a9e3884 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Oct 2025 12:34:45 +0000 Subject: [PATCH 348/409] build(deps): bump alpine from `4bcff63` to `4b7ce07` (#2449) Bumps alpine from `4bcff63` to `4b7ce07`. --- updated-dependencies: - dependency-name: alpine dependency-version: 4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 83bca8724..3ae80cf1e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,3 @@ -FROM alpine@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1 +FROM alpine@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 0411357713820ed77c0e41fc912e9b555db69ea6 Mon Sep 17 00:00:00 2001 From: Imad Saddik <79410781+ImadSaddik@users.noreply.github.com> Date: Thu, 9 Oct 2025 13:55:24 +0100 Subject: [PATCH 349/409] Fixed a few typos (#2447) --- docs/reference/api-reference.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/reference/api-reference.md b/docs/reference/api-reference.md index aba676e80..c99a840a0 100644 --- a/docs/reference/api-reference.md +++ b/docs/reference/api-reference.md @@ -223,7 +223,7 @@ elasticapm.get_trace_parent_header() apm_agent_python: ga 1.0.0 ``` -Instruments libraries automatically. This includes a wide range of standard library and 3rd party modules. A list of instrumented modules can be found in `elasticapm.instrumentation.register`. This function should be called as early as possibly in the startup of your application. For [supported frameworks](/reference/supported-technologies.md#framework-support), this is called automatically. Example: +Instruments libraries automatically. This includes a wide range of standard library and 3rd party modules. A list of instrumented modules can be found in `elasticapm.instrumentation.register`. This function should be called as early as possible in the startup of your application. For [supported frameworks](/reference/supported-technologies.md#framework-support), this is called automatically. Example: ```python import elasticapm @@ -308,7 +308,7 @@ elasticapm.set_transaction_outcome(OUTCOME.UNKNOWN) * `outcome`: One of `"success"`, `"failure"` or `"unknown"`. Can be omitted if `http_status_code` is provided. * `http_status_code`: if the transaction represents an HTTP response, its status code can be provided to determine the `outcome` automatically. -* `override`: if `True` (the default), any previously set `outcome` will be overriden. If `False`, the outcome will only be set if it was not set before. +* `override`: if `True` (the default), any previously set `outcome` will be overridden. If `False`, the outcome will only be set if it was not set before. ### `elasticapm.get_transaction_id()` [api-get-transaction-id] @@ -436,7 +436,7 @@ def coffee_maker(strength): * `name`: The name of the span. Defaults to the function name if used as a decorator. * `span_type`: (**optional**) The type of the span, usually in a dot-separated hierarchy of `type`, `subtype`, and `action`, e.g. `db.mysql.query`. Alternatively, type, subtype and action can be provided as three separate arguments, see `span_subtype` and `span_action`. * `skip_frames`: (**optional**) The number of stack frames to skip when collecting stack traces. Defaults to `0`. -* `leaf`: (**optional**) if `True`, all spans nested bellow this span will be ignored. Defaults to `False`. +* `leaf`: (**optional**) if `True`, all spans nested below this span will be ignored. Defaults to `False`. * `labels`: (**optional**) a dictionary of labels. Keys must be strings, values can be strings, booleans, or numerical (`int`, `float`, `decimal.Decimal`). Defaults to `None`. * `span_subtype`: (**optional**) subtype of the span, e.g. name of the database. Defaults to `None`. * `span_action`: (**optional**) action of the span, e.g. `query`. Defaults to `None`. @@ -471,7 +471,7 @@ async def coffee_maker(strength): * `name`: The name of the span. Defaults to the function name if used as a decorator. * `span_type`: (**optional**) The type of the span, usually in a dot-separated hierarchy of `type`, `subtype`, and `action`, e.g. `db.mysql.query`. Alternatively, type, subtype and action can be provided as three separate arguments, see `span_subtype` and `span_action`. * `skip_frames`: (**optional**) The number of stack frames to skip when collecting stack traces. Defaults to `0`. -* `leaf`: (**optional**) if `True`, all spans nested bellow this span will be ignored. Defaults to `False`. +* `leaf`: (**optional**) if `True`, all spans nested below this span will be ignored. Defaults to `False`. * `labels`: (**optional**) a dictionary of labels. Keys must be strings, values can be strings, booleans, or numerical (`int`, `float`, `decimal.Decimal`). Defaults to `None`. * `span_subtype`: (**optional**) subtype of the span, e.g. name of the database. Defaults to `None`. * `span_action`: (**optional**) action of the span, e.g. `query`. Defaults to `None`. From 41dd1d1c535177638d4a0d6eefcf680703f49322 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Oct 2025 12:57:58 +0000 Subject: [PATCH 350/409] build(deps): bump wolfi/chainguard-base from `5d0f094` to `b08320d` (#2448) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `5d0f094` to `b08320d`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Riccardo Magliocchetti --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 69bbc7e3a..7b4e40644 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:5d0f094bdbe4fcc12416da5d48fdcafef1aecc101088038b6392231c697d9038 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:b08320d6d7e9eb8054e8781a3b624f8e77e90d4aeb1756fd7665bb2f96f36fc8 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From f766be97600d2decb7bd2d999d124510756e6d2a Mon Sep 17 00:00:00 2001 From: Imad Saddik <79410781+ImadSaddik@users.noreply.github.com> Date: Thu, 9 Oct 2025 14:00:54 +0100 Subject: [PATCH 351/409] Docs: Replaced a broken link (#2443) * Replaced a broken link * Update docs/reference/how-agent-works.md --------- Co-authored-by: Riccardo Magliocchetti --- docs/reference/how-agent-works.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/how-agent-works.md b/docs/reference/how-agent-works.md index d6d6f447c..f8876cb32 100644 --- a/docs/reference/how-agent-works.md +++ b/docs/reference/how-agent-works.md @@ -40,7 +40,7 @@ To collect data from database drivers, HTTP libraries etc., we instrument certai * the executed query for database drivers * the fetched URL for HTTP libraries -We use a 3rd party library, [`wrapt`](https://github.com/GrahamDumpleton/wrapt), to wrap the callables. You can read more on how `wrapt` works in Graham Dumpleton’s excellent series of [blog posts](http://blog.dscpl.com.au/search/label/wrapt). +We use a 3rd party library, [`wrapt`](https://github.com/GrahamDumpleton/wrapt), to wrap the callables. You can read more on how `wrapt` works in Graham Dumpleton’s excellent series of [blog posts](https://grahamdumpleton.me/posts/?search=wrapt). Instrumentations are set up automatically and do not require any code changes. See [Automatic Instrumentation](/reference/supported-technologies.md#automatic-instrumentation) to learn more about which libraries we support. From 4ffaa7a642ecab91c31f8e504a1872f51fdab487 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:09:31 +0000 Subject: [PATCH 352/409] build(deps): bump wolfi/chainguard-base from `b08320d` to `36a56e3` (#2451) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `b08320d` to `36a56e3`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 7b4e40644..effe9ebd2 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:b08320d6d7e9eb8054e8781a3b624f8e77e90d4aeb1756fd7665bb2f96f36fc8 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:36a56e3d8a40080f81e3ccca6b0fed0167716225bbc305052db6d5a9f594ccb2 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 226e507286215f806a1e383d9f31a344ac70786b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 14:11:07 +0200 Subject: [PATCH 353/409] build(deps): bump wolfi/chainguard-base from `36a56e3` to `e7cba12` (#2453) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `36a56e3` to `e7cba12`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index effe9ebd2..6736ce930 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:36a56e3d8a40080f81e3ccca6b0fed0167716225bbc305052db6d5a9f594ccb2 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:e7cba125e50439f1bfd52d327dfbf10994ad8c2556265b30055d7af58c1aa600 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From ad737e8a8b003e287196303f4f3d3ae557c01edd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 14:11:35 +0200 Subject: [PATCH 354/409] build(deps): bump certifi from 2025.8.3 to 2025.10.5 in /dev-utils (#2444) Bumps [certifi](https://github.com/certifi/python-certifi) from 2025.8.3 to 2025.10.5. - [Commits](https://github.com/certifi/python-certifi/compare/2025.08.03...2025.10.05) --- updated-dependencies: - dependency-name: certifi dependency-version: 2025.10.5 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dev-utils/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-utils/requirements.txt b/dev-utils/requirements.txt index bab366f4e..2d5856cc8 100644 --- a/dev-utils/requirements.txt +++ b/dev-utils/requirements.txt @@ -1,4 +1,4 @@ # These are the pinned requirements for the lambda layer/docker image -certifi==2025.8.3 +certifi==2025.10.5 urllib3==1.26.20 wrapt==1.14.1 From a42741f4d71d13f88bce50d0cae97c368fb438df Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Oct 2025 14:34:49 +0200 Subject: [PATCH 355/409] build(deps): bump wolfi/chainguard-base from `e7cba12` to `eb38cad` (#2456) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `e7cba12` to `eb38cad`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 6736ce930..29e4db740 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:e7cba125e50439f1bfd52d327dfbf10994ad8c2556265b30055d7af58c1aa600 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:eb38cadc8aa4d1e5fc159d40e0fb9eb23f59e139fe27483b674ba617d52db4e9 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From f6bd01dd5e8772fa116c37018cdf24be9c5dbea9 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 09:12:43 +0000 Subject: [PATCH 356/409] chore: deps(updatecli): Bump updatecli version to v0.109.0 (#2461) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 54f4a9dfa..73cd39b69 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.108.0 \ No newline at end of file +updatecli v0.109.0 \ No newline at end of file From 1e33fd9585aaa1d2d82073528c83c2e0c2983a8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 09:14:42 +0000 Subject: [PATCH 357/409] build(deps): bump wolfi/chainguard-base from `eb38cad` to `b85d54c` (#2459) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `eb38cad` to `b85d54c`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 29e4db740..e0d4e942d 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:eb38cadc8aa4d1e5fc159d40e0fb9eb23f59e139fe27483b674ba617d52db4e9 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:b85d54c9019ff80e88aab7d357ede3341d1442ce190173eed42ae5a116753e4e ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 9751b4a19c2c2b71711abeed09ec8387e2e859b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 14:29:00 +0100 Subject: [PATCH 358/409] build(deps): bump wolfi/chainguard-base from `b85d54c` to `83f8ecd` (#2466) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `b85d54c` to `83f8ecd`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index e0d4e942d..2fb66f2ea 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:b85d54c9019ff80e88aab7d357ede3341d1442ce190173eed42ae5a116753e4e +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:83f8ecdb6f089d2d393cd8138eb95f73d6ca9e29c66efd9f3b3f448d9b1a45b5 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 684db5a4ac3f76bc68d4d8c7da903feb65e31701 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 31 Oct 2025 15:13:13 +0100 Subject: [PATCH 359/409] Cleanup elasticsearch testing (#2464) * Drop testing against ancient elasticsearch versions * Bump elasticsearch versions to latest of the major * Drop docker-compose.yml version * Drop unused volumes * Test against Elastic stack 9 --- .ci/.matrix_exclude.yml | 3 ++ .ci/.matrix_framework.yml | 2 +- .ci/.matrix_framework_full.yml | 4 +- tests/docker-compose.yml | 53 +++++---------------- tests/requirements/reqs-elasticsearch-2.txt | 2 - tests/requirements/reqs-elasticsearch-5.txt | 2 - tests/requirements/reqs-elasticsearch-6.txt | 2 - tests/requirements/reqs-elasticsearch-9.txt | 3 ++ tests/scripts/envs/elasticsearch-2.sh | 5 -- tests/scripts/envs/elasticsearch-5.sh | 5 -- tests/scripts/envs/elasticsearch-6.sh | 5 -- tests/scripts/envs/elasticsearch-9.sh | 5 ++ 12 files changed, 24 insertions(+), 67 deletions(-) delete mode 100644 tests/requirements/reqs-elasticsearch-2.txt delete mode 100644 tests/requirements/reqs-elasticsearch-5.txt delete mode 100644 tests/requirements/reqs-elasticsearch-6.txt create mode 100644 tests/requirements/reqs-elasticsearch-9.txt delete mode 100644 tests/scripts/envs/elasticsearch-2.sh delete mode 100644 tests/scripts/envs/elasticsearch-5.sh delete mode 100644 tests/scripts/envs/elasticsearch-6.sh create mode 100644 tests/scripts/envs/elasticsearch-9.sh diff --git a/.ci/.matrix_exclude.yml b/.ci/.matrix_exclude.yml index db796ee34..309c6fb34 100644 --- a/.ci/.matrix_exclude.yml +++ b/.ci/.matrix_exclude.yml @@ -37,6 +37,9 @@ exclude: FRAMEWORK: celery-5-django-5 - VERSION: python-3.9 FRAMEWORK: celery-5-django-5 + # Elasticsearch + - VERSION: python-3.6 + FRAMEWORK: elasticsearch-9 # Flask - VERSION: pypy-3 FRAMEWORK: flask-0.11 # see https://github.com/pallets/flask/commit/6e46d0cd, 0.11.2 was never released diff --git a/.ci/.matrix_framework.yml b/.ci/.matrix_framework.yml index 8c73d0810..64d1dc8ef 100644 --- a/.ci/.matrix_framework.yml +++ b/.ci/.matrix_framework.yml @@ -29,9 +29,9 @@ FRAMEWORK: - pyodbc-newest - memcached-newest - pylibmc-newest - - elasticsearch-2 - elasticsearch-7 - elasticsearch-8 + - elasticsearch-9 - cassandra-newest - psutil-newest #- eventlet-newest diff --git a/.ci/.matrix_framework_full.yml b/.ci/.matrix_framework_full.yml index 4adb9b25e..6b3a6ea08 100644 --- a/.ci/.matrix_framework_full.yml +++ b/.ci/.matrix_framework_full.yml @@ -63,11 +63,9 @@ FRAMEWORK: - psutil-5.0 - psutil-4.0 #- eventlet-newest - - elasticsearch-2 - - elasticsearch-5 - - elasticsearch-6 - elasticsearch-7 - elasticsearch-8 + - elasticsearch-9 - gevent-newest - aiohttp-3.0 - aiohttp-newest diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index 9b8e06da4..0629a045e 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -1,5 +1,3 @@ -version: '2.1' - services: postgres: user: postgres @@ -59,21 +57,8 @@ services: redis: image: redis - elasticsearch6: - image: docker.elastic.co/elasticsearch/elasticsearch:6.8.0 - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:9200"] - environment: - - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - - "network.host=" - - "transport.host=127.0.0.1" - - "http.host=0.0.0.0" - - "xpack.security.enabled=false" - volumes: - - pyesdata6:/usr/share/elasticsearch/data - elasticsearch7: - image: docker.elastic.co/elasticsearch/elasticsearch:7.17.0 + image: docker.elastic.co/elasticsearch/elasticsearch:7.17.29 healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9200"] environment: @@ -86,7 +71,7 @@ services: - pyesdata7:/usr/share/elasticsearch/data elasticsearch8: - image: docker.elastic.co/elasticsearch/elasticsearch:8.4.0 + image: docker.elastic.co/elasticsearch/elasticsearch:8.19.6 healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9200"] ports: @@ -101,31 +86,21 @@ services: volumes: - pyesdata8:/usr/share/elasticsearch/data - elasticsearch5: - image: docker.elastic.co/elasticsearch/elasticsearch:5.6.16 - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:9200"] - environment: - - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - - "network.host=" - - "transport.host=127.0.0.1" - - "http.host=0.0.0.0" - - "xpack.security.enabled=false" - volumes: - - pyesdata5:/usr/share/elasticsearch/data - - elasticsearch2: - image: elasticsearch:2 + elasticsearch9: + image: docker.elastic.co/elasticsearch/elasticsearch:9.2.0 healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9200"] + ports: + - "9200:9200" environment: - "ES_JAVA_OPTS=-Xms512m -Xmx512m" - - "network.host=" + - "network.host=_site_" - "transport.host=127.0.0.1" - "http.host=0.0.0.0" - "xpack.security.enabled=false" + - "action.destructive_requires_name=false" # allow for easy cleanup by calling DELETE * volumes: - - pyesdata2:/usr/share/elasticsearch/data + - pyesdata9:/usr/share/elasticsearch/data mssql: image: mcr.microsoft.com/mssql/server @@ -185,11 +160,9 @@ services: run_tests: image: ${REGISTRY:-elasticobservability}/${IMAGE_NAME:-apm-agent-python-testing}:${PYTHON_VERSION} environment: + ES_9_URL: 'http://elasticsearch9:9200' ES_8_URL: 'http://elasticsearch8:9200' ES_7_URL: 'http://elasticsearch7:9200' - ES_6_URL: 'http://elasticsearch6:9200' - ES_5_URL: 'http://elasticsearch5:9200' - ES_2_URL: 'http://elasticsearch2:9200' volumes: @@ -209,11 +182,7 @@ volumes: driver: local pyesdata8: driver: local - pyesdata6: - driver: local - pyesdata5: - driver: local - pyesdata2: + pyesdata9: driver: local pycassandradata3: driver: local diff --git a/tests/requirements/reqs-elasticsearch-2.txt b/tests/requirements/reqs-elasticsearch-2.txt deleted file mode 100644 index e3f92c1db..000000000 --- a/tests/requirements/reqs-elasticsearch-2.txt +++ /dev/null @@ -1,2 +0,0 @@ -elasticsearch>=2.0,<3.0 --r reqs-base.txt diff --git a/tests/requirements/reqs-elasticsearch-5.txt b/tests/requirements/reqs-elasticsearch-5.txt deleted file mode 100644 index 27a0e10c0..000000000 --- a/tests/requirements/reqs-elasticsearch-5.txt +++ /dev/null @@ -1,2 +0,0 @@ -elasticsearch>=5.0,<6.0 --r reqs-base.txt diff --git a/tests/requirements/reqs-elasticsearch-6.txt b/tests/requirements/reqs-elasticsearch-6.txt deleted file mode 100644 index ad34285bc..000000000 --- a/tests/requirements/reqs-elasticsearch-6.txt +++ /dev/null @@ -1,2 +0,0 @@ -elasticsearch>=6.0,<7.0 --r reqs-base.txt diff --git a/tests/requirements/reqs-elasticsearch-9.txt b/tests/requirements/reqs-elasticsearch-9.txt new file mode 100644 index 000000000..b310cbb47 --- /dev/null +++ b/tests/requirements/reqs-elasticsearch-9.txt @@ -0,0 +1,3 @@ +elasticsearch>=9.0,<10.0 +aiohttp +-r reqs-base.txt diff --git a/tests/scripts/envs/elasticsearch-2.sh b/tests/scripts/envs/elasticsearch-2.sh deleted file mode 100644 index d9d68f99f..000000000 --- a/tests/scripts/envs/elasticsearch-2.sh +++ /dev/null @@ -1,5 +0,0 @@ -export PYTEST_MARKER="-m elasticsearch" -export ES_URL="http://elasticsearch2:9200" -export DOCKER_DEPS="elasticsearch2" -export WAIT_FOR_HOST="elasticsearch2" -export WAIT_FOR_PORT=9200 diff --git a/tests/scripts/envs/elasticsearch-5.sh b/tests/scripts/envs/elasticsearch-5.sh deleted file mode 100644 index 0ea3d5279..000000000 --- a/tests/scripts/envs/elasticsearch-5.sh +++ /dev/null @@ -1,5 +0,0 @@ -export PYTEST_MARKER="-m elasticsearch" -export ES_URL="http://elasticsearch5:9200" -export DOCKER_DEPS="elasticsearch5" -export WAIT_FOR_HOST="elasticsearch5" -export WAIT_FOR_PORT=9200 diff --git a/tests/scripts/envs/elasticsearch-6.sh b/tests/scripts/envs/elasticsearch-6.sh deleted file mode 100644 index fb4aa19b0..000000000 --- a/tests/scripts/envs/elasticsearch-6.sh +++ /dev/null @@ -1,5 +0,0 @@ -export PYTEST_MARKER="-m elasticsearch" -export ES_URL="http://elasticsearch6:9200" -export DOCKER_DEPS="elasticsearch6" -export WAIT_FOR_HOST="elasticsearch6" -export WAIT_FOR_PORT=9200 diff --git a/tests/scripts/envs/elasticsearch-9.sh b/tests/scripts/envs/elasticsearch-9.sh new file mode 100644 index 000000000..dc64a205b --- /dev/null +++ b/tests/scripts/envs/elasticsearch-9.sh @@ -0,0 +1,5 @@ +export PYTEST_MARKER="-m elasticsearch" +export ES_URL="http://elasticsearch9:9200" +export DOCKER_DEPS="elasticsearch9" +export WAIT_FOR_HOST="elasticsearch9" +export WAIT_FOR_PORT=9200 From 7d093af1ec846bef44bbc7043886d327319d0b99 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 31 Oct 2025 16:11:53 +0100 Subject: [PATCH 360/409] ci: exclude python 3.7 from elasticsearch 9 tests (#2468) --- .ci/.matrix_exclude.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.ci/.matrix_exclude.yml b/.ci/.matrix_exclude.yml index 309c6fb34..93d0fa4df 100644 --- a/.ci/.matrix_exclude.yml +++ b/.ci/.matrix_exclude.yml @@ -40,6 +40,8 @@ exclude: # Elasticsearch - VERSION: python-3.6 FRAMEWORK: elasticsearch-9 + - VERSION: python-3.7 + FRAMEWORK: elasticsearch-9 # Flask - VERSION: pypy-3 FRAMEWORK: flask-0.11 # see https://github.com/pallets/flask/commit/6e46d0cd, 0.11.2 was never released From 91325b4ae6511b297d52553a2a1f47f91bca3160 Mon Sep 17 00:00:00 2001 From: qvalentin <36446499+qvalentin@users.noreply.github.com> Date: Mon, 3 Nov 2025 11:38:29 +0100 Subject: [PATCH 361/409] fix(contrib/opentelemetry): set_status should match base signature (#2457) * fix(contrib/opentelemetry): set_status should match base signature fixes https://github.com/elastic/apm-agent-python/issues/2455 Signed-off-by: qvalentin * Apply suggestions from code review --------- Signed-off-by: qvalentin Co-authored-by: Riccardo Magliocchetti --- elasticapm/contrib/opentelemetry/span.py | 14 +++++-- tests/contrib/opentelemetry/tests.py | 48 ++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/elasticapm/contrib/opentelemetry/span.py b/elasticapm/contrib/opentelemetry/span.py index a7d049faa..7917070d5 100644 --- a/elasticapm/contrib/opentelemetry/span.py +++ b/elasticapm/contrib/opentelemetry/span.py @@ -32,7 +32,7 @@ import types as python_types import typing import urllib.parse -from typing import Optional +from typing import Optional, Union from opentelemetry.context import Context from opentelemetry.sdk import trace as oteltrace @@ -157,13 +157,19 @@ def is_recording(self) -> bool: """ return self.elastic_span.transaction.is_sampled and not self.elastic_span.ended_time - def set_status(self, status: Status) -> None: + def set_status(self, status: Union[Status, StatusCode], description: Optional[str] = None) -> None: """Sets the Status of the Span. If used, this will override the default Span status. """ - if status.status_code == StatusCode.ERROR: + # Handle both Status objects and StatusCode enums + if isinstance(status, Status): + status_code = status.status_code + else: + status_code = status + + if status_code == StatusCode.ERROR: self.elastic_span.outcome = constants.OUTCOME.FAILURE - elif status.status_code == StatusCode.OK: + elif status_code == StatusCode.OK: self.elastic_span.outcome = constants.OUTCOME.SUCCESS else: self.elastic_span.outcome = constants.OUTCOME.UNKNOWN diff --git a/tests/contrib/opentelemetry/tests.py b/tests/contrib/opentelemetry/tests.py index 3a302289e..aa0160b74 100755 --- a/tests/contrib/opentelemetry/tests.py +++ b/tests/contrib/opentelemetry/tests.py @@ -34,6 +34,7 @@ from opentelemetry.trace import Link, SpanContext, SpanKind, TraceFlags from opentelemetry.trace.propagation import _SPAN_KEY +from opentelemetry.trace.status import Status, StatusCode import elasticapm.contrib.opentelemetry.context as context import elasticapm.contrib.opentelemetry.trace as trace @@ -155,4 +156,51 @@ def test_span_links(tracer: Tracer): assert span["links"][0]["span_id"] == "0011223344556677" +def test_set_status_with_status_object(tracer: Tracer): + with tracer.start_as_current_span("test") as span: + span.set_status(Status(StatusCode.OK)) + + client = tracer.client + transaction = client.events[constants.TRANSACTION][0] + assert transaction["outcome"] == "success" + + +def test_set_status_with_status_code(tracer: Tracer): + with tracer.start_as_current_span("test") as span: + span.set_status(StatusCode.ERROR) + + client = tracer.client + transaction = client.events[constants.TRANSACTION][0] + assert transaction["outcome"] == "failure" + + +def test_set_status_with_status_code_and_description(tracer: Tracer): + with tracer.start_as_current_span("test") as span: + span.set_status(StatusCode.OK, "Everything is fine") + + client = tracer.client + transaction = client.events[constants.TRANSACTION][0] + assert transaction["outcome"] == "success" + + +def test_set_status_unset(tracer: Tracer): + with tracer.start_as_current_span("test") as span: + span.set_status(StatusCode.UNSET) + + client = tracer.client + transaction = client.events[constants.TRANSACTION][0] + assert transaction["outcome"] == "unknown" + + +def test_set_status_on_span(tracer: Tracer): + """Test set_status on a child span (not transaction)""" + with tracer.start_as_current_span("test"): + with tracer.start_as_current_span("testspan") as span: + span.set_status(StatusCode.ERROR) + + client = tracer.client + span_event = client.events[constants.SPAN][0] + assert span_event["outcome"] == "failure" + + # TODO Add some span subtype testing? From 1348a6e0f1d1f3804b03920e134d8938277de2a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 14:09:30 +0100 Subject: [PATCH 362/409] build(deps): bump wolfi/chainguard-base from `83f8ecd` to `0833673` (#2471) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `83f8ecd` to `0833673`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 2fb66f2ea..eccdb1059 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:83f8ecdb6f089d2d393cd8138eb95f73d6ca9e29c66efd9f3b3f448d9b1a45b5 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:0833673bcb40fb65963f302651bcb8a7aaf74265cf08c6ca883d940fdf9f1eed ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 6a998390863ec6fa11c74a54fbe10a0930ed27fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 13:51:49 +0100 Subject: [PATCH 363/409] build(deps): bump wolfi/chainguard-base from `0833673` to `401d868` (#2473) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `0833673` to `401d868`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index eccdb1059..057d87f0f 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:0833673bcb40fb65963f302651bcb8a7aaf74265cf08c6ca883d940fdf9f1eed +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:401d8684fd5932c178d7cf524cb05a5088d0e4f74e886e2d0a3b4b5127349a49 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 4859140655f60e5f376c4a02b47780efddbb4ecc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Nov 2025 19:27:45 +0100 Subject: [PATCH 364/409] build(deps): bump the github-actions group across 3 directories with 2 updates (#2463) Bumps the github-actions group with 2 updates in the / directory: [actions/download-artifact](https://github.com/actions/download-artifact) and [actions/upload-artifact](https://github.com/actions/upload-artifact). Bumps the github-actions group with 1 update in the /.github/actions/build-distribution directory: [actions/upload-artifact](https://github.com/actions/upload-artifact). Bumps the github-actions group with 1 update in the /.github/actions/packages directory: [actions/upload-artifact](https://github.com/actions/upload-artifact). Updates `actions/download-artifact` from 5 to 6 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v5...v6) Updates `actions/upload-artifact` from 4 to 5 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v5) Updates `actions/upload-artifact` from 4 to 5 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v5) Updates `actions/upload-artifact` from 4 to 5 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/upload-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/upload-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/upload-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/build-distribution/action.yml | 2 +- .github/actions/packages/action.yml | 2 +- .github/workflows/release.yml | 10 +++++----- .github/workflows/run-matrix.yml | 4 ++-- .github/workflows/test-docs.yml | 2 +- .github/workflows/test.yml | 8 ++++---- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/actions/build-distribution/action.yml b/.github/actions/build-distribution/action.yml index 41d3dea01..5f659b747 100644 --- a/.github/actions/build-distribution/action.yml +++ b/.github/actions/build-distribution/action.yml @@ -14,7 +14,7 @@ runs: run: ./dev-utils/make-distribution.sh shell: bash - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 with: name: build-distribution path: ./build/ diff --git a/.github/actions/packages/action.yml b/.github/actions/packages/action.yml index e2daa0882..6c3053f5d 100644 --- a/.github/actions/packages/action.yml +++ b/.github/actions/packages/action.yml @@ -19,7 +19,7 @@ runs: run: ./dev-utils/make-packages.sh shell: bash - name: Upload Packages - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: packages path: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 740943749..8c3b031cf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -57,7 +57,7 @@ jobs: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - uses: actions/checkout@v5 - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: name: packages path: dist @@ -81,7 +81,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: name: build-distribution path: ./build @@ -96,7 +96,7 @@ jobs: VERSION=${VERSION//./-} ELASTIC_LAYER_NAME="elastic-apm-python-${VERSION}" .ci/publish-aws.sh - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v5 if: startsWith(github.ref, 'refs/tags') with: name: arn-file @@ -130,7 +130,7 @@ jobs: username: ${{ secrets.ELASTIC_DOCKER_USERNAME }} password: ${{ secrets.ELASTIC_DOCKER_PASSWORD }} - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: name: build-distribution path: ./build @@ -175,7 +175,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: name: arn-file - name: Create GitHub Draft Release diff --git a/.github/workflows/run-matrix.yml b/.github/workflows/run-matrix.yml index adf148f03..78aacfe21 100644 --- a/.github/workflows/run-matrix.yml +++ b/.github/workflows/run-matrix.yml @@ -32,13 +32,13 @@ jobs: LOCALSTACK_VOLUME_DIR: localstack_data - if: success() || failure() name: Upload JUnit Test Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: test-results-${{ matrix.framework }}-${{ matrix.version }} path: "**/*-python-agent-junit.xml" - if: success() || failure() name: Upload Coverage Reports - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: coverage-reports-${{ matrix.framework }}-${{ matrix.version }} path: "**/.coverage*" diff --git a/.github/workflows/test-docs.yml b/.github/workflows/test-docs.yml index e1c4c4ae4..a99c13216 100644 --- a/.github/workflows/test-docs.yml +++ b/.github/workflows/test-docs.yml @@ -36,7 +36,7 @@ jobs: ENDOFFILE - if: success() || failure() name: Upload JUnit Test Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: test-results-docs path: "docs-python-agent-junit.xml" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index af7403d11..eea489de0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -156,14 +156,14 @@ jobs: run: .\scripts\run-tests.bat - if: success() || failure() name: Upload JUnit Test Results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: test-results-${{ matrix.framework }}-${{ matrix.version }}-asyncio-${{ matrix.asyncio }} path: "**/*-python-agent-junit.xml" retention-days: 1 - if: success() || failure() name: Upload Coverage Reports - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: coverage-reports-${{ matrix.framework }}-${{ matrix.version }}-asyncio-${{ matrix.asyncio }} path: "**/.coverage*" @@ -210,7 +210,7 @@ jobs: - run: python -Im pip install --upgrade coverage[toml] - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: pattern: coverage-reports-* merge-multiple: true @@ -227,7 +227,7 @@ jobs: python -Im coverage report --fail-under=84 - name: Upload HTML report - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: html-coverage-report path: htmlcov From 0b70ea40308916d40b3540e636c9e79d4bb55355 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 12:40:34 +0100 Subject: [PATCH 365/409] build(deps): bump wolfi/chainguard-base from `401d868` to `6a3678e` (#2475) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `401d868` to `6a3678e`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 057d87f0f..0b9199952 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:401d8684fd5932c178d7cf524cb05a5088d0e4f74e886e2d0a3b4b5127349a49 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:6a3678e9bb481ee585c32c8d374efad5e63d49d8a9d19daec397fabeba45cc8c ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 0984aca05a5b3659b73f9a5e2f4bde295cf2b9fb Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 09:42:05 +0100 Subject: [PATCH 366/409] chore: deps(updatecli): Bump updatecli version to v0.110.0 (#2476) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 73cd39b69..2b57a8a12 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.109.0 \ No newline at end of file +updatecli v0.110.0 \ No newline at end of file From 88016fc2b58aa06eec6b4931990380edc40973b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Nov 2025 12:35:15 +0100 Subject: [PATCH 367/409] build(deps): bump wolfi/chainguard-base from `6a3678e` to `a83a91a` (#2478) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `6a3678e` to `a83a91a`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 0b9199952..770bc4997 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:6a3678e9bb481ee585c32c8d374efad5e63d49d8a9d19daec397fabeba45cc8c +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:a83a91a5c38b1c67fe12dde2ddd5bc03f094f098a100499b8140426a7901e36e ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From a1cfc9925356831035112bde2d0ae4a4fe1c1b10 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 09:49:21 +0100 Subject: [PATCH 368/409] chore: deps(updatecli): Bump updatecli version to v0.110.1 (#2482) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 2b57a8a12..55a79c84a 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.110.0 \ No newline at end of file +updatecli v0.110.1 \ No newline at end of file From b77259af3ae89a9193eca2f8c3ee35d478d47a5d Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 10 Nov 2025 10:19:57 +0100 Subject: [PATCH 369/409] docs: don't use open ended versions in supported technologies (#2479) * docs: don't use open ended versions in supported technologies Also add missing entry for psycopg. * Fix a couple of nits --- docs/reference/supported-technologies.md | 73 +++++++++++++++--------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/docs/reference/supported-technologies.md b/docs/reference/supported-technologies.md index 437928d6f..47ee5c12b 100644 --- a/docs/reference/supported-technologies.md +++ b/docs/reference/supported-technologies.md @@ -82,28 +82,28 @@ We support these Flask versions: We support these aiohttp versions: -* 3.0+ +* 3.x ### Tornado [supported-tornado] We support these tornado versions: -* 6.0+ +* 6.x ### Sanic [supported-sanic] We support these sanic versions: -* 20.12.2+ +* >20.12.2,<26 ### Starlette/FastAPI [supported-starlette] We support these Starlette versions: -* 0.13.0+ +* >0.13.0,<1 Any FastAPI version which uses a supported Starlette version should also be supported. @@ -112,7 +112,7 @@ Any FastAPI version which uses a supported Starlette version should also be supp We support these `grpcio` versions: -* 1.24.0+ +* >1.24.0,<2 ## Automatic Instrumentation [automatic-instrumentation] @@ -180,7 +180,7 @@ Collected trace data: #### MySQLdb [automatic-instrumentation-db-mysql] -Library: `MySQLdb` +Library: `MySQLdb` (`<2`) Instrumented methods: @@ -195,7 +195,7 @@ Collected trace data: #### mysql-connector [automatic-instrumentation-db-mysql-connector] -Library: `mysql-connector-python` +Library: `mysql-connector-python` (`<9`) Instrumented methods: @@ -210,7 +210,7 @@ Collected trace data: #### pymysql [automatic-instrumentation-db-pymysql] -Library: `pymysql` +Library: `pymysql` (`<2`) Instrumented methods: @@ -225,7 +225,7 @@ Collected trace data: #### aiomysql [automatic-instrumentation-db-aiomysql] -Library: `aiomysql` +Library: `aiomysql` (`<1`) Instrumented methods: @@ -236,9 +236,9 @@ Collected trace data: * parametrized SQL query -#### PostgreSQL [automatic-instrumentation-db-postgres] +#### PostgreSQL Psycopg2 [automatic-instrumentation-db-postgres] -Library: `psycopg2`, `psycopg2-binary` (`>=2.9`) +Library: `psycopg2`, `psycopg2-binary` (`>=2.9,<3`) Instrumented methods: @@ -250,10 +250,23 @@ Collected trace data: * parametrized SQL query +#### PostgreSQL Psycopg [automatic-instrumentation-db-postgres-psycopg] + +Library: `psycopg`, `psycopg-binary` (`>3.0.0,<4`) + +Instrumented methods: + +* `psycopg.connect` + +The instrumented `connect` method returns a wrapped connection/cursor which instruments the actual `Cursor.execute` calls. + +Collected trace data: + +* parametrized SQL query #### aiopg [automatic-instrumentation-db-aiopg] -Library: `aiopg` (`>=1.0`) +Library: `aiopg` (`>=1.0,<2`) Instrumented methods: @@ -267,7 +280,7 @@ Collected trace data: #### asyncpg [automatic-instrumentation-db-asyncg] -Library: `asyncpg` (`>=0.20`) +Library: `asyncpg` (`>=0.20,<2`) Instrumented methods: @@ -281,7 +294,7 @@ Collected trace data: #### PyODBC [automatic-instrumentation-db-pyodbc] -Library: `pyodbc`, (`>=4.0`) +Library: `pyodbc` (`>=4.0,<6`) Instrumented methods: @@ -296,7 +309,7 @@ Collected trace data: #### MS-SQL [automatic-instrumentation-db-mssql] -Library: `pymssql`, (`>=2.1.0`) +Library: `pymssql` (`>=2.1.0,<3`) Instrumented methods: @@ -311,7 +324,7 @@ Collected trace data: #### MongoDB [automatic-instrumentation-db-mongodb] -Library: `pymongo`, `>=2.9,<3.8` +Library: `pymongo` (`>=2.9,<5`) Instrumented methods: @@ -355,7 +368,7 @@ Collected trace data: #### Redis [automatic-instrumentation-db-redis] -Library: `redis` (`>=2.8`) +Library: `redis` (`>=2.8,<8`) Instrumented methods: @@ -369,7 +382,7 @@ Collected trace data: #### aioredis [automatic-instrumentation-db-aioredis] -Library: `aioredis` (`<2.0`) +Library: `aioredis` (`<=2.0.1`) Instrumented methods: @@ -398,7 +411,7 @@ Collected trace data: #### Python Memcache [automatic-instrumentation-db-python-memcache] -Library: `python-memcached` (`>=1.51`) +Library: `python-memcached` (`>=1.51,<2`) Instrumented methods: @@ -429,7 +442,7 @@ Collected trace data: #### pymemcache [automatic-instrumentation-db-pymemcache] -Library: `pymemcache` (`>=3.0`) +Library: `pymemcache` (`>=3.0,<4.1`) Instrumented methods: @@ -463,13 +476,13 @@ Collected trace data: #### kafka-python [automatic-instrumentation-db-kafka-python] -Library: `kafka-python` (`>=2.0`) +Library: `kafka-python` (`>=2.0,<3`) Instrumented methods: * `kafka.KafkaProducer.send`, * `kafka.KafkaConsumer.poll`, -* `kafka.KafkaConsumer.\__next__` +* `kafka.KafkaConsumer.__next__` Collected trace data: @@ -482,11 +495,11 @@ Collected trace data: #### Standard library [automatic-instrumentation-stdlib-urllib] -Library: `urllib2` (Python 2) / `urllib.request` (Python 3) +Library: `urllib.request` (Python 3) Instrumented methods: -* `urllib2.AbstractHTTPHandler.do_open` / `urllib.request.AbstractHTTPHandler.do_open` +* `urllib.request.AbstractHTTPHandler.do_open` Collected trace data: @@ -496,7 +509,7 @@ Collected trace data: #### urllib3 [automatic-instrumentation-urllib3] -Library: `urllib3` +Library: `urllib3` (`<3`) Instrumented methods: @@ -517,6 +530,8 @@ Collected trace data: #### requests [automatic-instrumentation-requests] +Library: `requests` (`<3`) + Instrumented methods: * `requests.sessions.Session.send` @@ -529,6 +544,8 @@ Collected trace data: #### AIOHTTP Client [automatic-instrumentation-aiohttp-client] +Library: `aiohttp` (`>=3,<4`) + Instrumented methods: * `aiohttp.client.ClientSession._request` @@ -541,6 +558,8 @@ Collected trace data: #### httpx [automatic-instrumentation-httpx] +Library: `httpx` (`<1`) + Instrumented methods: * `httpx.Client.send @@ -556,7 +575,7 @@ Collected trace data: #### AWS Boto3 / Botocore [automatic-instrumentation-boto3] -Library: `boto3` (`>=1.0`) +Library: `boto3` (`>=1.0,<2`) Instrumented methods: @@ -573,7 +592,7 @@ Additionally, some services collect more specific data #### AWS Aiobotocore [automatic-instrumentation-aiobotocore] -Library: `aiobotocore` (`>=2.2.0`) +Library: `aiobotocore` (`>=2.2.0,<3`) Instrumented methods: From 69a082641ddb3c47c819cefb9d20a0b9ffe45697 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Tue, 11 Nov 2025 09:43:21 +0100 Subject: [PATCH 370/409] chore: deps(updatecli): Bump updatecli version to v0.110.2 (#2484) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 55a79c84a..94ceda344 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.110.1 \ No newline at end of file +updatecli v0.110.2 \ No newline at end of file From 8f437ce87f43069a11008fe7b08fdf22f64f1a89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 12 Nov 2025 11:00:53 +0100 Subject: [PATCH 371/409] build(deps): bump docker/metadata-action (#2481) Bumps the github-actions group with 1 update in the / directory: [docker/metadata-action](https://github.com/docker/metadata-action). Updates `docker/metadata-action` from 5.8.0 to 5.9.0 - [Release notes](https://github.com/docker/metadata-action/releases) - [Commits](https://github.com/docker/metadata-action/compare/c1e51972afc2121e065aed6d45c65596fe445f3f...318604b99e75e41977312d83839a89be02ca4893) --- updated-dependencies: - dependency-name: docker/metadata-action dependency-version: 5.9.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8c3b031cf..ca868dd61 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -137,7 +137,7 @@ jobs: - name: Extract metadata (tags, labels) id: docker-meta - uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0 + uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0 with: images: ${{ env.DOCKER_IMAGE_NAME }} tags: | From ad270d3bab5b12b68bfcc37030a7bf16b60b50f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 09:31:47 +0100 Subject: [PATCH 372/409] build(deps): bump certifi from 2025.10.5 to 2025.11.12 in /dev-utils (#2487) Bumps [certifi](https://github.com/certifi/python-certifi) from 2025.10.5 to 2025.11.12. - [Commits](https://github.com/certifi/python-certifi/compare/2025.10.05...2025.11.12) --- updated-dependencies: - dependency-name: certifi dependency-version: 2025.11.12 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dev-utils/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-utils/requirements.txt b/dev-utils/requirements.txt index 2d5856cc8..55365dff2 100644 --- a/dev-utils/requirements.txt +++ b/dev-utils/requirements.txt @@ -1,4 +1,4 @@ # These are the pinned requirements for the lambda layer/docker image -certifi==2025.10.5 +certifi==2025.11.12 urllib3==1.26.20 wrapt==1.14.1 From a8703f238ece9a92a277c3a56899ba2b77cf5de2 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Tue, 18 Nov 2025 09:36:37 +0100 Subject: [PATCH 373/409] chore: deps(updatecli): Bump updatecli version to v0.110.3 (#2485) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 94ceda344..c82f19a6d 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.110.2 \ No newline at end of file +updatecli v0.110.3 \ No newline at end of file From 2e2e1041fb609e2eab9801bf0c996b166c1761e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Nov 2025 16:57:21 +0100 Subject: [PATCH 374/409] build(deps): bump wolfi/chainguard-base from `a83a91a` to `c14a89d` (#2483) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `a83a91a` to `c14a89d`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 770bc4997..53f2a0a37 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:a83a91a5c38b1c67fe12dde2ddd5bc03f094f098a100499b8140426a7901e36e +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:c14a89d20a7d8c8159a0a915be57b09a6267ad64c1e9b830c34be3ab85f0b5c2 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From eea079618d424616c74d69817d10c15b77ace84b Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Thu, 20 Nov 2025 09:25:30 +0100 Subject: [PATCH 375/409] psycopg/psycopg_async: fix handling of ServerCursor / AsyncServerCursor (#2489) ServerCursor and AsyncServerCursor execute method has a different interface than Cursor / AsyncCursor. So implement one that works with both in our ProxyCursor. --- .../instrumentation/packages/asyncio/psycopg_async.py | 4 ++-- elasticapm/instrumentation/packages/psycopg.py | 4 ++-- tests/instrumentation/asyncio_tests/psycopg_tests.py | 7 +++++++ tests/instrumentation/psycopg_tests.py | 9 +++++++++ 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/elasticapm/instrumentation/packages/asyncio/psycopg_async.py b/elasticapm/instrumentation/packages/asyncio/psycopg_async.py index fbcfb93fc..dda6db1dd 100644 --- a/elasticapm/instrumentation/packages/asyncio/psycopg_async.py +++ b/elasticapm/instrumentation/packages/asyncio/psycopg_async.py @@ -55,8 +55,8 @@ def _bake_sql(self, sql): def extract_signature(self, sql): return extract_signature(sql) - async def execute(self, query, params=None, *, prepare=None, binary=None, **kwargs): - return await self._trace_sql(self.__wrapped__.execute, query, params, prepare=prepare, binary=binary, **kwargs) + async def execute(self, query, params=None, **kwargs): + return await self._trace_sql(self.__wrapped__.execute, query, params, **kwargs) async def executemany(self, query, params_seq, **kwargs): return await self._trace_sql(self.__wrapped__.executemany, query, params_seq, **kwargs) diff --git a/elasticapm/instrumentation/packages/psycopg.py b/elasticapm/instrumentation/packages/psycopg.py index 3a0409c96..0d79cf686 100644 --- a/elasticapm/instrumentation/packages/psycopg.py +++ b/elasticapm/instrumentation/packages/psycopg.py @@ -55,8 +55,8 @@ def _bake_sql(self, sql): def extract_signature(self, sql): return extract_signature(sql) - def execute(self, query, params=None, *, prepare=None, binary=None, **kwargs): - return self._trace_sql(self.__wrapped__.execute, query, params, prepare=prepare, binary=binary, **kwargs) + def execute(self, query, params=None, **kwargs): + return self._trace_sql(self.__wrapped__.execute, query, params, **kwargs) def executemany(self, query, params_seq, **kwargs): return self._trace_sql(self.__wrapped__.executemany, query, params_seq, **kwargs) diff --git a/tests/instrumentation/asyncio_tests/psycopg_tests.py b/tests/instrumentation/asyncio_tests/psycopg_tests.py index 5fbe07c48..7dbc31438 100644 --- a/tests/instrumentation/asyncio_tests/psycopg_tests.py +++ b/tests/instrumentation/asyncio_tests/psycopg_tests.py @@ -119,3 +119,10 @@ async def test_executemany(instrument, postgres_connection, elasticapm_client): assert span["action"] == "query" assert span["sync"] == False assert span["name"] == "INSERT INTO test" + + +async def test_server_cursor_execute(instrument, postgres_connection, elasticapm_client): + cursor = postgres_connection.cursor(name="async_server_cursor") + assert isinstance(cursor, psycopg.AsyncServerCursor) + record = await cursor.execute(query="SELECT 1", params=None, binary=None) + assert record diff --git a/tests/instrumentation/psycopg_tests.py b/tests/instrumentation/psycopg_tests.py index e744663f7..f53565b34 100644 --- a/tests/instrumentation/psycopg_tests.py +++ b/tests/instrumentation/psycopg_tests.py @@ -279,3 +279,12 @@ def test_psycopg_connection(instrument, elasticapm_transaction, postgres_connect host = os.environ.get("POSTGRES_HOST", "localhost") assert span["name"] == f"psycopg.connect {host}:5432" assert span["action"] == "connect" + + +@pytest.mark.integrationtest +@pytest.mark.skipif(not has_postgres_configured, reason="PostgresSQL not configured") +def test_server_cursor_execute(instrument, postgres_connection, elasticapm_client): + cursor = postgres_connection.cursor(name="server_cursor") + assert isinstance(cursor, psycopg.ServerCursor) + record = cursor.execute(query="SELECT 1", params=None, binary=True) + assert record From 42462b9b0c5ff29453583cb54ff364fd50edd08b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Nov 2025 14:37:34 +0100 Subject: [PATCH 376/409] build(deps): bump wolfi/chainguard-base from `c14a89d` to `2539782` (#2491) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `c14a89d` to `2539782`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 53f2a0a37..f9556980f 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:c14a89d20a7d8c8159a0a915be57b09a6267ad64c1e9b830c34be3ab85f0b5c2 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:25397820ef168d951339e69cedb5460f354cea842e2af367e33b89e988c51615 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 9e13739fa6490cc70db7c9adfdabce915d64f942 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 21 Nov 2025 09:17:00 +0100 Subject: [PATCH 377/409] update CHANGELOG and bump version to 6.24.1 (#2490) --- CHANGELOG.asciidoc | 9 +++++++++ docs/release-notes/index.md | 8 ++++++++ elasticapm/version.py | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 484269c89..d766ca483 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -32,6 +32,15 @@ endif::[] [[release-notes-6.x]] === Python Agent version 6.x +[[release-notes-6.24.1]] +==== 6.24.1 - 2025-11-21 + +[float] +===== Bug fixes + +* Fix handling of psycopg ServerCursor and AsyncServerCursor instrumentation {pull}2489[#2489] +* Fix contrib/opentelemetry set_status to match base signature {pull}2457[#2457] + [[release-notes-6.24.0]] ==== 6.24.0 - 2025-08-12 diff --git a/docs/release-notes/index.md b/docs/release-notes/index.md index 0424a4b5c..dda90b393 100644 --- a/docs/release-notes/index.md +++ b/docs/release-notes/index.md @@ -26,6 +26,14 @@ To check for security updates, go to [Security announcements for the Elastic sta % ### Fixes [elastic-apm-python-agent-versionext-fixes] +## 6.24.1 [elastic-apm-python-agent-6241-release-notes] +**Release date:** November 21, 2025 + +### Fixes [elastic-apm-python-agent-6241-fixes] + +* Fix handling of psycopg ServerCursor and AsyncServerCursor instrumentation [#2489](https://github.com/elastic/apm-agent-python/pull/2489) +* Fix contrib/opentelemetry `set_status` to match base signature [#2457](https://github.com/elastic/apm-agent-python/pull/2457) + ## 6.24.0 [elastic-apm-python-agent-6240-release-notes] **Release date:** August 12, 2025 diff --git a/elasticapm/version.py b/elasticapm/version.py index a0a2626df..bea3b5f0d 100644 --- a/elasticapm/version.py +++ b/elasticapm/version.py @@ -28,5 +28,5 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = (6, 24, 0) +__version__ = (6, 24, 1) VERSION = ".".join(map(str, __version__)) From 6c4a5b7a5c0c3b9428bf06a002daaf9523fd1077 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Fri, 21 Nov 2025 10:09:48 +0100 Subject: [PATCH 378/409] docs: bump lambda layers version to latest 6-24-1:1 (#2492) --- docs/reference/lambda-support.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/lambda-support.md b/docs/reference/lambda-support.md index 354556870..839c4b8c2 100644 --- a/docs/reference/lambda-support.md +++ b/docs/reference/lambda-support.md @@ -9,8 +9,8 @@ applies_to: apm_agent_python: ga sub: apm-lambda-ext-v: ver-1-6-0 - apm-python-v: ver-6-24-0 - apm-python-layer-v: 3 + apm-python-v: ver-6-24-1 + apm-python-layer-v: 1 --- # Monitoring AWS Lambda Python Functions [lambda-support] From a8f6fe4bb5126ef055bff1ddb1e59f781ee42811 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Nov 2025 14:21:27 +0100 Subject: [PATCH 379/409] build(deps): bump wolfi/chainguard-base from `2539782` to `775e340` (#2494) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `2539782` to `775e340`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index f9556980f..dc626ac7a 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:25397820ef168d951339e69cedb5460f354cea842e2af367e33b89e988c51615 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:775e340540cbf2fbf81f406f0191824f3c691f2613d8d51da1f992eb26aec82b ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 846b03323c63f5bbc88d4fe6041dc3aded3a669b Mon Sep 17 00:00:00 2001 From: Francisco Ramon Date: Fri, 21 Nov 2025 16:27:43 +0100 Subject: [PATCH 380/409] Let's ensure the path of hte file (#2493) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ca868dd61..ea4a58d83 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -100,7 +100,7 @@ jobs: if: startsWith(github.ref, 'refs/tags') with: name: arn-file - path: ".arn-file.md" + path: "${{ github.workspace }}/.arn-file.md" if-no-files-found: error publish-docker: From c131408a5ebbc8afd6649f735a905efec00bb5d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 12:37:44 +0100 Subject: [PATCH 381/409] build(deps): bump actions/checkout (#2496) Bumps the github-actions group with 1 update in the / directory: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 5 to 6 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-images.yml | 2 +- .github/workflows/packages.yml | 2 +- .github/workflows/release.yml | 12 ++++++------ .github/workflows/run-matrix.yml | 2 +- .github/workflows/test-fips.yml | 4 ++-- .github/workflows/test.yml | 8 ++++---- .github/workflows/updatecli.yml | 2 +- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-images.yml b/.github/workflows/build-images.yml index 7d5f1c78b..7683ef02e 100644 --- a/.github/workflows/build-images.yml +++ b/.github/workflows/build-images.yml @@ -19,7 +19,7 @@ jobs: IMAGE_NAME: ${{ github.repository }}/apm-agent-python-testing steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Login to ghcr.io uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 974ca3d8f..cb4c268c7 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -20,5 +20,5 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: ./.github/actions/packages diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ea4a58d83..bffedc695 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: contents: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: ./.github/actions/build-distribution - name: generate build provenance uses: actions/attest-build-provenance@v3 @@ -40,7 +40,7 @@ jobs: contents: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: ./.github/actions/packages - name: generate build provenance uses: actions/attest-build-provenance@v3 @@ -56,7 +56,7 @@ jobs: permissions: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/download-artifact@v6 with: name: packages @@ -80,7 +80,7 @@ jobs: - build-distribution runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/download-artifact@v6 with: name: build-distribution @@ -118,7 +118,7 @@ jobs: env: DOCKER_IMAGE_NAME: docker.elastic.co/observability/apm-agent-python steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Set up Docker Buildx uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 @@ -174,7 +174,7 @@ jobs: if: startsWith(github.ref, 'refs/tags') runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: actions/download-artifact@v6 with: name: arn-file diff --git a/.github/workflows/run-matrix.yml b/.github/workflows/run-matrix.yml index 78aacfe21..118a898b5 100644 --- a/.github/workflows/run-matrix.yml +++ b/.github/workflows/run-matrix.yml @@ -25,7 +25,7 @@ jobs: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }}/apm-agent-python-testing steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Run tests run: ./tests/scripts/docker/run_tests.sh ${{ matrix.version }} ${{ matrix.framework }} env: diff --git a/.github/workflows/test-fips.yml b/.github/workflows/test-fips.yml index ca387979a..4c77c98f9 100644 --- a/.github/workflows/test-fips.yml +++ b/.github/workflows/test-fips.yml @@ -16,7 +16,7 @@ jobs: outputs: matrix: ${{ steps.generate.outputs.matrix }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - id: generate uses: elastic/oblt-actions/version-framework@v1 with: @@ -40,7 +40,7 @@ jobs: max-parallel: 10 matrix: ${{ fromJSON(needs.create-matrix.outputs.matrix) }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: check that python has fips mode enabled run: | python3 -c 'import _hashlib; assert _hashlib.get_fips_mode() == 1' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eea489de0..980f011f2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -48,7 +48,7 @@ jobs: if: ${{ !inputs.skip-build }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: ./.github/actions/build-distribution @@ -59,7 +59,7 @@ jobs: data: ${{ steps.split.outputs.data }} chunks: ${{ steps.split.outputs.chunks }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: ref: ${{ inputs.ref || github.ref }} - id: generate @@ -142,7 +142,7 @@ jobs: FRAMEWORK: ${{ matrix.framework }} ASYNCIO: ${{ matrix.asyncio }} steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: ref: ${{ inputs.ref || github.ref }} - uses: actions/setup-python@v6 @@ -199,7 +199,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 with: ref: ${{ inputs.ref || github.ref }} diff --git a/.github/workflows/updatecli.yml b/.github/workflows/updatecli.yml index 42a76d272..8897f4c52 100644 --- a/.github/workflows/updatecli.yml +++ b/.github/workflows/updatecli.yml @@ -15,7 +15,7 @@ jobs: contents: read packages: read steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Get token id: get_token From 1cf1a21f3b76582c773b56eab4bff17b89d17ccb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 12:34:08 +0100 Subject: [PATCH 382/409] build(deps): bump wolfi/chainguard-base from `775e340` to `2539782` (#2497) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `775e340` to `2539782`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index dc626ac7a..f9556980f 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:775e340540cbf2fbf81f406f0191824f3c691f2613d8d51da1f992eb26aec82b +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:25397820ef168d951339e69cedb5460f354cea842e2af367e33b89e988c51615 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From d8637aba0c83b1377ca2c260933552d145a003dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 09:56:57 +0100 Subject: [PATCH 383/409] build(deps): bump docker/metadata-action (#2500) Bumps the github-actions group with 1 update in the / directory: [docker/metadata-action](https://github.com/docker/metadata-action). Updates `docker/metadata-action` from 5.9.0 to 5.10.0 - [Release notes](https://github.com/docker/metadata-action/releases) - [Commits](https://github.com/docker/metadata-action/compare/318604b99e75e41977312d83839a89be02ca4893...c299e40c65443455700f0fdfc63efafe5b349051) --- updated-dependencies: - dependency-name: docker/metadata-action dependency-version: 5.10.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bffedc695..82e39bb71 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -137,7 +137,7 @@ jobs: - name: Extract metadata (tags, labels) id: docker-meta - uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0 + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0 with: images: ${{ env.DOCKER_IMAGE_NAME }} tags: | From 30c4075a7342efbe61c2f7085d93bfbceb75daf2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 12:28:40 +0100 Subject: [PATCH 384/409] build(deps): bump alpine from `4b7ce07` to `51183f2` (#2503) Bumps alpine from `4b7ce07` to `51183f2`. --- updated-dependencies: - dependency-name: alpine dependency-version: 51183f2cfa6320055da30872f211093f9ff1d3cf06f39a0bdb212314c5dc7375 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3ae80cf1e..1798fa6e8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,3 @@ -FROM alpine@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412 +FROM alpine@sha256:51183f2cfa6320055da30872f211093f9ff1d3cf06f39a0bdb212314c5dc7375 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 1750a1dcb9887965c8c85ed815ff7b86b5f62270 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Fri, 5 Dec 2025 08:50:33 +0100 Subject: [PATCH 385/409] chore: deps(updatecli): Bump updatecli version to v0.111.0 (#2505) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index c82f19a6d..b423ac5ee 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.110.3 \ No newline at end of file +updatecli v0.111.0 \ No newline at end of file From 648084b779dbff43858e6d05aff10ca2c9afb9ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Dec 2025 15:05:05 +0100 Subject: [PATCH 386/409] build(deps): bump wolfi/chainguard-base from `2539782` to `1038c51` (#2507) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `2539782` to `1038c51`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index f9556980f..d9b675571 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:25397820ef168d951339e69cedb5460f354cea842e2af367e33b89e988c51615 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:1038c51d3e88154a59aea6476f19030ceca257cddcae66abe1d047de7a5ce578 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From bbaa3bbd5fd4ca1e10eb31cf997413c7bd4581c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 11:19:51 +0000 Subject: [PATCH 387/409] build(deps): bump wolfi/chainguard-base from `1038c51` to `2b179e1` (#2508) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `1038c51` to `2b179e1`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index d9b675571..199a2ddde 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:1038c51d3e88154a59aea6476f19030ceca257cddcae66abe1d047de7a5ce578 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:2b179e1fe69c672bd0844147c6ebb039adb44ddaa3f9b4695f4915a9447da438 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From f5a12f8f301360a8a50234be65bf5a588d643c0a Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 16 Dec 2025 09:41:09 +0100 Subject: [PATCH 388/409] tornado: handle behaviour change in request.headers protocol (#2512) In 6.5.3 tornado made the __in__ protocol for the request headers case sensitive. Problem is that the code extracting the traceparent header name is using a lowercase string for the header name. So start normalizing all the headers keys to lowercase so that we are able to get the traceparent again. --- elasticapm/instrumentation/packages/tornado.py | 4 +++- tests/contrib/asyncio/tornado/tornado_tests.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/elasticapm/instrumentation/packages/tornado.py b/elasticapm/instrumentation/packages/tornado.py index 2f0ed04a0..c1f829bc1 100644 --- a/elasticapm/instrumentation/packages/tornado.py +++ b/elasticapm/instrumentation/packages/tornado.py @@ -71,7 +71,9 @@ async def call(self, module, method, wrapped, instance, args, kwargs): client = instance.application.elasticapm_client should_ignore = client.should_ignore_url(request.path) if not should_ignore: - trace_parent = TraceParent.from_headers(request.headers) + # In tornado 6.5.3 the __in__ protocol for the headers is case-sensitive so we need to normalize them + normalized_headers = {k.lower(): v for k, v in request.headers.items()} + trace_parent = TraceParent.from_headers(normalized_headers) client.begin_transaction("request", trace_parent=trace_parent) elasticapm.set_context( lambda: get_data_from_request(instance, request, client.config, constants.TRANSACTION), "request" diff --git a/tests/contrib/asyncio/tornado/tornado_tests.py b/tests/contrib/asyncio/tornado/tornado_tests.py index 3ce3bafed..499a28550 100644 --- a/tests/contrib/asyncio/tornado/tornado_tests.py +++ b/tests/contrib/asyncio/tornado/tornado_tests.py @@ -177,7 +177,7 @@ async def test_traceparent_handling(app, base_url, http_client): assert transaction["trace_id"] == "0af7651916cd43dd8448eb211c80319c" assert transaction["parent_id"] == "b7ad6b7169203331" - assert "foo=bar,bar=baz,baz=bazzinga" == wrapped_from_string.call_args[0][0]["TraceState"] + assert "foo=bar,bar=baz,baz=bazzinga" == wrapped_from_string.call_args[0][0]["tracestate"] @pytest.mark.gen_test From f8425150306e56179b6a714c304b936f42768b91 Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 09:46:45 +0100 Subject: [PATCH 389/409] chore: deps(updatecli): Bump updatecli version to v0.112.0 (#2510) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Co-authored-by: Riccardo Magliocchetti --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index b423ac5ee..787346ef0 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.111.0 \ No newline at end of file +updatecli v0.112.0 \ No newline at end of file From 43f4deb8aca228877480c9e8b62696aea7883dce Mon Sep 17 00:00:00 2001 From: Md Adil Date: Wed, 17 Dec 2025 16:16:23 +0530 Subject: [PATCH 390/409] =?UTF-8?q?updated=20the=20=5F=5Fall=5F=5F=20secti?= =?UTF-8?q?on=20to=20include=20all=20=2022=20publicly=20exposed=20varia?= =?UTF-8?q?=E2=80=A6=20(#2504)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * updated the __all__ section to include all 22 publicly exposed variables * Reorganized the __all__ tuple in alphabetical --------- Co-authored-by: Riccardo Magliocchetti --- elasticapm/__init__.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/elasticapm/__init__.py b/elasticapm/__init__.py index 4c19b0a17..b6c4499c4 100644 --- a/elasticapm/__init__.py +++ b/elasticapm/__init__.py @@ -49,7 +49,6 @@ ) from elasticapm.utils.disttracing import trace_parent_from_headers, trace_parent_from_string # noqa: F401 -__all__ = ("VERSION", "Client") _activation_method = None @@ -66,3 +65,28 @@ raise DeprecationWarning("The Elastic APM agent requires Python 3.6+") from elasticapm.contrib.asyncio.traces import async_capture_span # noqa: F401 E402 + +__all__ = ( + "Client", + "VERSION", + "async_capture_span", + "capture_serverless", + "capture_span", + "get_client", + "get_span_id", + "get_trace_id", + "get_trace_parent_header", + "get_transaction_id", + "instrument", + "label", + "set_context", + "set_custom_context", + "set_transaction_name", + "set_transaction_outcome", + "set_transaction_result", + "set_user_context", + "setup_logging", + "trace_parent_from_headers", + "trace_parent_from_string", + "uninstrument", +) From cf6fbf2ecbdd4ee3165413ce5a56d45eccffbe39 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 22 Dec 2025 09:44:46 +0100 Subject: [PATCH 391/409] elasticapm: introduce ELASTIC_APM_SKIP_SERVER_INFO (#2516) Introduce this option in order to avoid fetch the apm server info to get its version, off by default. Calling that endpoint with the aws lambda extension incurs in a measurable latency on AWS Lambda. Enabling this issue requires the receiving APM server version to be greater than 8.7.0. Option is still in technical preview. --- docs/reference/configuration.md | 17 +++++++++++++++++ elasticapm/conf/__init__.py | 1 + elasticapm/transport/http.py | 6 +++++- tests/transports/test_urllib3.py | 17 +++++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 5934f3b6e..1841631c7 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -969,6 +969,23 @@ If set to `True`, the agent will intercept the default `sys.excepthook`, which a Whether each transaction should have the process arguments attached. Disabled by default to save disk space. +### `skip_server_info` [config-skip-server-info] + +| Environment | Django/Flask | Default | +| --- | --- | --- | +| `ELASTIC_APM_SKIP_SERVER_INFO` | `SKIP_SERVER_INFO` | `False` | + +Whether we should skip the server info check to save some latency on constrained environments like AWS Lambda. Disabled by default. + +::::{warning} +This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features. +:::: + +::::{warning} +This requires sending data to an APM Server newer than 8.7.0 in order to work properly. +:::: + + ## Django-specific configuration [config-django-specific] diff --git a/elasticapm/conf/__init__.py b/elasticapm/conf/__init__.py index 6d19eb96c..f00a2cda9 100644 --- a/elasticapm/conf/__init__.py +++ b/elasticapm/conf/__init__.py @@ -743,6 +743,7 @@ class Config(_ConfigBase): default=TRACE_CONTINUATION_STRATEGY.CONTINUE, ) include_process_args = _BoolConfigValue("INCLUDE_PROCESS_ARGS", default=False) + skip_server_info = _BoolConfigValue("SKIP_SERVER_INFO", default=False) @property def is_recording(self): diff --git a/elasticapm/transport/http.py b/elasticapm/transport/http.py index ed132068c..cd960e40f 100644 --- a/elasticapm/transport/http.py +++ b/elasticapm/transport/http.py @@ -203,7 +203,11 @@ def _get_cache_control_max_age(self, response_headers): def _process_queue(self) -> None: if not self.client.server_version: - self.fetch_server_info() + # this is useful on aws lambda environments where this call incurs in unwanted latency + if self.client.config.skip_server_info: + logger.debug("Skipping to fetch server info") + else: + self.fetch_server_info() super()._process_queue() def fetch_server_info(self) -> None: diff --git a/tests/transports/test_urllib3.py b/tests/transports/test_urllib3.py index 32a5b7384..78bc26200 100644 --- a/tests/transports/test_urllib3.py +++ b/tests/transports/test_urllib3.py @@ -520,6 +520,23 @@ def test_fetch_server_info_flat_string(waiting_httpserver, caplog, elasticapm_cl assert_any_record_contains(caplog.records, "No version key found in server response") +def test_skip_server_info(waiting_httpserver, elasticapm_client): + elasticapm_client.config.update(version="1", skip_server_info=True) + waiting_httpserver.serve_content(code=202, content="", headers={"Location": "http://example.com/foo"}) + transport = Transport( + waiting_httpserver.url, client=elasticapm_client, headers=elasticapm_client._transport._headers + ) + transport.start_thread() + try: + url = transport.send("x".encode("latin-1")) + assert url == "http://example.com/foo" + finally: + transport.close() + + assert elasticapm_client.server_version is None + assert elasticapm_client.check_server_version(gte=(8, 7, 1)) + + def test_close(waiting_httpserver, elasticapm_client): elasticapm_client.server_version = (8, 0, 0) # avoid making server_info request waiting_httpserver.serve_content(code=202, content="", headers={"Location": "http://example.com/foo"}) From d962042ee6e7a7f9892e9fdfd2190f12598e0c58 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 09:39:14 +0000 Subject: [PATCH 392/409] build(deps): bump alpine from `51183f2` to `865b95f` (#2514) Bumps alpine from `51183f2` to `865b95f`. --- updated-dependencies: - dependency-name: alpine dependency-version: 865b95f46d98cf867a156fe4a135ad3fe50d2056aa3f25ed31662dff6da4eb62 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Riccardo Magliocchetti --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 1798fa6e8..0a8d60877 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,3 @@ -FROM alpine@sha256:51183f2cfa6320055da30872f211093f9ff1d3cf06f39a0bdb212314c5dc7375 +FROM alpine@sha256:865b95f46d98cf867a156fe4a135ad3fe50d2056aa3f25ed31662dff6da4eb62 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From f425c427ba226aa64a80d6582a43fd5ce175366f Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 23 Dec 2025 09:17:16 +0100 Subject: [PATCH 393/409] update CHANGELOG and bump version to 6.25.0 (#2518) --- CHANGELOG.asciidoc | 14 ++++++++++++++ docs/reference/configuration.md | 4 ++++ docs/release-notes/index.md | 12 ++++++++++++ elasticapm/version.py | 2 +- 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index d766ca483..5a6bed978 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -32,6 +32,20 @@ endif::[] [[release-notes-6.x]] === Python Agent version 6.x +[[release-notes-6.25.0]] +==== 6.25.0 - 2025-12-23 + +[float] +===== Features + +* Introduce `ELASTIC_APM_SKIP_SERVER_INFO` to reduce overhead on serverless with APM server 8.7.1+ {pull}2516[#2516] +* List all exported symbols in elasticapm module `__all__` {pull}2504[#2504] + +[float] +===== Bug fixes + +* Handle Tornado 6.5.3 `HttpHeaders` `in` operator behavior change {pull}2512[#2512] + [[release-notes-6.24.1]] ==== 6.24.1 - 2025-11-21 diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 1841631c7..cdc0c744e 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -971,6 +971,10 @@ Whether each transaction should have the process arguments attached. Disabled by ### `skip_server_info` [config-skip-server-info] +```{applies_to} +apm_agent_python: preview 6.25.0 +``` + | Environment | Django/Flask | Default | | --- | --- | --- | | `ELASTIC_APM_SKIP_SERVER_INFO` | `SKIP_SERVER_INFO` | `False` | diff --git a/docs/release-notes/index.md b/docs/release-notes/index.md index dda90b393..d29cec09c 100644 --- a/docs/release-notes/index.md +++ b/docs/release-notes/index.md @@ -26,6 +26,18 @@ To check for security updates, go to [Security announcements for the Elastic sta % ### Fixes [elastic-apm-python-agent-versionext-fixes] +## 6.25.0 [elastic-apm-python-agent-6250-release-notes] +**Release date:** December 23, 2025 + +### Features and enhancements [elastic-apm-python-agent-6250-features-enhancements] + +* Introduce `ELASTIC_APM_SKIP_SERVER_INFO` to reduce overhead on serverless with APM server 8.7.1+ [#2516](https://github.com/elastic/apm-agent-python/pull/2516) +* List all exported symbols in elasticapm module `__all__` [#2504](https://github.com/elastic/apm-agent-python/pull/2504) + +### Fixes [elastic-apm-python-agent-6250-fixes] + +* Handle Tornado 6.5.3 `HttpHeaders` `in` operator behavior change [#2512](https://github.com/elastic/apm-agent-python/pull/2512) + ## 6.24.1 [elastic-apm-python-agent-6241-release-notes] **Release date:** November 21, 2025 diff --git a/elasticapm/version.py b/elasticapm/version.py index bea3b5f0d..c55a11c61 100644 --- a/elasticapm/version.py +++ b/elasticapm/version.py @@ -28,5 +28,5 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -__version__ = (6, 24, 1) +__version__ = (6, 25, 0) VERSION = ".".join(map(str, __version__)) From 564aae31e44e8926c2a6f1c35761e0dc19ae2b3d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 Dec 2025 13:14:05 +0100 Subject: [PATCH 394/409] build(deps): bump the github-actions group across 3 directories with 3 updates (#2517) Bumps the github-actions group with 3 updates in the / directory: [actions/download-artifact](https://github.com/actions/download-artifact), [actions/upload-artifact](https://github.com/actions/upload-artifact) and [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action). Bumps the github-actions group with 1 update in the /.github/actions/build-distribution directory: [actions/upload-artifact](https://github.com/actions/upload-artifact). Bumps the github-actions group with 1 update in the /.github/actions/packages directory: [actions/upload-artifact](https://github.com/actions/upload-artifact). Updates `actions/download-artifact` from 6 to 7 - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v6...v7) Updates `actions/upload-artifact` from 5 to 6 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) Updates `docker/setup-buildx-action` from 3.11.1 to 3.12.0 - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/e468171a9de216ec08956ac3ada2f0791b6bd435...8d2750c68a42422c14e847fe6c8ac0403b4cbd6f) Updates `actions/upload-artifact` from 5 to 6 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) Updates `actions/upload-artifact` from 5 to 6 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) Updates `actions/upload-artifact` from 5 to 6 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) Updates `actions/upload-artifact` from 5 to 6 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) Updates `actions/upload-artifact` from 5 to 6 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) Updates `actions/upload-artifact` from 5 to 6 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) Updates `actions/upload-artifact` from 5 to 6 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) Updates `actions/upload-artifact` from 5 to 6 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v5...v6) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: docker/setup-buildx-action dependency-version: 3.12.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions - dependency-name: actions/upload-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/actions/build-distribution/action.yml | 2 +- .github/actions/packages/action.yml | 2 +- .github/workflows/release.yml | 12 ++++++------ .github/workflows/run-matrix.yml | 4 ++-- .github/workflows/test-docs.yml | 2 +- .github/workflows/test.yml | 8 ++++---- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/actions/build-distribution/action.yml b/.github/actions/build-distribution/action.yml index 5f659b747..6835dbec2 100644 --- a/.github/actions/build-distribution/action.yml +++ b/.github/actions/build-distribution/action.yml @@ -14,7 +14,7 @@ runs: run: ./dev-utils/make-distribution.sh shell: bash - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: build-distribution path: ./build/ diff --git a/.github/actions/packages/action.yml b/.github/actions/packages/action.yml index 6c3053f5d..45d71ecd5 100644 --- a/.github/actions/packages/action.yml +++ b/.github/actions/packages/action.yml @@ -19,7 +19,7 @@ runs: run: ./dev-utils/make-packages.sh shell: bash - name: Upload Packages - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: packages path: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 82e39bb71..ab32abca2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -57,7 +57,7 @@ jobs: id-token: write # IMPORTANT: this permission is mandatory for trusted publishing steps: - uses: actions/checkout@v6 - - uses: actions/download-artifact@v6 + - uses: actions/download-artifact@v7 with: name: packages path: dist @@ -81,7 +81,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: actions/download-artifact@v6 + - uses: actions/download-artifact@v7 with: name: build-distribution path: ./build @@ -96,7 +96,7 @@ jobs: VERSION=${VERSION//./-} ELASTIC_LAYER_NAME="elastic-apm-python-${VERSION}" .ci/publish-aws.sh - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 if: startsWith(github.ref, 'refs/tags') with: name: arn-file @@ -121,7 +121,7 @@ jobs: - uses: actions/checkout@v6 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Log in to the Elastic Container registry uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 @@ -130,7 +130,7 @@ jobs: username: ${{ secrets.ELASTIC_DOCKER_USERNAME }} password: ${{ secrets.ELASTIC_DOCKER_PASSWORD }} - - uses: actions/download-artifact@v6 + - uses: actions/download-artifact@v7 with: name: build-distribution path: ./build @@ -175,7 +175,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - - uses: actions/download-artifact@v6 + - uses: actions/download-artifact@v7 with: name: arn-file - name: Create GitHub Draft Release diff --git a/.github/workflows/run-matrix.yml b/.github/workflows/run-matrix.yml index 118a898b5..e14b0e6ea 100644 --- a/.github/workflows/run-matrix.yml +++ b/.github/workflows/run-matrix.yml @@ -32,13 +32,13 @@ jobs: LOCALSTACK_VOLUME_DIR: localstack_data - if: success() || failure() name: Upload JUnit Test Results - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: test-results-${{ matrix.framework }}-${{ matrix.version }} path: "**/*-python-agent-junit.xml" - if: success() || failure() name: Upload Coverage Reports - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: coverage-reports-${{ matrix.framework }}-${{ matrix.version }} path: "**/.coverage*" diff --git a/.github/workflows/test-docs.yml b/.github/workflows/test-docs.yml index a99c13216..1f28a567c 100644 --- a/.github/workflows/test-docs.yml +++ b/.github/workflows/test-docs.yml @@ -36,7 +36,7 @@ jobs: ENDOFFILE - if: success() || failure() name: Upload JUnit Test Results - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: test-results-docs path: "docs-python-agent-junit.xml" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 980f011f2..d82f75b27 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -156,14 +156,14 @@ jobs: run: .\scripts\run-tests.bat - if: success() || failure() name: Upload JUnit Test Results - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: test-results-${{ matrix.framework }}-${{ matrix.version }}-asyncio-${{ matrix.asyncio }} path: "**/*-python-agent-junit.xml" retention-days: 1 - if: success() || failure() name: Upload Coverage Reports - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: coverage-reports-${{ matrix.framework }}-${{ matrix.version }}-asyncio-${{ matrix.asyncio }} path: "**/.coverage*" @@ -210,7 +210,7 @@ jobs: - run: python -Im pip install --upgrade coverage[toml] - - uses: actions/download-artifact@v6 + - uses: actions/download-artifact@v7 with: pattern: coverage-reports-* merge-multiple: true @@ -227,7 +227,7 @@ jobs: python -Im coverage report --fail-under=84 - name: Upload HTML report - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: html-coverage-report path: htmlcov From 7bbd56f1dfbead66562c0097abc99f509051599b Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Tue, 23 Dec 2025 16:00:42 +0100 Subject: [PATCH 395/409] docs: bump lambda layers version to 6.25.0:1 (#2519) --- docs/reference/lambda-support.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/lambda-support.md b/docs/reference/lambda-support.md index 839c4b8c2..12f9922e3 100644 --- a/docs/reference/lambda-support.md +++ b/docs/reference/lambda-support.md @@ -9,7 +9,7 @@ applies_to: apm_agent_python: ga sub: apm-lambda-ext-v: ver-1-6-0 - apm-python-v: ver-6-24-1 + apm-python-v: ver-6-25-0 apm-python-layer-v: 1 --- From 36a08035dcec62c1691cf4a04ae28287bdacd097 Mon Sep 17 00:00:00 2001 From: Francisco Ramon Date: Thu, 8 Jan 2026 11:25:23 +0100 Subject: [PATCH 396/409] Fix arn table file generation file path (#2525) * Ensure the arn table file is being generated in the github workspace * Use realpath instead changing directory --- .ci/create-arn-table.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.ci/create-arn-table.sh b/.ci/create-arn-table.sh index a03ead4c6..470d03c50 100755 --- a/.ci/create-arn-table.sh +++ b/.ci/create-arn-table.sh @@ -7,7 +7,9 @@ set -o pipefail # AWS_FOLDER - that's the location of the publish-layer-version output for each region AWS_FOLDER=${AWS_FOLDER?:No aws folder provided} -ARN_FILE=".arn-file.md" +# Get the repository root directory (where .git is located) +REPO_ROOT="$(realpath $(dirname "${BASH_SOURCE[0]}")/..)" +ARN_FILE="${REPO_ROOT}/.arn-file.md" { echo "
" @@ -28,3 +30,5 @@ done echo '
' echo '' } >> "${ARN_FILE}" + +echo "INFO: Created ARN table at ${ARN_FILE}" From 9ad228cfe2838f5e192b1026cdf9c1e3df668227 Mon Sep 17 00:00:00 2001 From: Paul McCann Date: Wed, 14 Jan 2026 11:56:40 +0000 Subject: [PATCH 397/409] chore: delete SECURITY.md to use organization-wide policy (#2528) --- SECURITY.md | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 4ff826c5b..000000000 --- a/SECURITY.md +++ /dev/null @@ -1,7 +0,0 @@ -# Security Policy - -Thanks for your interest in the security of our products. -Our security policy can be found at [https://www.elastic.co/community/security](https://www.elastic.co/community/security). - -## Reporting a Vulnerability -Please send security vulnerability reports to security@elastic.co. From 5c9b064589e17540d5b24cd14c2327b02f3651ed Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Mon, 19 Jan 2026 11:19:33 +0100 Subject: [PATCH 398/409] Run tests in Python 3.14 in full matrix (#2531) * tests: use importlib instead of pkgutil.get_loader pkgutil.get_loader has been removed in 3.14 * tests: use plain open instead of codecs.open Since it has been deprecated * elasticapm/contrib/django: swallow exceptions explicitly In 3.14 after https://peps.python.org/pep-0765/ the pattern of using return in a finally block is considered harmful. I guess the intent here is to just swallow any exception raised in the try block and always return the response. * tests: drop unused import * Run tests against 3.14 in full matrix --- .ci/.matrix_exclude.yml | 78 ++++++++++++++++++- .ci/.matrix_python_full.yml | 1 + Makefile | 2 +- .../contrib/django/middleware/__init__.py | 10 ++- setup.py | 3 +- tests/contrib/asyncio/starlette_tests.py | 2 - tests/fixtures.py | 7 +- tests/utils/stacks/tests.py | 5 +- .../test_wildcard_matcher_cases/conftest.py | 3 +- 9 files changed, 93 insertions(+), 18 deletions(-) diff --git a/.ci/.matrix_exclude.yml b/.ci/.matrix_exclude.yml index 93d0fa4df..c7089061f 100644 --- a/.ci/.matrix_exclude.yml +++ b/.ci/.matrix_exclude.yml @@ -57,6 +57,8 @@ exclude: FRAMEWORK: flask-2.3 - VERSION: python-3.7 FRAMEWORK: flask-3.0 + - VERSION: python-3.14 + FRAMEWORK: flask-1.0 # Python 3.10 removed a bunch of classes from collections, now in collections.abc - VERSION: python-3.10 FRAMEWORK: django-1.11 @@ -80,6 +82,12 @@ exclude: FRAMEWORK: celery-5-django-3 - VERSION: python-3.13 # https://github.com/celery/billiard/issues/377 FRAMEWORK: celery-5-django-4 + - VERSION: python-3.14 # https://github.com/celery/billiard/issues/377 + FRAMEWORK: celery-5-flask-2 + - VERSION: python-3.14 # https://github.com/celery/billiard/issues/377 + FRAMEWORK: celery-5-django-3 + - VERSION: python-3.14 # https://github.com/celery/billiard/issues/377 + FRAMEWORK: celery-5-django-4 - VERSION: python-3.10 FRAMEWORK: graphene-2 - VERSION: python-3.10 @@ -146,7 +154,35 @@ exclude: FRAMEWORK: aiohttp-4.0 - VERSION: python-3.13 FRAMEWORK: cassandra-3.4 - - VERSION: python-3.13 + - VERSION: python-3.14 + FRAMEWORK: django-1.11 + - VERSION: python-3.14 + FRAMEWORK: django-2.0 + - VERSION: python-3.14 + FRAMEWORK: django-2.1 + - VERSION: python-3.14 + FRAMEWORK: django-2.2 + - VERSION: python-3.14 + FRAMEWORK: django-3.0 + - VERSION: python-3.14 + FRAMEWORK: django-3.1 + - VERSION: python-3.14 + FRAMEWORK: django-3.2 + - VERSION: python-3.14 + FRAMEWORK: django-4.0 + - VERSION: python-3.14 + FRAMEWORK: django-4.2 + - VERSION: python-3.14 + FRAMEWORK: django-5.0 + - VERSION: python-3.14 + FRAMEWORK: graphene-2 + - VERSION: python-3.14 + FRAMEWORK: aiohttp-3.0 + - VERSION: python-3.14 + FRAMEWORK: aiohttp-4.0 + - VERSION: python-3.14 + FRAMEWORK: cassandra-3.4 + - VERSION: python-3.14 FRAMEWORK: pymongo-3.5 # pymongo - VERSION: python-3.10 @@ -157,6 +193,8 @@ exclude: FRAMEWORK: pymongo-3.1 - VERSION: python-3.13 FRAMEWORK: pymongo-3.1 + - VERSION: python-3.14 + FRAMEWORK: pymongo-3.1 - VERSION: python-3.10 FRAMEWORK: pymongo-3.2 - VERSION: python-3.11 @@ -165,6 +203,8 @@ exclude: FRAMEWORK: pymongo-3.2 - VERSION: python-3.13 FRAMEWORK: pymongo-3.2 + - VERSION: python-3.14 + FRAMEWORK: pymongo-3.2 - VERSION: python-3.10 FRAMEWORK: pymongo-3.3 - VERSION: python-3.11 @@ -173,6 +213,8 @@ exclude: FRAMEWORK: pymongo-3.3 - VERSION: python-3.13 FRAMEWORK: pymongo-3.3 + - VERSION: python-3.14 + FRAMEWORK: pymongo-3.3 - VERSION: python-3.8 FRAMEWORK: pymongo-3.4 - VERSION: python-3.9 @@ -185,6 +227,12 @@ exclude: FRAMEWORK: pymongo-3.4 - VERSION: python-3.13 FRAMEWORK: pymongo-3.4 + - VERSION: python-3.13 + FRAMEWORK: pymongo-3.5 + - VERSION: python-3.14 + FRAMEWORK: pymongo-3.4 + - VERSION: python-3.14 + FRAMEWORK: pymongo-3.5 - VERSION: pypy-3 FRAMEWORK: pymongo-3.0 # pymssql @@ -212,6 +260,10 @@ exclude: FRAMEWORK: boto3-1.5 - VERSION: python-3.13 FRAMEWORK: boto3-1.6 + - VERSION: python-3.14 + FRAMEWORK: boto3-1.5 + - VERSION: python-3.14 + FRAMEWORK: boto3-1.6 # aiohttp client, only supported in Python 3.7+ - VERSION: pypy-3 FRAMEWORK: aiohttp-3.0 @@ -259,6 +311,8 @@ exclude: FRAMEWORK: asyncpg-0.28 - VERSION: python-3.13 FRAMEWORK: asyncpg-0.28 + - VERSION: python-3.14 + FRAMEWORK: asyncpg-0.28 # sanic - VERSION: pypy-3 FRAMEWORK: sanic-newest @@ -272,6 +326,8 @@ exclude: FRAMEWORK: sanic-newest - VERSION: python-3.13 FRAMEWORK: sanic-20.12 + - VERSION: python-3.14 + FRAMEWORK: sanic-20.12 # aioredis - VERSION: pypy-3 FRAMEWORK: aioredis-newest @@ -315,6 +371,14 @@ exclude: FRAMEWORK: twisted-16 - VERSION: python-3.13 FRAMEWORK: twisted-15 + - VERSION: python-3.14 + FRAMEWORK: twisted-18 + - VERSION: python-3.14 + FRAMEWORK: twisted-17 + - VERSION: python-3.14 + FRAMEWORK: twisted-16 + - VERSION: python-3.14 + FRAMEWORK: twisted-15 # pylibmc - VERSION: python-3.11 FRAMEWORK: pylibmc-1.4 @@ -322,6 +386,8 @@ exclude: FRAMEWORK: pylibmc-1.4 - VERSION: python-3.13 FRAMEWORK: pylibmc-1.4 + - VERSION: python-3.14 + FRAMEWORK: pylibmc-1.4 # grpc - VERSION: python-3.6 FRAMEWORK: grpc-newest @@ -339,6 +405,8 @@ exclude: FRAMEWORK: grpc-1.24 - VERSION: python-3.13 FRAMEWORK: grpc-1.24 + - VERSION: python-3.14 + FRAMEWORK: grpc-1.24 - VERSION: python-3.7 FRAMEWORK: flask-1.0 - VERSION: python-3.7 @@ -350,6 +418,8 @@ exclude: FRAMEWORK: sanic-20.12 # no wheels available yet - VERSION: python-3.13 FRAMEWORK: cassandra-newest # c extension issue + - VERSION: python-3.14 + FRAMEWORK: cassandra-newest # c extension issue # httpx - VERSION: python-3.13 FRAMEWORK: httpx-0.13 @@ -357,3 +427,9 @@ exclude: FRAMEWORK: httpx-0.14 - VERSION: python-3.13 FRAMEWORK: httpx-0.21 + - VERSION: python-3.14 + FRAMEWORK: httpx-0.13 + - VERSION: python-3.14 + FRAMEWORK: httpx-0.14 + - VERSION: python-3.14 + FRAMEWORK: httpx-0.21 diff --git a/.ci/.matrix_python_full.yml b/.ci/.matrix_python_full.yml index bb763b7ca..56b272fbc 100644 --- a/.ci/.matrix_python_full.yml +++ b/.ci/.matrix_python_full.yml @@ -7,4 +7,5 @@ VERSION: - python-3.11 - python-3.12 - python-3.13 + - python-3.14 # - pypy-3 # excluded due to build issues with SQLite/Django diff --git a/Makefile b/Makefile index b2d00f400..90dcd4744 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ test: # delete any __pycache__ folders to avoid hard-to-debug caching issues find . -type f -name '*.py[co]' -delete -o -type d -name __pycache__ -delete # pypy3 should be added to the first `if` once it supports py3.7 - if [[ "$$PYTHON_VERSION" =~ ^(3.7|3.8|3.9|3.10|3.11|3.12|3.13|nightly)$$ ]] ; then \ + if [[ "$$PYTHON_VERSION" =~ ^(3.7|3.8|3.9|3.10|3.11|3.12|3.13|3.14|nightly)$$ ]] ; then \ echo "Python 3.7+, with asyncio"; \ pytest -v $(PYTEST_ARGS) --showlocals $(PYTEST_MARKER) $(PYTEST_JUNIT); \ else \ diff --git a/elasticapm/contrib/django/middleware/__init__.py b/elasticapm/contrib/django/middleware/__init__.py index c4637d087..612bb5c96 100644 --- a/elasticapm/contrib/django/middleware/__init__.py +++ b/elasticapm/contrib/django/middleware/__init__.py @@ -110,8 +110,9 @@ def process_request_wrapper(wrapped, instance, args, kwargs): elasticapm.set_transaction_name( build_name_with_http_method_prefix(get_name_from_middleware(wrapped, instance), request) ) - finally: - return response + except Exception: + pass + return response def process_response_wrapper(wrapped, instance, args, kwargs): @@ -125,8 +126,9 @@ def process_response_wrapper(wrapped, instance, args, kwargs): elasticapm.set_transaction_name( build_name_with_http_method_prefix(get_name_from_middleware(wrapped, instance), request) ) - finally: - return response + except Exception: + pass + return response class TracingMiddleware(MiddlewareMixin, ElasticAPMClientMiddlewareMixin): diff --git a/setup.py b/setup.py index b41dcaca9..ffb038307 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,6 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import ast -import codecs import os from setuptools import setup @@ -68,7 +67,7 @@ def get_version(): :return: a string, indicating the version """ - version_file = codecs.open(os.path.join("elasticapm", "version.py"), encoding="utf-8") + version_file = open(os.path.join("elasticapm", "version.py"), encoding="utf-8") for line in version_file: if line.startswith("__version__"): version_tuple = ast.literal_eval(line.split(" = ")[1]) diff --git a/tests/contrib/asyncio/starlette_tests.py b/tests/contrib/asyncio/starlette_tests.py index 38c51fa08..055cf01ad 100644 --- a/tests/contrib/asyncio/starlette_tests.py +++ b/tests/contrib/asyncio/starlette_tests.py @@ -28,8 +28,6 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from shutil import ExecError - from tests.fixtures import TempStoreClient import pytest # isort:skip diff --git a/tests/fixtures.py b/tests/fixtures.py index a559791bf..25d21ee5d 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -28,7 +28,6 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import codecs import gzip import io import itertools @@ -75,11 +74,11 @@ SPAN_TYPES = json.load(f) -with codecs.open(ERRORS_SCHEMA, encoding="utf8") as errors_json, codecs.open( +with open(ERRORS_SCHEMA, encoding="utf8") as errors_json, open( TRANSACTIONS_SCHEMA, encoding="utf8" -) as transactions_json, codecs.open(SPAN_SCHEMA, encoding="utf8") as span_json, codecs.open( +) as transactions_json, open(SPAN_SCHEMA, encoding="utf8") as span_json, open( METRICSET_SCHEMA, encoding="utf8" -) as metricset_json, codecs.open( +) as metricset_json, open( METADATA_SCHEMA, encoding="utf8" ) as metadata_json: VALIDATORS = { diff --git a/tests/utils/stacks/tests.py b/tests/utils/stacks/tests.py index c4be88ff2..6f4d4094a 100644 --- a/tests/utils/stacks/tests.py +++ b/tests/utils/stacks/tests.py @@ -32,8 +32,8 @@ from __future__ import absolute_import +import importlib import os -import pkgutil import pytest from mock import Mock @@ -240,7 +240,8 @@ def test_get_lines_from_file(lineno, context, expected): def test_get_lines_from_loader(lineno, context, expected): stacks.get_lines_from_file.cache_clear() module = "tests.utils.stacks.linenos" - loader = pkgutil.get_loader(module) + spec = importlib.util.find_spec(module) + loader = spec.loader if spec is not None else None fname = os.path.join(os.path.dirname(__file__), "linenos.py") result = stacks.get_lines_from_file(fname, lineno, context, loader=loader, module_name=module) assert result == expected diff --git a/tests/utils/test_wildcard_matcher_cases/conftest.py b/tests/utils/test_wildcard_matcher_cases/conftest.py index c672ff6f4..9d075dd24 100644 --- a/tests/utils/test_wildcard_matcher_cases/conftest.py +++ b/tests/utils/test_wildcard_matcher_cases/conftest.py @@ -27,7 +27,6 @@ # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import codecs import json import os @@ -40,7 +39,7 @@ def pytest_generate_tests(metafunc): json_cases = os.path.join( os.path.dirname(__file__), "..", "..", "upstream", "json-specs", "wildcard_matcher_tests.json" ) - with codecs.open(json_cases, encoding="utf8") as test_cases_file: + with open(json_cases, encoding="utf8") as test_cases_file: test_cases = json.load(test_cases_file) for test_case, pattern_sets in test_cases.items(): for pattern, texts in pattern_sets.items(): From 3ec1a6edbb4e47fa22fea798e4004fda5047b802 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 11:21:50 +0100 Subject: [PATCH 399/409] build(deps): bump wolfi/chainguard-base from `2b179e1` to `9060788` (#2523) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `2b179e1` to `9060788`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 199a2ddde..03a3b5856 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:2b179e1fe69c672bd0844147c6ebb039adb44ddaa3f9b4695f4915a9447da438 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:9060788fe55e611245d1b4a53ef705018a2703b4879a950a276fe113ef750cd6 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 3180fe3109a1e174e72aa8454e9314905265f6cb Mon Sep 17 00:00:00 2001 From: "elastic-observability-automation[bot]" <180520183+elastic-observability-automation[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 11:22:15 +0100 Subject: [PATCH 400/409] chore: deps(updatecli): Bump updatecli version to v0.113.0 (#2529) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made with ❤️️ by updatecli Co-authored-by: elastic-observability-automation[bot] <180520183+elastic-observability-automation[bot]@users.noreply.github.com> --- .tool-versions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.tool-versions b/.tool-versions index 787346ef0..4e6189ddb 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -updatecli v0.112.0 \ No newline at end of file +updatecli v0.113.0 \ No newline at end of file From 406a4eafbf8fb3b884da690f5e4e1cb7fc0decb1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 11:22:35 +0100 Subject: [PATCH 401/409] build(deps): bump certifi from 2025.11.12 to 2026.1.4 in /dev-utils (#2522) Bumps [certifi](https://github.com/certifi/python-certifi) from 2025.11.12 to 2026.1.4. - [Commits](https://github.com/certifi/python-certifi/compare/2025.11.12...2026.01.04) --- updated-dependencies: - dependency-name: certifi dependency-version: 2026.1.4 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- dev-utils/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-utils/requirements.txt b/dev-utils/requirements.txt index 55365dff2..8b112eafa 100644 --- a/dev-utils/requirements.txt +++ b/dev-utils/requirements.txt @@ -1,4 +1,4 @@ # These are the pinned requirements for the lambda layer/docker image -certifi==2025.11.12 +certifi==2026.1.4 urllib3==1.26.20 wrapt==1.14.1 From 841d226e3bd5f2afd67f832002d131dcaf2a515e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 13:36:35 +0000 Subject: [PATCH 402/409] build(deps): bump wolfi/chainguard-base from `9060788` to `1235a5e` (#2533) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `9060788` to `1235a5e`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 03a3b5856..2d9366192 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:9060788fe55e611245d1b4a53ef705018a2703b4879a950a276fe113ef750cd6 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:1235a5eee51eb21cd63b0dec7d65c439119c4a6020c0d2d86dc1bf3e41797568 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 8019ca4b58d6cc8f51d51b3da9abf50dba417d2c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Jan 2026 10:58:49 +0100 Subject: [PATCH 403/409] build(deps): bump wolfi/chainguard-base from `1235a5e` to `a42fd0f` (#2535) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `1235a5e` to `a42fd0f`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 2d9366192..b5d08985b 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:1235a5eee51eb21cd63b0dec7d65c439119c4a6020c0d2d86dc1bf3e41797568 +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:a42fd0f8865a04b8ab4ff12e9ecbba681ebae4112b9e7e9e34ce9b08ebfa650d ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 43e5b7fc64f11fa59483b8f7f5a12377941b8b5d Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 26 Jan 2026 09:33:05 +0100 Subject: [PATCH 404/409] github-action: add artifact-metadata permission for attestations (#2538) --- .github/workflows/release.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ab32abca2..cb2af7778 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,9 +13,10 @@ permissions: jobs: build-distribution: permissions: + artifact-metadata: write attestations: write - id-token: write contents: write + id-token: write runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -35,9 +36,10 @@ jobs: packages: permissions: + artifact-metadata: write attestations: write - id-token: write contents: write + id-token: write runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -108,9 +110,10 @@ jobs: - build-distribution runs-on: ubuntu-latest permissions: + artifact-metadata: write attestations: write - id-token: write contents: write + id-token: write strategy: fail-fast: false matrix: From 02b5c411e7c0c5182892786da092c27e34af40d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Jan 2026 14:22:16 +0100 Subject: [PATCH 405/409] build(deps): bump wolfi/chainguard-base from `a42fd0f` to `30f2097` (#2540) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `a42fd0f` to `30f2097`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index b5d08985b..5ff8b3c1e 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:a42fd0f8865a04b8ab4ff12e9ecbba681ebae4112b9e7e9e34ce9b08ebfa650d +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:30f2097053535867d06399fd08649e5ae834f4baab638803bc4fd0f1855de89d ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From cbe49ee64bf44f6f0ad1e9d0eb294fb9db3e86f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:02:31 +0100 Subject: [PATCH 406/409] build(deps): bump wolfi/chainguard-base from `30f2097` to `b5a03b6` (#2541) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `30f2097` to `b5a03b6`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 5ff8b3c1e..0d91ff447 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:30f2097053535867d06399fd08649e5ae834f4baab638803bc4fd0f1855de89d +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:b5a03b6a754fa2f9a29e44e316a6c4df1f606cd8a3cd8810ce3d6a3a3134428b ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 9f3bdaebb71959c8d867a422a2129130b4b015c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:10:54 +0100 Subject: [PATCH 407/409] build(deps): bump alpine from `865b95f` to `2510918` (#2542) Bumps alpine from `865b95f` to `2510918`. --- updated-dependencies: - dependency-name: alpine dependency-version: 25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0a8d60877..44dfeb72b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,3 @@ -FROM alpine@sha256:865b95f46d98cf867a156fe4a135ad3fe50d2056aa3f25ed31662dff6da4eb62 +FROM alpine@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From 7613b7aa77d7f3b883754f2997403ada0d6e2376 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Jan 2026 14:11:25 +0100 Subject: [PATCH 408/409] build(deps): bump wolfi/chainguard-base from `b5a03b6` to `17c8370` (#2543) Bumps [wolfi/chainguard-base](https://github.com/chainguard-images/images-private) from `b5a03b6` to `17c8370`. - [Commits](https://github.com/chainguard-images/images-private/commits) --- updated-dependencies: - dependency-name: wolfi/chainguard-base dependency-version: latest dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile.wolfi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.wolfi b/Dockerfile.wolfi index 0d91ff447..be090b304 100644 --- a/Dockerfile.wolfi +++ b/Dockerfile.wolfi @@ -1,3 +1,3 @@ -FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:b5a03b6a754fa2f9a29e44e316a6c4df1f606cd8a3cd8810ce3d6a3a3134428b +FROM docker.elastic.co/wolfi/chainguard-base:latest@sha256:17c8370b33443a247d2b60ca6b19c6f2fe81e4b90a2de3fa3e42665bcf7a346f ARG AGENT_DIR COPY ${AGENT_DIR} /opt/python \ No newline at end of file From a7fec890e84b5563bac46b700e353d26e5a85113 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Feb 2026 09:20:44 +0100 Subject: [PATCH 409/409] build(deps): bump docker/login-action (#2545) Bumps the github-actions group with 1 update in the / directory: [docker/login-action](https://github.com/docker/login-action). Updates `docker/login-action` from 3.6.0 to 3.7.0 - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/5e57cd118135c172c3672efd75eb46360885c0ef...c94ce9fb468520275223c153574b00df6fe4bcc9) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: 3.7.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: github-actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build-images.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/updatecli.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-images.yml b/.github/workflows/build-images.yml index 7683ef02e..acef1e165 100644 --- a/.github/workflows/build-images.yml +++ b/.github/workflows/build-images.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/checkout@v6 - name: Login to ghcr.io - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cb2af7778..1b39cddd9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -127,7 +127,7 @@ jobs: uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Log in to the Elastic Container registry - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ${{ secrets.ELASTIC_DOCKER_REGISTRY }} username: ${{ secrets.ELASTIC_DOCKER_USERNAME }} diff --git a/.github/workflows/updatecli.yml b/.github/workflows/updatecli.yml index 8897f4c52..e7d6fedf7 100644 --- a/.github/workflows/updatecli.yml +++ b/.github/workflows/updatecli.yml @@ -26,7 +26,7 @@ jobs: permission-contents: write permission-pull-requests: write - - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0 + - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 with: registry: ghcr.io username: ${{ github.actor }}

CC{*pdz?Yy+=lm+sv^n4HX#_z(M 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 e`Jrv--XX{tECd-Zx$u%j*#84c>`Wm5 literal 0 HcmV?d00001 diff --git a/docs/images/python-lambda-env-vars.png b/docs/images/python-lambda-env-vars.png new file mode 100644 index 0000000000000000000000000000000000000000..cbf4a25d2496476cb17acebe3129e23859b9d438 GIT binary patch literal 54573 zcmd43Wmp`+)&__MCj>$W?hxD|z+eG_y9al7cXtTx?vNnCZE$yYcX#)lTubi#*uVSi z&^*)Ar|ejFRlVnQu(YHQ57#J9mh_HYx7#Ng17#O$?>>JRP+3heP&=-^;p9CKm zSa}4(qc$|?H-WCOtOOXC3mF)gmme6|J?N6xE*O|SJs8-&78n>uA{ZEkRceC_07S6R zR~9jlkN~3uox_4bfTMyzg3iD}e_(;eU{L=!2Ll7O1o}Wl2Y|tVzEMH{YMJ1FN@ha* zyCjr+Cgi`*!F7ID;*;YS5dnS6>DuV)Ti6;|+U+-%7=VF6m>J6}+bK(kbLd){(`xHk z>gdxtnOps?0tRs60G*oa+i4RxnVVVIayW4j{i6g2==}F(IwFF96tOeqB2t!+Cg8WU z(I;S~rKhDQ;)W+6AOP6t8F0u7eEnB-&@V0`BRe}Q4mvtVM@L#mCR$4yLpla_c6K^? zMmk1D8c+!uTW1S9Z6_KFTjGB<^6z#8^lf!*jIHd9EiDLsx2vsVX>Z3xMD)9(|9t*A zPJJiiKRsF4{);UTgLJz~~LemBO!Z)s*}BWI