0
# Logging Integration
1
2
Log filters that automatically attach correlation IDs to log records, enabling seamless log correlation without manual ID injection. These filters integrate with Python's standard logging framework to automatically include tracking identifiers in all log output.
3
4
## Capabilities
5
6
### Correlation ID Filter
7
8
Logging filter that automatically attaches the current correlation ID to log records, making it available for formatting and output.
9
10
```python { .api }
11
class CorrelationIdFilter(logging.Filter):
12
"""
13
Logging filter to attach correlation IDs to log records.
14
15
Parameters:
16
- name: Filter name (default: '')
17
- uuid_length: Optional length to trim UUID to (default: None)
18
- default_value: Default value when no correlation ID exists (default: None)
19
"""
20
def __init__(
21
self,
22
name: str = '',
23
uuid_length: Optional[int] = None,
24
default_value: Optional[str] = None
25
): ...
26
27
def filter(self, record: LogRecord) -> bool:
28
"""
29
Attach a correlation ID to the log record.
30
31
Adds 'correlation_id' attribute to the log record containing:
32
- Current correlation ID from context variable
33
- Trimmed to uuid_length if specified
34
- default_value if no correlation ID exists
35
36
Parameters:
37
- record: Log record to modify
38
39
Returns:
40
- bool: Always True (never filters out records)
41
"""
42
```
43
44
The filter adds a `correlation_id` attribute to every log record, which can be used in log formatters:
45
46
```python
47
# In log format string
48
'%(levelname)s [%(correlation_id)s] %(name)s %(message)s'
49
50
# Results in output like:
51
# INFO [a1b2c3d4] myapp.views Processing user request
52
# ERROR [a1b2c3d4] myapp.models Database connection failed
53
```
54
55
### Celery Tracing IDs Filter
56
57
Logging filter that attaches both parent and current Celery task IDs to log records, enabling hierarchical task tracing.
58
59
```python { .api }
60
class CeleryTracingIdsFilter(logging.Filter):
61
"""
62
Logging filter to attach Celery tracing IDs to log records.
63
64
Parameters:
65
- name: Filter name (default: '')
66
- uuid_length: Optional length to trim UUIDs to (default: None)
67
- default_value: Default value when no IDs exist (default: None)
68
"""
69
def __init__(
70
self,
71
name: str = '',
72
uuid_length: Optional[int] = None,
73
default_value: Optional[str] = None
74
): ...
75
76
def filter(self, record: LogRecord) -> bool:
77
"""
78
Append parent and current ID to the log record.
79
80
Adds two attributes to the log record:
81
- 'celery_parent_id': ID of the process that spawned current task
82
- 'celery_current_id': Unique ID for the current task process
83
84
Both IDs are trimmed to uuid_length if specified, and use
85
default_value when the respective context variable is None.
86
87
Parameters:
88
- record: Log record to modify
89
90
Returns:
91
- bool: Always True (never filters out records)
92
"""
93
```
94
95
This filter enables detailed task tracing in log output:
96
97
```python
98
# In log format string
99
'%(levelname)s [%(celery_current_id)s|%(celery_parent_id)s] %(name)s %(message)s'
100
101
# Results in output like:
102
# INFO [task-abc123|req-xyz789] tasks.email Sending notification email
103
# ERROR [task-def456|task-abc123] tasks.data Task failed processing batch
104
```
105
106
## Usage Examples
107
108
### Basic Logging Setup
109
110
```python
111
import logging
112
from asgi_correlation_id import CorrelationIdFilter
113
114
# Configure logging with correlation ID
115
logging.basicConfig(
116
level=logging.INFO,
117
format='%(asctime)s %(levelname)s [%(correlation_id)s] %(name)s %(message)s'
118
)
119
120
# Add filter to root logger
121
correlation_filter = CorrelationIdFilter()
122
logging.getLogger().addFilter(correlation_filter)
123
124
# Use logging normally - correlation ID is automatically included
125
logger = logging.getLogger(__name__)
126
logger.info("This will include the correlation ID")
127
```
128
129
### Advanced Logging Configuration
130
131
```python
132
LOGGING = {
133
'version': 1,
134
'disable_existing_loggers': False,
135
'formatters': {
136
'detailed': {
137
'format': '%(asctime)s %(levelname)s [%(correlation_id)s] %(name)s:%(lineno)d %(message)s',
138
'datefmt': '%Y-%m-%d %H:%M:%S'
139
},
140
'simple': {
141
'format': '%(levelname)s [%(correlation_id)s] %(message)s'
142
},
143
},
144
'filters': {
145
'correlation_id': {
146
'()': 'asgi_correlation_id.CorrelationIdFilter',
147
'uuid_length': 8, # Trim to 8 characters
148
'default_value': 'no-id'
149
},
150
},
151
'handlers': {
152
'console': {
153
'class': 'logging.StreamHandler',
154
'formatter': 'detailed',
155
'filters': ['correlation_id'],
156
},
157
'file': {
158
'class': 'logging.FileHandler',
159
'filename': 'app.log',
160
'formatter': 'simple',
161
'filters': ['correlation_id'],
162
},
163
},
164
'loggers': {
165
'myapp': {
166
'handlers': ['console', 'file'],
167
'level': 'INFO',
168
'propagate': False,
169
},
170
},
171
}
172
173
import logging.config
174
logging.config.dictConfig(LOGGING)
175
```
176
177
### Celery Logging Setup
178
179
```python
180
import logging
181
from asgi_correlation_id import CeleryTracingIdsFilter
182
183
# Configure Celery task logging
184
celery_filter = CeleryTracingIdsFilter(uuid_length=8, default_value='none')
185
186
# Add to Celery logger
187
celery_logger = logging.getLogger('celery')
188
celery_logger.addFilter(celery_filter)
189
190
# Configure format to include both IDs
191
logging.basicConfig(
192
format='%(asctime)s [%(celery_current_id)s|%(celery_parent_id)s] %(name)s %(message)s'
193
)
194
```
195
196
### Combined HTTP and Celery Logging
197
198
```python
199
import logging
200
from asgi_correlation_id import CorrelationIdFilter, CeleryTracingIdsFilter
201
202
# Create filters
203
http_filter = CorrelationIdFilter(uuid_length=8, default_value='no-req')
204
celery_filter = CeleryTracingIdsFilter(uuid_length=8, default_value='no-task')
205
206
# Configure different loggers for different contexts
207
http_logger = logging.getLogger('webapp')
208
http_logger.addFilter(http_filter)
209
210
celery_logger = logging.getLogger('tasks')
211
celery_logger.addFilter(celery_filter)
212
213
# Set up formatters for each context
214
logging.basicConfig(
215
format='%(asctime)s %(name)s %(message)s'
216
)
217
218
# HTTP logs will show: 2023-01-01 12:00:00 webapp [a1b2c3d4] Processing request
219
# Celery logs will show: 2023-01-01 12:00:00 tasks [task-123|req-456] Running background job
220
```
221
222
## Filter Behavior
223
224
### UUID Length Trimming
225
226
When `uuid_length` is specified:
227
- UUIDs longer than the specified length are truncated
228
- UUIDs shorter than the specified length remain unchanged
229
- `None` values are handled gracefully
230
231
```python
232
# With uuid_length=8
233
filter = CorrelationIdFilter(uuid_length=8)
234
235
# "a1b2c3d4-e5f6-7890-abcd-ef1234567890" becomes "a1b2c3d4"
236
# "short" remains "short"
237
# None remains None (or uses default_value)
238
```
239
240
### Default Values
241
242
The `default_value` parameter provides fallback text when no correlation ID is available:
243
244
```python
245
filter = CorrelationIdFilter(default_value='no-correlation-id')
246
247
# When correlation_id.get() returns None:
248
# Log shows: INFO [no-correlation-id] myapp Processing request
249
```
250
251
## Types
252
253
```python { .api }
254
from logging import Filter, LogRecord
255
from typing import TYPE_CHECKING, Optional
256
257
if TYPE_CHECKING:
258
from logging import LogRecord
259
260
# Internal utility function
261
def _trim_string(string: Optional[str], string_length: Optional[int]) -> Optional[str]:
262
"""Trim string to specified length if provided."""
263
```