From 939716f05426a225119b13da2c3fb879a8f76f93 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Thu, 13 Feb 2025 09:13:16 -0500 Subject: [PATCH 1/9] feat: factor our helpers --- .../test_otel_instrumentation.py | 10 +-- .../opentelemetry_instrumentation/triggers.py | 10 +-- hatchet_sdk/opentelemetry/instrumentor.py | 61 +++++++++---------- 3 files changed, 42 insertions(+), 39 deletions(-) diff --git a/examples/opentelemetry_instrumentation/test_otel_instrumentation.py b/examples/opentelemetry_instrumentation/test_otel_instrumentation.py index fa4e785d..a6f6e471 100644 --- a/examples/opentelemetry_instrumentation/test_otel_instrumentation.py +++ b/examples/opentelemetry_instrumentation/test_otel_instrumentation.py @@ -6,7 +6,11 @@ from hatchet_sdk import Hatchet, Worker from hatchet_sdk.clients.admin import TriggerWorkflowOptions from hatchet_sdk.clients.events import PushEventOptions -from hatchet_sdk.opentelemetry.instrumentor import HatchetInstrumentor +from hatchet_sdk.opentelemetry.instrumentor import ( + HatchetInstrumentor, + create_traceparent, + inject_traceparent_into_metadata, +) trace_provider = NoOpTracerProvider() @@ -17,9 +21,7 @@ def create_additional_metadata() -> dict[str, str]: - return instrumentor.inject_traceparent_into_metadata( - {"hello": "world"}, instrumentor.create_traceparent() - ) + return inject_traceparent_into_metadata({"hello": "world"}, create_traceparent()) def create_push_options() -> PushEventOptions: diff --git a/examples/opentelemetry_instrumentation/triggers.py b/examples/opentelemetry_instrumentation/triggers.py index 3a7b8cb0..cfb796c5 100644 --- a/examples/opentelemetry_instrumentation/triggers.py +++ b/examples/opentelemetry_instrumentation/triggers.py @@ -4,16 +4,18 @@ from examples.opentelemetry_instrumentation.tracer import trace_provider from hatchet_sdk.clients.admin import TriggerWorkflowOptions from hatchet_sdk.clients.events import PushEventOptions -from hatchet_sdk.opentelemetry.instrumentor import HatchetInstrumentor +from hatchet_sdk.opentelemetry.instrumentor import ( + HatchetInstrumentor, + create_traceparent, + inject_traceparent_into_metadata, +) instrumentor = HatchetInstrumentor(tracer_provider=trace_provider) tracer = trace_provider.get_tracer(__name__) def create_additional_metadata() -> dict[str, str]: - return instrumentor.inject_traceparent_into_metadata( - {"hello": "world"}, instrumentor.create_traceparent() - ) + return inject_traceparent_into_metadata({"hello": "world"}, create_traceparent()) def create_push_options() -> PushEventOptions: diff --git a/hatchet_sdk/opentelemetry/instrumentor.py b/hatchet_sdk/opentelemetry/instrumentor.py index d13e3fc5..b170c381 100644 --- a/hatchet_sdk/opentelemetry/instrumentor.py +++ b/hatchet_sdk/opentelemetry/instrumentor.py @@ -43,48 +43,47 @@ InstrumentKwargs = TracerProvider | MeterProvider | None +OTEL_TRACEPARENT_KEY = "traceparent" -class HatchetInstrumentor(BaseInstrumentor): # type: ignore[misc] - OTEL_TRACEPARENT_KEY = "traceparent" - def __init__( - self, - tracer_provider: TracerProvider, - meter_provider: MeterProvider = NoOpMeterProvider(), - ): - self.tracer_provider = tracer_provider - self.meter_provider = meter_provider +def create_traceparent() -> str | None: + carrier: dict[str, str] = {} + TraceContextTextMapPropagator().inject(carrier) - super().__init__() + return carrier.get("traceparent") - def create_traceparent(self) -> str | None: - carrier: dict[str, str] = {} - TraceContextTextMapPropagator().inject(carrier) - return carrier.get("traceparent") +def parse_carrier_from_metadata(metadata: dict[str, str] | None) -> Context | None: + if not metadata: + return None - def parse_carrier_from_metadata( - self, metadata: dict[str, str] | None - ) -> Context | None: - if not metadata: - return None + traceparent = metadata.get(OTEL_TRACEPARENT_KEY) - traceparent = metadata.get(self.OTEL_TRACEPARENT_KEY) + if not traceparent: + return None - if not traceparent: - return None + return TraceContextTextMapPropagator().extract({OTEL_TRACEPARENT_KEY: traceparent}) - return TraceContextTextMapPropagator().extract( - {self.OTEL_TRACEPARENT_KEY: traceparent} - ) - def inject_traceparent_into_metadata( - self, metadata: dict[str, str], traceparent: str | None - ) -> dict[str, str]: - if traceparent: - metadata[self.OTEL_TRACEPARENT_KEY] = traceparent +def inject_traceparent_into_metadata( + metadata: dict[str, str], traceparent: str | None +) -> dict[str, str]: + if traceparent: + metadata[OTEL_TRACEPARENT_KEY] = traceparent + + return metadata - return metadata + +class HatchetInstrumentor(BaseInstrumentor): # type: ignore[misc] + def __init__( + self, + tracer_provider: TracerProvider, + meter_provider: MeterProvider = NoOpMeterProvider(), + ): + self.tracer_provider = tracer_provider + self.meter_provider = meter_provider + + super().__init__() def instrumentation_dependencies(self) -> Collection[str]: return tuple() From 5b79582f09c515f679b5b267a7d226889318b420 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Thu, 13 Feb 2025 09:13:29 -0500 Subject: [PATCH 2/9] chore: bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 29cc6578..a3016a55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "hatchet-sdk" -version = "0.46.1" +version = "0.46.2" description = "" authors = ["Alexander Belanger "] readme = "README.md" From df9dd9fcb3d953a55f786b8c1105106476534cb9 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Thu, 13 Feb 2025 09:15:13 -0500 Subject: [PATCH 3/9] fix: replace none-default --- hatchet_sdk/opentelemetry/instrumentor.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/hatchet_sdk/opentelemetry/instrumentor.py b/hatchet_sdk/opentelemetry/instrumentor.py index b170c381..7030bd29 100644 --- a/hatchet_sdk/opentelemetry/instrumentor.py +++ b/hatchet_sdk/opentelemetry/instrumentor.py @@ -66,12 +66,18 @@ def parse_carrier_from_metadata(metadata: dict[str, str] | None) -> Context | No def inject_traceparent_into_metadata( - metadata: dict[str, str], traceparent: str | None + metadata: dict[str, str], traceparent: str | None = None ) -> dict[str, str]: - if traceparent: - metadata[OTEL_TRACEPARENT_KEY] = traceparent + if not traceparent: + traceparent = create_traceparent() + + if not traceparent: + return metadata - return metadata + return { + **metadata, + OTEL_TRACEPARENT_KEY: traceparent, + } class HatchetInstrumentor(BaseInstrumentor): # type: ignore[misc] From bbeef35724a5760af0bb5c8919b68ce545edfc24 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Thu, 13 Feb 2025 09:18:00 -0500 Subject: [PATCH 4/9] fix: auto-create parent --- .../opentelemetry_instrumentation/test_otel_instrumentation.py | 2 +- examples/opentelemetry_instrumentation/triggers.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/opentelemetry_instrumentation/test_otel_instrumentation.py b/examples/opentelemetry_instrumentation/test_otel_instrumentation.py index a6f6e471..4ec3def2 100644 --- a/examples/opentelemetry_instrumentation/test_otel_instrumentation.py +++ b/examples/opentelemetry_instrumentation/test_otel_instrumentation.py @@ -21,7 +21,7 @@ def create_additional_metadata() -> dict[str, str]: - return inject_traceparent_into_metadata({"hello": "world"}, create_traceparent()) + return inject_traceparent_into_metadata({"hello": "world"}) def create_push_options() -> PushEventOptions: diff --git a/examples/opentelemetry_instrumentation/triggers.py b/examples/opentelemetry_instrumentation/triggers.py index cfb796c5..b9ab4fd6 100644 --- a/examples/opentelemetry_instrumentation/triggers.py +++ b/examples/opentelemetry_instrumentation/triggers.py @@ -15,7 +15,7 @@ def create_additional_metadata() -> dict[str, str]: - return inject_traceparent_into_metadata({"hello": "world"}, create_traceparent()) + return inject_traceparent_into_metadata({"hello": "world"}) def create_push_options() -> PushEventOptions: From d9222a6b12817c04fa29f25969aa303902e9efe7 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Thu, 13 Feb 2025 09:22:50 -0500 Subject: [PATCH 5/9] feat: docs --- hatchet_sdk/opentelemetry/instrumentor.py | 43 +++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/hatchet_sdk/opentelemetry/instrumentor.py b/hatchet_sdk/opentelemetry/instrumentor.py index 7030bd29..cf243e2e 100644 --- a/hatchet_sdk/opentelemetry/instrumentor.py +++ b/hatchet_sdk/opentelemetry/instrumentor.py @@ -47,6 +47,19 @@ def create_traceparent() -> str | None: + """ + Creates and returns a W3C traceparent header value using OpenTelemetry's context propagation. + + The traceparent header is used to propagate context information across service boundaries + in distributed tracing systems. It follows the W3C Trace Context specification. + + Returns: + str | None: A W3C-formatted traceparent header value if successful, None if the context + injection fails or no active span exists. + + Example: '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' + """ + carrier: dict[str, str] = {} TraceContextTextMapPropagator().inject(carrier) @@ -54,6 +67,21 @@ def create_traceparent() -> str | None: def parse_carrier_from_metadata(metadata: dict[str, str] | None) -> Context | None: + """ + Parses OpenTelemetry trace context from metadata dictionary. + This function extracts the trace context from metadata using the W3C Trace Context format, + specifically looking for the traceparent header. + Args: + metadata (dict[str, str] | None): A dictionary containing metadata key-value pairs, + potentially including the traceparent header. Can be None. + Returns: + Context | None: The extracted OpenTelemetry Context object if a valid traceparent + is found in the metadata, None otherwise. + Example: + >>> metadata = {"traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"} + >>> context = parse_carrier_from_metadata(metadata) + """ + if not metadata: return None @@ -68,6 +96,21 @@ def parse_carrier_from_metadata(metadata: dict[str, str] | None) -> Context | No def inject_traceparent_into_metadata( metadata: dict[str, str], traceparent: str | None = None ) -> dict[str, str]: + """ + Injects OpenTelemetry traceparent into metadata dictionary. + This function takes a metadata dictionary and an optional traceparent string, + and returns a new metadata dictionary with the traceparent added under the + OTEL_TRACEPARENT_KEY. If no traceparent is provided, it attempts to create one. + + Args: + metadata (dict[str, str]): The metadata dictionary to inject the traceparent into + traceparent (str | None, optional): The traceparent string to inject. If None, + will attempt to use the current span. Defaults to None. + Returns: + dict[str, str]: A new metadata dictionary containing the original metadata plus + the injected traceparent if one was available or could be created. + """ + if not traceparent: traceparent = create_traceparent() From 79a329f83850efa5c3afc10aafb9a43d4a992c4f Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Thu, 13 Feb 2025 09:48:37 -0500 Subject: [PATCH 6/9] fix: doc format --- hatchet_sdk/opentelemetry/instrumentor.py | 65 +++++++++++++---------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/hatchet_sdk/opentelemetry/instrumentor.py b/hatchet_sdk/opentelemetry/instrumentor.py index cf243e2e..fd8d2475 100644 --- a/hatchet_sdk/opentelemetry/instrumentor.py +++ b/hatchet_sdk/opentelemetry/instrumentor.py @@ -53,11 +53,10 @@ def create_traceparent() -> str | None: The traceparent header is used to propagate context information across service boundaries in distributed tracing systems. It follows the W3C Trace Context specification. - Returns: - str | None: A W3C-formatted traceparent header value if successful, None if the context - injection fails or no active span exists. - - Example: '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' + :returns: A W3C-formatted traceparent header value if successful, None if the context + injection fails or no active span exists.\n + Example: `00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01` + :rtype: str | None: """ carrier: dict[str, str] = {} @@ -68,16 +67,19 @@ def create_traceparent() -> str | None: def parse_carrier_from_metadata(metadata: dict[str, str] | None) -> Context | None: """ - Parses OpenTelemetry trace context from metadata dictionary. - This function extracts the trace context from metadata using the W3C Trace Context format, - specifically looking for the traceparent header. - Args: - metadata (dict[str, str] | None): A dictionary containing metadata key-value pairs, - potentially including the traceparent header. Can be None. - Returns: - Context | None: The extracted OpenTelemetry Context object if a valid traceparent - is found in the metadata, None otherwise. - Example: + Parses OpenTelemetry trace context from a metadata dictionary. + + Extracts the trace context from metadata using the W3C Trace Context format, + specifically looking for the `traceparent` header. + + :param metadata: A dictionary containing metadata key-value pairs, + potentially including the `traceparent` header. Can be None. + :type metadata: dict[str, str] | None + :returns: The extracted OpenTelemetry Context object if a valid `traceparent` + is found in the metadata, otherwise None. + :rtype: Context | None + + :example: >>> metadata = {"traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"} >>> context = parse_carrier_from_metadata(metadata) """ @@ -97,18 +99,27 @@ def inject_traceparent_into_metadata( metadata: dict[str, str], traceparent: str | None = None ) -> dict[str, str]: """ - Injects OpenTelemetry traceparent into metadata dictionary. - This function takes a metadata dictionary and an optional traceparent string, - and returns a new metadata dictionary with the traceparent added under the - OTEL_TRACEPARENT_KEY. If no traceparent is provided, it attempts to create one. - - Args: - metadata (dict[str, str]): The metadata dictionary to inject the traceparent into - traceparent (str | None, optional): The traceparent string to inject. If None, - will attempt to use the current span. Defaults to None. - Returns: - dict[str, str]: A new metadata dictionary containing the original metadata plus - the injected traceparent if one was available or could be created. + Injects OpenTelemetry `traceparent` into a metadata dictionary. + + Takes a metadata dictionary and an optional `traceparent` string, + returning a new metadata dictionary with the `traceparent` added under the + `OTEL_TRACEPARENT_KEY`. If no `traceparent` is provided, it attempts to create one. + + :param metadata: The metadata dictionary to inject the `traceparent` into. + :type metadata: dict[str, str] + :param traceparent: The `traceparent` string to inject. If None, attempts to use + the current span. + :type traceparent: str | None, optional + :returns: A new metadata dictionary containing the original metadata plus + the injected `traceparent`, if one was available or could be created. + :rtype: dict[str, str] + + :Example: + + >>> metadata = {"key": "value"} + >>> new_metadata = inject_traceparent(metadata, "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01") + >>> print(new_metadata) + {"key": "value", "traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"} """ if not traceparent: From bfc0a3027eab52c2085dcfaee08a4b034d584755 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Thu, 13 Feb 2025 10:22:30 -0500 Subject: [PATCH 7/9] fix: example --- hatchet_sdk/opentelemetry/instrumentor.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/hatchet_sdk/opentelemetry/instrumentor.py b/hatchet_sdk/opentelemetry/instrumentor.py index fd8d2475..4e5b2b6d 100644 --- a/hatchet_sdk/opentelemetry/instrumentor.py +++ b/hatchet_sdk/opentelemetry/instrumentor.py @@ -79,9 +79,10 @@ def parse_carrier_from_metadata(metadata: dict[str, str] | None) -> Context | No is found in the metadata, otherwise None. :rtype: Context | None - :example: - >>> metadata = {"traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"} - >>> context = parse_carrier_from_metadata(metadata) + :Example: + + >>> metadata = {"traceparent": "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"} + >>> context = parse_carrier_from_metadata(metadata) """ if not metadata: From 31866ca12ba1a655833599d6f9166e86680c7a14 Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Fri, 14 Feb 2025 11:21:05 -0500 Subject: [PATCH 8/9] feat: use global tracer if none provided --- hatchet_sdk/opentelemetry/instrumentor.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/hatchet_sdk/opentelemetry/instrumentor.py b/hatchet_sdk/opentelemetry/instrumentor.py index 4e5b2b6d..4e1cca8c 100644 --- a/hatchet_sdk/opentelemetry/instrumentor.py +++ b/hatchet_sdk/opentelemetry/instrumentor.py @@ -13,6 +13,7 @@ StatusCode, TracerProvider, get_tracer, + get_tracer_provider, ) from opentelemetry.trace.propagation.tracecontext import ( TraceContextTextMapPropagator, @@ -138,11 +139,11 @@ def inject_traceparent_into_metadata( class HatchetInstrumentor(BaseInstrumentor): # type: ignore[misc] def __init__( self, - tracer_provider: TracerProvider, - meter_provider: MeterProvider = NoOpMeterProvider(), + tracer_provider: TracerProvider | None = None, + meter_provider: MeterProvider | None = None, ): - self.tracer_provider = tracer_provider - self.meter_provider = meter_provider + self.tracer_provider = tracer_provider or get_tracer_provider() + self.meter_provider = meter_provider or NoOpMeterProvider() super().__init__() @@ -214,7 +215,7 @@ async def _wrap_handle_start_step_run( kwargs: Any, ) -> Exception | None: action = args[0] - traceparent = self.parse_carrier_from_metadata(action.additional_metadata) + traceparent = parse_carrier_from_metadata(action.additional_metadata) with self._tracer.start_as_current_span( "hatchet.start_step_run", From e78d162746917b87d4a98f13b790cf0c5b3f3d9b Mon Sep 17 00:00:00 2001 From: mrkaye97 Date: Fri, 14 Feb 2025 11:24:06 -0500 Subject: [PATCH 9/9] fix: docs --- hatchet_sdk/opentelemetry/instrumentor.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/hatchet_sdk/opentelemetry/instrumentor.py b/hatchet_sdk/opentelemetry/instrumentor.py index 4e1cca8c..91474c52 100644 --- a/hatchet_sdk/opentelemetry/instrumentor.py +++ b/hatchet_sdk/opentelemetry/instrumentor.py @@ -142,6 +142,18 @@ def __init__( tracer_provider: TracerProvider | None = None, meter_provider: MeterProvider | None = None, ): + """ + Hatchet OpenTelemetry instrumentor. + + The instrumentor provides an OpenTelemetry integration for Hatchet by setting up + tracing and metrics collection. + + :param tracer_provider: TracerProvider | None: The OpenTelemetry TracerProvider to use. + If not provided, the global tracer provider will be used. + :param meter_provider: MeterProvider | None: The OpenTelemetry MeterProvider to use. + If not provided, a no-op meter provider will be used. + """ + self.tracer_provider = tracer_provider or get_tracer_provider() self.meter_provider = meter_provider or NoOpMeterProvider()