diff --git a/ddapm_test_agent/agent.py b/ddapm_test_agent/agent.py index c0cdaedd..adf08de7 100644 --- a/ddapm_test_agent/agent.py +++ b/ddapm_test_agent/agent.py @@ -50,6 +50,7 @@ from .trace_checks import CheckTraceCountHeader from .trace_checks import CheckTraceDDService from .trace_checks import CheckTracePeerService +from .trace_checks import CheckTraceSemantics from .trace_checks import CheckTraceStallAsync from .tracerflare import TracerFlareEvent from .tracerflare import v1_decode as v1_tracerflare_decode @@ -646,6 +647,7 @@ async def _handle_traces(self, request: Request, version: Literal["v0.4", "v0.5" await checks.check( "trace_peer_service", span=span, dd_config_env=request.get("_dd_trace_env_variables", {}) ) + await checks.check("trace_semantics", span=span) await checks.check( "trace_dd_service", trace=trace, dd_config_env=request.get("_dd_trace_env_variables", {}) @@ -1051,12 +1053,13 @@ def make_app( ) checks = Checks( checks=[ - CheckMetaTracerVersionHeader, - CheckTraceCountHeader, - CheckTraceContentLength, - CheckTraceStallAsync, - CheckTracePeerService, - CheckTraceDDService, + CheckMetaTracerVersionHeader(), + CheckTraceCountHeader(), + CheckTraceContentLength(), + CheckTraceStallAsync(), + CheckTracePeerService(), + CheckTraceDDService(), + CheckTraceSemantics(), ], enabled=enabled_checks, ) diff --git a/ddapm_test_agent/checks.py b/ddapm_test_agent/checks.py index 30cc35a9..9af8786c 100644 --- a/ddapm_test_agent/checks.py +++ b/ddapm_test_agent/checks.py @@ -8,7 +8,6 @@ from typing import Generator from typing import List from typing import Tuple -from typing import Type CHECK_TRACE: contextvars.ContextVar["CheckTrace"] = contextvars.ContextVar("check_trace") @@ -181,10 +180,10 @@ def check(self, *args, **kwargs): @dataclasses.dataclass() class Checks: - checks: List[Type[Check]] = dataclasses.field(init=True) + checks: List[Check] = dataclasses.field(init=True) enabled: List[str] = dataclasses.field(init=True) - def _get_check(self, name: str) -> Type[Check]: + def _get_check(self, name: str) -> Check: for c in self.checks: if c.name == name: return c @@ -199,7 +198,7 @@ def is_enabled(self, name: str) -> bool: async def check(self, name: str, *args: Any, **kwargs: Any) -> None: """Find and run the check with the given ``name`` if it is enabled.""" - check = self._get_check(name)() + check = self._get_check(name) if self.is_enabled(name): # Register the check with the current trace diff --git a/ddapm_test_agent/trace_checks.py b/ddapm_test_agent/trace_checks.py index 555ce09a..55a82620 100644 --- a/ddapm_test_agent/trace_checks.py +++ b/ddapm_test_agent/trace_checks.py @@ -5,7 +5,10 @@ from typing import List from aiohttp.web import Request +from jsonschema import ValidationError +from jsonschema import validate from multidict import CIMultiDictProxy +import requests from .checks import Check from .trace import Span @@ -221,3 +224,36 @@ def check(self, trace: List[Span], dd_config_env: Dict[str, str]) -> None: else: log.debug(f"Successfully completed ``service`` name Span Check for Span: {span['name']}") return + + +SCHEMA_URL = "https://raw.githubusercontent.com/DataDog/schema/main/semantic-core/v1/schema.json" + + +class CheckTraceSemantics(Check): + name = "trace_semantics" + description = """ +The trace should follow semantic conventions defined by semantic-core. +More info here: https://github.com/datadog/semantic-core. +""".strip() + schema = None + + def __init__(self): + log.debug("Initializing CheckTraceSemantics") + + resp = requests.get(SCHEMA_URL) + if resp.status_code != 200: + log.fatal(f"Failed to download trace semantics schema: {resp}") + + log.debug("Successfully downloaded semantic-core JSON Schema") + + self.schema = resp.json() + super().__init__() + + def check(self, span: Span) -> None: + log.info("Performing ``Trace Semantics`` Span Check") + + try: + validate(instance=span, schema=self.schema) + log.debug(f"Span Check ``trace_semantics`` succeeded for Span {span['name']}") + except ValidationError as err: + self.fail(json.dumps(span, indent=4) + f"\nSpan '{span['name']}' failed semantic validation: {err}.") diff --git a/setup.py b/setup.py index 3d152433..b1553946 100644 --- a/setup.py +++ b/setup.py @@ -33,6 +33,7 @@ "requests", "typing_extensions", "yarl", + "jsonschema", ], tests_require=testing_deps, setup_requires=["setuptools_scm"],