Skip to content
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
9 changes: 7 additions & 2 deletions spectator/common_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ def _add_non_empty(tags: Dict[str, str], tag: str, *env_vars: str) -> None:

def tags_from_env_vars() -> Dict[str, str]:
"""Extract common infrastructure tags from the Netflix environment variables, which are
specific to a process and thus cannot be managed by a shared SpectatorD instance."""
specific to a container or process and thus cannot be managed by a shared SpectatorD instance.

* `nf.container` - From `NETFLIX_CONTAINER_NAME`, falling back to `TITUS_CONTAINER_NAME`.
* `nf.process` - From `NETFLIX_PROCESS_NAME`.

Values are stripped of whitespace, and unset or blank variables are ignored."""
tags: Dict[str, str] = {}
_add_non_empty(tags, "nf.container", "TITUS_CONTAINER_NAME")
_add_non_empty(tags, "nf.container", "NETFLIX_CONTAINER_NAME", "TITUS_CONTAINER_NAME")
_add_non_empty(tags, "nf.process", "NETFLIX_PROCESS_NAME")
return tags

Expand Down
7 changes: 7 additions & 0 deletions spectator/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ class Config:
optional. The extra common tags are added to every metric, on top of the common tags provided
by `spectatord`.

The `nf.container` tag (from `NETFLIX_CONTAINER_NAME`, falling back to `TITUS_CONTAINER_NAME`) and
the `nf.process` tag (from `NETFLIX_PROCESS_NAME`) are read from the environment when the Config is
created, and they take precedence over the same keys in the extra common tags. The Configs used by
`Registry()` and `GlobalRegistry` are created when `spectator` is imported, so set these variables
before that import, ideally in the process environment before it starts; changing `os.environ`
after the import has no effect on them.

Possible values for `location` are:

* `none` - Configure a no-op writer that does nothing. Can be used to disable metrics collection.
Expand Down
7 changes: 7 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import os

# Remove the container and process names that the platform may inject into the environment, so that
# tests which create a Config or Registry from the real environment get the expected tags on any host.
# This runs before the test modules import spectator, which creates the GlobalRegistry at import time.
for key in ["NETFLIX_CONTAINER_NAME", "NETFLIX_PROCESS_NAME", "TITUS_CONTAINER_NAME"]:
os.environ.pop(key, None)
54 changes: 53 additions & 1 deletion tests/test_common_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,20 @@ def setup_environment() -> None:

@staticmethod
def clear_environment() -> None:
keys = ["NETFLIX_PROCESS_NAME", "TITUS_CONTAINER_NAME"]
keys = ["NETFLIX_CONTAINER_NAME", "NETFLIX_PROCESS_NAME", "TITUS_CONTAINER_NAME"]

for key in keys:
try:
del os.environ[key]
except KeyError:
pass

def setUp(self) -> None:
self.clear_environment()

def tearDown(self) -> None:
self.clear_environment()

def test_tags_from_env_vars(self):
self.setup_environment()
self.assertEqual(self.all_expected_tags(), tags_from_env_vars())
Expand Down Expand Up @@ -60,6 +66,52 @@ def test_tags_from_env_vars_whitespace_ignored(self):
self.assertEqual(self.all_expected_tags(), tags_from_env_vars())
self.clear_environment()

def test_tags_from_env_vars_netflix_container_name_preferred(self):
self.setup_environment()
os.environ["NETFLIX_CONTAINER_NAME"] = "app"

expected_tags = self.all_expected_tags()
expected_tags["nf.container"] = "app"
self.assertEqual(expected_tags, tags_from_env_vars())

self.clear_environment()

def test_tags_from_env_vars_netflix_container_name_blank_falls_back(self):
self.setup_environment()

os.environ["NETFLIX_CONTAINER_NAME"] = ""
self.assertEqual(self.all_expected_tags(), tags_from_env_vars())

os.environ["NETFLIX_CONTAINER_NAME"] = " \t"
self.assertEqual(self.all_expected_tags(), tags_from_env_vars())

self.clear_environment()

def test_tags_from_env_vars_netflix_container_name_only(self):
self.setup_environment()
del os.environ["TITUS_CONTAINER_NAME"]
os.environ["NETFLIX_CONTAINER_NAME"] = " app \t"

expected_tags = self.all_expected_tags()
expected_tags["nf.container"] = "app"
self.assertEqual(expected_tags, tags_from_env_vars())

self.clear_environment()

def test_tags_from_env_vars_process_empty_ignored(self):
self.setup_environment()
os.environ["NETFLIX_PROCESS_NAME"] = ""

expected_tags = self.all_expected_tags()
del expected_tags["nf.process"]
self.assertEqual(expected_tags, tags_from_env_vars())

self.clear_environment()

def test_tags_from_env_vars_none_set(self):
self.clear_environment()
self.assertEqual({}, tags_from_env_vars())

def test_validate_tags(self):
self.assertEqual({}, validate_tags({"": "empty key", 1: "non-string key"}))
self.assertEqual({}, validate_tags({"empty val": "", "non-string val": 1}))
15 changes: 14 additions & 1 deletion tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,20 @@ def setup_environment() -> None:

@staticmethod
def clear_environment() -> None:
keys = ["NETFLIX_PROCESS_NAME", "SPECTATOR_OUTPUT_LOCATION", "TITUS_CONTAINER_NAME"]
keys = ["NETFLIX_CONTAINER_NAME", "NETFLIX_PROCESS_NAME", "SPECTATOR_OUTPUT_LOCATION", "TITUS_CONTAINER_NAME"]

for key in keys:
try:
del os.environ[key]
except KeyError:
pass

def setUp(self) -> None:
self.clear_environment()

def tearDown(self) -> None:
self.clear_environment()

@staticmethod
def get_location(location: str) -> str:
return Config(location).location
Expand Down Expand Up @@ -71,6 +77,13 @@ def test_extra_common_tags(self):
self.assertEqual({"extra-tag": "foo", "nf.container": "main", "nf.process": "python"}, config.extra_common_tags)
self.clear_environment()

def test_env_tags_override_extra_common_tags(self):
self.setup_environment()
os.environ["NETFLIX_CONTAINER_NAME"] = "app"
config = Config(extra_common_tags={"nf.container": "passed-in", "nf.process": "passed-in"})
self.assertEqual({"nf.container": "app", "nf.process": "python"}, config.extra_common_tags)
self.clear_environment()

def test_valid_output_locations(self):
self.assertEqual("none", self.get_location("none"))
self.assertEqual("memory", self.get_location("memory"))
Expand Down
Loading