Skip to content

Logging PythonStackTraces

Truong Giang Vu edited this page Feb 17, 2026 · 4 revisions

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 <module>

🎨 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

# 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()

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 <module>

Found 156 walls
Traceback (Last call first):
  File "wall_script.py", in process_walls
  File "wall_script.py", in <module>

Wall 12345: 10.5 ft
Traceback (Last call first):
  File "wall_script.py", in process_walls
  File "wall_script.py", in <module>

Complete Example

pyRevit Script with trace.py

"""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

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

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

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

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 <module>

[DEBUG] In level_2  
Traceback (Last call first):
  File "script.py", in level_2
  File "script.py", in level_1
  File "script.py", in <module>

[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 <module>

2. Tracing Element Processing

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

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:

    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:

"""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

# ❌ 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

# βœ… 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

trace("[DEBUG] Detailed diagnostic info")
trace("[INFO] Important milestones")
trace("[WARN] Potential issues")
trace("[ERROR] Actual errors")

4. Limit Tracing in Loops

# βœ… 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


Source Code

Module: trace.py
Bridge: PyTrace.cs


Example Scripts

Demo scripts using trace():

Clone this wiki locally