Skip to content

Conversation

luke6Lh43
Copy link
Contributor

@luke6Lh43 luke6Lh43 commented Oct 10, 2025

Description

This PR updates the Redis instrumentation to support semantic convention opt-in, aligning the implementation with the pattern used in the Requests instrumentation (where stable semantic conventions were already implemented before). Depending on the value of the OTEL_SEMCONV_STABILITY_OPT_IN environment variable, the instrumentation will emit either the legacy (experimental) or stable Redis span attributes, or both (database/dup).

Fixes #2885 #2930

Type of change

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

  • Manual testing with different values for OTEL_SEMCONV_STABILITY_OPT_IN (unset, database, database/dup)
  • Verified emitted spans and their attributes using both debug exporter logs and Jaeger UI
  • Example test output attached below:

Test 1: Default behavior - no environment variable set

$ env | grep OTEL                    
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
OTEL_EXPORTER_OTLP_PROTOCOL=grpc
OTEL_TRACES_EXPORTER=otlp

$ docker logs otel-collector
(snipped)
Resource attributes:
     -> telemetry.sdk.language: Str(python)
     -> telemetry.sdk.name: Str(opentelemetry)
     -> telemetry.sdk.version: Str(1.37.0)
     -> telemetry.auto.version: Str(0.58b0)
     -> service.name: Str(unknown_service)
ScopeSpans #0
ScopeSpans SchemaURL: https://opentelemetry.io/schemas/1.11.0
InstrumentationScope opentelemetry.instrumentation.redis 0.59b0.dev
Span #0
    Trace ID       : a103b7612447282fa0c38b4fac2cd23f
    Parent ID      : 
    ID             : 93506e2f164d75fc
    Name           : SET
    Kind           : Client
    Start time     : 2025-10-10 03:56:57.700116 +0000 UTC
    End time       : 2025-10-10 03:56:57.708653 +0000 UTC
    Status code    : Unset
    Status message : 
Attributes:
     -> db.statement: Str(SET ? ?)
     -> db.name: Str(redis)
     -> db.redis.database_index: Int(0)
     -> net.peer.name: Str(localhost)
     -> net.peer.port: Int(6379)
     -> net.transport: Str(ip_tcp)
     -> db.redis.args_length: Int(3)
Span #1
    Trace ID       : ac7c5556b72438254e278ce77a537de3
    Parent ID      : 
    ID             : 45c0312f3e301c12
    Name           : GET
    Kind           : Client
    Start time     : 2025-10-10 03:56:57.708694 +0000 UTC
    End time       : 2025-10-10 03:56:57.709228 +0000 UTC
    Status code    : Unset
    Status message : 
Attributes:
     -> db.statement: Str(GET ?)
     -> db.name: Str(redis)
     -> db.redis.database_index: Int(0)
     -> net.peer.name: Str(localhost)
     -> net.peer.port: Int(6379)
     -> net.transport: Str(ip_tcp)
     -> db.redis.args_length: Int(2)
	{"resource": {"service.instance.id": "d69e8152-64fa-41e2-991c-fb65d341c9cd", "service.name": "otelcol-contrib", "service.version": "0.137.0"}, "otelcol.component.id": "debug", "otelcol.component.kind": "exporter", "otelcol.signal": "traces"}

Test 2: OTEL_SEMCONV_STABILITY_OPT_IN set to "database"

$ env | grep OTEL                              
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
OTEL_EXPORTER_OTLP_PROTOCOL=grpc
OTEL_TRACES_EXPORTER=otlp
OTEL_SEMCONV_STABILITY_OPT_IN=database

$ docker logs otel-collector
(snipped)
Resource attributes:
     -> telemetry.sdk.language: Str(python)
     -> telemetry.sdk.name: Str(opentelemetry)
     -> telemetry.sdk.version: Str(1.37.0)
     -> telemetry.auto.version: Str(0.58b0)
     -> service.name: Str(unknown_service)
ScopeSpans #0
ScopeSpans SchemaURL: https://opentelemetry.io/schemas/1.11.0
InstrumentationScope opentelemetry.instrumentation.redis 0.59b0.dev
Span #0
    Trace ID       : c0ef981c154a0f3b79a71e961d550bed
    Parent ID      : 
    ID             : 9bf2a159ba5e2f5c
    Name           : SET
    Kind           : Client
    Start time     : 2025-10-10 03:59:04.629258 +0000 UTC
    End time       : 2025-10-10 03:59:04.637654 +0000 UTC
    Status code    : Unset
    Status message : 
Attributes:
     -> db.system.name: Str(redis)
     -> db.operation.name: Str(SET)
     -> db.namespace: Str(0)
     -> db.query.text: Str(SET ? ?)
     -> server.address: Str(localhost)
     -> network.peer.address: Str(localhost)
     -> server.port: Int(6379)
     -> network.peer.port: Int(6379)
