0
# Context Management
1
2
Thread-safe context system for automatic user identification, session tracking, and property tagging. PostHog's context management enables consistent user tracking across related operations while providing automatic exception capture and nested context support.
3
4
## Capabilities
5
6
### Context Creation
7
8
Create isolated contexts for grouping related operations with automatic cleanup and exception handling.
9
10
```python { .api }
11
def new_context(fresh: bool = False, capture_exceptions: bool = True):
12
"""
13
Create a new context scope that will be active for the duration of the with block.
14
15
Parameters:
16
- fresh: bool - Whether to start with a fresh context (default: False)
17
- capture_exceptions: bool - Whether to capture exceptions raised within the context (default: True)
18
19
Returns:
20
Context manager for use with 'with' statement
21
22
Notes:
23
- Creates isolated scope for user identification and tags
24
- Automatically captures exceptions if enabled
25
- Context data is inherited by child contexts unless fresh=True
26
- Thread-safe for concurrent operations
27
"""
28
```
29
30
### Function-Level Contexts
31
32
Apply context management to individual functions using decorators for automatic scope management.
33
34
```python { .api }
35
def scoped(fresh: bool = False, capture_exceptions: bool = True):
36
"""
37
Decorator that creates a new context for the function.
38
39
Parameters:
40
- fresh: bool - Whether to start with a fresh context (default: False)
41
- capture_exceptions: bool - Whether to capture and track exceptions with posthog error tracking (default: True)
42
43
Returns:
44
Function decorator
45
46
Notes:
47
- Automatically creates context for decorated function
48
- Cleans up context when function exits
49
- Preserves function return values and exceptions
50
- Suitable for background tasks, request handlers, etc.
51
"""
52
```
53
54
### User Identification
55
56
Associate contexts with specific users for automatic user tracking across all operations.
57
58
```python { .api }
59
def identify_context(distinct_id: str):
60
"""
61
Identify the current context with a distinct ID.
62
63
Parameters:
64
- distinct_id: str - The distinct ID to associate with the current context and its children
65
66
Notes:
67
- Sets user ID for all subsequent operations in context
68
- Inherited by child contexts
69
- Used automatically by capture, set, and other operations
70
- Overrides any parent context user ID
71
"""
72
```
73
74
### Session Management
75
76
Manage user sessions within contexts for tracking related user activities across time.
77
78
```python { .api }
79
def set_context_session(session_id: str):
80
"""
81
Set the session ID for the current context.
82
83
Parameters:
84
- session_id: str - The session ID to associate with the current context and its children
85
86
Notes:
87
- Automatically included in all events as $session_id property
88
- Inherited by child contexts
89
- Used for session-based analytics and user journey tracking
90
"""
91
```
92
93
### Context Tagging
94
95
Add metadata and properties to contexts that are automatically included in all events and operations.
96
97
```python { .api }
98
def tag(name: str, value: Any):
99
"""
100
Add a tag to the current context.
101
102
Parameters:
103
- name: str - The tag key
104
- value: Any - The tag value (must be JSON serializable)
105
106
Notes:
107
- Tags are included in all subsequent events in the context
108
- Inherited by child contexts
109
- Useful for request IDs, feature flags, A/B test groups, etc.
110
- Automatically merged with event properties
111
"""
112
```
113
114
## Usage Examples
115
116
### Basic Context Usage
117
118
```python
119
import posthog
120
121
# Configure PostHog
122
posthog.api_key = 'phc_your_project_api_key'
123
124
# Simple context with user identification
125
with posthog.new_context():
126
posthog.identify_context('user123')
127
128
# All events automatically include user123
129
posthog.capture('page_viewed', {'page': 'dashboard'})
130
posthog.capture('button_clicked', {'button': 'export'})
131
posthog.set({'last_active': '2024-09-07'})
132
133
# Context with session tracking
134
with posthog.new_context():
135
posthog.identify_context('user456')
136
posthog.set_context_session('session_abc123')
137
138
# Events include both user ID and session ID
139
posthog.capture('session_started')
140
posthog.capture('feature_used', {'feature': 'reports'})
141
posthog.capture('session_ended')
142
```
143
144
### Context Tagging
145
146
```python
147
import posthog
148
149
# Context with multiple tags
150
with posthog.new_context():
151
posthog.identify_context('user789')
152
posthog.tag('request_id', 'req_12345')
153
posthog.tag('user_segment', 'premium')
154
posthog.tag('ab_test_group', 'variant_b')
155
posthog.tag('feature_flag_new_ui', True)
156
157
# All events automatically include these tags
158
posthog.capture('api_request', {
159
'endpoint': '/api/data',
160
'method': 'GET'
161
})
162
163
# Tags are merged with explicit properties
164
posthog.capture('error_occurred', {
165
'error_type': 'validation',
166
'error_code': 400
167
})
168
# Final event includes: user_id, request_id, user_segment, ab_test_group,
169
# feature_flag_new_ui, error_type, error_code
170
```
171
172
### Nested Contexts
173
174
```python
175
import posthog
176
177
# Parent context
178
with posthog.new_context():
179
posthog.identify_context('user123')
180
posthog.tag('request_type', 'api')
181
182
posthog.capture('request_started')
183
184
# Child context inherits parent data
185
with posthog.new_context():
186
posthog.tag('operation', 'data_processing')
187
188
posthog.capture('processing_started')
189
# Includes: user123, request_type=api, operation=data_processing
190
191
# Another child context
192
with posthog.new_context():
193
posthog.tag('step', 'validation')
194
195
posthog.capture('validation_completed')
196
# Includes: user123, request_type=api, operation=data_processing, step=validation
197
198
posthog.capture('request_completed')
199
# Back to parent context: user123, request_type=api
200
```
201
202
### Fresh Contexts
203
204
```python
205
import posthog
206
207
# Parent context with data
208
with posthog.new_context():
209
posthog.identify_context('user123')
210
posthog.tag('parent_tag', 'value')
211
212
# Fresh context ignores parent data
213
with posthog.new_context(fresh=True):
214
posthog.identify_context('admin456')
215
posthog.tag('admin_operation', True)
216
217
posthog.capture('admin_action')
218
# Only includes: admin456, admin_operation=True
219
# Does NOT include user123 or parent_tag
220
221
# Back to parent context
222
posthog.capture('user_action')
223
# Includes: user123, parent_tag=value
224
```
225
226
### Function-Level Contexts
227
228
```python
229
import posthog
230
231
# Decorator for automatic context management
232
@posthog.scoped()
233
def process_user_request(user_id, request_data):
234
posthog.identify_context(user_id)
235
posthog.tag('operation', 'user_request')
236
posthog.tag('request_id', request_data.get('id'))
237
238
posthog.capture('request_processing_started')
239
240
# Process request...
241
result = handle_request(request_data)
242
243
posthog.capture('request_processing_completed', {
244
'success': result['success'],
245
'processing_time': result['duration']
246
})
247
248
return result
249
250
# Background task with fresh context
251
@posthog.scoped(fresh=True)
252
def background_cleanup_task():
253
posthog.identify_context('system')
254
posthog.tag('task_type', 'cleanup')
255
256
posthog.capture('cleanup_started')
257
258
# Perform cleanup...
259
cleaned_items = perform_cleanup()
260
261
posthog.capture('cleanup_completed', {
262
'items_cleaned': len(cleaned_items)
263
})
264
265
# Usage
266
user_data = {'id': 'req_123', 'user': 'user456'}
267
process_user_request('user456', user_data)
268
269
# Run background task
270
background_cleanup_task()
271
```
272
273
### Exception Handling with Contexts
274
275
```python
276
import posthog
277
278
# Automatic exception capture (default behavior)
279
with posthog.new_context():
280
posthog.identify_context('user123')
281
posthog.tag('operation', 'risky_operation')
282
283
try:
284
posthog.capture('operation_started')
285
286
# This exception is automatically captured by context
287
raise ValueError("Something went wrong")
288
289
except ValueError as e:
290
# Exception was already captured by context
291
posthog.capture('operation_failed', {
292
'error_handled': True
293
})
294
295
# Disable automatic exception capture
296
with posthog.new_context(capture_exceptions=False):
297
posthog.identify_context('user456')
298
299
try:
300
dangerous_operation()
301
except Exception as e:
302
# Manual exception capture since auto-capture is disabled
303
posthog.capture_exception(e)
304
305
# Function-level exception handling
306
@posthog.scoped(capture_exceptions=True)
307
def risky_function():
308
posthog.identify_context('user789')
309
posthog.capture('function_started')
310
311
# Any exception here is automatically captured
312
raise RuntimeError("Function failed")
313
314
try:
315
risky_function()
316
except RuntimeError:
317
# Exception was already captured by the scoped decorator
318
pass
319
```
320
321
### Web Request Context Pattern
322
323
```python
324
import posthog
325
from flask import Flask, request, g
326
327
app = Flask(__name__)
328
329
@app.before_request
330
def before_request():
331
# Create context for each request
332
g.posthog_context = posthog.new_context()
333
g.posthog_context.__enter__()
334
335
# Set up request-level tracking
336
posthog.tag('request_id', request.headers.get('X-Request-ID', 'unknown'))
337
posthog.tag('user_agent', request.headers.get('User-Agent', 'unknown'))
338
posthog.tag('endpoint', request.endpoint)
339
340
# Identify user if available
341
if hasattr(g, 'current_user') and g.current_user:
342
posthog.identify_context(g.current_user.id)
343
344
@app.after_request
345
def after_request(response):
346
# Tag response information
347
posthog.tag('response_status', response.status_code)
348
349
# Capture request completion
350
posthog.capture('request_completed', {
351
'method': request.method,
352
'endpoint': request.endpoint,
353
'status_code': response.status_code
354
})
355
356
# Clean up context
357
if hasattr(g, 'posthog_context'):
358
g.posthog_context.__exit__(None, None, None)
359
360
return response
361
362
@app.route('/api/users/<user_id>')
363
def get_user(user_id):
364
posthog.capture('user_profile_viewed', {'viewed_user_id': user_id})
365
366
# Process request...
367
return {'user': user_id}
368
369
# Each request automatically gets its own context with request-level tags
370
```
371
372
### Async Context Management
373
374
```python
375
import posthog
376
import asyncio
377
378
async def async_operation():
379
# Contexts work with async operations
380
with posthog.new_context():
381
posthog.identify_context('async_user_123')
382
posthog.tag('operation_type', 'async')
383
384
posthog.capture('async_operation_started')
385
386
# Simulate async work
387
await asyncio.sleep(1)
388
389
posthog.capture('async_operation_completed')
390
391
# Run async operation
392
asyncio.run(async_operation())
393
394
# Async function decorator
395
@posthog.scoped()
396
async def async_task(task_data):
397
posthog.identify_context(task_data['user_id'])
398
posthog.tag('task_id', task_data['id'])
399
400
posthog.capture('async_task_started')
401
402
await process_task(task_data)
403
404
posthog.capture('async_task_completed')
405
406
# Usage
407
await async_task({'id': 'task_123', 'user_id': 'user_456'})
408
```
409
410
## Context Hierarchy and Inheritance
411
412
### Data Inheritance Rules
413
414
```python
415
import posthog
416
417
# Parent context
418
with posthog.new_context():
419
posthog.identify_context('parent_user')
420
posthog.tag('level', 'parent')
421
posthog.tag('shared', 'parent_value')
422
423
# Child context inherits parent data
424
with posthog.new_context():
425
posthog.tag('level', 'child') # Overrides parent tag
426
posthog.tag('child_only', 'child_value') # New tag
427
428
# Event includes:
429
# - user: parent_user (inherited)
430
# - level: child (overridden)
431
# - shared: parent_value (inherited)
432
# - child_only: child_value (new)
433
posthog.capture('child_event')
434
435
# Back to parent - child tags are gone
436
# Event includes:
437
# - user: parent_user
438
# - level: parent
439
# - shared: parent_value
440
posthog.capture('parent_event')
441
```
442
443
### Context Isolation
444
445
```python
446
import posthog
447
448
# Sibling contexts are isolated
449
with posthog.new_context():
450
posthog.identify_context('parent_user')
451
452
# First child
453
with posthog.new_context():
454
posthog.tag('branch', 'first')
455
posthog.capture('first_child_event')
456
457
# Second child (no access to first child's data)
458
with posthog.new_context():
459
posthog.tag('branch', 'second')
460
posthog.capture('second_child_event') # Does NOT include branch=first
461
```
462
463
## Best Practices
464
465
### Context Scope Management
466
467
```python
468
# Good - Clear context boundaries
469
with posthog.new_context():
470
posthog.identify_context('user123')
471
process_user_request()
472
473
# Good - Function-level contexts for discrete operations
474
@posthog.scoped()
475
def handle_api_endpoint():
476
posthog.identify_context(get_current_user())
477
# Process request
478
479
# Avoid - Contexts that span too long
480
# with posthog.new_context(): # Don't do this
481
# for user in all_users: # This context lasts too long
482
# process_user(user)
483
```
484
485
### Tag Naming and Organization
486
487
```python
488
# Good - Consistent, descriptive tag names
489
with posthog.new_context():
490
posthog.tag('request_id', 'req_123')
491
posthog.tag('user_segment', 'premium')
492
posthog.tag('feature_flag_new_ui', True)
493
posthog.tag('ab_test_variant', 'control')
494
495
# Avoid - Inconsistent or unclear tag names
496
with posthog.new_context():
497
posthog.tag('reqId', 'req_123') # Inconsistent naming
498
posthog.tag('flag1', True) # Unclear purpose
499
posthog.tag('test', 'control') # Too generic
500
```
501
502
### Exception Handling Strategy
503
504
```python
505
# Enable exception capture for user-facing operations
506
@posthog.scoped(capture_exceptions=True)
507
def user_request_handler():
508
# Exceptions automatically captured
509
pass
510
511
# Disable for internal/system operations where exceptions are expected
512
@posthog.scoped(capture_exceptions=False)
513
def system_health_check():
514
# Handle exceptions manually
515
try:
516
check_system()
517
except ExpectedError as e:
518
# Don't capture expected errors
519
pass
520
except UnexpectedError as e:
521
posthog.capture_exception(e)
522
```
523
524
### Thread Safety
525
526
PostHog contexts are thread-safe and work correctly in multi-threaded applications:
527
528
```python
529
import posthog
530
import threading
531
532
def worker_thread(worker_id):
533
# Each thread gets its own context
534
with posthog.new_context():
535
posthog.identify_context(f'worker_{worker_id}')
536
posthog.tag('thread_id', threading.current_thread().ident)
537
538
posthog.capture('worker_started')
539
# Do work...
540
posthog.capture('worker_completed')
541
542
# Start multiple worker threads
543
threads = []
544
for i in range(5):
545
t = threading.Thread(target=worker_thread, args=(i,))
546
threads.append(t)
547
t.start()
548
549
for t in threads:
550
t.join()
551
```