0
# Exception and Traceback Handling
1
2
Advanced exception processing including structured traceback extraction, rich formatting, and JSON-serializable exception representations. These tools provide comprehensive support for capturing, formatting, and analyzing exceptions in structured logging scenarios.
3
4
## Capabilities
5
6
### Exception Data Structures
7
8
Data classes for representing structured exception and traceback information.
9
10
```python { .api }
11
class Frame:
12
"""
13
Dataclass representing a single stack frame.
14
15
Contains information about a single frame in a stack trace,
16
including filename, line number, function name, and local variables.
17
"""
18
19
filename: str
20
"""Path to the source file."""
21
22
lineno: int
23
"""Line number in the source file."""
24
25
name: str
26
"""Function or method name."""
27
28
locals: dict[str, str] | None = None
29
"""Local variables in the frame (if captured)."""
30
31
class SyntaxError_:
32
"""
33
Dataclass representing SyntaxError details.
34
35
Specific information for syntax errors, including the problematic
36
line and character offset.
37
"""
38
39
offset: int
40
"""Character offset where syntax error occurred."""
41
42
filename: str
43
"""Filename where syntax error occurred."""
44
45
line: str
46
"""The problematic line of code."""
47
48
lineno: int
49
"""Line number where syntax error occurred."""
50
51
msg: str
52
"""Syntax error message."""
53
54
class Stack:
55
"""
56
Dataclass representing an exception and its stack frames.
57
58
Contains complete information about a single exception including
59
its type, value, notes, and the stack frames leading to it.
60
"""
61
62
exc_type: str
63
"""Exception type name."""
64
65
exc_value: str
66
"""String representation of exception value."""
67
68
exc_notes: list[str] = field(default_factory=list)
69
"""Exception notes (Python 3.11+)."""
70
71
syntax_error: SyntaxError_ | None = None
72
"""SyntaxError details if applicable."""
73
74
is_cause: bool = False
75
"""True if this exception was the cause of another."""
76
77
frames: list[Frame] = field(default_factory=list)
78
"""Stack frames for this exception."""
79
80
is_group: bool = False
81
"""True if this is part of an exception group."""
82
83
exceptions: list[Trace] = field(default_factory=list)
84
"""Nested exceptions for exception groups."""
85
86
class Trace:
87
"""
88
Container for complete stack trace information.
89
90
Top-level container that holds all the stacks for a complete
91
exception trace, including chained exceptions.
92
"""
93
94
stacks: list[Stack]
95
"""List of Stack objects representing the complete trace."""
96
```
97
98
### Exception Processing
99
100
Functions and classes for extracting and processing exception information.
101
102
```python { .api }
103
def extract(
104
exc_type,
105
exc_value,
106
traceback,
107
*,
108
show_locals=False,
109
locals_max_length=10,
110
locals_max_string=80,
111
locals_hide_dunder=True,
112
locals_hide_sunder=False,
113
use_rich=True
114
) -> Trace:
115
"""
116
Extract structured exception information from exc_info tuple.
117
118
Args:
119
exc_type: Exception type
120
exc_value: Exception instance
121
traceback: Traceback object
122
show_locals (bool): Include local variables in frames
123
locals_max_length (int): Maximum number of locals to show per frame
124
locals_max_string (int): Maximum length of local variable strings
125
locals_hide_dunder (bool): Hide dunder variables (__var__)
126
locals_hide_sunder (bool): Hide sunder variables (_var)
127
use_rich (bool): Use rich library for enhanced extraction if available
128
129
Returns:
130
Trace: Structured representation of the exception trace
131
"""
132
133
class ExceptionDictTransformer:
134
"""
135
Transform exceptions into JSON-serializable dictionaries.
136
137
Converts exception information into structured dictionaries
138
that can be easily serialized and analyzed.
139
"""
140
141
def __init__(
142
self,
143
show_locals=True,
144
locals_max_length=10,
145
locals_max_string=80,
146
locals_hide_dunder=True,
147
locals_hide_sunder=False,
148
suppress=(),
149
max_frames=50,
150
use_rich=True
151
):
152
"""
153
Initialize ExceptionDictTransformer.
154
155
Args:
156
show_locals (bool): Include local variables in output
157
locals_max_length (int): Maximum number of locals per frame
158
locals_max_string (int): Maximum length of local variable strings
159
locals_hide_dunder (bool): Hide dunder variables
160
locals_hide_sunder (bool): Hide sunder variables
161
suppress (tuple): Module names to suppress in tracebacks
162
max_frames (int): Maximum number of frames to include
163
use_rich (bool): Use rich library if available
164
"""
165
166
def __call__(self, exc_info) -> list[dict[str, Any]]:
167
"""
168
Transform exception info to structured dictionaries.
169
170
Args:
171
exc_info: Exception info tuple (type, value, traceback)
172
173
Returns:
174
list: List of dictionaries representing the exception trace
175
"""
176
```
177
178
### Utility Functions
179
180
Helper functions for safe string conversion and representation.
181
182
```python { .api }
183
def safe_str(obj) -> str:
184
"""
185
Safely convert object to string representation.
186
187
Handles cases where str() might raise an exception by
188
providing fallback representations.
189
190
Args:
191
obj: Object to convert to string
192
193
Returns:
194
str: String representation of the object
195
"""
196
197
def to_repr(obj, max_length=None, max_string=None, use_rich=True) -> str:
198
"""
199
Safe repr conversion with length limits and rich formatting.
200
201
Args:
202
obj: Object to represent
203
max_length (int, optional): Maximum length of result string
204
max_string (int, optional): Maximum length for string values
205
use_rich (bool): Use rich library for enhanced formatting
206
207
Returns:
208
str: String representation with length limits applied
209
"""
210
```
211
212
## Usage Examples
213
214
### Basic Exception Extraction
215
216
```python
217
import structlog
218
from structlog import tracebacks
219
220
def problematic_function():
221
local_var = "important data"
222
user_data = {"id": 123, "name": "Alice"}
223
raise ValueError("Something went wrong with the data")
224
225
try:
226
problematic_function()
227
except Exception:
228
import sys
229
230
# Extract structured exception information
231
trace = tracebacks.extract(*sys.exc_info(), show_locals=True)
232
233
# Examine the trace structure
234
for stack in trace.stacks:
235
print(f"Exception: {stack.exc_type}")
236
print(f"Message: {stack.exc_value}")
237
238
for frame in stack.frames:
239
print(f" File: {frame.filename}:{frame.lineno}")
240
print(f" Function: {frame.name}")
241
if frame.locals:
242
print(f" Locals: {frame.locals}")
243
```
244
245
### JSON-Serializable Exception Processing
246
247
```python
248
import structlog
249
from structlog import tracebacks, processors
250
import json
251
252
# Configure processor to transform exceptions to dictionaries
253
transformer = tracebacks.ExceptionDictTransformer(
254
show_locals=True,
255
locals_max_length=5,
256
locals_max_string=100
257
)
258
259
def exception_to_dict_processor(logger, method_name, event_dict):
260
"""Custom processor to handle exceptions."""
261
if "exc_info" in event_dict:
262
exc_info = event_dict.pop("exc_info")
263
if exc_info and exc_info != (None, None, None):
264
event_dict["exception_trace"] = transformer(exc_info)
265
return event_dict
266
267
structlog.configure(
268
processors=[
269
processors.TimeStamper(),
270
exception_to_dict_processor,
271
processors.JSONRenderer()
272
],
273
wrapper_class=structlog.BoundLogger,
274
)
275
276
logger = structlog.get_logger()
277
278
def nested_function():
279
data = {"items": [1, 2, 3], "config": {"debug": True}}
280
raise KeyError("Missing required key 'user_id'")
281
282
def calling_function():
283
context = "user_processing"
284
nested_function()
285
286
try:
287
calling_function()
288
except Exception:
289
logger.exception("Processing failed", component="user_service")
290
# Output includes structured exception trace as JSON
291
```
292
293
### Custom Exception Formatting
294
295
```python
296
import structlog
297
from structlog import tracebacks, processors
298
299
def custom_exception_processor(logger, method_name, event_dict):
300
"""Custom processor for detailed exception formatting."""
301
if "exc_info" in event_dict:
302
exc_info = event_dict.pop("exc_info")
303
if exc_info and exc_info != (None, None, None):
304
# Extract detailed trace information
305
trace = tracebacks.extract(
306
*exc_info,
307
show_locals=True,
308
locals_max_length=3,
309
locals_hide_dunder=True
310
)
311
312
# Create custom exception summary
313
exception_summary = {
314
"error_type": trace.stacks[-1].exc_type,
315
"error_message": trace.stacks[-1].exc_value,
316
"stack_depth": sum(len(stack.frames) for stack in trace.stacks),
317
"top_frame": {
318
"file": trace.stacks[-1].frames[-1].filename,
319
"line": trace.stacks[-1].frames[-1].lineno,
320
"function": trace.stacks[-1].frames[-1].name
321
}
322
}
323
324
event_dict["exception_summary"] = exception_summary
325
326
return event_dict
327
328
structlog.configure(
329
processors=[
330
processors.TimeStamper(),
331
custom_exception_processor,
332
processors.JSONRenderer()
333
],
334
wrapper_class=structlog.BoundLogger,
335
)
336
337
logger = structlog.get_logger()
338
339
def process_data(data):
340
if not data:
341
raise ValueError("Data cannot be empty")
342
return len(data)
343
344
try:
345
result = process_data(None)
346
except Exception:
347
logger.exception("Data processing failed", operation="length_calculation")
348
```
349
350
### Exception Chain Analysis
351
352
```python
353
import structlog
354
from structlog import tracebacks
355
356
def analyze_exception_chain():
357
"""Demonstrate exception chaining analysis."""
358
359
def database_operation():
360
raise ConnectionError("Database connection failed")
361
362
def service_operation():
363
try:
364
database_operation()
365
except ConnectionError as e:
366
raise RuntimeError("Service operation failed") from e
367
368
def api_handler():
369
try:
370
service_operation()
371
except RuntimeError as e:
372
raise ValueError("API request failed") from e
373
374
try:
375
api_handler()
376
except Exception:
377
import sys
378
379
# Extract the complete exception chain
380
trace = tracebacks.extract(*sys.exc_info())
381
382
print(f"Exception chain has {len(trace.stacks)} levels:")
383
for i, stack in enumerate(trace.stacks):
384
print(f" Level {i + 1}: {stack.exc_type} - {stack.exc_value}")
385
print(f" Is cause: {stack.is_cause}")
386
print(f" Frames: {len(stack.frames)}")
387
388
analyze_exception_chain()
389
```
390
391
### Rich Exception Formatting Integration
392
393
```python
394
import structlog
395
from structlog import tracebacks, processors, dev
396
397
# Configure with rich exception formatting
398
def rich_exception_processor(logger, method_name, event_dict):
399
"""Processor that uses rich for exception formatting."""
400
if "exc_info" in event_dict:
401
exc_info = event_dict.pop("exc_info")
402
if exc_info and exc_info != (None, None, None):
403
# Use ExceptionDictTransformer with rich enabled
404
transformer = tracebacks.ExceptionDictTransformer(
405
show_locals=True,
406
use_rich=True,
407
locals_max_length=5
408
)
409
410
event_dict["rich_exception"] = transformer(exc_info)
411
412
return event_dict
413
414
structlog.configure(
415
processors=[
416
processors.TimeStamper(),
417
rich_exception_processor,
418
processors.JSONRenderer(indent=2)
419
],
420
wrapper_class=structlog.BoundLogger,
421
)
422
423
logger = structlog.get_logger()
424
425
def complex_operation():
426
data = {"users": [{"id": 1, "name": "Alice"}], "config": {"retry": 3}}
427
items = [1, 2, 3, 4, 5]
428
429
# Simulate complex operation with multiple locals
430
for i, item in enumerate(items):
431
if item == 4:
432
raise IndexError(f"Invalid item at index {i}: {item}")
433
434
try:
435
complex_operation()
436
except Exception:
437
logger.exception("Complex operation failed", operation_id="op_123")
438
```
439
440
### Safe String Conversion
441
442
```python
443
from structlog import tracebacks
444
445
class ProblematicObject:
446
"""Object that raises exception in __str__."""
447
448
def __str__(self):
449
raise RuntimeError("Cannot convert to string")
450
451
def __repr__(self):
452
return "ProblematicObject(broken)"
453
454
# Test safe string conversion
455
obj = ProblematicObject()
456
457
# This would raise an exception:
458
# str(obj)
459
460
# But this handles it safely:
461
safe_string = tracebacks.safe_str(obj)
462
print(f"Safe string: {safe_string}")
463
464
# Test safe repr with limits
465
long_dict = {f"key_{i}": f"value_{i}" * 100 for i in range(10)}
466
limited_repr = tracebacks.to_repr(long_dict, max_length=200)
467
print(f"Limited repr: {limited_repr}")
468
```
469
470
### Exception Suppression
471
472
```python
473
import structlog
474
from structlog import tracebacks
475
476
# Configure transformer to suppress certain modules
477
transformer = tracebacks.ExceptionDictTransformer(
478
show_locals=False,
479
suppress=("logging", "structlog._config", "structlog.processors"),
480
max_frames=10
481
)
482
483
def application_code():
484
"""User application code."""
485
library_code()
486
487
def library_code():
488
"""Simulated library code that should be suppressed."""
489
raise ValueError("Library error")
490
491
try:
492
application_code()
493
except Exception:
494
import sys
495
496
# Transform with suppression
497
result = transformer(sys.exc_info())
498
499
# Examine which frames were included/suppressed
500
for trace_dict in result:
501
print(f"Exception: {trace_dict.get('exc_type')}")
502
print(f"Frames shown: {len(trace_dict.get('frames', []))}")
503
504
for frame in trace_dict.get('frames', []):
505
print(f" {frame['filename']}:{frame['lineno']} in {frame['name']}")
506
```
507
508
### Integration with Structured Logging
509
510
```python
511
import structlog
512
from structlog import tracebacks, processors
513
514
class StructuredExceptionProcessor:
515
"""Processor that creates structured exception data."""
516
517
def __init__(self):
518
self.transformer = tracebacks.ExceptionDictTransformer(
519
show_locals=True,
520
locals_max_length=3,
521
max_frames=20
522
)
523
524
def __call__(self, logger, method_name, event_dict):
525
if "exc_info" in event_dict:
526
exc_info = event_dict.pop("exc_info")
527
528
if exc_info and exc_info != (None, None, None):
529
# Create structured exception data
530
trace_data = self.transformer(exc_info)
531
532
# Extract key information
533
if trace_data:
534
last_exception = trace_data[-1]
535
event_dict.update({
536
"error": {
537
"type": last_exception["exc_type"],
538
"message": last_exception["exc_value"],
539
"traceback": trace_data
540
}
541
})
542
543
return event_dict
544
545
structlog.configure(
546
processors=[
547
processors.TimeStamper(),
548
StructuredExceptionProcessor(),
549
processors.JSONRenderer()
550
],
551
wrapper_class=structlog.BoundLogger,
552
)
553
554
logger = structlog.get_logger()
555
556
def business_logic(user_data):
557
if not user_data.get("email"):
558
raise ValueError("Email is required for user registration")
559
560
try:
561
business_logic({"name": "Alice"})
562
except Exception:
563
logger.exception(
564
"User registration failed",
565
user_name="Alice",
566
registration_step="validation"
567
)
568
```