rebrandly-otel 0.1.20__tar.gz → 0.1.23__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of rebrandly-otel might be problematic. Click here for more details.

Files changed (20) hide show
  1. {rebrandly_otel-0.1.20/rebrandly_otel.egg-info → rebrandly_otel-0.1.23}/PKG-INFO +52 -1
  2. rebrandly_otel-0.1.20/PKG-INFO → rebrandly_otel-0.1.23/README.md +47 -22
  3. rebrandly_otel-0.1.20/README.md → rebrandly_otel-0.1.23/rebrandly_otel.egg-info/PKG-INFO +73 -1
  4. {rebrandly_otel-0.1.20 → rebrandly_otel-0.1.23}/rebrandly_otel.egg-info/SOURCES.txt +2 -0
  5. rebrandly_otel-0.1.23/rebrandly_otel.egg-info/requires.txt +4 -0
  6. {rebrandly_otel-0.1.20 → rebrandly_otel-0.1.23}/setup.py +7 -1
  7. {rebrandly_otel-0.1.20 → rebrandly_otel-0.1.23}/src/__init__.py +4 -1
  8. {rebrandly_otel-0.1.20 → rebrandly_otel-0.1.23}/src/metrics.py +1 -1
  9. {rebrandly_otel-0.1.20 → rebrandly_otel-0.1.23}/src/otel_utils.py +1 -1
  10. rebrandly_otel-0.1.23/src/pymysql_instrumentation.py +206 -0
  11. {rebrandly_otel-0.1.20 → rebrandly_otel-0.1.23}/LICENSE +0 -0
  12. {rebrandly_otel-0.1.20 → rebrandly_otel-0.1.23}/rebrandly_otel.egg-info/dependency_links.txt +0 -0
  13. {rebrandly_otel-0.1.20 → rebrandly_otel-0.1.23}/rebrandly_otel.egg-info/top_level.txt +0 -0
  14. {rebrandly_otel-0.1.20 → rebrandly_otel-0.1.23}/setup.cfg +0 -0
  15. {rebrandly_otel-0.1.20 → rebrandly_otel-0.1.23}/src/fastapi_support.py +0 -0
  16. {rebrandly_otel-0.1.20 → rebrandly_otel-0.1.23}/src/flask_support.py +0 -0
  17. {rebrandly_otel-0.1.20 → rebrandly_otel-0.1.23}/src/logs.py +0 -0
  18. {rebrandly_otel-0.1.20 → rebrandly_otel-0.1.23}/src/rebrandly_otel.py +0 -0
  19. {rebrandly_otel-0.1.20 → rebrandly_otel-0.1.23}/src/traces.py +0 -0
  20. {rebrandly_otel-0.1.20 → rebrandly_otel-0.1.23}/tests/test_usage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rebrandly_otel
3
- Version: 0.1.20
3
+ Version: 0.1.23
4
4
  Summary: Python OTEL wrapper by Rebrandly
5
5
  Home-page: https://github.com/rebrandly/rebrandly-otel-python
6
6
  Author: Antonio Romano
@@ -10,6 +10,10 @@ Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
+ Requires-Dist: opentelemetry-api>=1.34.0
14
+ Requires-Dist: opentelemetry-sdk>=1.34.0
15
+ Requires-Dist: opentelemetry-exporter-otlp>=1.34.0
16
+ Requires-Dist: psutil>=5.0.0
13
17
  Dynamic: author
14
18
  Dynamic: author-email
15
19
  Dynamic: classifier
@@ -17,6 +21,7 @@ Dynamic: description
17
21
  Dynamic: description-content-type
18
22
  Dynamic: home-page
19
23
  Dynamic: license-file
24
+ Dynamic: requires-dist
20
25
  Dynamic: summary
21
26
 
22
27
  # Rebrandly OpenTelemetry SDK for Python
@@ -447,6 +452,52 @@ if __name__ == "__main__":
447
452
  uvicorn.run(app, host="0.0.0.0", port=8000)
