tracekit-apm 1.0.1__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 TraceKit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,616 @@
1
+ Metadata-Version: 2.4
2
+ Name: tracekit-apm
3
+ Version: 1.0.1
4
+ Summary: TraceKit APM for Python - Zero-config distributed tracing and code monitoring for Flask, FastAPI, and Django
5
+ Author-email: TraceKit <support@tracekit.dev>
6
+ License: MIT
7
+ Project-URL: Homepage, https://tracekit.dev
8
+ Project-URL: Documentation, https://app.tracekit.dev/docs/languages/python
9
+ Project-URL: Repository, https://github.com/Tracekit-Dev/python-apm
10
+ Project-URL: Issues, https://github.com/Tracekit-Dev/python-apm/issues
11
+ Keywords: tracekit,apm,tracing,monitoring,opentelemetry,flask,fastapi,django,performance,observability
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Topic :: System :: Monitoring
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: opentelemetry-api>=1.20.0
27
+ Requires-Dist: opentelemetry-sdk>=1.20.0
28
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.20.0
29
+ Requires-Dist: requests>=2.28.0
30
+ Provides-Extra: flask
31
+ Requires-Dist: flask>=2.0.0; extra == "flask"
32
+ Provides-Extra: fastapi
33
+ Requires-Dist: fastapi>=0.100.0; extra == "fastapi"
34
+ Requires-Dist: starlette>=0.27.0; extra == "fastapi"
35
+ Provides-Extra: django
36
+ Requires-Dist: django>=3.2; extra == "django"
37
+ Provides-Extra: all
38
+ Requires-Dist: flask>=2.0.0; extra == "all"
39
+ Requires-Dist: fastapi>=0.100.0; extra == "all"
40
+ Requires-Dist: starlette>=0.27.0; extra == "all"
41
+ Requires-Dist: django>=3.2; extra == "all"
42
+ Dynamic: license-file
43
+
44
+ # TraceKit APM for Python
45
+
46
+ Zero-config distributed tracing and performance monitoring for Flask, FastAPI, and Django applications.
47
+
48
+ [![PyPI version](https://img.shields.io/pypi/v/tracekit-apm.svg)](https://pypi.org/project/tracekit-apm/)
49
+ [![Python](https://img.shields.io/pypi/pyversions/tracekit-apm.svg)](https://pypi.org/project/tracekit-apm/)
50
+ [![License](https://img.shields.io/pypi/l/tracekit-apm.svg)](https://pypi.org/project/tracekit-apm/)
51
+
52
+ ## Features
53
+
54
+ - ✅ **Zero Configuration** - Works out of the box with sensible defaults
55
+ - ✅ **Automatic Instrumentation** - No code changes needed
56
+ - ✅ **Flask Support** - Simple middleware integration
57
+ - ✅ **FastAPI Support** - ASGI middleware-based tracing
58
+ - ✅ **Django Support** - Django middleware integration
59
+ - ✅ **HTTP Request Tracing** - Track every request, route, and handler
60
+ - ✅ **Error Tracking** - Capture exceptions with full Python stack traces
61
+ - ✅ **Code Discovery** - Automatically index code from exception stack traces
62
+ - ✅ **Code Monitoring** - Live debugging with breakpoints and variable inspection
63
+ - ✅ **Nested Spans** - Parent-child span relationships with OpenTelemetry
64
+ - ✅ **Low Overhead** - < 5% performance impact
65
+ - ✅ **OpenTelemetry Standard** - Built on industry-standard OpenTelemetry
66
+
67
+ ## Installation
68
+
69
+ ```bash
70
+ pip install tracekit-apm
71
+ ```
72
+
73
+ ### Framework-Specific Installation
74
+
75
+ ```bash
76
+ # Flask
77
+ pip install tracekit-apm[flask]
78
+
79
+ # FastAPI
80
+ pip install tracekit-apm[fastapi]
81
+
82
+ # Django
83
+ pip install tracekit-apm[django]
84
+
85
+ # All frameworks
86
+ pip install tracekit-apm[all]
87
+ ```
88
+
89
+ ## Quick Start
90
+
91
+ ### Flask
92
+
93
+ ```python
94
+ from flask import Flask
95
+ import tracekit
96
+ from tracekit.middleware.flask import init_flask_app
97
+
98
+ app = Flask(__name__)
99
+
100
+ # Initialize TraceKit with code monitoring enabled
101
+ client = tracekit.init(
102
+ api_key="your-api-key",
103
+ service_name="my-flask-app",
104
+ enable_code_monitoring=True # Enable live debugging
105
+ )
106
+
107
+ # Add middleware
108
+ init_flask_app(app, client)
109
+
110
+ @app.route('/')
111
+ def hello():
112
+ return 'Hello World!'
113
+
114
+ @app.route('/api/users/<int:user_id>')
115
+ def get_user(user_id):
116
+ # Capture snapshot for debugging (synchronous call)
117
+ if client.get_snapshot_client():
118
+ client.capture_snapshot('get-user', {
119
+ 'user_id': user_id,
120
+ 'request_path': request.path,
121
+ 'request_method': request.method
122
+ })
123
+
124
+ # Your business logic here
125
+ user = fetch_user_from_db(user_id)
126
+ return jsonify(user)
127
+
128
+ if __name__ == '__main__':
129
+ app.run()
130
+ ```
131
+
132
+ ### FastAPI
133
+
134
+ ```python
135
+ from fastapi import FastAPI
136
+ import tracekit
137
+ from tracekit.middleware.fastapi import init_fastapi_app
138
+
139
+ app = FastAPI()
140
+
141
+ # Initialize TraceKit
142
+ client = tracekit.init(
143
+ api_key="your-api-key",
144
+ service_name="my-fastapi-app"
145
+ )
146
+
147
+ # Add middleware
148
+ init_fastapi_app(app, client)
149
+
150
+ @app.get("/")
151
+ async def root():
152
+ return {"message": "Hello World"}
153
+ ```
154
+
155
+ ### Django
156
+
157
+ ```python
158
+ # settings.py
159
+
160
+ # Add middleware
161
+ MIDDLEWARE = [
162
+ 'tracekit.middleware.django.TracekitDjangoMiddleware',
163
+ # ... other middleware
164
+ ]
165
+
166
+ # Initialize TraceKit in your app's apps.py
167
+ # apps.py
168
+ from django.apps import AppConfig
169
+ import tracekit
170
+ import os
171
+
172
+ class MyAppConfig(AppConfig):
173
+ name = 'myapp'
174
+
175
+ def ready(self):
176
+ tracekit.init(
177
+ api_key=os.environ['TRACEKIT_API_KEY'],
178
+ service_name='my-django-app'
179
+ )
180
+ ```
181
+
182
+ ## Code Monitoring (Live Debugging)
183
+
184
+ TraceKit includes production-safe code monitoring for live debugging without redeployment.
185
+
186
+ ### Enable Code Monitoring
187
+
188
+ ```python
189
+ import tracekit
190
+
191
+ # Enable code monitoring
192
+ client = tracekit.init(
193
+ api_key="your-api-key",
194
+ service_name="my-app",
195
+ enable_code_monitoring=True # Enable live debugging
196
+ )
197
+ ```
198
+
199
+ ### Add Debug Points
200
+
201
+ Add checkpoints anywhere in your code to capture variable state and stack traces:
202
+
203
+ ```python
204
+ @app.post('/checkout')
205
+ async def checkout(request):
206
+ cart = await request.json()
207
+ user_id = cart['user_id']
208
+
209
+ # Capture snapshot at this point (synchronous - no await needed)
210
+ client.capture_snapshot('checkout-validation', {
211
+ 'user_id': user_id,
212
+ 'cart_items': len(cart.get('items', [])),
213
+ 'total_amount': cart.get('total', 0),
214
+ })
215
+
216
+ # Process payment...
217
+ result = await process_payment(cart)
218
+
219
+ # Another checkpoint
220
+ client.capture_snapshot('payment-complete', {
221
+ 'user_id': user_id,
222
+ 'payment_id': result['payment_id'],
223
+ 'success': result['success'],
224
+ })
225
+
226
+ return {'status': 'success', 'result': result}
227
+ ```
228
+
229
+ ### Automatic Breakpoint Management
230
+
231
+ - **Auto-Registration**: First call to `capture_snapshot()` automatically creates breakpoints in TraceKit
232
+ - **Smart Matching**: Breakpoints match by function name + label (stable across code changes)
233
+ - **Background Sync**: SDK polls for active breakpoints every 30 seconds
234
+ - **Production Safe**: No performance impact when breakpoints are inactive
235
+ - **Synchronous API**: `capture_snapshot()` is synchronous - no `await` needed (works in both sync and async code)
236
+
237
+ ### Important Notes
238
+
239
+ 🔑 **Key Points:**
240
+ - `capture_snapshot()` is **synchronous** (no `await` needed)
241
+ - Works in both sync and async functions
242
+ - Automatically registers breakpoints on first call
243
+ - Captures are rate-limited (1 per second per breakpoint)
244
+ - Max 100 captures per breakpoint by default
245
+
246
+ ### View Captured Data
247
+
248
+ Snapshots include:
249
+ - **Variables**: Local variables at capture point
250
+ - **Stack Trace**: Full call stack with file/line numbers
251
+ - **Request Context**: HTTP method, URL, headers, query params
252
+ - **Execution Time**: When the snapshot was captured
253
+
254
+ Get your API key at [https://app.tracekit.dev](https://app.tracekit.dev)
255
+
256
+ ## Configuration
257
+
258
+ ### Basic Configuration
259
+
260
+ ```python
261
+ import tracekit
262
+
263
+ client = tracekit.init(
264
+ # Required: Your TraceKit API key
265
+ api_key="your-api-key",
266
+
267
+ # Optional: Service name (default: 'python-app')
268
+ service_name="my-service",
269
+
270
+ # Optional: TraceKit endpoint (default: 'https://app.tracekit.dev/v1/traces')
271
+ endpoint="https://app.tracekit.dev/v1/traces",
272
+
273
+ # Optional: Enable/disable tracing (default: True)
274
+ enabled=True,
275
+
276
+ # Optional: Sample rate 0.0-1.0 (default: 1.0 = 100%)
277
+ sample_rate=0.5, # Trace 50% of requests
278
+
279
+ # Optional: Enable live code debugging (default: False)
280
+ enable_code_monitoring=True
281
+ )
282
+ ```
283
+
284
+ ### Environment Variables
285
+
286
+ Create a `.env` file or set these environment variables:
287
+
288
+ ```bash
289
+ TRACEKIT_API_KEY=your_api_key_here
290
+ TRACEKIT_ENDPOINT=https://app.tracekit.dev/v1/traces
291
+ TRACEKIT_SERVICE_NAME=my-python-app
292
+ ```
293
+
294
+ Then use them in your code:
295
+
296
+ ```python
297
+ import os
298
+ import tracekit
299
+
300
+ client = tracekit.init(
301
+ api_key=os.getenv('TRACEKIT_API_KEY'),
302
+ service_name=os.getenv('TRACEKIT_SERVICE_NAME', 'python-app'),
303
+ endpoint=os.getenv('TRACEKIT_ENDPOINT', 'https://app.tracekit.dev/v1/traces')
304
+ )
305
+ ```
306
+
307
+ ## What Gets Traced?
308
+
309
+ ### HTTP Requests
310
+
311
+ Every HTTP request is automatically traced with:
312
+
313
+ - Route path and HTTP method
314
+ - Request URL and query parameters
315
+ - HTTP status code
316
+ - Request duration
317
+ - User agent and client IP
318
+ - Controller and handler names
319
+
320
+ ### Errors and Exceptions
321
+
322
+ All exceptions are automatically captured with:
323
+
324
+ - Exception type and message
325
+ - Full stack trace (Python format preserved)
326
+ - Request context
327
+ - Handler information
328
+
329
+ ### Automatic Code Discovery
330
+
331
+ TraceKit automatically discovers and indexes your Python code from exception stack traces:
332
+
333
+ **Python Stack Trace Format (Native):**
334
+ ```python
335
+ File "/path/to/app.py", line 123, in function_name
336
+ ```
337
+
338
+ **What Gets Indexed:**
339
+ - File paths: `app.py`, `handlers.py`, etc.
340
+ - Function names: `get_user`, `process_payment`, etc.
341
+ - Line numbers: Exact location in your code
342
+ - First/last seen timestamps
343
+ - Occurrence counts
344
+
345
+ **Example:**
346
+ When this exception occurs:
347
+ ```python
348
+ File "/app/handlers/users.py", line 42, in get_user
349
+ user = User.objects.get(id=user_id)
350
+ DoesNotExist: User matching query does not exist.
351
+ ```
352
+
353
+ TraceKit indexes:
354
+ - **File**: `users.py`
355
+ - **Function**: `get_user`
356
+ - **Line**: 42
357
+ - **Service**: Your service name
358
+
359
+ View discovered code in the TraceKit UI under **Code Inventory**.
360
+
361
+ ## Advanced Usage
362
+
363
+ ### Manual Tracing
364
+
365
+ ```python
366
+ from opentelemetry import trace
367
+
368
+ @app.post('/process-order')
369
+ async def process_order(request):
370
+ # Get the client
371
+ client = tracekit.get_client()
372
+
373
+ # Start a custom span
374
+ span = client.start_span('process-order', {
375
+ 'order.id': order_id,
376
+ 'customer.id': customer_id
377
+ })
378
+
379
+ try:
380
+ # Your business logic
381
+ result = await process_order_logic(order_id)
382
+
383
+ # Add attributes
384
+ client.end_span(span, {
385
+ 'order.status': result['status'],
386
+ 'order.total': result['total']
387
+ })
388
+
389
+ return result
390
+
391
+ except Exception as error:
392
+ # Record exception
393
+ client.record_exception(span, error)
394
+ client.end_span(span, {}, status='ERROR')
395
+ raise
396
+ ```
397
+
398
+ ### Custom Spans with Context Manager (Nested Spans)
399
+
400
+ Use OpenTelemetry's context manager for automatic parent-child span relationships:
401
+
402
+ ```python
403
+ from opentelemetry import trace
404
+ from flask import Flask, jsonify
405
+ import time
406
+
407
+ @app.route('/api/users/<int:user_id>')
408
+ def get_user(user_id):
409
+ # Get the tracer
410
+ tracer = trace.get_tracer(__name__)
411
+
412
+ # Parent span is automatically managed by Flask middleware
413
+ # Create child span using context manager
414
+ with tracer.start_as_current_span('db.query.user') as span:
415
+ span.set_attributes({
416
+ 'db.system': 'postgresql',
417
+ 'db.operation': 'SELECT',
418
+ 'db.table': 'users',
419
+ 'db.statement': 'SELECT * FROM users WHERE id = ?',
420
+ 'user.id': user_id
421
+ })
422
+
423
+ time.sleep(0.01) # Simulate DB query
424
+ user = fetch_user_from_db(user_id)
425
+
426
+ span.set_attributes({
427
+ 'user.found': user is not None,
428
+ 'user.role': user.get('role') if user else None
429
+ })
430
+
431
+ return jsonify(user)
432
+ ```
433
+
434
+ This creates a **nested trace** with parent-child relationships:
435
+ ```
436
+ GET /api/users/1 (parent span - auto-created by middleware)
437
+ └─ db.query.user (child span - manually created)
438
+ ```
439
+
440
+ ### Database Query Tracing
441
+
442
+ ```python
443
+ @app.get('/users')
444
+ async def list_users():
445
+ client = tracekit.get_client()
446
+
447
+ # Start a database query span
448
+ span = client.start_span('db.query.users', {
449
+ 'db.system': 'postgresql',
450
+ 'db.operation': 'SELECT',
451
+ 'db.table': 'users'
452
+ })
453
+
454
+ try:
455
+ users = await db.query('SELECT * FROM users')
456
+
457
+ client.end_span(span, {
458
+ 'db.rows_affected': len(users)
459
+ })
460
+
461
+ return users
462
+
463
+ except Exception as error:
464
+ client.record_exception(span, error)
465
+ client.end_span(span, {}, status='ERROR')
466
+ raise
467
+ ```
468
+
469
+ ### External API Call Tracing
470
+
471
+ ```python
472
+ import httpx
473
+
474
+ @app.get('/external-data')
475
+ async def fetch_external_data():
476
+ client = tracekit.get_client()
477
+
478
+ span = client.start_span('http.client.get', {
479
+ 'http.url': 'https://api.example.com/data',
480
+ 'http.method': 'GET'
481
+ })
482
+
483
+ try:
484
+ async with httpx.AsyncClient() as http_client:
485
+ response = await http_client.get('https://api.example.com/data')
486
+
487
+ client.end_span(span, {
488
+ 'http.status_code': response.status_code,
489
+ 'response.size': len(response.content)
490
+ })
491
+
492
+ return response.json()
493
+
494
+ except Exception as error:
495
+ client.record_exception(span, error)
496
+ client.end_span(span, {}, status='ERROR')
497
+ raise
498
+ ```
499
+
500
+ ## Environment-Based Configuration
501
+
502
+ ### Disable tracing in development
503
+
504
+ ```python
505
+ import os
506
+
507
+ tracekit.init(
508
+ api_key=os.getenv('TRACEKIT_API_KEY'),
509
+ enabled=os.getenv('ENVIRONMENT') == 'production'
510
+ )
511
+ ```
512
+
513
+ ### Sample only 10% of requests
514
+
515
+ ```python
516
+ tracekit.init(
517
+ api_key=os.getenv('TRACEKIT_API_KEY'),
518
+ sample_rate=0.1 # Trace 10% of requests
519
+ )
520
+ ```
521
+
522
+ ## Performance
523
+
524
+ TraceKit APM is designed to have minimal performance impact:
525
+
526
+ - **< 5% overhead** on average request time
527
+ - Asynchronous trace sending (doesn't block responses)
528
+ - Automatic batching and compression
529
+ - Configurable sampling for high-traffic apps
530
+
531
+ ## Requirements
532
+
533
+ - Python 3.8 or higher
534
+ - Flask 2.0+ (for Flask support)
535
+ - FastAPI 0.100+ (for FastAPI support)
536
+ - Django 3.2+ (for Django support)
537
+
538
+ ## Examples
539
+
540
+ See the [examples/](examples/) directory for complete working applications:
541
+
542
+ - Flask example: [examples/flask_example.py](examples/flask_example.py)
543
+ - FastAPI example: [examples/fastapi_example.py](examples/fastapi_example.py)
544
+ - Django example: [examples/django_example/](examples/django_example/)
545
+
546
+ ## Troubleshooting
547
+
548
+ ### Snapshots Not Capturing
549
+
550
+ If snapshots are registered but not capturing:
551
+
552
+ 1. **Check if code monitoring is enabled:**
553
+ ```python
554
+ client = tracekit.init(
555
+ api_key="your-api-key",
556
+ enable_code_monitoring=True # Must be True
557
+ )
558
+ ```
559
+
560
+ 2. **Verify snapshot client is available:**
561
+ ```python
562
+ if client.get_snapshot_client():
563
+ client.capture_snapshot('label', {'var': value})
564
+ ```
565
+
566
+ 3. **Check backend logs for errors:**
567
+ - Look for "Snapshot captured successfully" messages
568
+ - Check for datetime format errors (should use UTC with Z suffix)
569
+
570
+ ### Code Discovery Not Working
571
+
572
+ If Python code isn't being indexed:
573
+
574
+ 1. **Trigger an exception** - Code discovery works from exception stack traces
575
+ 2. **Check stack trace format** - Should be native Python format:
576
+ ```
577
+ File "/path/to/file.py", line 123, in function_name
578
+ ```
579
+ 3. **View backend logs** - Look for "Parsed X frames from stack trace"
580
+
581
+ ### Context Token Errors
582
+
583
+ If you see `RuntimeError: Token has already been used once`:
584
+
585
+ - This was fixed in the latest version
586
+ - Make sure you're using the latest `tracekit-apm` package
587
+ - The error occurs when exceptions are not properly cleaned up
588
+
589
+ ### Nested Spans Not Showing
590
+
591
+ To create nested spans, use OpenTelemetry's context manager:
592
+
593
+ ```python
594
+ from opentelemetry import trace
595
+
596
+ tracer = trace.get_tracer(__name__)
597
+
598
+ # This creates a child span under the parent request span
599
+ with tracer.start_as_current_span('operation-name') as span:
600
+ span.set_attribute('key', 'value')
601
+ # Your code here
602
+ ```
603
+
604
+ ## Support
605
+
606
+ - Documentation: [https://app.tracekit.dev/docs/languages/python](https://app.tracekit.dev/docs/languages/python)
607
+ - Issues: [https://github.com/Tracekit-Dev/python-apm/issues](https://github.com/Tracekit-Dev/python-apm/issues)
608
+ - Email: support@tracekit.dev
609
+
610
+ ## License
611
+
612
+ MIT License. See [LICENSE](LICENSE) for details.
613
+
614
+ ## Credits
615
+
616
+ Built with ❤️ by the TraceKit team using OpenTelemetry.