# Python Stack Traces **Enhanced error formatting and stack traces for Python scripts in RevitDevTool** RevitDevTool provides a Python helper module (`trace.py`) that captures and displays Python call stack information directly in the trace log panel, making debugging Python scripts much easier. --- ## What It Does ### 📸 Full Call Stack Traces The `trace()` helper captures: - File names and paths - Function/method names - Complete call stack from your code - Works with IronPython 2.7, IronPython 3.4, and CPython 3.x ### 🔍 Pretty Formatting Stack traces appear in readable format: ``` Processing element Traceback (Last call first): File "C:\Scripts\wall_analysis.py", in process_walls File "C:\Scripts\wall_analysis.py", in analyze_project File "C:\Scripts\wall_analysis.py", in ``` ### 🎨 Color-Coded Output - Messages appear with appropriate color based on keywords - Stack frames in readable formatting - Respects depth settings (configurable in UI) --- ## How to Use ### 1. Copy the Helper Module Copy `trace.py` from RevitDevTool source to your script folder: **Source:** `source/RevitDevTool/Logging/Python/trace.py` ### 2. Import in Your Script ```python # If trace.py is in the same folder as your script: from trace import trace # If you've installed it as a package: from revit_dev_tool.trace import trace ``` ### 3. Use Instead of print() ```python from trace import trace def process_walls(doc): trace("Starting wall analysis") # Will show call stack walls = FilteredElementCollector(doc)\ .OfCategory(BuiltInCategory.OST_Walls)\ .WhereElementIsNotElementType() trace(f"Found {walls.GetElementCount()} walls") for wall in walls: height = get_wall_height(wall) trace(f"Wall {wall.Id}: {height} ft") def get_wall_height(wall): param = wall.get_Parameter(BuiltInParameter.WALL_USER_HEIGHT_PARAM) if param: return param.AsDouble() return 0.0 # Run process_walls(__revit__.ActiveUIDocument.Document) ``` **Output in RevitDevTool:** ``` Starting wall analysis Traceback (Last call first): File "wall_script.py", in process_walls File "wall_script.py", in Found 156 walls Traceback (Last call first): File "wall_script.py", in process_walls File "wall_script.py", in Wall 12345: 10.5 ft Traceback (Last call first): File "wall_script.py", in process_walls File "wall_script.py", in ``` --- ## Complete Example ### pyRevit Script with trace.py ```python """Wall Height Analyzer Analyzes wall heights with detailed tracing """ __title__ = 'Wall Heights' __author__ = 'Your Name' from pyrevit import revit, DB, forms # Import the trace helper (copy trace.py to your scripts folder first) from trace import trace def analyze_walls(): trace("[INFO] Starting wall analysis") doc = revit.doc walls = DB.FilteredElementCollector(doc, doc.ActiveView.Id)\ .OfCategory(DB.BuiltInCategory.OST_Walls)\ .WhereElementIsNotElementType() wall_count = walls.GetElementCount() trace(f"[INFO] Found {wall_count} walls in current view") results = [] for wall in walls: try: height = get_wall_height(wall) results.append((wall.Id, height)) trace(f"Wall {wall.Id}: {height:.2f} ft") except Exception as ex: trace(f"[ERROR] Failed to process wall {wall.Id}: {ex}") trace(f"[INFO] Analysis complete: {len(results)} walls processed") return results def get_wall_height(wall): """Get wall height parameter value""" trace(f"[DEBUG] Getting height for wall {wall.Id}") param = wall.get_Parameter(DB.BuiltInParameter.WALL_USER_HEIGHT_PARAM) if not param: trace(f"[WARN] Wall {wall.Id} has no height parameter") return 0.0 return param.AsDouble() # Run analysis if __name__ == '__main__': results = analyze_walls() forms.alert(f"Analyzed {len(results)} walls", title="Complete") ``` **Each `trace()` call will show:** 1. Your message (with color based on [INFO]/[ERROR]/[WARN] keywords) 2. Call stack showing which function called it 3. File paths and line numbers (when available) --- ## How trace.py Works Internally The helper module: 1. **Captures Stack** - Uses Python's `traceback.extract_stack()` to get call information 2. **Formats Frames** - Converts stack frames to readable format 3. **Calls PyTrace** - Sends message + stack string to `PyTrace.Write()` in RevitDevTool 4. **Respects Settings** - PyTrace filters based on your Stack Trace Depth setting ### Compatibility The helper works with: - ✅ **IronPython 2.7** (pyRevit default) - ✅ **IronPython 3.4** (pyRevit 3) - ✅ **CPython 3.x** (PythonNet3 in RevitDevTool) ### Fallback Behavior If RevitDevTool is not installed, `trace()` falls back to `System.Diagnostics.Trace` (without stack traces). --- ## Advanced Usage ### Conditional Tracing ```python from trace import trace DEBUG = True # Set to False in production def process_element(element): if DEBUG: trace(f"[DEBUG] Processing element {element.Id}") # Your processing logic here result = do_work(element) if DEBUG: trace(f"[DEBUG] Result: {result}") return result ``` ### Tracing with Context Managers ```python from trace import trace class TracedOperation: def __init__(self, operation_name): self.name = operation_name def __enter__(self): trace(f"[INFO] Starting: {self.name}") return self def __exit__(self, exc_type, exc_val, exc_tb): if exc_type: trace(f"[ERROR] Failed: {self.name} - {exc_val}") else: trace(f"[INFO] Completed: {self.name}") return False # Usage with TracedOperation("Wall Analysis"): analyze_walls(doc) ``` ### Tracing in Class Methods ```python from trace import trace class WallAnalyzer: def __init__(self, doc): self.doc = doc trace("[INFO] WallAnalyzer initialized") def analyze(self): trace("[INFO] Starting analysis") walls = self._collect_walls() results = self._process_walls(walls) trace(f"[INFO] Analysis complete: {len(results)} results") return results def _collect_walls(self): trace("[DEBUG] Collecting walls") # Collection logic return walls def _process_walls(self, walls): trace(f"[DEBUG] Processing {len(walls)} walls") # Processing logic return results ``` --- ## Settings Configuration Stack trace behavior can be configured in RevitDevTool Settings: ### Include Stack Trace - **Enabled** (default): Shows call stack with each `trace()` call - **Disabled**: Only shows the message (like regular print) ### Stack Trace Depth - **Default**: 10 frames - **Range**: 1-50 frames - Controls how many stack frames are displayed **Access**: RevitDevTool Panel → ⚙️ Settings → Logging Tab --- ## Common Scenarios ### 1. Debugging Nested Functions ```python from trace import trace def level_3(): trace("[DEBUG] In level_3") return "result" def level_2(): trace("[DEBUG] In level_2") return level_3() def level_1(): trace("[DEBUG] In level_1") return level_2() # Call result = level_1() ``` **Output shows complete call chain:** ``` [DEBUG] In level_1 Traceback (Last call first): File "script.py", in level_1 File "script.py", in [DEBUG] In level_2 Traceback (Last call first): File "script.py", in level_2 File "script.py", in level_1 File "script.py", in [DEBUG] In level_3 Traceback (Last call first): File "script.py", in level_3 File "script.py", in level_2 File "script.py", in level_1 File "script.py", in ``` ### 2. Tracing Element Processing ```python from trace import trace def process_elements(elements): trace(f"[INFO] Processing {len(elements)} elements") for i, element in enumerate(elements): if i % 100 == 0: trace(f"[INFO] Progress: {i}/{len(elements)}") try: do_something(element) except Exception as ex: trace(f"[ERROR] Element {element.Id} failed: {ex}") # Stack trace shows which element caused the error ``` ### 3. Tracing API Calls ```python from trace import trace def get_parameter_value(element, param_name): trace(f"[DEBUG] Getting '{param_name}' from element {element.Id}") param = element.LookupParameter(param_name) if not param: trace(f"[WARN] Parameter '{param_name}' not found") return None try: value = param.AsString() or param.AsValueString() trace(f"[DEBUG] Value: {value}") return value except Exception as ex: trace(f"[ERROR] Failed to get value: {ex}") return None ``` --- ## Comparison: trace() vs print() vs Trace.WriteLine() | Method | Stack Trace | Color Coding | Settings Aware | Compatibility | |--------|-------------|--------------|----------------|---------------| | `trace()` | ✅ Yes | ✅ Yes | ✅ Yes | RevitDevTool only | | `print()` | ❌ No | ✅ Yes | ❌ No | All environments | | `Trace.WriteLine()` | ❌ No | ✅ Yes | ❌ No | .NET environments | **Recommendation**: Use `trace()` in RevitDevTool for best debugging experience, fall back to `print()` for compatibility. --- ## Installation Notes ### Where to Get trace.py The helper module is included in RevitDevTool source: **Path**: `source/RevitDevTool/Logging/Python/trace.py` **Installation Options:** 1. **Copy to Script Folder** (Simplest) ``` YourScripts/ ├── trace.py # Copy here └── your_script.py # Your script ``` 2. **Install as Package** (For multiple scripts) ``` C:\RevitScripts\ ├── revit_dev_tool/ │ ├── __init__.py │ └── trace.py └── my_scripts/ └── script_1.py └── script_2.py ``` Then in your scripts: ```python from revit_dev_tool.trace import trace ``` 3. **Copy to your script folder** - Copy `trace.py` to the same folder as your scripts - Import directly: `from trace import trace` --- ## pyRevit Integration ### Using trace() with pyRevit The `trace()` function works seamlessly with pyRevit scripts: ```python """Element Counter with Tracing """ __title__ = 'Count Elements' from pyrevit import revit, DB from trace import trace # Assumes trace.py is in same folder trace("[INFO] Starting element counter") doc = revit.doc categories = [ DB.BuiltInCategory.OST_Walls, DB.BuiltInCategory.OST_Doors, DB.BuiltInCategory.OST_Windows ] for cat in categories: collector = DB.FilteredElementCollector(doc)\ .OfCategory(cat)\ .WhereElementIsNotElementType() count = collector.GetElementCount() trace(f"[INFO] {cat}: {count} elements") trace("[INFO] Counting complete") ``` ### pyRevit Output Window When using `trace()`: - Messages appear in both RevitDevTool panel AND pyRevit output window - Stack traces only appear in RevitDevTool (pyRevit shows just the message) - Best of both worlds for debugging --- ## Comparison with Other Tools | Feature | RevitDevTool trace() | pyRevit output.print() | Dynamo print() | |---------|---------------------|------------------------|----------------| | Call stack traces | ✅ Yes | ❌ No | ❌ No | | Color coding | ✅ Auto | ⚠️ Manual | ❌ No | | Configurable depth | ✅ Yes | N/A | N/A | | File export | ✅ Yes | ❌ No | ❌ No | | Geometry viz | ✅ Yes | ❌ No | ⚠️ Limited | | CPython support | ✅ Yes | ❌ No | ❌ No | --- ## Best Practices ### 1. Use Descriptive Messages ```python # ❌ Bad trace("Processing") trace("Done") # ✅ Good trace("[INFO] Processing walls in current view") trace("[INFO] Wall processing completed successfully") ``` ### 2. Include Context in Error Messages ```python # ✅ Good - Shows which element failed for element in elements: try: process_element(element) except Exception as ex: trace(f"[ERROR] Element {element.Id} ({element.Category.Name}): {ex}") ``` ### 3. Use Log Levels Consistently ```python trace("[DEBUG] Detailed diagnostic info") trace("[INFO] Important milestones") trace("[WARN] Potential issues") trace("[ERROR] Actual errors") ``` ### 4. Limit Tracing in Loops ```python # ✅ Good - Log every 100 items for i, element in enumerate(elements): if i % 100 == 0: trace(f"[INFO] Progress: {i}/{len(elements)}") process_element(element) ``` --- ## Troubleshooting ### "Cannot import trace" **Problem**: `ImportError: No module named trace` **Solutions**: 1. Verify trace.py is in the same folder as your script 2. Check the import statement matches your folder structure 3. Add the folder to sys.path before importing ### Stack Traces Not Showing **Problem**: trace() works but no stack traces appear **Solutions**: 1. Open RevitDevTool trace panel 2. Check Settings → Logging → "Include Stack Trace" is enabled 3. Verify you're using the trace() function, not print() ### Stack Traces Too Deep **Problem**: Too many stack frames clutter the output **Solution**: Reduce Stack Trace Depth in Settings → Logging → Stack Trace Depth (default: 10) ### Performance Concerns **Problem**: Excessive tracing slows down script **Solutions**: 1. Reduce trace frequency in loops 2. Use conditional tracing (if DEBUG:) 3. Disable stack traces for production runs --- ## Related Documentation - [Logging Overview](Logging-Overview.md) - General logging features - [Color Keywords](Logging-ColorKeywords.md) - Automatic log level detection - [Python Runtime](https://github.com/trgiangv/RevitDevTool/wiki/CodeExecute-PythonExecution) - Python integration details --- ## Source Code **Module**: [`trace.py`](https://github.com/trgiangv/RevitDevTool/blob/master/source/RevitDevTool/Logging/Python/trace.py) **Bridge**: [`PyTrace.cs`](https://github.com/trgiangv/RevitDevTool/blob/master/source/RevitDevTool/Logging/Python/PyTrace.cs) --- ## Example Scripts Demo scripts using trace(): - [logging_format_script.py](https://github.com/trgiangv/RevitDevTool/blob/master/source/RevitDevTool.PythonDemo/commands/logging_format_script.py) - Logging examples - [data_analysis_script.py](https://github.com/trgiangv/RevitDevTool/blob/master/source/RevitDevTool.PythonDemo/commands/data_analysis_script.py) - Real-world usage