448
453
  ```
449
454
 
455
+ ### PyMySQL Database Instrumentation
456
+
457
+ The SDK provides connection-level instrumentation for PyMySQL that automatically traces all queries without requiring you to instrument each query individually.
458
+
459
+ ```python
460
+ import pymysql
461
+ from rebrandly_otel import otel, logger, instrument_pymysql
462
+
463
+ # Initialize OTEL
464
+ otel.initialize()
465
+
466
+ # Create and instrument your connection
467
+ connection = pymysql.connect(
468
+ host='localhost',
469
+ user='your_user',
470
+ password='your_password',
471
+ database='your_database'
472
+ )
473
+
474
+ # Instrument the connection - all queries are now automatically traced
475
+ connection = instrument_pymysql(otel, connection, options={
476
+ 'slow_query_threshold_ms': 1000, # Queries over 1s flagged as slow
477
+ 'capture_bindings': False # Set True to capture query parameters
478
+ })
479
+
480
+ # Use normally - all queries automatically traced
481
+ with connection.cursor() as cursor:
482
+ cursor.execute("SELECT * FROM users WHERE id = %s", (123,))
483
+ result = cursor.fetchone()
484
+ logger.info(f"Found user: {result}")
485
+
486
+ connection.close()
487
+ otel.force_flush()
488
+ ```
489
+
490
+ Features:
491
+ - Automatic span creation for all queries
492
+ - Query operation detection (SELECT, INSERT, UPDATE, etc.)
493
+ - Slow query detection and flagging
494
+ - Duration tracking
495
+ - Error recording with exception details
496
+ - Optional query parameter capture (disabled by default for security)
497
+
498
+ Environment configuration:
499
+ - `PYMYSQL_SLOW_QUERY_THRESHOLD_MS`: Threshold for slow query detection (default: 1500ms)
500
+
450
501
  ### More examples
451
502
  You can find More examples [here](examples)
452
503
 
@@ -1,24 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: rebrandly_otel
3
- Version: 0.1.20
4
- Summary: Python OTEL wrapper by Rebrandly
5
- Home-page: https://github.com/rebrandly/rebrandly-otel-python
6
- Author: Antonio Romano
7
- Author-email: antonio@rebrandly.com
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Description-Content-Type: text/markdown
12
- License-File: LICENSE
13
- Dynamic: author
14
- Dynamic: author-email
15
- Dynamic: classifier
16
- Dynamic: description
17
- Dynamic: description-content-type
18
- Dynamic: home-page
19
- Dynamic: license-file
20
- Dynamic: summary
21
-
22
1
  # Rebrandly OpenTelemetry SDK for Python
23
2
 
24
3
  A comprehensive OpenTelemetry instrumentation SDK designed specifically for Rebrandly services, with built-in support for AWS Lambda functions and message processing.
@@ -447,6 +426,52 @@ if __name__ == "__main__":
447
426
  uvicorn.run(app, host="0.0.0.0", port=8000)
448
427
  ```
449
428
 
429
+ ### PyMySQL Database Instrumentation
430
+
431
+ The SDK provides connection-level instrumentation for PyMySQL that automatically traces all queries without requiring you to instrument each query individually.
432
+
433
+ ```python
434
+ import pymysql
435
+ from rebrandly_otel import otel, logger, instrument_pymysql
436
+
437
+ # Initialize OTEL
438
+ otel.initialize()
439
+
440
+ # Create and instrument your connection
441
+ connection = pymysql.connect(
442
+ host='localhost',
443
+ user='your_user',
444
+ password='your_password',
445
+ database='your_database'
446
+ )
447
+
448
+ # Instrument the connection - all queries are now automatically traced
449
+ connection = instrument_pymysql(otel, connection, options={
450
+ 'slow_query_threshold_ms': 1000, # Queries over 1s flagged as slow
451
+ 'capture_bindings': False # Set True to capture query parameters
452
+ })
453
+
454
+ # Use normally - all queries automatically traced
455
+ with connection.cursor() as cursor:
456
+ cursor.execute("SELECT * FROM users WHERE id = %s", (123,))
457
+ result = cursor.fetchone()
458
+ logger.info(f"Found user: {result}")
459
+
460
+ connection.close()
461
+ otel.force_flush()
462
+ ```
463
+
464
+ Features:
465
+ - Automatic span creation for all queries
466
+ - Query operation detection (SELECT, INSERT, UPDATE, etc.)
467
+ - Slow query detection and flagging
468
+ - Duration tracking
469
+ - Error recording with exception details
470
+ - Optional query parameter capture (disabled by default for security)
471
+
472
+ Environment configuration:
473
+ - `PYMYSQL_SLOW_QUERY_THRESHOLD_MS`: Threshold for slow query detection (default: 1500ms)
474
+
450
475
  ### More examples
