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

Add support for DD_ENV, DD_SERVICE, and DD_VERSION environment variables #548

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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"
jirikuncar marked this conversation as resolved.
Show resolved Hide resolved
jirikuncar marked this conversation as resolved.
Show resolved Hide resolved

# 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 @@ -29,7 +29,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 @@ -775,6 +775,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)
jdgumz marked this conversation as resolved.
Show resolved Hide resolved
)
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