Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle redis.exceptions.WatchError as a non-error event in instrumentation #2668

Merged
merged 26 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3a801a7
add test
zhihali Jul 1, 2024
e0c326b
add test
zhihali Jul 1, 2024
b5fdaf5
Merge branch 'bugfix-2639' into bugfix-2639-dev
zhihali Jul 1, 2024
99fab66
Merge pull request #1 from Charlie-lizhihan/bugfix-2639-dev
zhihali Jul 1, 2024
d329c7f
add test
zhihali Jul 3, 2024
f54ddff
Merge remote-tracking branch 'origin/bugfix-2639-dev' into bugfix-263…
zhihali Jul 3, 2024
bb00df7
add test
zhihali Jul 5, 2024
251d07a
Merge pull request #2 from Charlie-lizhihan/bugfix-2639-dev
zhihali Jul 5, 2024
045f84a
finish the fix
zhihali Jul 5, 2024
4064762
Merge pull request #3 from Charlie-lizhihan/main
zhihali Jul 5, 2024
0a4dbfc
black and isort
zhihali Jul 5, 2024
cda09c3
Merge pull request #4 from Charlie-lizhihan/bugfix-2639-dev
zhihali Jul 5, 2024
830ab12
update changelog
zhihali Jul 5, 2024
dbda317
lint check
zhihali Jul 5, 2024
d90f66a
lint check
zhihali Jul 6, 2024
bc71666
bugfix
zhihali Jul 6, 2024
963cca3
update the test
zhihali Jul 10, 2024
8d21f23
Merge branch 'main' into bugfix-2639
zhihali Jul 10, 2024
ab023c8
add more error test
zhihali Jul 10, 2024
7cdad8a
Merge remote-tracking branch 'origin/bugfix-2639' into bugfix-2639
zhihali Jul 10, 2024
995966f
lint check
zhihali Jul 10, 2024
f0655e3
bug fix and add new test for sync watcherror
zhihali Jul 10, 2024
1706bc1
Merge branch 'main' into bugfix-2639
zhihali Jul 10, 2024
84247a9
Merge branch 'main' into bugfix-2639
zhihali Jul 11, 2024
952b44c
Merge branch 'main' into bugfix-2639
zhihali Jul 15, 2024
1c55cba
Merge branch 'main' into bugfix-2639
lzchen Jul 15, 2024
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0


### Fixed

