Skip to content

Commit 840fd77

Browse files
Kludexalexmojaki
andauthored
Allow to set instrument_httpx(capture_all=True) via env var (#1295)
Co-authored-by: Alex Hall <[email protected]>
1 parent 120df1f commit 840fd77

File tree

4 files changed

+81
-2
lines changed

4 files changed

+81
-2
lines changed

logfire/_internal/config_params.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ class _DefaultCallback:
104104
"""The base URL of the Logfire backend. Primarily for testing purposes."""
105105
DISTRIBUTED_TRACING = ConfigParam(env_vars=['LOGFIRE_DISTRIBUTED_TRACING'], allow_file_config=True, default=None, tp=bool)
106106
"""Whether to extract incoming trace context. By default, will extract but warn about it."""
107+
108+
# Instrumentation packages parameters
109+
HTTPX_CAPTURE_ALL = ConfigParam(env_vars=['LOGFIRE_HTTPX_CAPTURE_ALL'], allow_file_config=True, default=False, tp=bool)
110+
"""Whether to capture all HTTP headers, request and response bodies when using `logfire.instrument_httpx()`"""
107111
# fmt: on
108112

109113
CONFIG_PARAMS = {
@@ -130,6 +134,8 @@ class _DefaultCallback:
130134
'inspect_arguments': INSPECT_ARGUMENTS,
131135
'ignore_no_config': IGNORE_NO_CONFIG,
132136
'distributed_tracing': DISTRIBUTED_TRACING,
137+
# Instrumentation packages parameters
138+
'httpx_capture_all': HTTPX_CAPTURE_ALL,
133139
}
134140

135141

logfire/_internal/integrations/httpx.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import httpx
1313
from opentelemetry.trace import NonRecordingSpan, Span, use_span
1414

15+
from logfire._internal.config import GLOBAL_CONFIG
1516
from logfire._internal.stack_info import warn_at_user_stacklevel
1617

1718
try:
@@ -45,7 +46,7 @@
4546
def instrument_httpx(
4647
logfire_instance: Logfire,
4748
client: httpx.Client | httpx.AsyncClient | None,
48-
capture_all: bool,
49+
capture_all: bool | None,
4950
capture_headers: bool,
5051
capture_request_body: bool,
5152
capture_response_body: bool,
@@ -76,6 +77,8 @@ def instrument_httpx(
7677
'The `capture_response_headers` parameter is deprecated. Use `capture_headers` instead.', DeprecationWarning
7778
)
7879

80+
capture_all = cast(bool, GLOBAL_CONFIG.param_manager.load_param('httpx_capture_all', capture_all))
81+
7982
should_capture_request_headers = capture_request_headers or capture_headers or capture_all
8083
should_capture_response_headers = capture_response_headers or capture_headers or capture_all
8184
should_capture_request_body = capture_request_body or capture_all

logfire/_internal/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1353,7 +1353,7 @@ def instrument_httpx(
13531353
self,
13541354
client: httpx.Client | httpx.AsyncClient | None = None,
13551355
*,
1356-
capture_all: bool = False,
1356+
capture_all: bool | None = None,
13571357
capture_headers: bool = False,
13581358
capture_request_body: bool = False,
13591359
capture_response_body: bool = False,
@@ -1375,6 +1375,7 @@ def instrument_httpx(
13751375
client: The `httpx.Client` or `httpx.AsyncClient` instance to instrument.
13761376
If `None`, the default, all clients will be instrumented.
13771377
capture_all: Set to `True` to capture all HTTP headers, request and response bodies.
1378+
By default checks the environment variable `LOGFIRE_HTTPX_CAPTURE_ALL`.
13781379
capture_headers: Set to `True` to capture all HTTP headers.
13791380
13801381
If you don't want to capture all headers, you can customize the headers captured. See the

tests/otel_integrations/test_httpx.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import importlib
4+
import os
45
from contextlib import contextmanager
56
from typing import Any
67
from unittest import mock
@@ -842,6 +843,74 @@ async def test_httpx_client_capture_all(exporter: TestExporter):
842843
)
843844

844845

846+
async def test_httpx_client_capture_all_environment_variable(exporter: TestExporter):
847+
with mock.patch.dict(os.environ, {'LOGFIRE_HTTPX_CAPTURE_ALL': 'true'}):
848+
with httpx.Client(transport=create_transport()) as client:
849+
logfire.instrument_httpx(client)
850+
response = client.get('https://example.org:8080/foo')
851+
assert response.json() == {'good': 'response'}
852+
assert await response.aread() == b'{"good": "response"}'
853+
854+
assert without_metrics(exporter.exported_spans_as_dict()) == snapshot(
855+
[
856+
{
857+
'name': 'GET',
858+
'context': {'trace_id': 1, 'span_id': 1, 'is_remote': False},
859+
'parent': None,
860+
'start_time': 1000000000,
861+
'end_time': 2000000000,
862+
'attributes': {
863+
'http.method': 'GET',
864+
'http.request.method': 'GET',
865+
'http.url': 'https://example.org:8080/foo',
866+
'url.full': 'https://example.org:8080/foo',
867+
'http.host': 'example.org',
868+
'server.address': 'example.org',
869+
'network.peer.address': 'example.org',
870+
'net.peer.port': 8080,
871+
'server.port': 8080,
872+
'network.peer.port': 8080,
873+
'logfire.span_type': 'span',
874+
'logfire.msg': 'GET example.org/foo',
875+
'http.request.header.host': ('example.org:8080',),
876+
'http.request.header.accept': ('*/*',),
877+
'http.request.header.accept-encoding': ('gzip, deflate, zstd',),
878+
'http.request.header.connection': ('keep-alive',),
879+
'http.request.header.user-agent': ('python-httpx/0.28.1',),
880+
'http.status_code': 200,
881+
'http.response.status_code': 200,
882+
'http.flavor': '1.1',
883+
'network.protocol.version': '1.1',
884+
'http.response.header.host': ('example.org:8080',),
885+
'http.response.header.accept': ('*/*',),
886+
'http.response.header.accept-encoding': ('gzip, deflate, zstd',),
887+
'http.response.header.connection': ('keep-alive',),
888+
'http.response.header.user-agent': ('python-httpx/0.28.1',),
889+
'http.response.header.traceparent': ('00-00000000000000000000000000000001-0000000000000001-01',),
890+
'http.target': '/foo',
891+
},
892+
},
893+
{
894+
'name': 'Reading response body',
895+
'context': {'trace_id': 1, 'span_id': 3, 'is_remote': False},
896+
'parent': {'trace_id': 1, 'span_id': 1, 'is_remote': False},
897+
'start_time': 3000000000,
898+
'end_time': 4000000000,
899+
'attributes': {
900+
'code.filepath': 'test_httpx.py',
901+
'code.function': 'test_httpx_client_capture_all_environment_variable',
902+
'code.lineno': 123,
903+
'logfire.msg_template': 'Reading response body',
904+
'logfire.msg': 'Reading response body',
905+
'logfire.span_type': 'span',
906+
'http.response.body.text': '{"good": "response"}',
907+
'logfire.json_schema': '{"type":"object","properties":{"http.response.body.text":{"type":"object"}}}',
908+
},
909+
},
910+
]
911+
)
912+
913+
845914
async def test_httpx_client_no_capture_empty_body(exporter: TestExporter):
846915
async with httpx.AsyncClient(transport=create_transport()) as client:
847916
logfire.instrument_httpx(client, capture_request_body=True)

0 commit comments

Comments
 (0)