Span #1
    Trace ID       : a7bf90d8925725a17836a8917b0e083e
    Parent ID      : 
    ID             : c1da703a67063484
    Name           : GET
    Kind           : Client
    Start time     : 2025-10-10 03:59:04.637689 +0000 UTC
    End time       : 2025-10-10 03:59:04.638218 +0000 UTC
    Status code    : Unset
    Status message : 
Attributes:
     -> db.system.name: Str(redis)
     -> db.operation.name: Str(GET)
     -> db.namespace: Str(0)
     -> db.query.text: Str(GET ?)
     -> server.address: Str(localhost)
     -> network.peer.address: Str(localhost)
     -> server.port: Int(6379)
     -> network.peer.port: Int(6379)
	{"resource": {"service.instance.id": "4a9b5ace-dfb5-409e-8288-8fc2b63c234a", "service.name": "otelcol-contrib", "service.version": "0.137.0"}, "otelcol.component.id": "debug", "otelcol.component.kind": "exporter", "otelcol.signal": "traces"}

Test 3: OTEL_SEMCONV_STABILITY_OPT_IN set to "database/dup"

$ env | grep OTEL
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
OTEL_EXPORTER_OTLP_PROTOCOL=grpc
OTEL_TRACES_EXPORTER=otlp
OTEL_SEMCONV_STABILITY_OPT_IN=database/dup

$ docker logs otel-collector
(snipped)
Resource attributes:
     -> telemetry.sdk.language: Str(python)
     -> telemetry.sdk.name: Str(opentelemetry)
     -> telemetry.sdk.version: Str(1.37.0)
     -> telemetry.auto.version: Str(0.58b0)
     -> service.name: Str(unknown_service)
ScopeSpans #0
ScopeSpans SchemaURL: https://opentelemetry.io/schemas/1.11.0
InstrumentationScope opentelemetry.instrumentation.redis 0.59b0.dev
Span #0
    Trace ID       : adf6b96ba007d11d87b2e2349b8dee0a
    Parent ID      : 
    ID             : a02ac31b5d5e4c5f
    Name           : SET
    Kind           : Client
    Start time     : 2025-10-10 04:01:30.788719 +0000 UTC
    End time       : 2025-10-10 04:01:30.797171 +0000 UTC
    Status code    : Unset
    Status message : 
Attributes:
     -> db.statement: Str(SET ? ?)
     -> db.redis.database_index: Int(0)
     -> net.peer.name: Str(localhost)
     -> net.peer.port: Int(6379)
     -> net.transport: Str(ip_tcp)
     -> db.redis.args_length: Int(3)
     -> db.system.name: Str(redis)
     -> db.operation.name: Str(SET)
     -> db.namespace: Str(0)
     -> db.query.text: Str(SET ? ?)
     -> server.address: Str(localhost)
     -> network.peer.address: Str(localhost)
     -> server.port: Int(6379)
     -> network.peer.port: Int(6379)
Span #1
    Trace ID       : fbd020409aa651df9d5ec4f0a0876c13
    Parent ID      : 
    ID             : c4fb7ab173e13460
    Name           : GET
    Kind           : Client
    Start time     : 2025-10-10 04:01:30.797209 +0000 UTC
    End time       : 2025-10-10 04:01:30.797736 +0000 UTC
    Status code    : Unset
    Status message : 
Attributes:
     -> db.statement: Str(GET ?)
     -> db.redis.database_index: Int(0)
     -> net.peer.name: Str(localhost)
     -> net.peer.port: Int(6379)
     -> net.transport: Str(ip_tcp)
     -> db.redis.args_length: Int(2)
     -> db.system.name: Str(redis)
     -> db.operation.name: Str(GET)
     -> db.namespace: Str(0)
     -> db.query.text: Str(GET ?)
     -> server.address: Str(localhost)
     -> network.peer.address: Str(localhost)
     -> server.port: Int(6379)
     -> network.peer.port: Int(6379)
	{"resource": {"service.instance.id": "238255d8-21c9-4087-942b-10f448146683", "service.name": "otelcol-contrib", "service.version": "0.137.0"}, "otelcol.component.id": "debug", "otelcol.component.kind": "exporter", "otelcol.signal": "traces"}

Does This PR Require a Core Repo Change?

  • No.

Checklist:

See contributing.md for styleguide, changelog guidelines, and more.

  • Followed the style guidelines of this project
  • Changelogs have been updated
  • Unit tests have been added
  • Documentation has been updated

@luke6Lh43
Copy link
Contributor Author

Unit tests coming soon; will update this PR accordingly.

@luke6Lh43 luke6Lh43 force-pushed the fix/redis-semconv-stable branch from e82684e to c98b9fe Compare October 10, 2025 21:46
@luke6Lh43 luke6Lh43 changed the title [WIP] fix(redis): support semantic convention opt-in and emit stable Redis attributes [WIP] feat(redis): support semantic convention opt-in and emit stable Redis attributes Oct 10, 2025
@luke6Lh43
Copy link
Contributor Author

