A comprehensive observability framework providing distributed tracing, metrics collection, and statistics gathering capabilities for Python applications.
—
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.
Extract current OpenCensus trace context for inclusion in log records and correlation with distributed traces.
def get_log_attrs():
"""
Get logging attributes from OpenCensus context.
Returns:
LogAttrs: Named tuple with trace_id, span_id, and sampling_decision
or ATTR_DEFAULTS if no context available
"""
LogAttrs = namedtuple('LogAttrs', ['trace_id', 'span_id', 'sampling_decision'])
"""
Named tuple containing trace context for logging.
Fields:
- trace_id: str, 32-character hex trace ID or default
- span_id: str, 16-character hex span ID or default
- sampling_decision: bool, whether trace is sampled
"""
ATTR_DEFAULTS = LogAttrs("00000000000000000000000000000000", "0000000000000000", False)
"""Default log attributes when no trace context is available."""Logging adapter that automatically adds OpenCensus trace context to all log records for correlation and filtering.
class TraceLoggingAdapter:
"""
LoggerAdapter that adds OpenCensus context to log records.
Inherits from logging.LoggerAdapter and automatically includes
trace context in all log messages.
Parameters:
- logger: logging.Logger, underlying logger instance
- extra: dict, additional context to include in logs
"""
def __init__(self, logger, extra=None): ...
def process(self, msg, kwargs):
"""
Process log message and add trace context.
Automatically adds current trace context to the log record's
extra data for correlation with distributed traces.
Parameters:
- msg: str, log message
- kwargs: dict, keyword arguments for logging call
Returns:
tuple: (processed_message, updated_kwargs)
"""Custom logger class that automatically includes OpenCensus trace context in log record creation.
class TraceLogger:
"""
Logger subclass that includes OpenCensus context in records.
Inherits from logging.getLoggerClass() and automatically adds
trace context to all log records at creation time.
"""
def makeRecord(self, *args, **kwargs):
"""
Create log record with OpenCensus trace context.
Extends standard makeRecord to automatically include current
trace context in the log record's attributes.
Parameters:
- *args: positional arguments for standard makeRecord
- **kwargs: keyword arguments for standard makeRecord
Returns:
LogRecord: Enhanced log record with trace context
"""Standard field names and default values for trace context in log records.
TRACE_ID_KEY = 'traceId'
"""str: Log record field name for trace ID"""
SPAN_ID_KEY = 'spanId'
"""str: Log record field name for span ID"""
SAMPLING_DECISION_KEY = 'traceSampled'
"""str: Log record field name for sampling decision"""import logging
from opencensus.log import TraceLoggingAdapter, get_log_attrs
from opencensus.trace.tracer import Tracer
# Set up standard logger
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
# Create handler with formatter that includes trace fields
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - '
'TraceID=%(traceId)s SpanID=%(spanId)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
# Wrap with trace logging adapter
trace_logger = TraceLoggingAdapter(logger)
# Use within traced operations
tracer = Tracer()
with tracer.span('process_order') as span:
span.add_attribute('order_id', '12345')
# Log messages automatically include trace context
trace_logger.info('Starting order processing')
try:
# Process order logic
process_order_data()
trace_logger.info('Order processed successfully')
except Exception as e:
trace_logger.error('Order processing failed', exc_info=True)
raise
# Log output will include trace ID and span ID:
# 2024-01-15 10:30:15 - __main__ - INFO - TraceID=abc123... SpanID=def456... - Starting order processingimport logging
from opencensus.log import TraceLogger, get_log_attrs
# Set TraceLogger as default logger class
logging.setLoggerClass(TraceLogger)
# Create logger - will automatically include trace context
logger = logging.getLogger(__name__)
# Configure with trace-aware formatting
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(levelname)s [%(traceId)s:%(spanId)s] %(name)s: %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)
from opencensus.trace.tracer import Tracer
def process_user_request(user_id):
tracer = Tracer()
with tracer.span('process_user_request') as span:
span.add_attribute('user_id', user_id)
# All log calls automatically include trace context
logger.info(f'Processing request for user {user_id}')
with tracer.span('validate_user') as child_span:
logger.debug('Validating user credentials')
validate_user(user_id)
logger.debug('User validation completed')
with tracer.span('load_user_data') as child_span:
logger.debug('Loading user data from database')
user_data = load_user_data(user_id)
logger.info(f'Loaded {len(user_data)} records for user')
logger.info('Request processing completed')
# Usage
process_user_request('user123')import logging
from opencensus.log import get_log_attrs, TRACE_ID_KEY, SPAN_ID_KEY, SAMPLING_DECISION_KEY
# Standard logger setup
logger = logging.getLogger(__name__)
def custom_log_with_trace(level, message, **kwargs):
"""Custom logging function that manually adds trace context."""
# Get current trace context
trace_attrs = get_log_attrs()
# Add trace context to extra data
extra = kwargs.get('extra', {})
extra.update({
TRACE_ID_KEY: trace_attrs.trace_id,
SPAN_ID_KEY: trace_attrs.span_id,
SAMPLING_DECISION_KEY: trace_attrs.sampling_decision
})
# Log with enhanced context
logger.log(level, message, extra=extra, **kwargs)
# Usage within traced context
from opencensus.trace.tracer import Tracer
tracer = Tracer()
with tracer.span('database_operation') as span:
custom_log_with_trace(logging.INFO, 'Executing database query')
# Check if we have active trace context
attrs = get_log_attrs()
if attrs.trace_id != "00000000000000000000000000000000":
logger.info('Active trace detected', extra={
TRACE_ID_KEY: attrs.trace_id,
SPAN_ID_KEY: attrs.span_id
})
else:
logger.info('No active trace context')import logging
import json
from opencensus.log import TraceLoggingAdapter, get_log_attrs
class StructuredTraceFormatter(logging.Formatter):
"""JSON formatter that includes OpenCensus trace context."""
def format(self, record):
# Get trace context
trace_attrs = get_log_attrs()
# Build structured log entry
log_entry = {
'timestamp': self.formatTime(record),
'level': record.levelname,
'logger': record.name,
'message': record.getMessage(),
'trace': {
'trace_id': trace_attrs.trace_id,
'span_id': trace_attrs.span_id,
'sampled': trace_attrs.sampling_decision
}
}
# Add exception info if present
if record.exc_info:
log_entry['exception'] = self.formatException(record.exc_info)
# Add any extra fields
for key, value in record.__dict__.items():
if key not in ['name', 'levelname', 'levelno', 'pathname', 'filename',
'module', 'lineno', 'funcName', 'created', 'msecs',
'relativeCreated', 'thread', 'threadName', 'processName',
'process', 'getMessage', 'exc_info', 'exc_text', 'stack_info',
'args', 'msg']:
log_entry[key] = value
return json.dumps(log_entry)
# Setup structured logging with trace context
logger = logging.getLogger(__name__)
handler = logging.StreamHandler()
handler.setFormatter(StructuredTraceFormatter())
logger.addHandler(handler)
logger.setLevel(logging.INFO)
# Use with trace adapter
trace_logger = TraceLoggingAdapter(logger)
from opencensus.trace.tracer import Tracer
tracer = Tracer()
with tracer.span('api_request') as span:
span.add_attribute('endpoint', '/api/users')
span.add_attribute('method', 'GET')
# Structured log with automatic trace context
trace_logger.info('API request received', extra={
'endpoint': '/api/users',
'method': 'GET',
'user_agent': 'OpenCensus/1.0'
})
# Output will be JSON with embedded trace context:
# {"timestamp": "2024-01-15 10:30:15,123", "level": "INFO",
# "logger": "__main__", "message": "API request received",
# "trace": {"trace_id": "abc123...", "span_id": "def456...", "sampled": true},
# "endpoint": "/api/users", "method": "GET", "user_agent": "OpenCensus/1.0"}import logging
from opencensus.log import TraceLoggingAdapter
from opencensus.trace.tracer import Tracer
from opencensus.trace.propagation import trace_context_http_header_format
# Flask example
from flask import Flask, request, g
app = Flask(__name__)
# Setup trace-aware logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
trace_logger = TraceLoggingAdapter(logger)
@app.before_request
def before_request():
# Extract trace context from incoming request
propagator = trace_context_http_header_format.TraceContextPropagator()
span_context = propagator.from_headers(request.headers)
# Create tracer with incoming context
g.tracer = Tracer(span_context=span_context)
g.span = g.tracer.span(f'{request.method} {request.path}')
g.span.start()
# Log request with trace context
trace_logger.info(f'Incoming request: {request.method} {request.path}',
extra={'method': request.method, 'path': request.path})
@app.after_request
def after_request(response):
# Log response with trace context
trace_logger.info(f'Response status: {response.status_code}',
extra={'status_code': response.status_code})
if hasattr(g, 'span'):
g.span.add_attribute('http.status_code', response.status_code)
g.span.finish()
return response
@app.route('/api/users/<user_id>')
def get_user(user_id):
# All log messages within request automatically have trace context
trace_logger.info(f'Fetching user data for {user_id}')
with g.tracer.span('database_query') as span:
span.add_attribute('user_id', user_id)
trace_logger.debug('Executing database query')
# Simulate database operation
user_data = {'id': user_id, 'name': 'John Doe'}
trace_logger.info('User data retrieved successfully')
return user_data
if __name__ == '__main__':
app.run(debug=True)Install with Tessl CLI
npx tessl i tessl/pypi-opencensus