Class-Level Decorator Patterns
Problem: You need to trace entire classes systematically, apply tracing to all methods automatically, or create reusable tracing patterns for object-oriented code.
Solution: Use class-level decorators and metaclasses to instrument entire classes with structured, consistent tracing.
Basic Class Decoration
When to Use: Trace all public methods of a class automatically.
Simple Class Decorator
from honeyhive import HoneyHiveTracer, trace, enrich_span
from honeyhive.models import EventType
from functools import wraps
import inspect
tracer = HoneyHiveTracer.init(project="class-tracing")
def trace_class(cls):
"""Decorator to trace all methods of a class."""
for name, method in inspect.getmembers(cls, predicate=inspect.isfunction):
if not name.startswith('_'): # Skip private methods
setattr(cls, name, trace(tracer=tracer)(method))
return cls
@trace_class
class DataProcessor:
"""Example class with automatic method tracing."""
def load_data(self, source: str):
"""Load data from source."""
return {"data": [...]}
def transform_data(self, data: dict):
"""Transform loaded data."""
return {"transformed": [...]}
def save_data(self, data: dict, destination: str):
"""Save processed data."""
pass
Usage:
processor = DataProcessor()
processor.load_data("input.csv") # Automatically traced
processor.transform_data(data) # Automatically traced
processor.save_data(data, "output.csv") # Automatically traced
Benefits:
✅ Consistent tracing across all methods
✅ No need to decorate each method individually
✅ Easy to apply to existing classes
Selective Method Tracing
When to Use: Trace only specific methods based on custom criteria.
Attribute-Based Selection
def trace_class_selective(event_type=EventType.tool):
"""Decorator to trace methods marked with _trace attribute."""
def decorator(cls):
for name, method in inspect.getmembers(cls, predicate=inspect.isfunction):
if getattr(method, '_trace', False):
wrapped = trace(tracer=tracer, event_type=event_type)(method)
setattr(cls, name, wrapped)
return cls
return decorator
def traced_method(func):
"""Mark a method for tracing."""
func._trace = True
return func
@trace_class_selective(event_type=EventType.chain)
class LLMAgent:
"""Agent with selective method tracing."""
@traced_method
def run(self, query: str) -> str:
"""Main agent execution - TRACED."""
plan = self._create_plan(query)
return self._execute_plan(plan)
def _create_plan(self, query: str):
"""Internal planning - NOT TRACED."""
return {"steps": [...]}
@traced_method
def _execute_plan(self, plan: dict) -> str:
"""Plan execution - TRACED."""
return "result"
Trace Output:
Only run() and _execute_plan() are traced, while _create_plan() remains untraced for performance.
Advanced Patterns
Enrichment at Class Level
Problem: Automatically add class-level context to all method traces.
Solution:
def trace_class_with_context(class_name_attr: str = None):
"""Trace class methods with automatic class context enrichment."""
def decorator(cls):
class_name = cls.__name__
for name, method in inspect.getmembers(cls, predicate=inspect.isfunction):
if not name.startswith('_'):
original_method = method
@wraps(original_method)
def wrapped(self, *args, **kwargs):
# Add class-level context
enrich_span({
"class.name": class_name,
"class.method": name,
"instance.id": id(self)
})
# Add custom class attribute if specified
if class_name_attr and hasattr(self, class_name_attr):
enrich_span({
f"class.{class_name_attr}": getattr(self, class_name_attr)
})
return original_method(self, *args, **kwargs)
traced_wrapped = trace(tracer=tracer)(wrapped)
setattr(cls, name, traced_wrapped)
return cls
return decorator
@trace_class_with_context(class_name_attr="agent_type")
class ConfigurableAgent:
"""Agent with class-level configuration tracing."""
def __init__(self, agent_type: str):
self.agent_type = agent_type
def process(self, query: str) -> str:
"""Process query with agent."""
return f"Processed by {self.agent_type}"
Trace Span Enrichment:
Every method call automatically includes:
{
"class.name": "ConfigurableAgent",
"class.method": "process",
"instance.id": 140234567890,
"class.agent_type": "research"
}
Metaclass-Based Tracing
Problem: Apply tracing at class definition time with full control.
Solution:
from honeyhive import trace
from honeyhive.models import EventType
class TracedMeta(type):
"""Metaclass that automatically traces all public methods."""
def __new__(mcs, name, bases, namespace, **kwargs):
trace_config = kwargs.get('trace_config', {})
event_type = trace_config.get('event_type', EventType.tool)
for attr_name, attr_value in namespace.items():
if callable(attr_value) and not attr_name.startswith('_'):
namespace[attr_name] = trace(
tracer=tracer,
event_type=event_type
)(attr_value)
return super().__new__(mcs, name, bases, namespace)
class TracedService(metaclass=TracedMeta, trace_config={'event_type': EventType.chain}):
"""Service with metaclass-based automatic tracing."""
def fetch_data(self, source: str):
"""Fetch data from source."""
return {"data": [...]}
def process_data(self, data: dict):
"""Process fetched data."""
return {"processed": [...]}
Benefits:
✅ Tracing applied at class definition time
✅ Configurable event types per class
✅ No explicit decorator syntax needed
Hierarchical Tracing
Problem: Trace class hierarchies while preserving inheritance.
Parent-Child Trace Hierarchy
def trace_class_hierarchy(base_event_type=EventType.chain):
"""Trace classes with parent-child awareness."""
def decorator(cls):
class_hierarchy = [c.__name__ for c in cls.__mro__[:-1]]
for name, method in inspect.getmembers(cls, predicate=inspect.isfunction):
if not name.startswith('_'):
original_method = method
@wraps(original_method)
def wrapped(self, *args, **kwargs):
enrich_span({
"class.hierarchy": " -> ".join(class_hierarchy),
"class.current": cls.__name__,
"class.method": name
})
return original_method(self, *args, **kwargs)
traced = trace(tracer=tracer, event_type=base_event_type)(wrapped)
setattr(cls, name, traced)
return cls
return decorator
@trace_class_hierarchy()
class BaseAgent:
"""Base agent class."""
def initialize(self):
"""Initialize agent."""
pass
@trace_class_hierarchy()
class ResearchAgent(BaseAgent):
"""Research-specialized agent."""
def research(self, topic: str):
"""Perform research."""
self.initialize() # Calls parent method
return {"findings": [...]}
Trace Hierarchy Output:
{
"class.hierarchy": "ResearchAgent -> BaseAgent",
"class.current": "ResearchAgent",
"class.method": "research"
}
Real-World Patterns
Pattern 1: Repository Pattern with Tracing
def trace_repository(entity_name: str):
"""Decorator for repository pattern classes."""
def decorator(cls):
for name, method in inspect.getmembers(cls, predicate=inspect.isfunction):
if not name.startswith('_'):
original_method = method
@wraps(original_method)
def wrapped(self, *args, **kwargs):
# Repository-specific enrichment
enrich_span({
"repository.entity": entity_name,
"repository.operation": name,
"repository.class": cls.__name__
})
# Add operation timing
import time
start = time.time()
result = original_method(self, *args, **kwargs)
duration = (time.time() - start) * 1000
enrich_span({
"repository.duration_ms": duration,
"repository.success": True
})
return result
traced = trace(tracer=tracer, event_type=EventType.tool)(wrapped)
setattr(cls, name, traced)
return cls
return decorator
@trace_repository(entity_name="User")
class UserRepository:
"""User data repository with automatic tracing."""
def find_by_id(self, user_id: str):
"""Find user by ID."""
return {"id": user_id, "name": "John"}
def save(self, user: dict):
"""Save user to database."""
pass
def delete(self, user_id: str):
"""Delete user from database."""
pass
Trace Output:
{
"repository.entity": "User",
"repository.operation": "find_by_id",
"repository.class": "UserRepository",
"repository.duration_ms": 12.5,
"repository.success": True
}
Pattern 2: Service Layer with Error Handling
def trace_service(service_name: str):
"""Decorator for service layer with error handling."""
def decorator(cls):
for name, method in inspect.getmembers(cls, predicate=inspect.isfunction):
if not name.startswith('_'):
original_method = method
@wraps(original_method)
def wrapped(self, *args, **kwargs):
enrich_span({
"service.name": service_name,
"service.operation": name,
"service.method": method.__name__
})
try:
result = original_method(self, *args, **kwargs)
enrich_span({"service.status": "success"})
return result
except Exception as e:
enrich_span({
"service.status": "error",
"service.error_type": type(e).__name__,
"service.error_message": str(e)
})
raise
traced = trace(tracer=tracer, event_type=EventType.chain)(wrapped)
setattr(cls, name, traced)
return cls
return decorator
@trace_service(service_name="LLMOrchestrator")
class LLMOrchestrationService:
"""Service for orchestrating LLM calls."""
def generate_response(self, prompt: str) -> str:
"""Generate LLM response."""
# LLM logic here
return "response"
def batch_generate(self, prompts: list) -> list:
"""Batch generate responses."""
return [self.generate_response(p) for p in prompts]
Best Practices
1. Choose the Right Approach
Simple decorator (`@trace_class`): Quick, all public methods
Selective decorator: Performance-critical code
Metaclass: Framework-level instrumentation
Custom decorator: Domain-specific patterns (Repository, Service)
2. Performance Considerations
# Good: Trace high-level operations
@trace_class
class WorkflowOrchestrator:
def execute_workflow(self): pass # Traced
def _validate_step(self): pass # Not traced
# Avoid: Tracing low-level utility methods
# @trace_class # DON'T trace utility classes
class StringUtils:
def trim(self, s: str): pass
def uppercase(self, s: str): pass
3. Enrichment Strategy
# Good: Add meaningful class-level context
enrich_span({
"class.name": cls.__name__,
"class.instance_id": id(self),
"business.entity_type": "User",
"business.operation": "create"
})
# Avoid: Generic low-value attributes
# enrich_span({"class": "SomeClass"}) # Too generic
4. Error Handling
Always wrap decorated methods with try-except to capture errors in spans:
try:
result = original_method(self, *args, **kwargs)
enrich_span({"success": True})
return result
except Exception as e:
enrich_span({
"error": True,
"error_type": type(e).__name__,
"error_message": str(e)
})
raise
Comparison with Method Decorators
Class Decorators:
✅ Apply to all methods at once
✅ Consistent tracing strategy
❌ Less granular control per method
Method Decorators:
✅ Fine-grained control
✅ Method-specific event types
❌ Repetitive for large classes
Recommendation: Use class decorators for uniform tracing, method decorators for exceptions.
@trace_class # Default tracing for most methods
class DataPipeline:
@trace(tracer=tracer, event_type=EventType.chain) # Override for specific method
def run_full_pipeline(self):
"""Critical operation with custom event type."""
pass
def load_data(self):
"""Standard method - uses class-level tracing."""
pass
Next Steps
Custom Span Management - Create custom span structures
Span Enrichment Patterns - Advanced enrichment patterns
LLM Application Patterns - Apply to LLM agent patterns
HoneyHiveTracer API Reference - Tracing API reference
Key Takeaway: Class-level decorators enable systematic, consistent tracing across object-oriented codebases. Use them to instrument entire classes automatically while maintaining flexibility for method-specific customization. ✨