Skip to content
Closed
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
313 changes: 297 additions & 16 deletions semantic-core/gen_semantic_defs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
from typing import List, Dict

from pydantic import BaseModel, Field
from pydantic.config import ConfigDict
from typing_extensions import Annotated

# Semantic Types

NonEmptyString = Annotated[str, Field(min_length=1)]
PositiveFloat = Annotated[float, Field(gt=0)]


TraceId = Annotated[
int,
Field(
Expand Down Expand Up @@ -417,6 +417,282 @@ class IntakeResolvedDbSpan(BaseModel):
] = None


class HTTPTags(BaseModel):
span_kind: Annotated[
str,
Field(
alias="span.kind",
title="span.kind",
description="",
pattern=r"^(client|server)$",
)
]
http_status_code: Annotated[
HttpStatusCode,
Field(
alias="http.status_code",
title="http.status_code",
)
]


class DBTags(BaseModel):
pass


class Span(BaseModel):
model_config = ConfigDict(
# json_schema_extra={
# 'allOf': [
# {
# 'if': {'properties': {'type': {'const': 'web'}}},
# 'then': {'properties': {'meta': {'$ref': '#/$defs/HTTPTags'}}},
# },
# {
# 'if': {'properties': {'type': {'const': 'db'}}},
# 'then': {'properties': {'meta': {'$ref': '#/$defs/DBTags'}}},
# },
# ]
# }
)

service: Annotated[
str,
Field(
alias="service",
title="Service",
description="The name of the service with which this span is associated"
)
]
name: Annotated[
str,
Field(
alias="name",
title="Name",
description="The operation name of this span"
)
]
resource: Annotated[
str,
Field(
alias="resource",
title="Resource",
description="The resource name of this span, also sometimes called the endpoint (for web spans)"
)
]
traceID: Annotated[
str, # TODO: find out why these are sent as string
Field(
alias="traceID",
title="Trace ID",
description="The ID of the trace to which this span belongs"
)
]
spanID: Annotated[
str, # TODO: find out why these are sent as string
Field(
alias="spanID",
title="Span ID",
description="The ID of this span"
)
]
parentID: Annotated[
str, # TODO: find out why these are sent as string
Field(
alias="parentID",
title="Parent ID",
description="The ID of this span's parent, or zero if this span has no parent"
)
] = None
start: Annotated[
str, # TODO: find out why these are sent as string
Field(
alias="start",
title="Start",
description="The number of nanoseconds between the Unix epoch and the beginning of this span"
# TODO: this can probably be validated better
)
]
duration: Annotated[
str, # TODO: find out why these are sent as string
Field(
alias="duration",
title="Duration",
description="The time length of this span in nanoseconds"
)
]
error: Annotated[
int,
Field(
alias="error",
title="Error",
description="Error is 1 if there is an error associated with this span, or 0 if there is not"
)
] = None
meta: Annotated[
dict[str, str],
Field(
alias="meta",
title="Meta",
description="Meta is a mapping from tag name to tag value for string-valued tags"
)
] = None
metrics: Annotated[
dict[str, float],
Field(
alias="metrics",
title="Metrics",
description="Metrics is a mapping from tag name to tag value for numeric-valued tags"
)
] = None
type: Annotated[
str,
Field(
alias="type",
title="Type",
description="Represents the type of the service with which this span is associated. Example values: `web`, `db`, `lambda`"
)
] = None
meta_struct: Annotated[
dict[str, int],
Field(
alias="meta_struct",
title="Meta Struct",
description="Represents a registry of structured \"other\" data used by, e.g., AppSec"
)
] = None


class TraceChunk(BaseModel):
priority: Annotated[
int,
Field(
alias="priority",
title="Priority",
description="Specifies the sampling priority of the trace"
)
]
origin: Annotated[
str,
Field(
alias="origin",
title="Origin",
description="Specifies the origin product (`lambda`, `rum`, etc.) of the trace"
)
] = None
spans: Annotated[
List[Span],
Field(
alias="spans",
title="Spans",
description="Specifies the list of containing spans"
)
]
tags: Annotated[
dict[str, str],
Field(
alias="tags",
title="Tags",
description="Specifies the list of tags common in all Spans",
)
] = None
droppedTrace: Annotated[
bool,
Field(
alias="droppedTrace",
title="Dropped Trace",
description="Specifies whether the trace was dropped by samplers or not",
)
] = None


class TracerPayload(BaseModel):
containerID: Annotated[
str,
Field(
alias="containerID",
title="Container ID",
description="Specifies the ID of the container where the tracer is running on"
)
] = None
languageName: Annotated[
str,
Field(
alias="languageName",
title="Language Name",
description="Specifies the language of the tracer",
pattern=r"^(golang|python|php|ruby|jvm|dotnet|js)$",
)
]
languageVersion: Annotated[
str,
Field(
alias="languageVersion",
title="Language Version",
description="Specifies the language version of the tracer",
# TODO: add pattern to validate version string
)
]
tracerVersion: Annotated[
str,
Field(
alias="tracerVersion",
title="Tracer Version",
description="Specifies the version of the tracer",
# TODO: add pattern to validate version string
)
]
runtimeID: Annotated[
str,
Field(
alias="runtimeID",
title="Runtime ID",
description="Specifies V4 UUID representation of a tracer session",
# TODO: add pattern to validate UUID
)
] = None
chunks: Annotated[
List[TraceChunk],
Field(
alias="chunks",
title="Trace Chunks",
description="Specifies the list of containing trace chunks",
)
]
tags: Annotated[
dict[str, str],
Field(
alias="tags",
title="Trace Tags",
description="Specifies the list of tags common in all Trace Chunks",
)
] = None
env: Annotated[
str,
Field(
alias="env",
title="Env",
description="Specifies the `env` tag that is set in the tracer configuration",
)
]
hostname: Annotated[
str,
Field(
alias="hostname",
title="Hostname",
description="Specifies the hostname where the tracer is running",
)
] = None
appVersion: Annotated[
str,
Field(
alias="appVersion",
title="App Version",
description="Specifies the `version` tag that set in the tracer configuration",
)
]


class SpanLink(BaseModel):
traceID: Annotated[TraceId, Field(alias="traceID", title="Trace ID")] = ...
traceID_High: Annotated[
Expand Down Expand Up @@ -457,6 +733,9 @@ class AgentPayload(BaseModel):
"""
Represents the generic semantics for the agent payload, structurally defined here: https://github.com/DataDog/datadog-agent/blob/main/pkg/proto/datadog/trace/agent_payload.proto
"""
# adding this here since otherwise these models are not included in the $defs object
http_tags: HTTPTags = None
db_tags: DBTags = None

hostName: Annotated[
Hostname,
Expand Down Expand Up @@ -518,12 +797,17 @@ class AgentPayload(BaseModel):
description="""Holds `RareSamplerEnabled` value in AgentConfig""",
),
] = None

# TODO: tracerPayloads
tracerPayloads: Annotated[
List[TracerPayload],
Field(
alias="tracerPayloads",
title="Tracer Payloads",
description="""Specifies the list of the payloads received from tracers""",
)
]


def generate_schema(payload_type, version):

json_schema_str = json.dumps(payload_type.model_json_schema(), indent=2)

# Create the directory if it doesn't exist
Expand Down Expand Up @@ -552,17 +836,14 @@ def generate_schema(payload_type, version):

args = parser.parse_args()

try:
payload_types = [
IntakeResolvedSpan,
IntakeResolvedHttpSpan,
IntakeResolvedDbSpan,
AgentPayload,
]
payload_types = [
IntakeResolvedSpan,
IntakeResolvedHttpSpan,
IntakeResolvedDbSpan,
AgentPayload,
]

for pt in payload_types:
generate_schema(pt, version=args.version)
for pt in payload_types:
generate_schema(pt, version=args.version)

logger.info(f"Schema successfully generated for version: {args.version}")
except Exception as e:
logger.error(f"Error generating schema: {e}")
logger.info(f"Schema successfully generated for version: {args.version}")
Loading