Skip to content

Commit 929fcc5

Browse files
committed
implement dedotting of context keys and tag names (#353)
(and destaring and dedoublequoting) These characters are invalid in elasticsearch field names. closes #353
1 parent da79c18 commit 929fcc5

File tree

4 files changed

+42
-5
lines changed

4 files changed

+42
-5
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
* implemented de-dotting of tag names and context keys (#353)
6+
37
## v4.0.2
48
[Check the diff](https://github.com/elastic/apm-agent-python/compare/v4.0.1...v4.0.2)
59

docs/api.asciidoc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,9 @@ elasticapm.set_custom_context({'billing_amount': product.price * item_count})
210210
----
211211

212212
* `data`: (*required*) A dictionary with the data to be attached. This should be a flat key/value `dict` object.
213+
214+
NOTE: `.`, `*`, and `"` are invalid characters for key names and will be replaced with `_`.
215+
213216

214217
Errors that happen after this call will also have the custom context attached to them.
215218
You can call this function multiple times, new context data will be merged with existing data,
@@ -255,3 +258,7 @@ elasticapm.tag(ecommerce=True, dollar_value=47.12)
255258
Errors that happen after this call will also have the tags attached to them.
256259
You can call this function multiple times, new tags will be merged with existing tags,
257260
following the `update()` semantics of Python dictionaries.
261+
262+
The value is converted to a string.
263+
264+
NOTE: `.`, `*`, and `"` are invalid characters for tag names and will be replaced with `_`.

elasticapm/traces.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
_time_func = timeit.default_timer
1919

2020

21-
TAG_RE = re.compile('^[^.*"]+$')
21+
TAG_RE = re.compile('[.*"]')
2222

2323

2424
try:
@@ -290,10 +290,9 @@ def tag(**tags):
290290
if not transaction:
291291
error_logger.warning("Ignored tag %s. No transaction currently active.", name)
292292
return
293-
if TAG_RE.match(name):
294-
transaction.tags[compat.text_type(name)] = encoding.keyword_field(compat.text_type(value))
295-
else:
296-
error_logger.warning("Ignored tag %s. Tag names can't contain stars, dots or double quotes.", name)
293+
# replace invalid characters for Elasticsearch field names with underscores
294+
name = TAG_RE.sub("_", compat.text_type(name))
295+
transaction.tags[compat.text_type(name)] = encoding.keyword_field(compat.text_type(value))
297296

298297

299298
def set_transaction_name(name, override=True):
@@ -318,6 +317,12 @@ def set_context(data, key="custom"):
318317
return
319318
if callable(data) and transaction.is_sampled:
320319
data = data()
320+
321+
# remove invalid characters from key names
322+
for k in list(data.keys()):
323+
if TAG_RE.search(k):
324+
data[TAG_RE.sub("_", k)] = data.pop(k)
325+
321326
if key in transaction.context:
322327
transaction.context[key].update(data)
323328
else:

tests/instrumentation/transactions_store_tests.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,19 @@ def test_tags_merge(elasticapm_client):
220220
assert transactions[0]["context"]["tags"] == {"foo": "1", "bar": "3", "boo": "biz"}
221221

222222

223+
def test_tags_dedot(elasticapm_client):
224+
elasticapm_client.begin_transaction("test")
225+
elasticapm.tag(**{"d.o.t": "dot"})
226+
elasticapm.tag(**{"s*t*a*r": "star"})
227+
elasticapm.tag(**{'q"u"o"t"e': "quote"})
228+
229+
elasticapm_client.end_transaction("test_name", 200)
230+
231+
transactions = elasticapm_client.events[TRANSACTION]
232+
233+
assert transactions[0]["context"]["tags"] == {"d_o_t": "dot", "s_t_a_r": "star", "q_u_o_t_e": "quote"}
234+
235+
223236
def test_set_transaction_name(elasticapm_client):
224237
elasticapm_client.begin_transaction("test")
225238
elasticapm_client.end_transaction("test_name", 200)
@@ -281,6 +294,14 @@ def test_set_user_context_merge(elasticapm_client):
281294
assert transactions[0]["context"]["user"] == {"username": "foo", "email": "[email protected]", "id": 42}
282295

283296

297+
def test_dedot_context_keys(elasticapm_client):
298+
elasticapm_client.begin_transaction("test")
299+
elasticapm.set_context({"d.o.t": "d_o_t", "s*t*a*r": "s_t_a_r", "q*u*o*t*e": "q_u_o_t_e"})
300+
elasticapm_client.end_transaction("foo", 200)
301+
transaction = elasticapm_client.events[TRANSACTION][0]
302+
assert transaction["context"]["custom"] == {"s_t_a_r": "s_t_a_r", "q_u_o_t_e": "q_u_o_t_e", "d_o_t": "d_o_t"}
303+
304+
284305
def test_transaction_name_none_is_converted_to_empty_string(elasticapm_client):
285306
elasticapm_client.begin_transaction("test")
286307
transaction = elasticapm_client.end_transaction(None, 200)

0 commit comments

Comments
 (0)