Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tracing): support listing headers in DD_TRACE_HEADER_TAGS without tag names #9589

Merged
merged 26 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
601476c
feat(tracing): support list of keys in DD_TRACE_HEADER_TAGS
mabdinur Jun 18, 2024
da02228
rn
mabdinur Jun 18, 2024
9dceb92
fix failing tests and update release note
mabdinur Jul 3, 2024
d778ad5
update docs
mabdinur Jul 3, 2024
d1f62a0
use an empty string and fix docs
mabdinur Jul 5, 2024
0b4232c
fix docs and add end to end test
mabdinur Jul 5, 2024
61588ff
fix docs
mabdinur Jul 5, 2024
429eb8c
fix docs 2
mabdinur Jul 5, 2024
5fcfb36
fix release note
mabdinur Jul 5, 2024
098b66f
fix test
mabdinur Jul 5, 2024
4a080e6
Merge branch 'main' into munir/update-header-tags
mabdinur Jul 5, 2024
2f421a4
Update docs/configuration.rst
mabdinur Jul 6, 2024
5fcc4cf
Update docs/configuration.rst
mabdinur Jul 6, 2024
39627af
Update ddtrace/contrib/trace_utils.py
mabdinur Jul 8, 2024
5668fce
Merge branch 'main' into munir/update-header-tags
mabdinur Jul 10, 2024
0b2afc0
Merge branch 'main' into munir/update-header-tags
mabdinur Jul 16, 2024
cea3859
Merge branch 'main' into munir/update-header-tags
mabdinur Jul 16, 2024
f76cd42
Merge branch 'main' into munir/update-header-tags
mabdinur Jul 17, 2024
8305232
Merge branch 'main' into munir/update-header-tags
mabdinur Jul 18, 2024
fe615da
Merge branch 'main' into munir/update-header-tags
mabdinur Jul 18, 2024
c25c281
Merge branch 'main' into munir/update-header-tags
mabdinur Jul 19, 2024
bc70c0f
Merge branch 'main' into munir/update-header-tags
mabdinur Jul 22, 2024
d9d405a
fix dd_tags test
mabdinur Jul 22, 2024
6fa0740
Merge branch 'main' into munir/update-header-tags
mabdinur Jul 24, 2024
165c988
Merge branch 'main' into munir/update-header-tags
mabdinur Jul 24, 2024
80aff99
Merge branch 'main' into munir/update-header-tags
mabdinur Jul 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ddtrace/contrib/trace_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def _store_headers(headers, span, integration_config, request_or_response):
return

