0
# Logging Integration
1
2
Integration with Python's logging system to automatically include trace context in log records, enabling correlation between logs and traces for better observability and debugging.
3
4
## Capabilities
5
6
### Log Context Extraction
7
8
Extract current OpenCensus trace context for inclusion in log records and correlation with distributed traces.
9
10
```python { .api }
11
def get_log_attrs():
12
"""
13
Get logging attributes from OpenCensus context.
14
15
Returns:
16
LogAttrs: Named tuple with trace_id, span_id, and sampling_decision
17
or ATTR_DEFAULTS if no context available
18
"""
19
20
LogAttrs = namedtuple('LogAttrs', ['trace_id', 'span_id', 'sampling_decision'])
21
"""
22
Named tuple containing trace context for logging.
23
24
Fields:
25
- trace_id: str, 32-character hex trace ID or default
26
- span_id: str, 16-character hex span ID or default
27
- sampling_decision: bool, whether trace is sampled
28
"""
29
30
ATTR_DEFAULTS = LogAttrs("00000000000000000000000000000000", "0000000000000000", False)
31
"""Default log attributes when no trace context is available."""
32
```
33
34
### Enhanced Logger Adapter
35
36
Logging adapter that automatically adds OpenCensus trace context to all log records for correlation and filtering.
37
38
```python { .api }
39
class TraceLoggingAdapter:
40
"""
41
LoggerAdapter that adds OpenCensus context to log records.
42
43
Inherits from logging.LoggerAdapter and automatically includes
44
trace context in all log messages.
45
46
Parameters:
47
- logger: logging.Logger, underlying logger instance
48
- extra: dict, additional context to include in logs
49
"""
50
def __init__(self, logger, extra=None): ...
51
52
def process(self, msg, kwargs):
53
"""
54
Process log message and add trace context.
55
56
Automatically adds current trace context to the log record's
57
extra data for correlation with distributed traces.
58
59
Parameters:
60
- msg: str, log message
61
- kwargs: dict, keyword arguments for logging call
62
63
Returns:
64
tuple: (processed_message, updated_kwargs)
65
"""
66
```
67
68
### Context-Aware Logger
69
70
Custom logger class that automatically includes OpenCensus trace context in log record creation.
71
72
```python { .api }
73
class TraceLogger:
74
"""
75
Logger subclass that includes OpenCensus context in records.
76
77
Inherits from logging.getLoggerClass() and automatically adds
78
trace context to all log records at creation time.
79
"""
80
def makeRecord(self, *args, **kwargs):
81
"""
82
Create log record with OpenCensus trace context.
83
84
Extends standard makeRecord to automatically include current
85
trace context in the log record's attributes.
86
87
Parameters:
88
- *args: positional arguments for standard makeRecord
89
- **kwargs: keyword arguments for standard makeRecord
90
91
Returns:
92
LogRecord: Enhanced log record with trace context
93
"""
94
```
95
96
### Log Context Constants
97
98
Standard field names and default values for trace context in log records.
99
100
```python { .api }
101
TRACE_ID_KEY = 'traceId'
102
"""str: Log record field name for trace ID"""
103
104
SPAN_ID_KEY = 'spanId'
105
"""str: Log record field name for span ID"""
106
107
SAMPLING_DECISION_KEY = 'traceSampled'
108
"""str: Log record field name for sampling decision"""
109
```
110
111
## Usage Examples
112
113
### Basic Trace Logging Adapter
114
115
```python
116
import logging
117
from opencensus.log import TraceLoggingAdapter, get_log_attrs
118
from opencensus.trace.tracer import Tracer
119
120
# Set up standard logger
121
logger = logging.getLogger(__name__)
122
logger.setLevel(logging.INFO)
123
124
# Create handler with formatter that includes trace fields
125
handler = logging.StreamHandler()
126
formatter = logging.Formatter(
127
'%(asctime)s - %(name)s - %(levelname)s - '
128
'TraceID=%(traceId)s SpanID=%(spanId)s - %(message)s'
129
)
130
handler.setFormatter(formatter)
131
logger.addHandler(handler)
132
133
# Wrap with trace logging adapter
134
trace_logger = TraceLoggingAdapter(logger)
135
136
# Use within traced operations
137
tracer = Tracer()
138
139
with tracer.span('process_order') as span:
140
span.add_attribute('order_id', '12345')
141
142
# Log messages automatically include trace context
143
trace_logger.info('Starting order processing')
144
145
try:
146
# Process order logic
147
process_order_data()
148
trace_logger.info('Order processed successfully')
149
150
except Exception as e:
151
trace_logger.error('Order processing failed', exc_info=True)
152
raise
153
154
# Log output will include trace ID and span ID:
155
# 2024-01-15 10:30:15 - __main__ - INFO - TraceID=abc123... SpanID=def456... - Starting order processing
156
```
157
158
### Custom Logger Integration
159
160
```python
161
import logging
162
from opencensus.log import TraceLogger, get_log_attrs
163
164
# Set TraceLogger as default logger class
165
logging.setLoggerClass(TraceLogger)
166
167
# Create logger - will automatically include trace context
168
logger = logging.getLogger(__name__)
169
170
# Configure with trace-aware formatting
171
handler = logging.StreamHandler()
172
formatter = logging.Formatter(
173
'%(levelname)s [%(traceId)s:%(spanId)s] %(name)s: %(message)s'
174
)
175
handler.setFormatter(formatter)
176
logger.addHandler(handler)
177
logger.setLevel(logging.DEBUG)
178
179
from opencensus.trace.tracer import Tracer
180
181
def process_user_request(user_id):
182
tracer = Tracer()
183
184
with tracer.span('process_user_request') as span:
185
span.add_attribute('user_id', user_id)
186
187
# All log calls automatically include trace context
188
logger.info(f'Processing request for user {user_id}')
189
190
with tracer.span('validate_user') as child_span:
191
logger.debug('Validating user credentials')
192
validate_user(user_id)
193
logger.debug('User validation completed')
194
195
with tracer.span('load_user_data') as child_span:
196
logger.debug('Loading user data from database')
197
user_data = load_user_data(user_id)
198
logger.info(f'Loaded {len(user_data)} records for user')
199
200
logger.info('Request processing completed')
201
202
# Usage
203
process_user_request('user123')
204
```
205
206
### Manual Context Extraction
207
208
```python
209
import logging
210
from opencensus.log import get_log_attrs, TRACE_ID_KEY, SPAN_ID_KEY, SAMPLING_DECISION_KEY
211
212
# Standard logger setup
213
logger = logging.getLogger(__name__)
214
215
def custom_log_with_trace(level, message, **kwargs):
216
"""Custom logging function that manually adds trace context."""
217
218
# Get current trace context
219
trace_attrs = get_log_attrs()
220
221
# Add trace context to extra data
222
extra = kwargs.get('extra', {})
223
extra.update({
224
TRACE_ID_KEY: trace_attrs.trace_id,
225
SPAN_ID_KEY: trace_attrs.span_id,
226
SAMPLING_DECISION_KEY: trace_attrs.sampling_decision
227
})
228
229
# Log with enhanced context
230
logger.log(level, message, extra=extra, **kwargs)
231
232
# Usage within traced context
233
from opencensus.trace.tracer import Tracer
234
235
tracer = Tracer()
236
with tracer.span('database_operation') as span:
237
custom_log_with_trace(logging.INFO, 'Executing database query')
238
239
# Check if we have active trace context
240
attrs = get_log_attrs()
241
if attrs.trace_id != "00000000000000000000000000000000":
242
logger.info('Active trace detected', extra={
243
TRACE_ID_KEY: attrs.trace_id,
244
SPAN_ID_KEY: attrs.span_id
245
})
246
else:
247
logger.info('No active trace context')
248
```
249
250
### Structured Logging with Trace Context
251
252
```python
253
import logging
254
import json
255
from opencensus.log import TraceLoggingAdapter, get_log_attrs
256
257
class StructuredTraceFormatter(logging.Formatter):
258
"""JSON formatter that includes OpenCensus trace context."""
259
260
def format(self, record):
261
# Get trace context
262
trace_attrs = get_log_attrs()
263
264
# Build structured log entry
265
log_entry = {
266
'timestamp': self.formatTime(record),
267
'level': record.levelname,
268
'logger': record.name,
269
'message': record.getMessage(),
270
'trace': {
271
'trace_id': trace_attrs.trace_id,
272
'span_id': trace_attrs.span_id,
273
'sampled': trace_attrs.sampling_decision
274
}
275
}
276
277
# Add exception info if present
278
if record.exc_info:
279
log_entry['exception'] = self.formatException(record.exc_info)
280
281
# Add any extra fields
282
for key, value in record.__dict__.items():
283
if key not in ['name', 'levelname', 'levelno', 'pathname', 'filename',
284
'module', 'lineno', 'funcName', 'created', 'msecs',
285
'relativeCreated', 'thread', 'threadName', 'processName',
286
'process', 'getMessage', 'exc_info', 'exc_text', 'stack_info',
287
'args', 'msg']:
288
log_entry[key] = value
289
290
return json.dumps(log_entry)
291
292
# Setup structured logging with trace context
293
logger = logging.getLogger(__name__)
294
handler = logging.StreamHandler()
295
handler.setFormatter(StructuredTraceFormatter())
296
logger.addHandler(handler)
297
logger.setLevel(logging.INFO)
298
299
# Use with trace adapter
300
trace_logger = TraceLoggingAdapter(logger)
301
302
from opencensus.trace.tracer import Tracer
303
304
tracer = Tracer()
305
with tracer.span('api_request') as span:
306
span.add_attribute('endpoint', '/api/users')
307
span.add_attribute('method', 'GET')
308
309
# Structured log with automatic trace context
310
trace_logger.info('API request received', extra={
311
'endpoint': '/api/users',
312
'method': 'GET',
313
'user_agent': 'OpenCensus/1.0'
314
})
315
316
# Output will be JSON with embedded trace context:
317
# {"timestamp": "2024-01-15 10:30:15,123", "level": "INFO",
318
# "logger": "__main__", "message": "API request received",
319
# "trace": {"trace_id": "abc123...", "span_id": "def456...", "sampled": true},
320
# "endpoint": "/api/users", "method": "GET", "user_agent": "OpenCensus/1.0"}
321
```
322
323
### Integration with Application Frameworks
324
325
```python
326
import logging
327
from opencensus.log import TraceLoggingAdapter
328
from opencensus.trace.tracer import Tracer
329
from opencensus.trace.propagation import trace_context_http_header_format
330
331
# Flask example
332
from flask import Flask, request, g
333
334
app = Flask(__name__)
335
336
# Setup trace-aware logging
337
logging.basicConfig(level=logging.INFO)
338
logger = logging.getLogger(__name__)
339
trace_logger = TraceLoggingAdapter(logger)
340
341
@app.before_request
342
def before_request():
343
# Extract trace context from incoming request
344
propagator = trace_context_http_header_format.TraceContextPropagator()
345
span_context = propagator.from_headers(request.headers)
346
347
# Create tracer with incoming context
348
g.tracer = Tracer(span_context=span_context)
349
g.span = g.tracer.span(f'{request.method} {request.path}')
350
g.span.start()
351
352
# Log request with trace context
353
trace_logger.info(f'Incoming request: {request.method} {request.path}',
354
extra={'method': request.method, 'path': request.path})
355
356
@app.after_request
357
def after_request(response):
358
# Log response with trace context
359
trace_logger.info(f'Response status: {response.status_code}',
360
extra={'status_code': response.status_code})
361
362
if hasattr(g, 'span'):
363
g.span.add_attribute('http.status_code', response.status_code)
364
g.span.finish()
365
366
return response
367
368
@app.route('/api/users/<user_id>')
369
def get_user(user_id):
370
# All log messages within request automatically have trace context
371
trace_logger.info(f'Fetching user data for {user_id}')
372
373
with g.tracer.span('database_query') as span:
374
span.add_attribute('user_id', user_id)
375
trace_logger.debug('Executing database query')
376
377
# Simulate database operation
378
user_data = {'id': user_id, 'name': 'John Doe'}
379
380
trace_logger.info('User data retrieved successfully')
381
382
return user_data
383
384
if __name__ == '__main__':
385
app.run(debug=True)
386
```