Skip to content

Commit

Permalink
Add support for DD_ENV, DD_SERVICE, and DD_VERSION to set global tags…
Browse files Browse the repository at this point in the history
… for env, service, and version
  • Loading branch information
jdgumz committed Mar 12, 2020
1 parent f9cea02 commit 4054a10
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 11 deletions.
41 changes: 31 additions & 10 deletions datadog/dogstatsd/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@
# Tag name of entity_id
ENTITY_ID_TAG_NAME = "dd.internal.entity_id"

# Mapping of each "DD_" prefixed environment variable to a specific tag name
DD_ENV_TAGS_MAPPING = {
'DD_ENTITY_ID': ENTITY_ID_TAG_NAME,
'DD_ENV': 'env',
'DD_SERVICE': 'service',
'DD_VERSION': 'version',
}

# Telemetry minimum flush interval in seconds
DEFAULT_TELEMETRY_MIN_FLUSH_INTERVAL = 10

Expand All @@ -56,6 +64,24 @@ def __init__(self, host=DEFAULT_HOST, port=DEFAULT_PORT, max_buffer_size=50, nam
If set, it overrides default value.
:type DD_DOGSTATSD_PORT: integer
:envvar DATADOG_TAGS: Tags to attach to every metric reported by dogstatsd client.
:type DATADOG_TAGS: comma-delimited string
:envvar DD_ENTITY_ID: Tag to identify the client entity.
:type DD_ENTITY_ID: string
:envvar DD_ENV: the env of the service running the dogstatsd client.
If set, it is appended to the constant (global) tags of the statsd client.
:type DD_ENV: string
:envvar DD_SERVICE: the name of the service running the dogstatsd client.
If set, it is appended to the constant (global) tags of the statsd client.
:type DD_SERVICE: string
:envvar DD_VERSION: the version of the service running the dogstatsd client.
If set, it is appended to the constant (global) tags of the statsd client.
:type DD_VERSION: string
:param host: the host of the DogStatsd server.
:type host: string
Expand All @@ -75,12 +101,6 @@ def __init__(self, host=DEFAULT_HOST, port=DEFAULT_PORT, max_buffer_size=50, nam
:param use_ms: Report timed values in milliseconds instead of seconds (default False)
:type use_ms: boolean
:envvar DATADOG_TAGS: Tags to attach to every metric reported by dogstatsd client
:type DATADOG_TAGS: list of strings
:envvar DD_ENTITY_ID: Tag to identify the client entity.
:type DD_ENTITY_ID: string
:param use_default_route: Dynamically set the DogStatsd host to the default route
(Useful when running the client in a container) (Linux only)
:type use_default_route: boolean
Expand Down Expand Up @@ -128,13 +148,14 @@ def __init__(self, host=DEFAULT_HOST, port=DEFAULT_PORT, max_buffer_size=50, nam

# Options
env_tags = [tag for tag in os.environ.get('DATADOG_TAGS', '').split(',') if tag]
# Inject values of DD_* environment variables as global tags.
for var, tag_name in DD_ENV_TAGS_MAPPING.items():
value = os.environ.get(var, '')
if value:
env_tags.append('{name}:{value}'.format(name=tag_name, value=value))
if constant_tags is None:
constant_tags = []
self.constant_tags = constant_tags + env_tags
entity_id = os.environ.get('DD_ENTITY_ID')
if entity_id:
entity_tag = '{name}:{value}'.format(name=ENTITY_ID_TAG_NAME, value=entity_id)
self.constant_tags.append(entity_tag)
if namespace is not None:
namespace = text(namespace)
self.namespace = namespace
Expand Down
58 changes: 57 additions & 1 deletion tests/unit/dogstatsd/test_statsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from datadog.dogstatsd.context import TimedContextManagerDecorator
from datadog.util.compat import is_higher_py35, is_p3k
from datadog.util.config import get_version
from tests.util.contextmanagers import preserve_environment_variable
from tests.util.contextmanagers import preserve_environment_variable, EnvVars
from tests.unit.dogstatsd.fixtures import load_fixtures


Expand Down Expand Up @@ -767,6 +767,62 @@ def test_entity_tag_and_tags_from_environment_and_constant(self):
tags = "country:canada,red,country:china,age:45,blue,dd.internal.entity_id:04652bb7-19b7-11e9-9cc6-42010a9c016d"
assert_equal_telemetry('gt:123.4|g|#'+tags, statsd.socket.recv(), telemetry=telemetry_metrics(tags=tags))

def test_dogstatsd_initialization_with_dd_env_service_version(self):
"""
Dogstatsd should automatically use DD_ENV, DD_SERVICE, and DD_VERSION (if present)
to set {env, service, version} as global tags for all metrics emitted.
"""
cases = [
# Test various permutations of setting DD_* env vars, as well as other global tag configuration.
# An empty string signifies that the env var either isn't set or that it is explicitly set to empty string.
('', '', '', '', [], []),
('prod', '', '', '', [], ['env:prod']),
('prod', 'dog', '', '', [], ['env:prod', 'service:dog']),
('prod', 'dog', 'abc123', '', [], ['env:prod', 'service:dog', 'version:abc123']),
('prod', 'dog', 'abc123', 'env:prod,type:app', [], ['env:prod', 'env:prod', 'service:dog', 'type:app', 'version:abc123']),
('prod', 'dog', 'abc123', 'env:prod2,type:app', [], ['env:prod', 'env:prod2', 'service:dog', 'type:app', 'version:abc123']),
('prod', 'dog', 'abc123', '', ['env:prod', 'type:app'], ['env:prod', 'env:prod', 'service:dog', 'type:app', 'version:abc123']),
('prod', 'dog', 'abc123', '', ['env:prod2', 'type:app'], ['env:prod', 'env:prod2', 'service:dog', 'type:app', 'version:abc123']),
('prod', 'dog', 'abc123', 'env:prod3,custom_tag:cat', ['env:prod2', 'type:app'], ['custom_tag:cat', 'env:prod', 'env:prod2', 'env:prod3', 'service:dog', 'type:app', 'version:abc123']),
]
for c in cases:
dd_env, dd_service, dd_version, datadog_tags, constant_tags, global_tags = c
with EnvVars(
env_vars={
'DATADOG_TAGS': datadog_tags,
'DD_ENV': dd_env,
'DD_SERVICE': dd_service,
'DD_VERSION': dd_version,
}
):
statsd = DogStatsd(constant_tags=constant_tags, telemetry_min_flush_interval=0)
statsd.socket = FakeSocket()

# Guarantee consistent ordering, regardless of insertion order.
statsd.constant_tags.sort()
assert global_tags == statsd.constant_tags

# Make call with no tags passed; only the globally configured tags will be used.
global_tags_str = ','.join([t for t in global_tags])
statsd.gauge('gt', 123.4)
assert_equal_telemetry(
# Protect against the no tags case.
'gt:123.4|g|#{}'.format(global_tags_str) if global_tags_str else 'gt:123.4|g',
statsd.socket.recv(),
telemetry=telemetry_metrics(tags=global_tags_str)
)
statsd._reset_telementry()

# Make another call with local tags passed.
passed_tags = ['env:prod', 'version:def456', 'custom_tag:toad']
all_tags_str = ','.join([t for t in passed_tags + global_tags])
statsd.gauge('gt', 123.4, tags=passed_tags)
assert_equal_telemetry(
'gt:123.4|g|#{}'.format(all_tags_str),
statsd.socket.recv(),
telemetry=telemetry_metrics(tags=global_tags_str)
)

def test_gauge_doesnt_send_None(self):
self.statsd.gauge('metric', None)
assert self.recv() is None
Expand Down

0 comments on commit 4054a10

Please sign in to comment.