- Handle `redis.exceptions.WatchError` as a non-error event in redis instrumentation
([#2668](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2668))
- `opentelemetry-instrumentation-httpx` Ensure httpx.get or httpx.request like methods are instrumented
([#2538](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/2538))
- Add Python 3.12 support
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def response_hook(span, instance, response):
from opentelemetry.instrumentation.redis.version import __version__
from opentelemetry.instrumentation.utils import unwrap
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.trace import Span
from opentelemetry.trace import Span, StatusCode

_DEFAULT_SERVICE = "redis"

Expand Down Expand Up @@ -212,9 +212,16 @@ def _traced_execute_pipeline(func, instance, args, kwargs):
span.set_attribute(
"db.redis.pipeline_length", len(command_stack)
)
response = func(*args, **kwargs)

response = None
try:
response = func(*args, **kwargs)
except redis.WatchError:
span.set_status(StatusCode.UNSET)

if callable(response_hook):
response_hook(span, instance, response)

return response

pipeline_class = (
Expand Down Expand Up @@ -281,7 +288,13 @@ async def _async_traced_execute_pipeline(func, instance, args, kwargs):
span.set_attribute(
"db.redis.pipeline_length", len(command_stack)
)
response = await func(*args, **kwargs)

response = None
try:
response = func(*args, **kwargs)
zhihali marked this conversation as resolved.
Show resolved Hide resolved
except redis.WatchError:
span.set_status(StatusCode.UNSET)

if callable(response_hook):
response_hook(span, instance, response)
return response
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
asgiref==3.7.2
async-timeout==4.0.3
Deprecated==1.2.14
fakeredis==2.23.3
importlib-metadata==6.11.0
iniconfig==2.0.0
packaging==24.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import asyncio
from unittest import mock
from unittest import IsolatedAsyncioTestCase, mock
from unittest.mock import AsyncMock

import fakeredis
import pytest
import redis
import redis.asyncio
from fakeredis.aioredis import FakeRedis
from redis.exceptions import ConnectionError as redis_ConnectionError
from redis.exceptions import WatchError

from opentelemetry import trace
from opentelemetry.instrumentation.redis import RedisInstrumentor
Expand Down Expand Up @@ -311,3 +316,83 @@ def test_attributes_unix_socket(self):
span.attributes[SpanAttributes.NET_TRANSPORT],
NetTransportValues.OTHER.value,
)

def test_connection_error(self):
server = fakeredis.FakeServer()
server.connected = False
redis_client = fakeredis.FakeStrictRedis(server=server)
try:
redis_client.set("foo", "bar")
except redis_ConnectionError:
pass

spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 1)
span = spans[0]

self.assertEqual(span.name, "SET")
self.assertEqual(span.kind, SpanKind.CLIENT)
self.assertEqual(span.status.status_code, trace.StatusCode.ERROR)

def test_response_error(self):
redis_client = fakeredis.FakeStrictRedis()
redis_client.lpush("mylist", "value")
try:
redis_client.incr(
"mylist"
) # Trying to increment a list, which is invalid
except redis.ResponseError:
pass

spans = self.memory_exporter.get_finished_spans()
self.assertEqual(len(spans), 2)

span = spans[0]
self.assertEqual(span.name, "LPUSH")
self.assertEqual(span.kind, SpanKind.CLIENT)
self.assertEqual(span.status.status_code, trace.StatusCode.UNSET)

span = spans[1]
self.assertEqual(span.name, "INCRBY")
self.assertEqual(span.kind, SpanKind.CLIENT)
self.assertEqual(span.status.status_code, trace.StatusCode.ERROR)


class TestRedisAsync(TestBase, IsolatedAsyncioTestCase):
def setUp(self):
super().setUp()
RedisInstrumentor().instrument(tracer_provider=self.tracer_provider)

def tearDown(self):
super().tearDown()
RedisInstrumentor().uninstrument()

@pytest.mark.asyncio
async def test_watch_error(self):
async def redis_operations():
try:
redis_client = FakeRedis()
async with redis_client.pipeline(transaction=False) as pipe:
await pipe.watch("a")
await redis_client.set("a", "bad")
pipe.multi()
await pipe.set("a", "1")
await pipe.execute()
except WatchError:
pass
Comment on lines +403 to +412
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend this instead, so that we ensure the WatchError is still raised

Suggested change
try:
redis_client = FakeRedis()
async with redis_client.pipeline(transaction=False) as pipe:
await pipe.watch("a")
await redis_client.set("a", "bad")
pipe.multi()
await pipe.set("a", "1")
await pipe.execute()
except WatchError:
pass
with pytest.raises(WatchError):
redis_client = FakeRedis()
async with redis_client.pipeline(transaction=False) as pipe:
await pipe.watch("a")
await redis_client.set("a", "bad")
pipe.multi()
await pipe.set("a", "1")
await pipe.execute()


await redis_operations()

spans = self.memory_exporter.get_finished_spans()
zhihali marked this conversation as resolved.
Show resolved Hide resolved

# there should be 3 tests, we start watch operation and have 2 set operation on same key
self.assertEqual(len(spans), 3)

self.assertEqual(spans[0].attributes.get("db.statement"), "WATCH ?")
self.assertEqual(spans[0].kind, SpanKind.CLIENT)
self.assertEqual(spans[0].status.status_code, trace.StatusCode.UNSET)

for span in spans[1:]:
self.assertEqual(span.attributes.get("db.statement"), "SET ? ?")
self.assertEqual(span.kind, SpanKind.CLIENT)
self.assertEqual(span.status.status_code, trace.StatusCode.UNSET)