Skip to content

Commit 22d4f3f

Browse files
committed
deprecate tags, add labels, and add support for typed labels (#538)
fixes #517 closes #538
1 parent d802522 commit 22d4f3f

File tree

11 files changed

+204
-45
lines changed

11 files changed

+204
-45
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Breaking changes
66

77
* implemented type/subtype/action hierachy for spans. Ensure that you run at least APM Server 6.6 (#377)
8+
* renamed tags to labels and changed API. The old API remains for backwards compatibility until 6.0 of the agent (#538)
89

910
## Other changes
1011

docs/api.asciidoc

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ import elasticapm
263263
def coffee_maker(strength):
264264
fetch_water()
265265
266-
with elasticapm.capture_span('near-to-machine', tags={"type": "arabica"}):
266+
with elasticapm.capture_span('near-to-machine', labels={"type": "arabica"}):
267267
insert_filter()
268268
for i in range(strengh):
269269
pour_coffee()
@@ -277,17 +277,17 @@ def coffee_maker(strength):
277277
* `span_type`: 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`.
278278
* `skip_frames`: The number of stack frames to skip when collecting stack traces. Defaults to `0`.
279279
* `leaf`: if `True`, all spans nested bellow this span will be ignored. Defaults to `False`.
280-
* `tags`: a dictionary of tags. Both keys and values must be strings. Defaults to `None`.
280+
* `labels`: a dictionary of labels. Keys must be strings, values can be strings, booleans, or numerical (`int`, `float`, `decimal.Decimal`). Defaults to `None`.
281281
* `span_subtype`: subtype of the span, e.g. name of the database. Defaults to `None`.
282282
* `span_action`: action of the span, e.g. `query`. Defaults to `None`
283283

284284
[float]
285-
[[api-tag]]
286-
==== `elasticapm.tag()`
285+
[[api-label]]
286+
==== `elasticapm.label()`
287287

288-
Attach tags to the the current transaction and errors.
288+
Attach labels to the the current transaction and errors.
289289

290-
TIP: Before using custom tags, ensure you understand the different types of
290+
TIP: Before using custom labels, ensure you understand the different types of
291291
{apm-overview-ref-v}/metadata.html[metadata] that are available.
292292

293293
Example:
@@ -296,16 +296,16 @@ Example:
296296
----
297297
import elasticapm
298298
299-
elasticapm.tag(ecommerce=True, dollar_value=47.12)
299+
elasticapm.label(ecommerce=True, dollar_value=47.12)
300300
----
301301

302-
Errors that happen after this call will also have the tags attached to them.
303-
You can call this function multiple times, new tags will be merged with existing tags,
302+
Errors that happen after this call will also have the labels attached to them.
303+
You can call this function multiple times, new labels will be merged with existing labels,
304304
following the `update()` semantics of Python dictionaries.
305305

306-
The value is converted to a string.
307-
`.`, `*`, and `"` are invalid characters for tag names and will be replaced with `_`.
306+
Keys must be strings, values can be strings, booleans, or numerical (`int`, `float`, `decimal.Decimal`)
307+
`.`, `*`, and `"` are invalid characters for label names and will be replaced with `_`.
308308

309-
WARNING: Avoid defining too many user-specified tags.
309+
WARNING: Avoid defining too many user-specified labels.
310310
Defining too many unique fields in an index is a condition that can lead to a
311311
{ref}/mapping.html#mapping-limit-settings[mapping explosion].

docs/configuration.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,7 @@ WARNING: We recommend to always include the default set of validators if you cus
501501

502502
By default, the agent will sample every transaction (e.g. request to your service).
503503
To reduce overhead and storage requirements, you can set the sample rate to a value between `0.0` and `1.0`.
504-
We still record overall time and the result for unsampled transactions, but no context information, tags, or spans.
504+
We still record overall time and the result for unsampled transactions, but no context information, labels, or spans.
505505

506506
[float]
507507
[[config-include-paths]]

elasticapm/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,5 @@
4040
from elasticapm.conf import setup_logging # noqa: F401
4141
from elasticapm.instrumentation.control import instrument, uninstrument # noqa: F401
4242
from elasticapm.traces import capture_span, set_context, set_custom_context # noqa: F401
43-
from elasticapm.traces import set_transaction_name, set_user_context, tag # noqa: F401
43+
from elasticapm.traces import set_transaction_name, set_user_context, tag, label # noqa: F401
4444
from elasticapm.traces import set_transaction_result # noqa: F401

elasticapm/base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,8 +341,8 @@ def _build_msg_for_logging(
341341
else:
342342
context = transaction_context
343343
event_data["context"] = context
344-
if transaction and transaction.tags:
345-
context["tags"] = deepcopy(transaction.tags)
344+
if transaction and transaction.labels:
345+
context["tags"] = deepcopy(transaction.labels)
346346

347347
# if '.' not in event_type:
348348
# Assume it's a builtin

elasticapm/conf/constants.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2929
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3030

31+
import decimal
32+
3133
EVENTS_API_PATH = "intake/v2/events"
3234

3335
TRACE_CONTEXT_VERSION = 0
@@ -45,3 +47,11 @@
4547
TRANSACTION = "transaction"
4648
SPAN = "span"
4749
METRICSET = "metricset"
50+
51+
52+
try:
53+
# Python 2
54+
TAG_TYPES = (bool, int, long, float, decimal.Decimal)
55+
except NameError:
56+
# Python 3
57+
TAG_TYPES = (bool, int, float, decimal.Decimal)

elasticapm/contrib/opentracing/span.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def set_tag(self, key, value):
101101
elif key == tags.COMPONENT:
102102
traces.set_context({"framework": {"name": value}}, "service")
103103
else:
104-
self.elastic_apm_ref.tag(**{key: value})
104+
self.elastic_apm_ref.label(**{key: value})
105105
else:
106106
if key.startswith("db."):
107107
span_context = self.elastic_apm_ref.context or {}
@@ -115,12 +115,12 @@ def set_tag(self, key, value):
115115
span_context["db"]["type"] = value
116116
self.elastic_apm_ref.type = "db." + value
117117
else:
118-
self.elastic_apm_ref.tag(**{key: value})
118+
self.elastic_apm_ref.label(**{key: value})
119119
self.elastic_apm_ref.context = span_context
120120
elif key == tags.SPAN_KIND:
121121
self.elastic_apm_ref.type = value
122122
else:
123-
self.elastic_apm_ref.tag(**{key: value})
123+
self.elastic_apm_ref.label(**{key: value})
124124
return self
125125

126126
def finish(self, finish_time=None):

elasticapm/traces.py

Lines changed: 71 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,18 @@
3535
import threading
3636
import time
3737
import timeit
38+
import warnings
3839
from collections import defaultdict
3940

4041
from elasticapm.conf import constants
4142
from elasticapm.conf.constants import SPAN, TRANSACTION
4243
from elasticapm.context import init_execution_context
4344
from elasticapm.metrics.base_metrics import Timer
4445
from elasticapm.utils import compat, encoding, get_name_from_func
46+
from elasticapm.utils.deprecation import deprecated
4547
from elasticapm.utils.disttracing import TraceParent, TracingOptions
4648

47-
__all__ = ("capture_span", "tag", "set_transaction_name", "set_custom_context", "set_user_context")
49+
__all__ = ("capture_span", "tag", "label", "set_transaction_name", "set_custom_context", "set_user_context")
4850

4951
error_logger = logging.getLogger("elasticapm.errors")
5052
logger = logging.getLogger("elasticapm.traces")
@@ -86,11 +88,11 @@ def duration(self):
8688

8789

8890
class BaseSpan(object):
89-
def __init__(self, tags=None):
91+
def __init__(self, labels=None):
9092
self._child_durations = ChildDuration(self)
91-
self.tags = {}
92-
if tags:
93-
self.tag(**tags)
93+
self.labels = {}
94+
if labels:
95+
self.label(**labels)
9496

9597
def child_started(self, timestamp):
9698
self._child_durations.start(timestamp)
@@ -101,16 +103,39 @@ def child_ended(self, timestamp):
101103
def end(self, skip_frames=0):
102104
raise NotImplementedError()
103105

106+
def label(self, **labels):
107+
"""
108+
Label this span with one or multiple key/value labels. Keys should be strings, values can be strings, booleans,
109+
or numerical values (int, float, Decimal)
110+
111+
span_obj.label(key1="value1", key2=True, key3=42)
112+
113+
Note that keys will be dedotted, replacing dot (.), star (*) and double quote (") with an underscore (_)
114+
115+
:param labels: key/value pairs of labels
116+
:return: None
117+
"""
118+
for key, value in compat.iteritems(labels):
119+
if not isinstance(value, constants.TAG_TYPES):
120+
value = encoding.keyword_field(compat.text_type(value))
121+
self.labels[TAG_RE.sub("_", compat.text_type(key))] = value
122+
123+
@deprecated("transaction/span.label()")
104124
def tag(self, **tags):
105125
"""
126+
This method is deprecated, please use "label()" instead.
127+
106128
Tag this span with one or multiple key/value tags. Both the values should be strings
107129
108130
span_obj.tag(key1="value1", key2="value2")
109131
110132
Note that keys will be dedotted, replacing dot (.), star (*) and double quote (") with an underscore (_)
133+
134+
:param tags: key/value pairs of tags
135+
:return: None
111136
"""
112137
for key in tags.keys():
113-
self.tags[TAG_RE.sub("_", compat.text_type(key))] = encoding.keyword_field(compat.text_type(tags[key]))
138+
self.labels[TAG_RE.sub("_", compat.text_type(key))] = encoding.keyword_field(compat.text_type(tags[key]))
114139

115140

116141
class Transaction(BaseSpan):
@@ -178,7 +203,7 @@ def _begin_span(
178203
span_type,
179204
context=None,
180205
leaf=False,
181-
tags=None,
206+
labels=None,
182207
parent_span_id=None,
183208
span_subtype=None,
184209
span_action=None,
@@ -198,7 +223,7 @@ def _begin_span(
198223
span_type=span_type or "code.custom",
199224
context=context,
200225
leaf=leaf,
201-
tags=tags,
226+
labels=labels,
202227
parent=parent_span,
203228
parent_span_id=parent_span_id,
204229
span_subtype=span_subtype,
@@ -209,14 +234,14 @@ def _begin_span(
209234
execution_context.set_span(span)
210235
return span
211236

212-
def begin_span(self, name, span_type, context=None, leaf=False, tags=None, span_subtype=None, span_action=None):
237+
def begin_span(self, name, span_type, context=None, leaf=False, labels=None, span_subtype=None, span_action=None):
213238
"""
214239
Begin a new span
215240
:param name: name of the span
216241
:param span_type: type of the span
217242
:param context: a context dict
218243
:param leaf: True if this is a leaf span
219-
:param tags: a flat string/string dict of tags
244+
:param labels: a flat string/string dict of labels
220245
:param span_subtype: sub type of the span, e.g. "postgresql"
221246
:param span_action: action of the span , e.g. "query"
222247
:return: the Span object
@@ -226,7 +251,7 @@ def begin_span(self, name, span_type, context=None, leaf=False, tags=None, span_
226251
span_type,
227252
context=context,
228253
leaf=leaf,
229-
tags=tags,
254+
labels=labels,
230255
parent_span_id=None,
231256
span_subtype=span_subtype,
232257
span_action=span_action,
@@ -257,7 +282,7 @@ def ensure_parent_id(self):
257282
return self.trace_parent.span_id
258283

259284
def to_dict(self):
260-
self.context["tags"] = self.tags
285+
self.context["tags"] = self.labels
261286
result = {
262287
"id": self.id,
263288
"trace_id": self.trace_parent.trace_id,
@@ -301,7 +326,7 @@ class Span(BaseSpan):
301326
"parent",
302327
"parent_span_id",
303328
"frames",
304-
"tags",
329+
"labels",
305330
"_child_durations",
306331
)
307332

@@ -312,7 +337,7 @@ def __init__(
312337
span_type,
313338
context=None,
314339
leaf=False,
315-
tags=None,
340+
labels=None,
316341
parent=None,
317342
parent_span_id=None,
318343
span_subtype=None,
@@ -326,7 +351,7 @@ def __init__(
326351
:param span_type: type of the span, e.g. db
327352
:param context: context dictionary
328353
:param leaf: is this span a leaf span?
329-
:param tags: a dict of tags
354+
:param labels: a dict of labels
330355
:param parent_span_id: override of the span ID
331356
:param span_subtype: sub type of the span, e.g. mysql
332357
:param span_action: sub type of the span, e.g. query
@@ -359,7 +384,7 @@ def __init__(
359384
if self.transaction._breakdown:
360385
p = self.parent if self.parent else self.transaction
361386
p.child_started(self.start_time)
362-
super(Span, self).__init__(tags=tags)
387+
super(Span, self).__init__(labels=labels)
363388

364389
def to_dict(self):
365390
result = {
@@ -375,10 +400,10 @@ def to_dict(self):
375400
"timestamp": int(self.timestamp * 1000000), # microseconds
376401
"duration": self.duration * 1000, # milliseconds
377402
}
378-
if self.tags:
403+
if self.labels:
379404
if self.context is None:
380405
self.context = {}
381-
self.context["tags"] = self.tags
406+
self.context["tags"] = self.labels
382407
if self.context:
383408
result["context"] = self.context
384409
if self.frames:
@@ -491,7 +516,7 @@ def end_transaction(self, result=None, transaction_name=None):
491516

492517

493518
class capture_span(object):
494-
__slots__ = ("name", "type", "subtype", "action", "extra", "skip_frames", "leaf", "tags")
519+
__slots__ = ("name", "type", "subtype", "action", "extra", "skip_frames", "leaf", "labels")
495520

496521
def __init__(
497522
self,
@@ -501,6 +526,7 @@ def __init__(
501526
skip_frames=0,
502527
leaf=False,
503528
tags=None,
529+
labels=None,
504530
span_subtype=None,
505531
span_action=None,
506532
):
@@ -511,7 +537,15 @@ def __init__(
511537
self.extra = extra
512538
self.skip_frames = skip_frames
513539
self.leaf = leaf
514-
self.tags = tags
540+
if tags and not labels:
541+
warnings.warn(
542+
'The tags argument to capture_span is deprecated, use "labels" instead',
543+
category=DeprecationWarning,
544+
stacklevel=2,
545+
)
546+
labels = tags
547+
548+
self.labels = labels
515549

516550
def __call__(self, func):
517551
self.name = self.name or get_name_from_func(func)
@@ -531,7 +565,7 @@ def __enter__(self):
531565
self.type,
532566
context=self.extra,
533567
leaf=self.leaf,
534-
tags=self.tags,
568+
labels=self.labels,
535569
span_subtype=self.subtype,
536570
span_action=self.action,
537571
)
@@ -545,9 +579,24 @@ def __exit__(self, exc_type, exc_val, exc_tb):
545579
logger.info("ended non-existing span %s of type %s", self.name, self.type)
546580

547581

582+
def label(**labels):
583+
"""
584+
Labels current transaction. Keys should be strings, values can be strings, booleans,
585+
or numerical values (int, float, Decimal)
586+
587+
:param labels: key/value map of labels
588+
"""
589+
transaction = execution_context.get_transaction()
590+
if not transaction:
591+
error_logger.warning("Ignored labels %s. No transaction currently active.", ", ".join(labels.keys()))
592+
else:
593+
transaction.label(**labels)
594+
595+
596+
@deprecated("elasticapm.label")
548597
def tag(**tags):
549598
"""
550-
Tags current transaction. Both key and value of the tag should be strings.
599+
Tags current transaction. Both key and value of the label should be strings.
551600
"""
552601
transaction = execution_context.get_transaction()
553602
if not transaction:

elasticapm/utils/json_encoder.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131

3232
import datetime
33+
import decimal
3334
import uuid
3435

3536
try:
@@ -45,6 +46,7 @@ class BetterJSONEncoder(json.JSONEncoder):
4546
datetime.datetime: lambda obj: obj.strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
4647
uuid.UUID: lambda obj: obj.hex,
4748
bytes: lambda obj: obj.decode("utf-8", errors="replace"),
49+
decimal.Decimal: lambda obj: float(obj),
4850
}
4951

5052
def default(self, obj):

0 commit comments

Comments
 (0)