451
476
  You can find More examples [here](examples)
452
477
 
@@ -471,4 +496,4 @@ If `build` gives you an error, try:
471
496
 
472
497
  > pyproject-build
473
498
  >
474
- > twine upload dist/*
499
+ > twine upload dist/*
@@ -1,3 +1,29 @@
1
+ Metadata-Version: 2.4
2
+ Name: rebrandly_otel
3
+ Version: 0.1.23
4
+ Summary: Python OTEL wrapper by Rebrandly
5
+ Home-page: https://github.com/rebrandly/rebrandly-otel-python
6
+ Author: Antonio Romano
7
+ Author-email: antonio@rebrandly.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: opentelemetry-api>=1.34.0
14
+ Requires-Dist: opentelemetry-sdk>=1.34.0
15
+ Requires-Dist: opentelemetry-exporter-otlp>=1.34.0
16
+ Requires-Dist: psutil>=5.0.0
17
+ Dynamic: author
18
+ Dynamic: author-email
19
+ Dynamic: classifier
20
+ Dynamic: description
21
+ Dynamic: description-content-type
22
+ Dynamic: home-page
23
+ Dynamic: license-file
24
+ Dynamic: requires-dist
25
+ Dynamic: summary
26
+
1
27
  # Rebrandly OpenTelemetry SDK for Python
2
28
 
3
29
  A comprehensive OpenTelemetry instrumentation SDK designed specifically for Rebrandly services, with built-in support for AWS Lambda functions and message processing.
@@ -426,6 +452,52 @@ if __name__ == "__main__":
426
452
  uvicorn.run(app, host="0.0.0.0", port=8000)
427
453
  ```
428
454
 
455
+ ### PyMySQL Database Instrumentation
456
+
457
+ The SDK provides connection-level instrumentation for PyMySQL that automatically traces all queries without requiring you to instrument each query individually.
458
+
459
+ ```python
460
+ import pymysql
461
+ from rebrandly_otel import otel, logger, instrument_pymysql
462
+
463
+ # Initialize OTEL
464
+ otel.initialize()
465
+
466
+ # Create and instrument your connection
467
+ connection = pymysql.connect(
468
+ host='localhost',
469
+ user='your_user',
470
+ password='your_password',
471
+ database='your_database'
472
+ )
473
+
474
+ # Instrument the connection - all queries are now automatically traced
475
+ connection = instrument_pymysql(otel, connection, options={
476
+ 'slow_query_threshold_ms': 1000, # Queries over 1s flagged as slow
477
+ 'capture_bindings': False # Set True to capture query parameters
478
+ })
479
+
480
+ # Use normally - all queries automatically traced
481
+ with connection.cursor() as cursor:
482
+ cursor.execute("SELECT * FROM users WHERE id = %s", (123,))
483
+ result = cursor.fetchone()
484
+ logger.info(f"Found user: {result}")
485
+
486
+ connection.close()
487
+ otel.force_flush()
488
+ ```
489
+
490
+ Features:
491
+ - Automatic span creation for all queries
492
+ - Query operation detection (SELECT, INSERT, UPDATE, etc.)
493
+ - Slow query detection and flagging
494
+ - Duration tracking
495
+ - Error recording with exception details
496
+ - Optional query parameter capture (disabled by default for security)
497
+
498
+ Environment configuration:
499
+ - `PYMYSQL_SLOW_QUERY_THRESHOLD_MS`: Threshold for slow query detection (default: 1500ms)
500
+
429
501
  ### More examples
430
502
  You can find More examples [here](examples)
431
503
 
@@ -450,4 +522,4 @@ If `build` gives you an error, try:
450
522
 
451
523
  > pyproject-build
452
524
  >
453
- > twine upload dist/*
525
+ > twine upload dist/*
@@ -4,6 +4,7 @@ setup.py
4
4
  rebrandly_otel.egg-info/PKG-INFO
5
5
  rebrandly_otel.egg-info/SOURCES.txt
6
6
  rebrandly_otel.egg-info/dependency_links.txt
7
+ rebrandly_otel.egg-info/requires.txt
7
8
  rebrandly_otel.egg-info/top_level.txt
8
9
  src/__init__.py
9
10
  src/fastapi_support.py
@@ -11,6 +12,7 @@ src/flask_support.py
11
12
  src/logs.py
12
13
  src/metrics.py
13
14
  src/otel_utils.py
15
+ src/pymysql_instrumentation.py
14
16
  src/rebrandly_otel.py
15
17
  src/traces.py
16
18
  tests/test_usage.py
@@ -0,0 +1,4 @@
1
+ opentelemetry-api>=1.34.0
2
+ opentelemetry-sdk>=1.34.0
3
+ opentelemetry-exporter-otlp>=1.34.0
4
+ psutil>=5.0.0
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name="rebrandly_otel",
8
- version="0.1.20",
8
+ version="0.1.23",
9
9
  author="Antonio Romano",
10
10
  author_email="antonio@rebrandly.com",
11
11
  description="Python OTEL wrapper by Rebrandly",
@@ -14,6 +14,12 @@ setuptools.setup(
14
14
  url="https://github.com/rebrandly/rebrandly-otel-python",
15
15
  packages=["rebrandly_otel"],
16
16
  package_dir={"rebrandly_otel": "src"},
17
+ install_requires=[
18
+ "opentelemetry-api>=1.34.0",
19
+ "opentelemetry-sdk>=1.34.0",
20
+ "opentelemetry-exporter-otlp>=1.34.0",
21
+ "psutil>=5.0.0",
22
+ ],
17
23
  classifiers=[
18
24
  "Programming Language :: Python :: 3",
19
25
  "License :: OSI Approved :: MIT License",
@@ -2,9 +2,11 @@
2
2
  from .rebrandly_otel import *
3
3
  from .flask_support import setup_flask
4
4
  from .fastapi_support import setup_fastapi
5
+ from .pymysql_instrumentation import instrument_pymysql
5
6
 
6
7
  # Explicitly define what's available
7
8
  __all__ = [
9
+ 'otel',
8
10
  'lambda_handler',
9
11
  'span',
10
12
  'aws_message_span',
@@ -16,5 +18,6 @@ __all__ = [
16
18
  'aws_message_handler',
17
19
  'shutdown',
18
20
  'setup_flask',
19
- 'setup_fastapi'
21
+ 'setup_fastapi',
22
+ 'instrument_pymysql'
20
23
  ]
@@ -111,7 +111,7 @@ class RebrandlyMeter:
111
111
  histogram_view = View(
112
112
  instrument_type=Histogram,
113
113
  instrument_name="*",
114
- aggregation=ExplicitBucketHistogramAggregation((0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10)) # todo <-- define buckets
114
+ aggregation=ExplicitBucketHistogramAggregation((0.001, 0.004, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5)) # todo <-- define buckets
115
115
  )
116
116
  views.append(histogram_view)
117
117
 
@@ -22,7 +22,7 @@ def create_resource(name: str = None, version: str = None) -> Resource:
22
22
  service_attributes.SERVICE_NAME: name,
23
23
  service_attributes.SERVICE_VERSION: version,
24
24
  process_attributes.PROCESS_RUNTIME_VERSION: f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
25
- deployment_attributes.DEPLOYMENT_ENVIRONMENT: os.environ.get('ENV', os.environ.get('ENVIRONMENT', os.environ.get('NODE_ENV', 'local')))
25
+ deployment_attributes.DEPLOYMENT_ENVIRONMENT_NAME: os.environ.get('ENV', os.environ.get('ENVIRONMENT', os.environ.get('NODE_ENV', 'local')))
26
26
  }
27
27
  )
28
28
  return resource
@@ -0,0 +1,206 @@
1
+ """
2
+ PyMySQL instrumentation for Rebrandly OTEL SDK
3
+ Provides query tracing and slow query detection
4
+ """
5
+
6
+ import os
7
+ import time
8
+ import functools
9
+ from opentelemetry.trace import Status, StatusCode, SpanKind
10
+
11
+ # Environment configuration
12
+ SLOW_QUERY_THRESHOLD_MS = int(os.getenv('PYMYSQL_SLOW_QUERY_THRESHOLD_MS', '1500'))
13
+ MAX_QUERY_LENGTH = 2000 # Truncate long queries
14
+
15
+
16
+ def instrument_pymysql(otel_instance, connection, options=None):
17
+ """
18
+ Instrument a PyMySQL connection for OpenTelemetry tracing
19
+
20
+ Args:
21
+ otel_instance: The RebrandlyOTEL instance
22
+ connection: The PyMySQL connection to instrument
23
+ options: Configuration options dict with:
24
+ - slow_query_threshold_ms: Threshold for slow query detection (default: 1500ms)
25
+ - capture_bindings: Include query bindings in spans (default: False for security)
26
+
27
+ Returns:
28
+ The instrumented connection
29
+ """
30
+ if options is None:
31
+ options = {}
32
+
33
+ slow_query_threshold_ms = options.get('slow_query_threshold_ms', SLOW_QUERY_THRESHOLD_MS)
34
+ capture_bindings = options.get('capture_bindings', False)
35
+
36
+ if not connection:
37
+ print('[Rebrandly OTEL PyMySQL] No connection provided for instrumentation')
38
+ return connection
39
+
40
+ if not otel_instance or not hasattr(otel_instance, 'tracer'):
41
+ print('[Rebrandly OTEL PyMySQL] No valid OTEL instance provided for instrumentation')
42
+ return connection
43
+
44
+ # Get tracer from RebrandlyOTEL instance
45
+ tracer = otel_instance.tracer
46
+
47
+ # Wrap the cursor method to return instrumented cursors
48
+ original_cursor = connection.cursor
49
+
50
+ def instrumented_cursor(*args, **kwargs):
51
+ cursor = original_cursor(*args, **kwargs)
52
+ return _instrument_cursor(cursor, tracer, slow_query_threshold_ms, capture_bindings)
53
+
54
+ connection.cursor = instrumented_cursor
55
+
56
+ return connection
57
+
58
+
59
+ def _instrument_cursor(cursor, tracer, slow_query_threshold_ms, capture_bindings):
60
+ """
61
+ Instrument a cursor's execute methods
62
+ """
63
+ original_execute = cursor.execute
64
+ original_executemany = cursor.executemany
65
+
66
+ @functools.wraps(original_execute)
67
+ def instrumented_execute(query, args=None):
68
+ return _trace_query(
69
+ original_execute,
70
+ tracer,
71
+ slow_query_threshold_ms,
72
+ capture_bindings,
73
+ query,
74
+ args,
75
+ many=False
76
+ )
77
+
78
+ @functools.wraps(original_executemany)
79
+ def instrumented_executemany(query, args):
80
+ return _trace_query(
81
+ original_executemany,
82
+ tracer,
83
+ slow_query_threshold_ms,
84
+ capture_bindings,
85
+ query,
86
+ args,
87
+ many=True
88
+ )
89
+
90
+ cursor.execute = instrumented_execute
91
+ cursor.executemany = instrumented_executemany
92
+
93
+ return cursor
94
+
95
+
96
+ def _trace_query(func, tracer, slow_query_threshold_ms, capture_bindings, query, args, many=False):
97
+ """
98
+ Trace a query execution with OpenTelemetry
99
+ """
100
+ operation = _extract_operation(query)
101
+ truncated_query = _truncate_query(query)
102
+
103
+ # Start span
104
+ span_name = f"pymysql.{'executemany' if many else 'execute'}"
105
+
106
+ with tracer.start_span(
107
+ name=span_name,
108
+ kind=SpanKind.CLIENT
109
+ ) as span:
110
+ # Set database attributes
111
+ span.set_attribute('db.system', 'mysql')
112
+ span.set_attribute('db.operation.name', operation)
113
+ span.set_attribute('db.statement', truncated_query)
114
+
115
+ # Add bindings if enabled (be cautious with sensitive data)
116
+ if capture_bindings and args:
117
+ if many:
118
+ span.set_attribute('db.bindings_count', len(args))
119
+ else:
120
+ span.set_attribute('db.bindings', str(args))
121
+
122
+ start_time = time.time()
123
+
124
+ try:
125
+ # Execute the query
126
+ result = func(query, args)
127
+
128
+ # Calculate duration
129
+ duration_ms = (time.time() - start_time) * 1000
130
+ span.set_attribute('db.duration_ms', duration_ms)
131
+
132
+ # Check for slow query
133
+ if duration_ms >= slow_query_threshold_ms:
134
+ span.set_attribute('db.slow_query', True)
135
+ span.add_event('slow_query_detected', {
136
+ 'db.duration_ms': duration_ms,
137
+ 'db.threshold_ms': slow_query_threshold_ms
138
+ })
139
+
140
+ # Set success status
141
+ span.set_status(Status(StatusCode.OK))
142
+
143
+ return result
144
+
145
+ except Exception as error:
146
+ # Calculate duration even on error
147
+ duration_ms = (time.time() - start_time) * 1000
148
+ span.set_attribute('db.duration_ms', duration_ms)
149
+
150
+ # Record exception
151
+ span.record_exception(error)
152
+ span.set_status(Status(StatusCode.ERROR, str(error)))
153
+
154
+ raise
155
+
156
+
157
+ def _extract_operation(sql):
158
+ """
159
+ Extract operation type from SQL statement
160
+
161
+ Args:
162
+ sql: SQL query string
163
+
164
+ Returns:
165
+ Operation type (SELECT, INSERT, UPDATE, etc.)
166
+ """
167
+ if not sql:
168
+ return 'unknown'
169
+
170
+ normalized = sql.strip().upper()
171
+
172
+ if normalized.startswith('SELECT'):
173
+ return 'SELECT'
174
+ if normalized.startswith('INSERT'):
175
+ return 'INSERT'
176
+ if normalized.startswith('UPDATE'):
177
+ return 'UPDATE'
178
+ if normalized.startswith('DELETE'):
179
+ return 'DELETE'
180
+ if normalized.startswith('CREATE'):
181
+ return 'CREATE'
182
+ if normalized.startswith('DROP'):
183
+ return 'DROP'
184
+ if normalized.startswith('ALTER'):
185
+ return 'ALTER'
186
+ if normalized.startswith('TRUNCATE'):
187
+ return 'TRUNCATE'
188
+
189
+ return 'unknown'
190
+
191
+
192
+ def _truncate_query(sql):
193
+ """
194
+ Truncate long queries for span attributes
195
+
196
+ Args:
197
+ sql: SQL query string
198
+
199
+ Returns:
200
+ Truncated query
201
+ """
202
+ if not sql:
203
+ return ''
204
+ if len(sql) <= MAX_QUERY_LENGTH:
205
+ return sql
206
+ return sql[:MAX_QUERY_LENGTH] + '... [truncated]'
File without changes