From a85d216e4270a5ff960780b007be7fffcae1c29d Mon Sep 17 00:00:00 2001 From: JWinermaSplunk Date: Thu, 11 Dec 2025 12:37:09 -0800 Subject: [PATCH 1/9] initial commit --- pyproject.toml | 16 ++++++++-------- src/splunk_otel/profile.py | 34 +++++++++++++++++++--------------- tests/test_profile.py | 6 +++--- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6d96b0c8..87fee756 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,14 +23,14 @@ classifiers = [ "Programming Language :: Python :: Implementation :: PyPy", ] dependencies = [ - "opentelemetry-api==1.38.0", - "opentelemetry-sdk==1.38.0", - "opentelemetry-propagator-b3==1.38.0", - "opentelemetry-exporter-otlp-proto-grpc==1.38.0", - "opentelemetry-exporter-otlp-proto-http==1.38.0", - "opentelemetry-instrumentation==0.59b0", - "opentelemetry-instrumentation-system-metrics==0.59b0", - "opentelemetry-semantic-conventions==0.59b0", + "opentelemetry-api==1.39.1", + "opentelemetry-sdk==1.39.1", + "opentelemetry-propagator-b3==1.39.1", + "opentelemetry-exporter-otlp-proto-grpc==1.39.1", + "opentelemetry-exporter-otlp-proto-http==1.39.1", + "opentelemetry-instrumentation==0.60b1", + "opentelemetry-instrumentation-system-metrics==0.60b1", + "opentelemetry-semantic-conventions==0.60b1", "protobuf>=6.31.1", # not our direct dep, prevents installing vulnerable proto versions (CVE‑2025‑4565) ] diff --git a/src/splunk_otel/profile.py b/src/splunk_otel/profile.py index a088da14..6a39064f 100644 --- a/src/splunk_otel/profile.py +++ b/src/splunk_otel/profile.py @@ -4,16 +4,15 @@ import sys import threading import time -import traceback from collections import OrderedDict from traceback import StackSummary import opentelemetry.context import wrapt -from opentelemetry._logs import Logger, SeverityNumber, get_logger +from opentelemetry._logs import Logger, SeverityNumber, get_logger, LogRecord from opentelemetry.context import Context from opentelemetry.instrumentation.version import __version__ as version -from opentelemetry.sdk._logs import LogRecord +from opentelemetry.sdk._logs import ReadWriteLogRecord from opentelemetry.sdk.environment_variables import OTEL_SERVICE_NAME from opentelemetry.sdk.resources import Resource from opentelemetry.trace import TraceFlags @@ -172,28 +171,33 @@ def tick(self): def mk_log_record(self, stacktraces): lengths = (len(trace["frames"]) for trace in stacktraces) total_frame_count = sum(lengths) - time_seconds = self.time() pb_profile = _stacktraces_to_cpu_profile(stacktraces, self.thread_states, self.interval_millis, time_seconds) pb_profile_str = _pb_profile_to_str(pb_profile) - return LogRecord( - timestamp=int(time_seconds * 1e9), + context = Context( trace_id=0, span_id=0, trace_flags=TraceFlags(0x01), - severity_number=SeverityNumber.UNSPECIFIED, - body=pb_profile_str, - resource=self.resource, - attributes={ - "profiling.data.format": "pprof-gzip-base64", - "profiling.data.type": "cpu", - "com.splunk.sourcetype": "otel.profiling", - "profiling.data.total.frame.count": total_frame_count, - }, ) + return ReadWriteLogRecord._from_api_log_record( + record=LogRecord( + timestamp=int(time_seconds * 1e9), + observed_timestamp=int(time_seconds * 1e9), + context=context, + severity_number=SeverityNumber.UNSPECIFIED, + body=pb_profile_str, + attributes={ + "profiling.data.format": "pprof-gzip-base64", + "profiling.data.type": "cpu", + "com.splunk.sourcetype": "otel.profiling", + "profiling.data.total.frame.count": total_frame_count, + }, + ), + resource=self.resource, + ) def _pb_profile_to_str(pb_profile) -> str: serialized = pb_profile.SerializeToString() diff --git a/tests/test_profile.py b/tests/test_profile.py index 7946e789..d0fb4d8a 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -85,9 +85,9 @@ def test_profile_scraper(stacktraces_fixture): log_record = logger.log_records[0] - assert log_record.timestamp == int(time_seconds * 1e9) - assert len(MessageToDict(_pb_profile_from_str(log_record.body))) == 4 # sanity check - assert log_record.attributes["profiling.data.total.frame.count"] == 30 + assert log_record.log_record.timestamp == int(time_seconds * 1e9) + assert len(MessageToDict(_pb_profile_from_str(log_record.log_record.body))) == 4 # sanity check + assert log_record.log_record.attributes["profiling.data.total.frame.count"] == 30 def _pb_profile_from_str(stringified: str) -> profile_pb2.Profile: From b573d3bff939beb4c7493b6bf4782f0484f5da9e Mon Sep 17 00:00:00 2001 From: JWinermaSplunk Date: Thu, 11 Dec 2025 12:44:16 -0800 Subject: [PATCH 2/9] add back traceback --- src/splunk_otel/profile.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/splunk_otel/profile.py b/src/splunk_otel/profile.py index 6a39064f..b9f0b894 100644 --- a/src/splunk_otel/profile.py +++ b/src/splunk_otel/profile.py @@ -4,6 +4,7 @@ import sys import threading import time +import traceback from collections import OrderedDict from traceback import StackSummary From 7f6fb534636dd2730bfc321bb2b834423244befc Mon Sep 17 00:00:00 2001 From: JWinermaSplunk Date: Mon, 15 Dec 2025 16:04:39 -0800 Subject: [PATCH 3/9] try API LogRecord --- src/splunk_otel/profile.py | 28 ++++++++++++---------------- tests/test_profile.py | 6 +++--- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/splunk_otel/profile.py b/src/splunk_otel/profile.py index b9f0b894..c903b6b4 100644 --- a/src/splunk_otel/profile.py +++ b/src/splunk_otel/profile.py @@ -13,7 +13,6 @@ from opentelemetry._logs import Logger, SeverityNumber, get_logger, LogRecord from opentelemetry.context import Context from opentelemetry.instrumentation.version import __version__ as version -from opentelemetry.sdk._logs import ReadWriteLogRecord from opentelemetry.sdk.environment_variables import OTEL_SERVICE_NAME from opentelemetry.sdk.resources import Resource from opentelemetry.trace import TraceFlags @@ -183,21 +182,18 @@ def mk_log_record(self, stacktraces): trace_flags=TraceFlags(0x01), ) - return ReadWriteLogRecord._from_api_log_record( - record=LogRecord( - timestamp=int(time_seconds * 1e9), - observed_timestamp=int(time_seconds * 1e9), - context=context, - severity_number=SeverityNumber.UNSPECIFIED, - body=pb_profile_str, - attributes={ - "profiling.data.format": "pprof-gzip-base64", - "profiling.data.type": "cpu", - "com.splunk.sourcetype": "otel.profiling", - "profiling.data.total.frame.count": total_frame_count, - }, - ), - resource=self.resource, + return LogRecord( + timestamp=int(time_seconds * 1e9), + observed_timestamp=int(time_seconds * 1e9), + context=context, + severity_number=SeverityNumber.UNSPECIFIED, + body=pb_profile_str, + attributes={ + "profiling.data.format": "pprof-gzip-base64", + "profiling.data.type": "cpu", + "com.splunk.sourcetype": "otel.profiling", + "profiling.data.total.frame.count": total_frame_count, + }, ) def _pb_profile_to_str(pb_profile) -> str: diff --git a/tests/test_profile.py b/tests/test_profile.py index d0fb4d8a..7946e789 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -85,9 +85,9 @@ def test_profile_scraper(stacktraces_fixture): log_record = logger.log_records[0] - assert log_record.log_record.timestamp == int(time_seconds * 1e9) - assert len(MessageToDict(_pb_profile_from_str(log_record.log_record.body))) == 4 # sanity check - assert log_record.log_record.attributes["profiling.data.total.frame.count"] == 30 + assert log_record.timestamp == int(time_seconds * 1e9) + assert len(MessageToDict(_pb_profile_from_str(log_record.body))) == 4 # sanity check + assert log_record.attributes["profiling.data.total.frame.count"] == 30 def _pb_profile_from_str(stringified: str) -> profile_pb2.Profile: From 33956cdd6ce7f99b5d3ac09d2984226b5aa198c9 Mon Sep 17 00:00:00 2001 From: JWinermaSplunk Date: Mon, 15 Dec 2025 16:11:17 -0800 Subject: [PATCH 4/9] fix linting --- src/splunk_otel/distro.py | 8 ++++++-- src/splunk_otel/env.py | 14 +++++++++++--- src/splunk_otel/profile.py | 29 ++++++++++++++++++++++------- src/splunk_otel/profile_pb2.py | 4 +++- tests/ott_logging.py | 9 +++++++-- tests/ott_profile.py | 14 +++++++++++--- tests/ott_spec.py | 9 +++++++-- tests/ott_svcname_unset.py | 5 ++++- tests/ott_trace_loop.py | 9 ++++++++- tests/test_distro.py | 10 ++++++++-- tests/test_profile.py | 12 +++++++++--- tests/test_propagator.py | 5 ++++- 12 files changed, 100 insertions(+), 28 deletions(-) diff --git a/src/splunk_otel/distro.py b/src/splunk_otel/distro.py index 55b24a99..4c5ee988 100644 --- a/src/splunk_otel/distro.py +++ b/src/splunk_otel/distro.py @@ -83,8 +83,12 @@ def set_profiling_env(self): self.env.setval(OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, logs_endpt) def set_resource_attributes(self): - self.env.list_append(OTEL_RESOURCE_ATTRIBUTES, f"telemetry.distro.name={_DISTRO_NAME}") - self.env.list_append(OTEL_RESOURCE_ATTRIBUTES, f"telemetry.distro.version={version}") + self.env.list_append( + OTEL_RESOURCE_ATTRIBUTES, f"telemetry.distro.name={_DISTRO_NAME}" + ) + self.env.list_append( + OTEL_RESOURCE_ATTRIBUTES, f"telemetry.distro.version={version}" + ) def handle_realm(self): realm = self.env.getval(SPLUNK_REALM) diff --git a/src/splunk_otel/env.py b/src/splunk_otel/env.py index f2d35785..4acb1621 100644 --- a/src/splunk_otel/env.py +++ b/src/splunk_otel/env.py @@ -14,7 +14,11 @@ import logging import os -from opentelemetry.environment_variables import OTEL_LOGS_EXPORTER, OTEL_METRICS_EXPORTER, OTEL_TRACES_EXPORTER +from opentelemetry.environment_variables import ( + OTEL_LOGS_EXPORTER, + OTEL_METRICS_EXPORTER, + OTEL_TRACES_EXPORTER, +) from opentelemetry.sdk.environment_variables import ( OTEL_ATTRIBUTE_COUNT_LIMIT, OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, @@ -27,7 +31,9 @@ OTEL_TRACES_SAMPLER, ) -OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED = "OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED" +OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED = ( + "OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED" +) DEFAULTS = { OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED: "true", @@ -82,7 +88,9 @@ def getint(self, key, default=0): try: return int(val) except ValueError: - _pylogger.warning("Invalid integer value of '%s' for env var '%s'", val, key) + _pylogger.warning( + "Invalid integer value of '%s' for env var '%s'", val, key + ) return default def setval(self, key, value): diff --git a/src/splunk_otel/profile.py b/src/splunk_otel/profile.py index c903b6b4..ff8a75db 100644 --- a/src/splunk_otel/profile.py +++ b/src/splunk_otel/profile.py @@ -43,7 +43,9 @@ def _start_profiling_if_enabled(env=None): def start_profiling(env=None): env = env or Env() - interval_millis = env.getint(SPLUNK_PROFILER_CALL_STACK_INTERVAL, _DEFAULT_PROF_CALL_STACK_INTERVAL_MILLIS) + interval_millis = env.getint( + SPLUNK_PROFILER_CALL_STACK_INTERVAL, _DEFAULT_PROF_CALL_STACK_INTERVAL_MILLIS + ) svcname = env.getval(OTEL_SERVICE_NAME) tcm = _ThreadContextMapping() @@ -51,7 +53,9 @@ def start_profiling(env=None): resource = _mk_resource(svcname) logger = get_logger(_SCOPE_NAME, _SCOPE_VERSION) - scraper = _ProfileScraper(resource, tcm.get_thread_states(), interval_millis, logger) + scraper = _ProfileScraper( + resource, tcm.get_thread_states(), interval_millis, logger + ) global _timer # noqa PLW0603 _timer = _IntervalTimer(interval_millis, scraper.tick) @@ -79,8 +83,12 @@ def get_thread_states(self): return self.thread_states def wrap_context_methods(self): - wrapt.wrap_function_wrapper(opentelemetry.context, "attach", self.wrap_context_attach()) - wrapt.wrap_function_wrapper(opentelemetry.context, "detach", self.wrap_context_detach()) + wrapt.wrap_function_wrapper( + opentelemetry.context, "attach", self.wrap_context_attach() + ) + wrapt.wrap_function_wrapper( + opentelemetry.context, "detach", self.wrap_context_detach() + ) def wrap_context_attach(self): def wrapper(wrapped, _instance, args, kwargs): @@ -173,7 +181,9 @@ def mk_log_record(self, stacktraces): total_frame_count = sum(lengths) time_seconds = self.time() - pb_profile = _stacktraces_to_cpu_profile(stacktraces, self.thread_states, self.interval_millis, time_seconds) + pb_profile = _stacktraces_to_cpu_profile( + stacktraces, self.thread_states, self.interval_millis, time_seconds + ) pb_profile_str = _pb_profile_to_str(pb_profile) context = Context( @@ -196,6 +206,7 @@ def mk_log_record(self, stacktraces): }, ) + def _pb_profile_to_str(pb_profile) -> str: serialized = pb_profile.SerializeToString() compressed = gzip.compress(serialized) @@ -260,7 +271,9 @@ def _get_location(functions_table, str_table, locations_table, frame): def _get_line(functions_table, str_table, file_name, function_name, line_no): line = profile_pb2.Line() - line.function_id = _get_function(functions_table, str_table, file_name, function_name).id + line.function_id = _get_function( + functions_table, str_table, file_name, function_name + ).id if line_no is None or line_no == 0: line.line = -1 else: @@ -291,7 +304,9 @@ def _extract_stack_summary(frame): return out -def _stacktraces_to_cpu_profile(stacktraces, thread_states, interval_millis, time_seconds): +def _stacktraces_to_cpu_profile( + stacktraces, thread_states, interval_millis, time_seconds +): str_table = _StringTable() locations_table = OrderedDict() functions_table = OrderedDict() diff --git a/src/splunk_otel/profile_pb2.py b/src/splunk_otel/profile_pb2.py index 15aadbb2..d66fdc61 100644 --- a/src/splunk_otel/profile_pb2.py +++ b/src/splunk_otel/profile_pb2.py @@ -22,7 +22,9 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "profile_pb2", _globals) if _descriptor._USE_C_DESCRIPTORS is False: DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = b"\n\035com.google.perftools.profilesB\014ProfileProto" + DESCRIPTOR._serialized_options = ( + b"\n\035com.google.perftools.profilesB\014ProfileProto" + ) _globals["_PROFILE"]._serialized_start = 38 _globals["_PROFILE"]._serialized_end = 507 _globals["_VALUETYPE"]._serialized_start = 509 diff --git a/tests/ott_logging.py b/tests/ott_logging.py index 541a6feb..9477ac86 100644 --- a/tests/ott_logging.py +++ b/tests/ott_logging.py @@ -17,7 +17,10 @@ class LoggingOtelTest: def requirements(self): - return project_path(), f"opentelemetry-instrumentation-logging=={UPSTREAM_PRERELEASE_VERSION}" + return ( + project_path(), + f"opentelemetry-instrumentation-logging=={UPSTREAM_PRERELEASE_VERSION}", + ) def environment_variables(self): return { @@ -46,7 +49,9 @@ def get_scope_log_records(telemetry, scope_name): from oteltest.telemetry import extract_leaves out = [] - scope_logs = extract_leaves(telemetry, "log_requests", "pbreq", "resource_logs", "scope_logs") + scope_logs = extract_leaves( + telemetry, "log_requests", "pbreq", "resource_logs", "scope_logs" + ) for scope_log in scope_logs: if scope_log.scope.name == scope_name: out.extend(scope_log.log_records) diff --git a/tests/ott_profile.py b/tests/ott_profile.py index cabeb137..98eb6740 100644 --- a/tests/ott_profile.py +++ b/tests/ott_profile.py @@ -28,7 +28,15 @@ def on_start(self): def on_stop(self, tel, stdout: str, stderr: str, returncode: int): from oteltest.telemetry import extract_leaves, get_attribute - scope_logs = extract_leaves(tel, "log_requests", "pbreq", "resource_logs", "scope_logs") - profiling_scope_logs = [scope_log for scope_log in scope_logs if scope_log.scope.name == "otel.profiling"] - fmt_attr = get_attribute(profiling_scope_logs[0].log_records[0].attributes, "profiling.data.format") + scope_logs = extract_leaves( + tel, "log_requests", "pbreq", "resource_logs", "scope_logs" + ) + profiling_scope_logs = [ + scope_log + for scope_log in scope_logs + if scope_log.scope.name == "otel.profiling" + ] + fmt_attr = get_attribute( + profiling_scope_logs[0].log_records[0].attributes, "profiling.data.format" + ) assert fmt_attr.value.string_value == "pprof-gzip-base64" diff --git a/tests/ott_spec.py b/tests/ott_spec.py index 4d587d66..12de400f 100644 --- a/tests/ott_spec.py +++ b/tests/ott_spec.py @@ -39,11 +39,16 @@ def on_stop(self, telemetry, stdout: str, stderr: str, returncode: int) -> None: assert get_attribute(attributes, "telemetry.sdk.language") assert get_attribute(attributes, "telemetry.distro.version").value.string_value - assert get_attribute(attributes, "telemetry.distro.name").value.string_value == "splunk-opentelemetry" + assert ( + get_attribute(attributes, "telemetry.distro.name").value.string_value + == "splunk-opentelemetry" + ) assert get_attribute(attributes, "process.pid") - assert get_attribute(attributes, "service.name").value.string_value == SERVICE_NAME + assert ( + get_attribute(attributes, "service.name").value.string_value == SERVICE_NAME + ) def is_http(self): return False diff --git a/tests/ott_svcname_unset.py b/tests/ott_svcname_unset.py index 9e274a9e..61184a13 100644 --- a/tests/ott_svcname_unset.py +++ b/tests/ott_svcname_unset.py @@ -32,7 +32,10 @@ def on_stop(self, telemetry, stdout: str, stderr: str, returncode: int) -> None: "resource", "attributes", ) - assert get_attribute(attributes, "service.name").value.string_value == "unnamed-python-service" + assert ( + get_attribute(attributes, "service.name").value.string_value + == "unnamed-python-service" + ) def is_http(self): return False diff --git a/tests/ott_trace_loop.py b/tests/ott_trace_loop.py index a74d061e..8aca29b8 100644 --- a/tests/ott_trace_loop.py +++ b/tests/ott_trace_loop.py @@ -26,7 +26,14 @@ def on_stop(self, telemetry, stdout: str, stderr: str, returncode: int) -> None: assert count_spans(telemetry) == NUM_SPANS - attributes = extract_leaves(telemetry, "trace_requests", "pbreq", "resource_spans", "resource", "attributes") + attributes = extract_leaves( + telemetry, + "trace_requests", + "pbreq", + "resource_spans", + "resource", + "attributes", + ) assert get_attribute(attributes, "host.name") def is_http(self): diff --git a/tests/test_distro.py b/tests/test_distro.py index c11c40c7..8410d1cf 100644 --- a/tests/test_distro.py +++ b/tests/test_distro.py @@ -103,8 +103,14 @@ def test_service_name(caplog): def test_realm(): env_store = {"SPLUNK_REALM": "us2"} configure_distro(env_store) - assert env_store["OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"] == "https://ingest.us2.signalfx.com/v2/trace/otlp" - assert env_store["OTEL_EXPORTER_OTLP_METRICS_ENDPOINT"] == "https://ingest.us2.signalfx.com/v2/datapoint/otlp" + assert ( + env_store["OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"] + == "https://ingest.us2.signalfx.com/v2/trace/otlp" + ) + assert ( + env_store["OTEL_EXPORTER_OTLP_METRICS_ENDPOINT"] + == "https://ingest.us2.signalfx.com/v2/datapoint/otlp" + ) assert env_store["OTEL_EXPORTER_OTLP_PROTOCOL"] == "http/protobuf" diff --git a/tests/test_profile.py b/tests/test_profile.py index 7946e789..b25ece7f 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -53,10 +53,14 @@ def test_basic_proto_serialization(): assert profile == decoded_profile -def test_stacktraces_to_cpu_profile(stacktraces_fixture, pb_profile_fixture, thread_states_fixture): +def test_stacktraces_to_cpu_profile( + stacktraces_fixture, pb_profile_fixture, thread_states_fixture +): time_seconds = 1726760000 # corresponds to the timestamp in the fixture interval_millis = 100 - profile = _stacktraces_to_cpu_profile(stacktraces_fixture, thread_states_fixture, interval_millis, time_seconds) + profile = _stacktraces_to_cpu_profile( + stacktraces_fixture, thread_states_fixture, interval_millis, time_seconds + ) assert pb_profile_fixture == MessageToDict(profile) @@ -86,7 +90,9 @@ def test_profile_scraper(stacktraces_fixture): log_record = logger.log_records[0] assert log_record.timestamp == int(time_seconds * 1e9) - assert len(MessageToDict(_pb_profile_from_str(log_record.body))) == 4 # sanity check + assert ( + len(MessageToDict(_pb_profile_from_str(log_record.body))) == 4 + ) # sanity check assert log_record.attributes["profiling.data.total.frame.count"] == 30 diff --git a/tests/test_propagator.py b/tests/test_propagator.py index 6708fc7b..b912c6a1 100644 --- a/tests/test_propagator.py +++ b/tests/test_propagator.py @@ -32,4 +32,7 @@ def test_inject(): carrier = {} prop.inject(carrier, ctx) assert carrier["Access-Control-Expose-Headers"] == "Server-Timing" - assert carrier["Server-Timing"] == 'traceparent;desc="00-00000000000000000000000000000001-0000000000000002-01"' + assert ( + carrier["Server-Timing"] + == 'traceparent;desc="00-00000000000000000000000000000001-0000000000000002-01"' + ) From f970467c3c079b0716f0b97d0df1abb2d3ba7694 Mon Sep 17 00:00:00 2001 From: JWinermaSplunk Date: Mon, 15 Dec 2025 16:13:20 -0800 Subject: [PATCH 5/9] Revert "fix linting" This reverts commit 33956cdd6ce7f99b5d3ac09d2984226b5aa198c9. --- src/splunk_otel/distro.py | 8 ++------ src/splunk_otel/env.py | 14 +++----------- src/splunk_otel/profile.py | 29 +++++++---------------------- src/splunk_otel/profile_pb2.py | 4 +--- tests/ott_logging.py | 9 ++------- tests/ott_profile.py | 14 +++----------- tests/ott_spec.py | 9 ++------- tests/ott_svcname_unset.py | 5 +---- tests/ott_trace_loop.py | 9 +-------- tests/test_distro.py | 10 ++-------- tests/test_profile.py | 12 +++--------- tests/test_propagator.py | 5 +---- 12 files changed, 28 insertions(+), 100 deletions(-) diff --git a/src/splunk_otel/distro.py b/src/splunk_otel/distro.py index 4c5ee988..55b24a99 100644 --- a/src/splunk_otel/distro.py +++ b/src/splunk_otel/distro.py @@ -83,12 +83,8 @@ def set_profiling_env(self): self.env.setval(OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, logs_endpt) def set_resource_attributes(self): - self.env.list_append( - OTEL_RESOURCE_ATTRIBUTES, f"telemetry.distro.name={_DISTRO_NAME}" - ) - self.env.list_append( - OTEL_RESOURCE_ATTRIBUTES, f"telemetry.distro.version={version}" - ) + self.env.list_append(OTEL_RESOURCE_ATTRIBUTES, f"telemetry.distro.name={_DISTRO_NAME}") + self.env.list_append(OTEL_RESOURCE_ATTRIBUTES, f"telemetry.distro.version={version}") def handle_realm(self): realm = self.env.getval(SPLUNK_REALM) diff --git a/src/splunk_otel/env.py b/src/splunk_otel/env.py index 4acb1621..f2d35785 100644 --- a/src/splunk_otel/env.py +++ b/src/splunk_otel/env.py @@ -14,11 +14,7 @@ import logging import os -from opentelemetry.environment_variables import ( - OTEL_LOGS_EXPORTER, - OTEL_METRICS_EXPORTER, - OTEL_TRACES_EXPORTER, -) +from opentelemetry.environment_variables import OTEL_LOGS_EXPORTER, OTEL_METRICS_EXPORTER, OTEL_TRACES_EXPORTER from opentelemetry.sdk.environment_variables import ( OTEL_ATTRIBUTE_COUNT_LIMIT, OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT, @@ -31,9 +27,7 @@ OTEL_TRACES_SAMPLER, ) -OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED = ( - "OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED" -) +OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED = "OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED" DEFAULTS = { OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED: "true", @@ -88,9 +82,7 @@ def getint(self, key, default=0): try: return int(val) except ValueError: - _pylogger.warning( - "Invalid integer value of '%s' for env var '%s'", val, key - ) + _pylogger.warning("Invalid integer value of '%s' for env var '%s'", val, key) return default def setval(self, key, value): diff --git a/src/splunk_otel/profile.py b/src/splunk_otel/profile.py index ff8a75db..c903b6b4 100644 --- a/src/splunk_otel/profile.py +++ b/src/splunk_otel/profile.py @@ -43,9 +43,7 @@ def _start_profiling_if_enabled(env=None): def start_profiling(env=None): env = env or Env() - interval_millis = env.getint( - SPLUNK_PROFILER_CALL_STACK_INTERVAL, _DEFAULT_PROF_CALL_STACK_INTERVAL_MILLIS - ) + interval_millis = env.getint(SPLUNK_PROFILER_CALL_STACK_INTERVAL, _DEFAULT_PROF_CALL_STACK_INTERVAL_MILLIS) svcname = env.getval(OTEL_SERVICE_NAME) tcm = _ThreadContextMapping() @@ -53,9 +51,7 @@ def start_profiling(env=None): resource = _mk_resource(svcname) logger = get_logger(_SCOPE_NAME, _SCOPE_VERSION) - scraper = _ProfileScraper( - resource, tcm.get_thread_states(), interval_millis, logger - ) + scraper = _ProfileScraper(resource, tcm.get_thread_states(), interval_millis, logger) global _timer # noqa PLW0603 _timer = _IntervalTimer(interval_millis, scraper.tick) @@ -83,12 +79,8 @@ def get_thread_states(self): return self.thread_states def wrap_context_methods(self): - wrapt.wrap_function_wrapper( - opentelemetry.context, "attach", self.wrap_context_attach() - ) - wrapt.wrap_function_wrapper( - opentelemetry.context, "detach", self.wrap_context_detach() - ) + wrapt.wrap_function_wrapper(opentelemetry.context, "attach", self.wrap_context_attach()) + wrapt.wrap_function_wrapper(opentelemetry.context, "detach", self.wrap_context_detach()) def wrap_context_attach(self): def wrapper(wrapped, _instance, args, kwargs): @@ -181,9 +173,7 @@ def mk_log_record(self, stacktraces): total_frame_count = sum(lengths) time_seconds = self.time() - pb_profile = _stacktraces_to_cpu_profile( - stacktraces, self.thread_states, self.interval_millis, time_seconds - ) + pb_profile = _stacktraces_to_cpu_profile(stacktraces, self.thread_states, self.interval_millis, time_seconds) pb_profile_str = _pb_profile_to_str(pb_profile) context = Context( @@ -206,7 +196,6 @@ def mk_log_record(self, stacktraces): }, ) - def _pb_profile_to_str(pb_profile) -> str: serialized = pb_profile.SerializeToString() compressed = gzip.compress(serialized) @@ -271,9 +260,7 @@ def _get_location(functions_table, str_table, locations_table, frame): def _get_line(functions_table, str_table, file_name, function_name, line_no): line = profile_pb2.Line() - line.function_id = _get_function( - functions_table, str_table, file_name, function_name - ).id + line.function_id = _get_function(functions_table, str_table, file_name, function_name).id if line_no is None or line_no == 0: line.line = -1 else: @@ -304,9 +291,7 @@ def _extract_stack_summary(frame): return out -def _stacktraces_to_cpu_profile( - stacktraces, thread_states, interval_millis, time_seconds -): +def _stacktraces_to_cpu_profile(stacktraces, thread_states, interval_millis, time_seconds): str_table = _StringTable() locations_table = OrderedDict() functions_table = OrderedDict() diff --git a/src/splunk_otel/profile_pb2.py b/src/splunk_otel/profile_pb2.py index d66fdc61..15aadbb2 100644 --- a/src/splunk_otel/profile_pb2.py +++ b/src/splunk_otel/profile_pb2.py @@ -22,9 +22,7 @@ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, "profile_pb2", _globals) if _descriptor._USE_C_DESCRIPTORS is False: DESCRIPTOR._options = None - DESCRIPTOR._serialized_options = ( - b"\n\035com.google.perftools.profilesB\014ProfileProto" - ) + DESCRIPTOR._serialized_options = b"\n\035com.google.perftools.profilesB\014ProfileProto" _globals["_PROFILE"]._serialized_start = 38 _globals["_PROFILE"]._serialized_end = 507 _globals["_VALUETYPE"]._serialized_start = 509 diff --git a/tests/ott_logging.py b/tests/ott_logging.py index 9477ac86..541a6feb 100644 --- a/tests/ott_logging.py +++ b/tests/ott_logging.py @@ -17,10 +17,7 @@ class LoggingOtelTest: def requirements(self): - return ( - project_path(), - f"opentelemetry-instrumentation-logging=={UPSTREAM_PRERELEASE_VERSION}", - ) + return project_path(), f"opentelemetry-instrumentation-logging=={UPSTREAM_PRERELEASE_VERSION}" def environment_variables(self): return { @@ -49,9 +46,7 @@ def get_scope_log_records(telemetry, scope_name): from oteltest.telemetry import extract_leaves out = [] - scope_logs = extract_leaves( - telemetry, "log_requests", "pbreq", "resource_logs", "scope_logs" - ) + scope_logs = extract_leaves(telemetry, "log_requests", "pbreq", "resource_logs", "scope_logs") for scope_log in scope_logs: if scope_log.scope.name == scope_name: out.extend(scope_log.log_records) diff --git a/tests/ott_profile.py b/tests/ott_profile.py index 98eb6740..cabeb137 100644 --- a/tests/ott_profile.py +++ b/tests/ott_profile.py @@ -28,15 +28,7 @@ def on_start(self): def on_stop(self, tel, stdout: str, stderr: str, returncode: int): from oteltest.telemetry import extract_leaves, get_attribute - scope_logs = extract_leaves( - tel, "log_requests", "pbreq", "resource_logs", "scope_logs" - ) - profiling_scope_logs = [ - scope_log - for scope_log in scope_logs - if scope_log.scope.name == "otel.profiling" - ] - fmt_attr = get_attribute( - profiling_scope_logs[0].log_records[0].attributes, "profiling.data.format" - ) + scope_logs = extract_leaves(tel, "log_requests", "pbreq", "resource_logs", "scope_logs") + profiling_scope_logs = [scope_log for scope_log in scope_logs if scope_log.scope.name == "otel.profiling"] + fmt_attr = get_attribute(profiling_scope_logs[0].log_records[0].attributes, "profiling.data.format") assert fmt_attr.value.string_value == "pprof-gzip-base64" diff --git a/tests/ott_spec.py b/tests/ott_spec.py index 12de400f..4d587d66 100644 --- a/tests/ott_spec.py +++ b/tests/ott_spec.py @@ -39,16 +39,11 @@ def on_stop(self, telemetry, stdout: str, stderr: str, returncode: int) -> None: assert get_attribute(attributes, "telemetry.sdk.language") assert get_attribute(attributes, "telemetry.distro.version").value.string_value - assert ( - get_attribute(attributes, "telemetry.distro.name").value.string_value - == "splunk-opentelemetry" - ) + assert get_attribute(attributes, "telemetry.distro.name").value.string_value == "splunk-opentelemetry" assert get_attribute(attributes, "process.pid") - assert ( - get_attribute(attributes, "service.name").value.string_value == SERVICE_NAME - ) + assert get_attribute(attributes, "service.name").value.string_value == SERVICE_NAME def is_http(self): return False diff --git a/tests/ott_svcname_unset.py b/tests/ott_svcname_unset.py index 61184a13..9e274a9e 100644 --- a/tests/ott_svcname_unset.py +++ b/tests/ott_svcname_unset.py @@ -32,10 +32,7 @@ def on_stop(self, telemetry, stdout: str, stderr: str, returncode: int) -> None: "resource", "attributes", ) - assert ( - get_attribute(attributes, "service.name").value.string_value - == "unnamed-python-service" - ) + assert get_attribute(attributes, "service.name").value.string_value == "unnamed-python-service" def is_http(self): return False diff --git a/tests/ott_trace_loop.py b/tests/ott_trace_loop.py index 8aca29b8..a74d061e 100644 --- a/tests/ott_trace_loop.py +++ b/tests/ott_trace_loop.py @@ -26,14 +26,7 @@ def on_stop(self, telemetry, stdout: str, stderr: str, returncode: int) -> None: assert count_spans(telemetry) == NUM_SPANS - attributes = extract_leaves( - telemetry, - "trace_requests", - "pbreq", - "resource_spans", - "resource", - "attributes", - ) + attributes = extract_leaves(telemetry, "trace_requests", "pbreq", "resource_spans", "resource", "attributes") assert get_attribute(attributes, "host.name") def is_http(self): diff --git a/tests/test_distro.py b/tests/test_distro.py index 8410d1cf..c11c40c7 100644 --- a/tests/test_distro.py +++ b/tests/test_distro.py @@ -103,14 +103,8 @@ def test_service_name(caplog): def test_realm(): env_store = {"SPLUNK_REALM": "us2"} configure_distro(env_store) - assert ( - env_store["OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"] - == "https://ingest.us2.signalfx.com/v2/trace/otlp" - ) - assert ( - env_store["OTEL_EXPORTER_OTLP_METRICS_ENDPOINT"] - == "https://ingest.us2.signalfx.com/v2/datapoint/otlp" - ) + assert env_store["OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"] == "https://ingest.us2.signalfx.com/v2/trace/otlp" + assert env_store["OTEL_EXPORTER_OTLP_METRICS_ENDPOINT"] == "https://ingest.us2.signalfx.com/v2/datapoint/otlp" assert env_store["OTEL_EXPORTER_OTLP_PROTOCOL"] == "http/protobuf" diff --git a/tests/test_profile.py b/tests/test_profile.py index b25ece7f..7946e789 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -53,14 +53,10 @@ def test_basic_proto_serialization(): assert profile == decoded_profile -def test_stacktraces_to_cpu_profile( - stacktraces_fixture, pb_profile_fixture, thread_states_fixture -): +def test_stacktraces_to_cpu_profile(stacktraces_fixture, pb_profile_fixture, thread_states_fixture): time_seconds = 1726760000 # corresponds to the timestamp in the fixture interval_millis = 100 - profile = _stacktraces_to_cpu_profile( - stacktraces_fixture, thread_states_fixture, interval_millis, time_seconds - ) + profile = _stacktraces_to_cpu_profile(stacktraces_fixture, thread_states_fixture, interval_millis, time_seconds) assert pb_profile_fixture == MessageToDict(profile) @@ -90,9 +86,7 @@ def test_profile_scraper(stacktraces_fixture): log_record = logger.log_records[0] assert log_record.timestamp == int(time_seconds * 1e9) - assert ( - len(MessageToDict(_pb_profile_from_str(log_record.body))) == 4 - ) # sanity check + assert len(MessageToDict(_pb_profile_from_str(log_record.body))) == 4 # sanity check assert log_record.attributes["profiling.data.total.frame.count"] == 30 diff --git a/tests/test_propagator.py b/tests/test_propagator.py index b912c6a1..6708fc7b 100644 --- a/tests/test_propagator.py +++ b/tests/test_propagator.py @@ -32,7 +32,4 @@ def test_inject(): carrier = {} prop.inject(carrier, ctx) assert carrier["Access-Control-Expose-Headers"] == "Server-Timing" - assert ( - carrier["Server-Timing"] - == 'traceparent;desc="00-00000000000000000000000000000001-0000000000000002-01"' - ) + assert carrier["Server-Timing"] == 'traceparent;desc="00-00000000000000000000000000000001-0000000000000002-01"' From fdd3f7040b9d73a3c6b949b1849a007b9dacfb85 Mon Sep 17 00:00:00 2001 From: JWinermaSplunk Date: Mon, 15 Dec 2025 16:17:07 -0800 Subject: [PATCH 6/9] lint profile.py --- src/splunk_otel/profile.py | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/splunk_otel/profile.py b/src/splunk_otel/profile.py index c903b6b4..ff8a75db 100644 --- a/src/splunk_otel/profile.py +++ b/src/splunk_otel/profile.py @@ -43,7 +43,9 @@ def _start_profiling_if_enabled(env=None): def start_profiling(env=None): env = env or Env() - interval_millis = env.getint(SPLUNK_PROFILER_CALL_STACK_INTERVAL, _DEFAULT_PROF_CALL_STACK_INTERVAL_MILLIS) + interval_millis = env.getint( + SPLUNK_PROFILER_CALL_STACK_INTERVAL, _DEFAULT_PROF_CALL_STACK_INTERVAL_MILLIS + ) svcname = env.getval(OTEL_SERVICE_NAME) tcm = _ThreadContextMapping() @@ -51,7 +53,9 @@ def start_profiling(env=None): resource = _mk_resource(svcname) logger = get_logger(_SCOPE_NAME, _SCOPE_VERSION) - scraper = _ProfileScraper(resource, tcm.get_thread_states(), interval_millis, logger) + scraper = _ProfileScraper( + resource, tcm.get_thread_states(), interval_millis, logger + ) global _timer # noqa PLW0603 _timer = _IntervalTimer(interval_millis, scraper.tick) @@ -79,8 +83,12 @@ def get_thread_states(self): return self.thread_states def wrap_context_methods(self): - wrapt.wrap_function_wrapper(opentelemetry.context, "attach", self.wrap_context_attach()) - wrapt.wrap_function_wrapper(opentelemetry.context, "detach", self.wrap_context_detach()) + wrapt.wrap_function_wrapper( + opentelemetry.context, "attach", self.wrap_context_attach() + ) + wrapt.wrap_function_wrapper( + opentelemetry.context, "detach", self.wrap_context_detach() + ) def wrap_context_attach(self): def wrapper(wrapped, _instance, args, kwargs): @@ -173,7 +181,9 @@ def mk_log_record(self, stacktraces): total_frame_count = sum(lengths) time_seconds = self.time() - pb_profile = _stacktraces_to_cpu_profile(stacktraces, self.thread_states, self.interval_millis, time_seconds) + pb_profile = _stacktraces_to_cpu_profile( + stacktraces, self.thread_states, self.interval_millis, time_seconds + ) pb_profile_str = _pb_profile_to_str(pb_profile) context = Context( @@ -196,6 +206,7 @@ def mk_log_record(self, stacktraces): }, ) + def _pb_profile_to_str(pb_profile) -> str: serialized = pb_profile.SerializeToString() compressed = gzip.compress(serialized) @@ -260,7 +271,9 @@ def _get_location(functions_table, str_table, locations_table, frame): def _get_line(functions_table, str_table, file_name, function_name, line_no): line = profile_pb2.Line() - line.function_id = _get_function(functions_table, str_table, file_name, function_name).id + line.function_id = _get_function( + functions_table, str_table, file_name, function_name + ).id if line_no is None or line_no == 0: line.line = -1 else: @@ -291,7 +304,9 @@ def _extract_stack_summary(frame): return out -def _stacktraces_to_cpu_profile(stacktraces, thread_states, interval_millis, time_seconds): +def _stacktraces_to_cpu_profile( + stacktraces, thread_states, interval_millis, time_seconds +): str_table = _StringTable() locations_table = OrderedDict() functions_table = OrderedDict() From bf4eeb8061524c4907685825b4c8e4c508f80df9 Mon Sep 17 00:00:00 2001 From: JWinermaSplunk Date: Wed, 17 Dec 2025 14:07:03 -0800 Subject: [PATCH 7/9] fix linting --- src/splunk_otel/profile.py | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/src/splunk_otel/profile.py b/src/splunk_otel/profile.py index ff8a75db..b340de3c 100644 --- a/src/splunk_otel/profile.py +++ b/src/splunk_otel/profile.py @@ -43,9 +43,7 @@ def _start_profiling_if_enabled(env=None): def start_profiling(env=None): env = env or Env() - interval_millis = env.getint( - SPLUNK_PROFILER_CALL_STACK_INTERVAL, _DEFAULT_PROF_CALL_STACK_INTERVAL_MILLIS - ) + interval_millis = env.getint(SPLUNK_PROFILER_CALL_STACK_INTERVAL, _DEFAULT_PROF_CALL_STACK_INTERVAL_MILLIS) svcname = env.getval(OTEL_SERVICE_NAME) tcm = _ThreadContextMapping() @@ -53,9 +51,7 @@ def start_profiling(env=None): resource = _mk_resource(svcname) logger = get_logger(_SCOPE_NAME, _SCOPE_VERSION) - scraper = _ProfileScraper( - resource, tcm.get_thread_states(), interval_millis, logger - ) + scraper = _ProfileScraper(resource, tcm.get_thread_states(), interval_millis, logger) global _timer # noqa PLW0603 _timer = _IntervalTimer(interval_millis, scraper.tick) @@ -83,12 +79,8 @@ def get_thread_states(self): return self.thread_states def wrap_context_methods(self): - wrapt.wrap_function_wrapper( - opentelemetry.context, "attach", self.wrap_context_attach() - ) - wrapt.wrap_function_wrapper( - opentelemetry.context, "detach", self.wrap_context_detach() - ) + wrapt.wrap_function_wrapper(opentelemetry.context, "attach", self.wrap_context_attach()) + wrapt.wrap_function_wrapper(opentelemetry.context, "detach", self.wrap_context_detach()) def wrap_context_attach(self): def wrapper(wrapped, _instance, args, kwargs): @@ -181,9 +173,7 @@ def mk_log_record(self, stacktraces): total_frame_count = sum(lengths) time_seconds = self.time() - pb_profile = _stacktraces_to_cpu_profile( - stacktraces, self.thread_states, self.interval_millis, time_seconds - ) + pb_profile = _stacktraces_to_cpu_profile(stacktraces, self.thread_states, self.interval_millis, time_seconds) pb_profile_str = _pb_profile_to_str(pb_profile) context = Context( @@ -271,9 +261,7 @@ def _get_location(functions_table, str_table, locations_table, frame): def _get_line(functions_table, str_table, file_name, function_name, line_no): line = profile_pb2.Line() - line.function_id = _get_function( - functions_table, str_table, file_name, function_name - ).id + line.function_id = _get_function(functions_table, str_table, file_name, function_name).id if line_no is None or line_no == 0: line.line = -1 else: @@ -304,9 +292,7 @@ def _extract_stack_summary(frame): return out -def _stacktraces_to_cpu_profile( - stacktraces, thread_states, interval_millis, time_seconds -): +def _stacktraces_to_cpu_profile(stacktraces, thread_states, interval_millis, time_seconds): str_table = _StringTable() locations_table = OrderedDict() functions_table = OrderedDict() From 17560dad8de90356348ef7e6936c09c8e7febc07 Mon Sep 17 00:00:00 2001 From: JWinermaSplunk Date: Wed, 17 Dec 2025 14:19:38 -0800 Subject: [PATCH 8/9] updates --- src/splunk_otel/profile.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/splunk_otel/profile.py b/src/splunk_otel/profile.py index b340de3c..b3b36879 100644 --- a/src/splunk_otel/profile.py +++ b/src/splunk_otel/profile.py @@ -176,16 +176,11 @@ def mk_log_record(self, stacktraces): pb_profile = _stacktraces_to_cpu_profile(stacktraces, self.thread_states, self.interval_millis, time_seconds) pb_profile_str = _pb_profile_to_str(pb_profile) - context = Context( + return LogRecord( + timestamp=int(time_seconds * 1e9), trace_id=0, span_id=0, trace_flags=TraceFlags(0x01), - ) - - return LogRecord( - timestamp=int(time_seconds * 1e9), - observed_timestamp=int(time_seconds * 1e9), - context=context, severity_number=SeverityNumber.UNSPECIFIED, body=pb_profile_str, attributes={ From f687ddcb7ab98a595e7b34f87514e086a6dd0d46 Mon Sep 17 00:00:00 2001 From: JWinermaSplunk Date: Thu, 18 Dec 2025 11:16:12 -0800 Subject: [PATCH 9/9] re-add Context to LogRecord --- src/splunk_otel/profile.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/splunk_otel/profile.py b/src/splunk_otel/profile.py index b3b36879..d2176da1 100644 --- a/src/splunk_otel/profile.py +++ b/src/splunk_otel/profile.py @@ -176,11 +176,15 @@ def mk_log_record(self, stacktraces): pb_profile = _stacktraces_to_cpu_profile(stacktraces, self.thread_states, self.interval_millis, time_seconds) pb_profile_str = _pb_profile_to_str(pb_profile) - return LogRecord( - timestamp=int(time_seconds * 1e9), + context = Context( trace_id=0, span_id=0, trace_flags=TraceFlags(0x01), + ) + + return LogRecord( + timestamp=int(time_seconds * 1e9), + context=context, severity_number=SeverityNumber.UNSPECIFIED, body=pb_profile_str, attributes={