1+ """
2+ OpenTelemetry semantic convention attributes for Redis.
3+
4+ This module provides constants and helper functions for building OTel attributes
5+ according to the semantic conventions for database clients.
6+
7+ Reference: https://opentelemetry.io/docs/specs/semconv/database/redis/
8+ """
9+ from enum import Enum
10+ from typing import Any , Dict , Optional
11+
12+ # Database semantic convention attributes
13+ DB_SYSTEM = "db.system"
14+ DB_NAMESPACE = "db.namespace"
15+ DB_OPERATION_NAME = "db.operation.name"
16+ DB_OPERATION_BATCH_SIZE = "db.operation.batch.size"
17+ DB_RESPONSE_STATUS_CODE = "db.response.status_code"
18+ DB_STORED_PROCEDURE_NAME = "db.stored_procedure.name"
19+
20+ # Error attributes
21+ ERROR_TYPE = "error.type"
22+
23+ # Network attributes
24+ NETWORK_PEER_ADDRESS = "network.peer.address"
25+ NETWORK_PEER_PORT = "network.peer.port"
26+
27+ # Server attributes
28+ SERVER_ADDRESS = "server.address"
29+ SERVER_PORT = "server.port"
30+
31+ # Connection pool attributes
32+ DB_CLIENT_CONNECTION_POOL_NAME = "db.client.connection.pool.name"
33+ DB_CLIENT_CONNECTION_STATE = "db.client.connection.state"
34+
35+ # Redis-specific attributes
36+ REDIS_CLIENT_LIBRARY = "redis.client.library"
37+ REDIS_CLIENT_CONNECTION_PUBSUB = "redis.client.connection.pubsub"
38+ REDIS_CLIENT_CONNECTION_CLOSE_REASON = "redis.client.connection.close.reason"
39+ REDIS_CLIENT_CONNECTION_NOTIFICATION = "redis.client.connection.notification"
40+ REDIS_CLIENT_OPERATION_RETRY_ATTEMPTS = "redis.client.operation.retry_attempts"
41+ REDIS_CLIENT_OPERATION_BLOCKING = "redis.client.operation.blocking"
42+ REDIS_CLIENT_PUBSUB_MESSAGE_DIRECTION = "redis.client.pubsub.message.direction"
43+ REDIS_CLIENT_PUBSUB_CHANNEL = "redis.client.pubsub.channel"
44+ REDIS_CLIENT_PUBSUB_SHARDED = "redis.client.pubsub.sharded"
45+ REDIS_CLIENT_ERROR_INTERNAL = "redis.client.errors.internal"
46+ REDIS_CLIENT_STREAM_NAME = "redis.client.stream.name"
47+ REDIS_CLIENT_CONSUMER_GROUP = "redis.client.consumer_group"
48+ REDIS_CLIENT_CONSUMER_NAME = "redis.client.consumer_name"
49+
50+ class ConnectionState (Enum ):
51+ IDLE = "idle"
52+ USED = "used"
53+
54+ class PubSubDirection (Enum ):
55+ PUBLISH = "publish"
56+ RECEIVE = "receive"
57+
58+
59+ class AttributeBuilder :
60+ """
61+ Helper class to build OTel semantic convention attributes for Redis operations.
62+ """
63+
64+ @staticmethod
65+ def build_base_attributes (
66+ server_address : Optional [str ] = None ,
67+ server_port : Optional [int ] = None ,
68+ db_namespace : Optional [int ] = None ,
69+ ) -> Dict [str , Any ]:
70+ """
71+ Build base attributes common to all Redis operations.
72+
73+ Args:
74+ server_address: Redis server address (FQDN or IP)
75+ server_port: Redis server port
76+ db_namespace: Redis database index
77+
78+ Returns:
79+ Dictionary of base attributes
80+ """
81+ attrs : Dict [str , Any ] = {
82+ DB_SYSTEM : "redis" ,
83+ REDIS_CLIENT_LIBRARY : "redis-py"
84+ }
85+
86+ if server_address is not None :
87+ attrs [SERVER_ADDRESS ] = server_address
88+
89+ if server_port is not None :
90+ attrs [SERVER_PORT ] = server_port
91+
92+ if db_namespace is not None :
93+ attrs [DB_NAMESPACE ] = str (db_namespace )
94+
95+ return attrs
96+
97+ @staticmethod
98+ def build_operation_attributes (
99+ command_name : Optional [str ] = None ,
100+ batch_size : Optional [int ] = None ,
101+ response_status_code : Optional [str ] = None ,
102+ error_type : Optional [Exception ] = None ,
103+ network_peer_address : Optional [str ] = None ,
104+ network_peer_port : Optional [int ] = None ,
105+ stored_procedure_name : Optional [str ] = None ,
106+ retry_attempts : Optional [int ] = None ,
107+ is_blocking : Optional [bool ] = None ,
108+ ) -> Dict [str , Any ]:
109+ """
110+ Build attributes for a Redis operation (command execution).
111+
112+ Args:
113+ command_name: Redis command name (e.g., 'GET', 'SET', 'MULTI')
114+ batch_size: Number of commands in batch (for pipelines/transactions)
115+ response_status_code: Redis error prefix (e.g., 'ERR', 'WRONGTYPE')
116+ error_type: Error type if operation failed
117+ network_peer_address: Resolved peer address
118+ network_peer_port: Peer port number
119+ stored_procedure_name: Lua script name or SHA1 digest
120+ retry_attempts: Number of retry attempts made
121+ is_blocking: Whether the operation is a blocking command
122+
123+ Returns:
124+ Dictionary of operation attributes
125+ """
126+ attrs : Dict [str , Any ] = {}
127+
128+ if command_name is not None :
129+ attrs [DB_OPERATION_NAME ] = command_name .upper ()
130+
131+ if batch_size is not None and batch_size >= 2 :
132+ attrs [DB_OPERATION_BATCH_SIZE ] = batch_size
133+
134+ if response_status_code is not None :
135+ attrs [DB_RESPONSE_STATUS_CODE ] = response_status_code
136+
137+ if error_type is not None :
138+ attrs [ERROR_TYPE ] = AttributeBuilder .extract_error_type (error_type )
139+
140+ if network_peer_address is not None :
141+ attrs [NETWORK_PEER_ADDRESS ] = network_peer_address
142+
143+ if network_peer_port is not None :
144+ attrs [NETWORK_PEER_PORT ] = network_peer_port
145+
146+ if stored_procedure_name is not None :
147+ attrs [DB_STORED_PROCEDURE_NAME ] = stored_procedure_name
148+
149+ if retry_attempts is not None and retry_attempts > 0 :
150+ attrs [REDIS_CLIENT_OPERATION_RETRY_ATTEMPTS ] = retry_attempts
151+
152+ if is_blocking is not None :
153+ attrs [REDIS_CLIENT_OPERATION_BLOCKING ] = is_blocking
154+
155+ return attrs
156+
157+ @staticmethod
158+ def build_connection_attributes (
159+ pool_name : str ,
160+ connection_state : Optional [ConnectionState ] = None ,
161+ is_pubsub : Optional [bool ] = None ,
162+ ) -> Dict [str , Any ]:
163+ """
164+ Build attributes for connection pool metrics.
165+
166+ Args:
167+ pool_name: Unique connection pool name
168+ connection_state: Connection state ('idle' or 'used')
169+ is_pubsub: Whether this is a PubSub connection
170+
171+ Returns:
172+ Dictionary of connection pool attributes
173+ """
174+ attrs : Dict [str , Any ] = AttributeBuilder .build_base_attributes ()
175+ attrs [DB_CLIENT_CONNECTION_POOL_NAME ] = pool_name
176+
177+ if connection_state is not None :
178+ attrs [DB_CLIENT_CONNECTION_STATE ] = connection_state .value
179+
180+ if is_pubsub is not None :
181+ attrs [REDIS_CLIENT_CONNECTION_PUBSUB ] = is_pubsub
182+
183+ return attrs
184+
185+ @staticmethod
186+ def build_error_attributes (
187+ is_internal : bool = False ,
188+ error_type : Optional [Exception ] = None ,
189+ ) -> Dict [str , Any ]:
190+ """
191+ Build error attributes.
192+
193+ Args:
194+ is_internal: Whether the error is internal (e.g., timeout, network error)
195+ error_type: The exception that occurred
196+
197+ Returns:
198+ Dictionary of error attributes
199+ """
200+ attrs : Dict [str , Any ] = {REDIS_CLIENT_ERROR_INTERNAL : is_internal }
201+
202+ if error_type is not None :
203+ attrs [DB_RESPONSE_STATUS_CODE ] = None
204+
205+ return attrs
206+
207+ @staticmethod
208+ def build_pubsub_message_attributes (
209+ direction : PubSubDirection ,
210+ channel : Optional [str ] = None ,
211+ sharded : Optional [bool ] = None ,
212+ ) -> Dict [str , Any ]:
213+ """
214+ Build attributes for a PubSub message.
215+
216+ Args:
217+ direction: Message direction ('publish' or 'receive')
218+ channel: Pub/Sub channel name
219+ sharded: True if sharded Pub/Sub channel
220+
221+ Returns:
222+ Dictionary of PubSub message attributes
223+ """
224+ attrs : Dict [str , Any ] = AttributeBuilder .build_base_attributes ()
225+ attrs [REDIS_CLIENT_PUBSUB_MESSAGE_DIRECTION ] = direction .value
226+
227+ if channel is not None :
228+ attrs [REDIS_CLIENT_PUBSUB_CHANNEL ] = channel
229+
230+ if sharded is not None :
231+ attrs [REDIS_CLIENT_PUBSUB_SHARDED ] = sharded
232+
233+ return attrs
234+
235+ @staticmethod
236+ def build_streaming_attributes (
237+ stream_name : Optional [str ] = None ,
238+ consumer_group : Optional [str ] = None ,
239+ consumer_name : Optional [str ] = None ,
240+ ) -> Dict [str , Any ]:
241+ """
242+ Build attributes for a streaming operation.
243+
244+ Args:
245+ stream_name: Name of the stream
246+ consumer_group: Name of the consumer group
247+ consumer_name: Name of the consumer
248+
249+ Returns:
250+ Dictionary of streaming attributes
251+ """
252+ attrs : Dict [str , Any ] = AttributeBuilder .build_base_attributes ()
253+
254+ if stream_name is not None :
255+ attrs [REDIS_CLIENT_STREAM_NAME ] = stream_name
256+
257+ if consumer_group is not None :
258+ attrs [REDIS_CLIENT_CONSUMER_GROUP ] = consumer_group
259+
260+ if consumer_name is not None :
261+ attrs [REDIS_CLIENT_CONSUMER_NAME ] = consumer_name
262+
263+ return attrs
264+
265+
266+ @staticmethod
267+ def extract_error_type (exception : Exception ) -> str :
268+ """
269+ Extract error type from an exception.
270+
271+ Args:
272+ exception: The exception that occurred
273+
274+ Returns:
275+ Error type string (exception class name)
276+ """
277+ return type (exception ).__name__
278+
279+ @staticmethod
280+ def build_pool_name (
281+ server_address : str ,
282+ server_port : int ,
283+ db_namespace : int = 0 ,
284+ ) -> str :
285+ """
286+ Build a unique connection pool name.
287+
288+ Args:
289+ server_address: Redis server address
290+ server_port: Redis server port
291+ db_namespace: Redis database index
292+
293+ Returns:
294+ Unique pool name in format "address:port/db"
295+ """
296+ return f"{ server_address } :{ server_port } /{ db_namespace } "
0 commit comments