Just realized this change is more of a feature enhancement than a bug fix. I have amended the last commit to use "feat(redis)" prefix and force-pushed the updated branch

@luke6Lh43 luke6Lh43 requested a review from a team as a code owner October 10, 2025 21:53
@luke6Lh43 luke6Lh43 force-pushed the fix/redis-semconv-stable branch 2 times, most recently from 84e6cf7 to c98b9fe Compare October 10, 2025 22:13
@luke6Lh43 luke6Lh43 marked this pull request as draft October 11, 2025 01:09
@luke6Lh43
Copy link
Contributor Author

luke6Lh43 commented Oct 11, 2025

Hi All,

My changes for Redis instrumentation's stable semantic conventions are working as expected and thoroughly tested locally. However, CI shows some test failures that I'd like to discuss.

1. Redis Async Tests (test_watch_error_async, test_watch_error_async_only_client)

Issue: AssertionError: <StatusCode.ERROR: 2> != <StatusCode.UNSET: 0>

Explanation: My updated code now correctly sets StatusCode.ERROR for general failed Redis commands and populates the error.type attribute. This aligns with OpenTelemetry semantic conventions, which state that error.type is "Conditionally Required If and only if the operation failed." However, WatchError in Redis signifies an aborted transaction, not an execution failure of individual commands. The tests' FakeRedis seems to cause WatchError exceptions for individual SET commands. My instrumentation is currently catching these as general Exception and setting StatusCode.ERROR, but the tests still expect StatusCode.UNSET.

Proposed: I am thinking about updating the instrumentation code to specifically handle redis.WatchError by setting the span's StatusCode to UNSET (as an aborted operation) and re-raising the exception. This aligns with how WatchError is already handled for pipeline spans and ensures semantic correctness, allowing the current test assertions to pass. Any thoughts on that?

pypy3-test-util-genai Test Run

Issue: ImportError: No module named 'opentelemetry.util.genai.handler'

Explanation: This is a tox.ini configuration issue; the opentelemetry-util-genai package isn't being correctly installed in the pypy3 environment. I have a local fix for the tox.ini (to explicitly install the package using -e {toxinidir}/util/opentelemetry-util-genai), which made this test pass locally.

Proposed: Should I go ahead and include this tox.ini update as part of this PR, or would you prefer it in a separate change?

I'm ready to implement these fixes based on your guidance.

Thanks!

cc: @pmcollins


Update: Following some research into the semantic meaning of redis.WatchError, I have already implemented the proposed change. The instrumentation now specifically handles redis.WatchError by setting the span's StatusCode to UNSET (as an aborted operation) and re-raising the exception. This aligns with how WatchError is already handled for pipeline spans and ensures semantic correctness. Also, all tests are passed locally.

Update 2: Looks like pypy3-test-util-genai issue is gone. Please disregard.

luke6Lh43 added a commit to luke6Lh43/opentelemetry-python-contrib that referenced this pull request Oct 11, 2025
luke6Lh43 added a commit to luke6Lh43/opentelemetry-python-contrib that referenced this pull request Oct 12, 2025
@luke6Lh43 luke6Lh43 force-pushed the fix/redis-semconv-stable branch from d6313cb to 2c36ebc Compare October 12, 2025 18:11
@luke6Lh43 luke6Lh43 force-pushed the fix/redis-semconv-stable branch from 2c36ebc to 420b76c Compare October 12, 2025 18:16
@luke6Lh43 luke6Lh43 force-pushed the fix/redis-semconv-stable branch from 265b57b to 9f16358 Compare October 13, 2025 04:34
@luke6Lh43
Copy link
Contributor Author

I added unit tests for the Redis semantic convention stability feature, verifying that span attributes are correctly emitted based on the OTEL_SEMCONV_STABILITY_OPT_IN environment variable.

To ensure reliable and isolated testing, the tests are structured to run in separate subprocesses. This approach is necessary to overcome Python's module caching, which would otherwise cause test pollution, as the environment variable is read only once at import time.

A main test file (test_semconv.py) now launches a helper script (_test_semconv_helper.py) for each mode (default, database, and database/dup), with each run validating attributes for single commands, pipelines, and error scenarios.

@luke6Lh43 luke6Lh43 marked this pull request as ready for review October 13, 2025 04:40
@luke6Lh43 luke6Lh43 changed the title [WIP] feat(redis): support semantic convention opt-in and emit stable Redis attributes feat(redis): support semantic convention opt-in and emit stable Redis attributes Oct 13, 2025
@luke6Lh43 luke6Lh43 force-pushed the fix/redis-semconv-stable branch from 9f16358 to e20cea6 Compare October 13, 2025 04:50
@xrmx xrmx moved this to Ready for review in @xrmx's Python PR digest Oct 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Ready for review

Development

Successfully merging this pull request may close these issues.

Redis attributes not aligned with latest semantic conventions

1 participant