From b459fce9a845e3072fc8afd8c5fa943480e3d96e Mon Sep 17 00:00:00 2001 From: disqualifier Date: Sun, 9 Aug 2026 02:18:08 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20log-XOR-raise=20=E2=80=94=20demote=20the?= =?UTF-8?q?=20pre-raise=20log=20to=20DEBUG,=20revise=20the=20contract=20(D?= =?UTF-8?q?1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit every wrapped method did log.exception (ERROR + traceback) immediately before re-raising the driver error — the same failure reported at two layers (the log AND the raised exception). that is the log-and-raise violation: a method that re-raises must not also error-log, because the caller — which alone knows whether the failure is fatal or routine — is the one that logs. demote all wrapped-method logs to log.debug(..., exc_info=True): the traceback stays available at DEBUG, and the raised exception is the single loud terminal signal. docstring updated from "logs via getLogger and re-raises" to the corrected raise-XOR-log contract. no behavior change beyond log level — the fail-loud re-raise is unchanged. Signed-off-by: disqualifier --- src/redis_store/redis_store.py | 36 ++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/redis_store/redis_store.py b/src/redis_store/redis_store.py index fc9cd91..892d7b7 100644 --- a/src/redis_store/redis_store.py +++ b/src/redis_store/redis_store.py @@ -12,9 +12,11 @@ context manager: async with RedisDB(host="localhost") as kv: await kv.incr("hits") -fail loud: wrapped methods catch RedisError, log, and re-raise; see each method's - docstring for its own contract. unwrapped ops go through the raw `.client` escape - hatch: full driver surface, raises. +fail loud: wrapped methods catch RedisError and re-raise it - the raised exception IS + the signal, and the caller (which alone knows fatal-vs-routine) decides and logs; the + wrapped method logs only at DEBUG (with the traceback) so a failure isn't reported + twice (raise XOR log). see each method's docstring for its own contract. unwrapped ops + go through the raw `.client` escape hatch: full driver surface, raises. notes: - import is `redis_store`; repo is `redis`, distribution is `redis_store` (the @@ -87,7 +89,7 @@ class RedisDB: try: await self._client.ping() except RedisError: - log.exception("redis.connect() ping failed") + log.debug("redis.connect() ping failed", exc_info=True) await self._pool.disconnect() raise return self @@ -104,7 +106,7 @@ class RedisDB: finally: await self._pool.disconnect() except RedisError: - log.exception("redis.close()") + log.debug("redis.close()", exc_info=True) raise async def __aenter__(self) -> "RedisDB": @@ -128,7 +130,7 @@ class RedisDB: try: return await self._client.get(key) except RedisError: - log.exception("redis.get(%s)", key) + log.debug("redis.get(%s)", key, exc_info=True) raise async def set(self, key: str, value, ex: Optional[int] = None) -> bool: @@ -136,7 +138,7 @@ class RedisDB: try: return bool(await self._client.set(key, value, ex=ex)) except RedisError: - log.exception("redis.set(%s)", key) + log.debug("redis.set(%s)", key, exc_info=True) raise async def delete(self, *keys: str) -> int: @@ -144,7 +146,7 @@ class RedisDB: try: return await self._client.delete(*keys) except RedisError: - log.exception("redis.delete(%s)", keys) + log.debug("redis.delete(%s)", keys, exc_info=True) raise async def exists(self, *keys: str) -> int: @@ -152,7 +154,7 @@ class RedisDB: try: return await self._client.exists(*keys) except RedisError: - log.exception("redis.exists(%s)", keys) + log.debug("redis.exists(%s)", keys, exc_info=True) raise async def incr(self, key: str, amount: int = 1) -> int: @@ -160,7 +162,7 @@ class RedisDB: try: return await self._client.incrby(key, amount) except RedisError: - log.exception("redis.incr(%s)", key) + log.debug("redis.incr(%s)", key, exc_info=True) raise async def decr(self, key: str, amount: int = 1) -> int: @@ -168,7 +170,7 @@ class RedisDB: try: return await self._client.decrby(key, amount) except RedisError: - log.exception("redis.decr(%s)", key) + log.debug("redis.decr(%s)", key, exc_info=True) raise async def hget(self, name: str, field: str) -> Optional[str]: @@ -176,7 +178,7 @@ class RedisDB: try: return await self._client.hget(name, field) except RedisError: - log.exception("redis.hget(%s, %s)", name, field) + log.debug("redis.hget(%s, %s)", name, field, exc_info=True) raise async def hset(self, name: str, key: Optional[str] = None, value=None, mapping: Optional[dict] = None) -> int: @@ -187,7 +189,7 @@ class RedisDB: try: return await self._client.hset(name, key=key, value=value, mapping=mapping) except RedisError: - log.exception("redis.hset(%s)", name) + log.debug("redis.hset(%s)", name, exc_info=True) raise async def hgetall(self, name: str) -> dict: @@ -195,7 +197,7 @@ class RedisDB: try: return await self._client.hgetall(name) except RedisError: - log.exception("redis.hgetall(%s)", name) + log.debug("redis.hgetall(%s)", name, exc_info=True) raise async def hdel(self, name: str, *fields: str) -> int: @@ -203,7 +205,7 @@ class RedisDB: try: return await self._client.hdel(name, *fields) except RedisError: - log.exception("redis.hdel(%s)", name) + log.debug("redis.hdel(%s)", name, exc_info=True) raise async def expire(self, key: str, seconds: int) -> bool: @@ -211,7 +213,7 @@ class RedisDB: try: return bool(await self._client.expire(key, seconds)) except RedisError: - log.exception("redis.expire(%s)", key) + log.debug("redis.expire(%s)", key, exc_info=True) raise async def ttl(self, key: str) -> int: @@ -223,5 +225,5 @@ class RedisDB: try: return await self._client.ttl(key) except RedisError: - log.exception("redis.ttl(%s)", key) + log.debug("redis.ttl(%s)", key, exc_info=True) raise