for header_name, header_value in headers.items():
"""config._header_tag_name gets an element of the dictionary in config.http._header_tags
"""config._header_tag_name gets an element of the dictionary in config.trace_http_header_tags
which gets the value from DD_TRACE_HEADER_TAGS environment variable."""
tag_name = integration_config._header_tag_name(header_name)
if tag_name is None:
Expand Down
10 changes: 8 additions & 2 deletions ddtrace/internal/utils/formats.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ def parse_tags_str(tags_str):
The expected string is of the form::
"key1:value1,key2:value2"
"key1:value1 key2:value2"
"key1,key2"
"key1 key2"

:param tags_str: A string of the above form to parse tags from.
:return: A dict containing the tags that were parsed.
Expand All @@ -86,10 +88,14 @@ def parse_tags(tags):

for tag in tags:
key, sep, value = tag.partition(":")
if not sep or not key or "," in key:
if not key.strip() or "," in key or (sep and not value):
invalids.append(tag)
else:
elif sep:
# parse key:val,key2:value2
parsed_tags.append((key, value))
else:
# parse key,key2
parsed_tags.append((key, ""))

return parsed_tags, invalids

Expand Down
8 changes: 6 additions & 2 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ The following environment variables for the tracer are supported:
DD_TAGS:
description: |
Set global tags to be attached to every span. Value must be either comma or space separated. e.g. ``key1:value1,key2:value2`` or ``key1:value key2:value2``.

If a tag value is not supplied the value will be an empty string. e.g. ``key1,key2`` or ``key1 key2``.
version_added:
v0.38.0: Comma separated support added
v0.48.0: Space separated support added
Expand Down Expand Up @@ -290,9 +292,11 @@ The following environment variables for the tracer are supported:

DD_TRACE_HEADER_TAGS:
description: |
A map of case-insensitive header keys to tag names. Automatically applies matching header values as tags on root spans.
A map of case-insensitive http headers to tag names. Automatically applies matching header values as tags on request and response spans. For example if
``DD_TRACE_HEADER_TAGS=User-Agent:http.useragent,content-type:http.content_type``. The value of the header will be stored in tags with the name ``http.useragent`` and ``http.content_type``.

For example, ``User-Agent:http.useragent,content-type:http.content_type``.
If a tag name is not supplied the header name will be used. For example if
``DD_TRACE_HEADER_TAGS=User-Agent,content-type``. The value of http header will be stored in tags with the names ``http.<response/request>.headers.user-agent`` and ``http.<response/request>.headers.content-type``.
mabdinur marked this conversation as resolved.
Show resolved Hide resolved
ZStriker19 marked this conversation as resolved.
Show resolved Hide resolved

DD_TRACE_API_VERSION:
default: |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
fixes:
- |
tracing: Updates ``DD_HEADER_TAGS`` and ``DD_TAGS`` to support the following formats:
``key1,key2,key3``, ``key1:val,key2:val,key3:val3``, ``key1:val key2:val key3:val3``, and ``key1 key2 key3``.
Key value pairs that do not match an expected format will be logged and ignored by the tracer.
11 changes: 11 additions & 0 deletions tests/internal/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,17 @@ def _deleted_rc_config():
"expected": {"trace_http_header_tags": {"header": "value"}},
"expected_source": {"trace_http_header_tags": "code"},
},
{
mabdinur marked this conversation as resolved.
Show resolved Hide resolved
"env": {"DD_TRACE_HEADER_TAGS": "X-Header-Tag-1,X-Header-Tag-2,X-Header-Tag-3:specific_tag3"},
"expected": {
"trace_http_header_tags": {
"X-Header-Tag-1": "",
"X-Header-Tag-2": "",
mabdinur marked this conversation as resolved.
Show resolved Hide resolved
"X-Header-Tag-3": "specific_tag3",
}
},
"expected_source": {"trace_http_header_tags": "env_var"},
},
{
"env": {"DD_TRACE_HEADER_TAGS": "X-Header-Tag-1:header_tag_1,X-Header-Tag-2:header_tag_2"},
"rc": {
Expand Down
41 changes: 41 additions & 0 deletions tests/tracer/test_trace_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,47 @@ def test_ext_service(int_config, pin, config_val, default, expected):
assert trace_utils.ext_service(pin, int_config.myint, default) == expected


@pytest.mark.subprocess(
parametrize={
"DD_TRACE_HEADER_TAGS": ["header1 header2 header3:third-header", "header1,header2,header3:third-header"]
}
)
def test_set_http_meta_with_http_header_tags_config():
from ddtrace import config
from ddtrace._trace.span import Span
from ddtrace.contrib.trace_utils import set_http_meta

assert config.trace_http_header_tags == {
"header1": "",
"header2": "",
"header3": "third-header",
}, config.trace_http_header_tags
integration_config = config.new_integration
assert integration_config.is_header_tracing_configured

# test request headers
request_span = Span(name="new_integration.request")
set_http_meta(
request_span,
integration_config,
request_headers={"header1": "value1", "header2": "value2", "header3": "value3"},
)
assert request_span.get_tag("http.request.headers.header1") == "value1"
assert request_span.get_tag("http.request.headers.header2") == "value2"
assert request_span.get_tag("third-header") == "value3"

# test response headers
response_span = Span(name="new_integration.response")
set_http_meta(
response_span,
integration_config,
response_headers={"header1": "value1", "header2": "value2", "header3": "value3"},
)
assert response_span.get_tag("http.response.headers.header1") == "value1"
assert response_span.get_tag("http.response.headers.header2") == "value2"
assert response_span.get_tag("third-header") == "value3"


@pytest.mark.parametrize("appsec_enabled", [False, True])
@pytest.mark.parametrize("span_type", [SpanTypes.WEB, SpanTypes.HTTP, None])
@pytest.mark.parametrize(
Expand Down
4 changes: 2 additions & 2 deletions tests/tracer/test_tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -979,11 +979,11 @@ def test_dd_tags(self):
assert self.tracer._tags.get("key1") == "value1"
assert self.tracer._tags.get("key2") == "value2"

@run_in_subprocess(env_overrides=dict(DD_TAGS="key1:value1,key2:value2,key3"))
@run_in_subprocess(env_overrides=dict(DD_TAGS="key1:value1,key2:value2, key3"))
def test_dd_tags_invalid(self):
assert self.tracer._tags.get("key1")
assert self.tracer._tags.get("key2")
assert self.tracer._tags.get("key3") is None
assert not self.tracer._tags.get("key3")

@run_in_subprocess(env_overrides=dict(DD_TAGS="service:mysvc,env:myenv,version:myvers"))
def test_tags_from_DD_TAGS(self):
Expand Down
33 changes: 23 additions & 10 deletions tests/tracer/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,33 +56,46 @@ def test_asbool(self):
("key:val,key2:val2,key3:1234.23", dict(key="val", key2="val2", key3="1234.23"), None),
("key:val key2:val2 key3:1234.23", dict(key="val", key2="val2", key3="1234.23"), None),
("key: val", dict(key=" val"), None),
("key key: val", {"key key": " val"}, None),
(
"key key: val",
{"key": "", "val": ""},
[mock.call(_LOG_ERROR_MALFORMED_TAG, "key:", "key key: val")],
),
("key: val,key2:val2", dict(key=" val", key2="val2"), None),
(" key: val,key2:val2", {"key": " val", "key2": "val2"}, None),
("key key2:val1", {"key key2": "val1"}, None),
("key key2:val1", {"key": "", "key2": "val1"}, None),
("key:val key2:val:2", {"key": "val", "key2": "val:2"}, None),
(
"key:val,key2:val2 key3:1234.23",
dict(),
[mock.call(_LOG_ERROR_FAIL_SEPARATOR, "key:val,key2:val2 key3:1234.23")],
),
("key:val key2:val2 key3: ", dict(key="val", key2="val2", key3=""), None),
(
"key:val key2:val2 key3: ",
{"key": "val", "key2": "val2"},
[mock.call(_LOG_ERROR_MALFORMED_TAG, "key3:", "key:val key2:val2 key3:")],
),
(
"key:val key2:val 2",
dict(key="val", key2="val"),
[mock.call(_LOG_ERROR_MALFORMED_TAG, "2", "key:val key2:val 2")],
{"2": "", "key": "val", "key2": "val"},
None,
),
(
"key: val key2:val2 key3:val3",
{"key": "", "key2": "val2", "key3": "val3"},
[mock.call(_LOG_ERROR_MALFORMED_TAG, "val", "key: val key2:val2 key3:val3")],
{"key2": "val2", "key3": "val3", "val": ""},
[mock.call(_LOG_ERROR_MALFORMED_TAG, "key:", "key: val key2:val2 key3:val3")],
),
(
"key:,key3:val1,",
{"key3": "val1"},
[mock.call(_LOG_ERROR_MALFORMED_TAG, "key:", "key:,key3:val1")],
),
("key:,key3:val1,", dict(key3="val1", key=""), None),
(",", dict(), [mock.call(_LOG_ERROR_FAIL_SEPARATOR, "")]),
(":,:", dict(), [mock.call(_LOG_ERROR_FAIL_SEPARATOR, ":,:")]),
("key,key2:val1", {"key2": "val1"}, [mock.call(_LOG_ERROR_MALFORMED_TAG, "key", "key,key2:val1")]),
("key,key2:val1", {"key": "", "key2": "val1"}, None),
("key2:val1:", {"key2": "val1:"}, None),
("key,key2,key3", dict(), [mock.call(_LOG_ERROR_FAIL_SEPARATOR, "key,key2,key3")]),
("key,key2,key3", {"key": "", "key2": "", "key3": ""}, None),
("key key2 key3", {"key": "", "key2": "", "key3": ""}, None),
("foo:bar,foo:baz", dict(foo="baz"), None),
("hash:asd url:https://github.com/foo/bar", dict(hash="asd", url="https://github.com/foo/bar"), None),
],
Expand Down
Loading