0
# Logging and Events
1
2
Comprehensive structured logging and event recording capabilities with OpenTelemetry integration. The logging API provides severity-based log record emission with trace correlation, while the events API enables structured event recording with rich attributes and context integration.
3
4
## Capabilities
5
6
### Logger Creation and Management
7
8
Get loggers for structured log record emission with proper instrumentation scope identification.
9
10
```python { .api }
11
def get_logger(
12
name: str,
13
instrumenting_library_version: Optional[str] = None,
14
logger_provider: Optional[LoggerProvider] = None,
15
schema_url: Optional[str] = None,
16
attributes: Optional[Attributes] = None,
17
) -> Logger:
18
"""
19
Returns a Logger for use by the given instrumentation library.
20
21
Parameters:
22
- name: The name of the instrumentation scope
23
- instrumenting_library_version: Optional version of the instrumentation library
24
- logger_provider: Optional specific LoggerProvider to use
25
- schema_url: Optional Schema URL of the emitted telemetry
26
- attributes: Optional attributes of the emitted telemetry
27
28
Returns:
29
Logger instance for emitting log records
30
"""
31
32
def get_logger_provider() -> LoggerProvider:
33
"""Gets the current global LoggerProvider object."""
34
35
def set_logger_provider(logger_provider: LoggerProvider) -> None:
36
"""
37
Sets the current global LoggerProvider object.
38
This can only be done once, a warning will be logged if any further attempt is made.
39
"""
40
```
41
42
### Log Record Structure and Emission
43
44
Create and emit structured log records with severity levels and rich attributes.
45
46
```python { .api }
47
class Logger(ABC):
48
"""Abstract base class for loggers."""
49
50
def emit(self, log_record: LogRecord) -> None:
51
"""
52
Emit a LogRecord.
53
54
Parameters:
55
- log_record: The LogRecord to emit
56
"""
57
58
class LogRecord:
59
"""A LogRecord instance represents an occurrence of an event."""
60
61
def __init__(
62
self,
63
timestamp: Optional[int] = None,
64
observed_timestamp: Optional[int] = None,
65
trace_id: Optional[int] = None,
66
span_id: Optional[int] = None,
67
trace_flags: Optional[TraceFlags] = None,
68
severity_text: Optional[str] = None,
69
severity_number: Optional[SeverityNumber] = None,
70
body: Optional[Any] = None,
71
resource: Optional[Resource] = None,
72
attributes: Optional[Attributes] = None,
73
) -> None:
74
"""
75
Create a new LogRecord.
76
77
Parameters:
78
- timestamp: Time when the log record occurred
79
- observed_timestamp: Time when the log record was observed
80
- trace_id: Trace ID for correlation
81
- span_id: Span ID for correlation
82
- trace_flags: Trace flags for correlation
83
- severity_text: Severity level as text
84
- severity_number: Severity level as number
85
- body: The log record body/message
86
- resource: Resource information
87
- attributes: Additional attributes
88
"""
89
90
@property
91
def timestamp(self) -> Optional[int]:
92
"""Returns the timestamp of the log record."""
93
94
@property
95
def observed_timestamp(self) -> Optional[int]:
96
"""Returns the observed timestamp of the log record."""
97
98
@property
99
def trace_id(self) -> Optional[int]:
100
"""Returns the trace ID for correlation."""
101
102
@property
103
def span_id(self) -> Optional[int]:
104
"""Returns the span ID for correlation."""
105
106
@property
107
def trace_flags(self) -> Optional[TraceFlags]:
108
"""Returns the trace flags for correlation."""
109
110
@property
111
def severity_text(self) -> Optional[str]:
112
"""Returns the severity text."""
113
114
@property
115
def severity_number(self) -> Optional[SeverityNumber]:
116
"""Returns the severity number."""
117
118
@property
119
def body(self) -> Optional[Any]:
120
"""Returns the log record body."""
121
122
@property
123
def attributes(self) -> Optional[Attributes]:
124
"""Returns the log record attributes."""
125
```
126
127
### Severity Levels
128
129
Standard severity levels for log records following OpenTelemetry specification.
130
131
```python { .api }
132
class SeverityNumber(Enum):
133
"""Severity levels for log records."""
134
135
TRACE = 1 # Trace level (most verbose)
136
TRACE2 = 2
137
TRACE3 = 3
138
TRACE4 = 4
139
DEBUG = 5 # Debug level
140
DEBUG2 = 6
141
DEBUG3 = 7
142
DEBUG4 = 8
143
INFO = 9 # Info level
144
INFO2 = 10
145
INFO3 = 11
146
INFO4 = 12
147
WARN = 13 # Warning level
148
WARN2 = 14
149
WARN3 = 15
150
WARN4 = 16
151
ERROR = 17 # Error level
152
ERROR2 = 18
153
ERROR3 = 19
154
ERROR4 = 20
155
FATAL = 21 # Fatal level (least verbose)
156
FATAL2 = 22
157
FATAL3 = 23
158
FATAL4 = 24
159
```
160
161
### Logger Provider Implementations
162
163
Provider classes for different deployment scenarios.
164
165
```python { .api }
166
class LoggerProvider(ABC):
167
"""Abstract base class for logger providers."""
168
169
def get_logger(
170
self,
171
name: str,
172
instrumenting_library_version: Optional[str] = None,
173
schema_url: Optional[str] = None,
174
attributes: Optional[Attributes] = None,
175
) -> Logger:
176
"""Returns a Logger for use by the given instrumentation library."""
177
178
class NoOpLoggerProvider(LoggerProvider):
179
"""The default LoggerProvider, used when no implementation is available."""
180
181
def get_logger(
182
self,
183
name: str,
184
instrumenting_library_version: Optional[str] = None,
185
schema_url: Optional[str] = None,
186
attributes: Optional[Attributes] = None,
187
) -> Logger:
188
"""Returns a no-op logger."""
189
190
class NoOpLogger(Logger):
191
"""No-op implementation of Logger."""
192
193
def emit(self, log_record: LogRecord) -> None:
194
"""No-op emit implementation."""
195
```
196
197
### Event Logger System
198
199
Structured event recording with enhanced semantics and type safety.
200
201
```python { .api }
202
def get_event_logger_provider() -> EventLoggerProvider:
203
"""Gets the current global EventLoggerProvider object."""
204
205
def set_event_logger_provider(event_logger_provider: EventLoggerProvider) -> None:
206
"""
207
Sets the current global EventLoggerProvider object.
208
This can only be done once, a warning will be logged if any further attempt is made.
209
"""
210
211
def get_event_logger(
212
name: str,
213
instrumenting_library_version: Optional[str] = None,
214
event_logger_provider: Optional[EventLoggerProvider] = None,
215
schema_url: Optional[str] = None,
216
attributes: Optional[Attributes] = None,
217
) -> EventLogger:
218
"""
219
Returns an EventLogger for use by the given instrumentation library.
220
221
Parameters:
222
- name: The name of the instrumentation scope
223
- instrumenting_library_version: Optional version of the instrumentation library
224
- event_logger_provider: Optional specific EventLoggerProvider to use
225
- schema_url: Optional Schema URL of the emitted telemetry
226
- attributes: Optional attributes of the emitted telemetry
227
228
Returns:
229
EventLogger instance for emitting events
230
"""
231
```
232
233
### Event Structure and Operations
234
235
Create and emit structured events with rich semantic information.
236
237
```python { .api }
238
class EventLogger(ABC):
239
"""Abstract base class for event loggers."""
240
241
def emit(self, event: Event) -> None:
242
"""
243
Emit an Event.
244
245
Parameters:
246
- event: The Event to emit
247
"""
248
249
class Event(LogRecord):
250
"""An Event is a specialized LogRecord with additional semantics."""
251
252
def __init__(
253
self,
254
name: str,
255
timestamp: Optional[int] = None,
256
attributes: Optional[Attributes] = None,
257
**kwargs,
258
) -> None:
259
"""
260
Create a new Event.
261
262
Parameters:
263
- name: The name of the event
264
- timestamp: Time when the event occurred
265
- attributes: Event attributes
266
- kwargs: Additional LogRecord parameters
267
"""
268
269
@property
270
def name(self) -> str:
271
"""Returns the event name."""
272
```
273
274
### Event Logger Provider Implementations
275
276
Provider classes for event logger creation and management.
277
278
```python { .api }
279
class EventLoggerProvider(ABC):
280
"""Abstract base class for event logger providers."""
281
282
def get_event_logger(
283
self,
284
name: str,
285
instrumenting_library_version: Optional[str] = None,
286
schema_url: Optional[str] = None,
287
attributes: Optional[Attributes] = None,
288
) -> EventLogger:
289
"""Returns an EventLogger for use by the given instrumentation library."""
290
291
class NoOpEventLoggerProvider(EventLoggerProvider):
292
"""The default EventLoggerProvider, used when no implementation is available."""
293
294
def get_event_logger(
295
self,
296
name: str,
297
instrumenting_library_version: Optional[str] = None,
298
schema_url: Optional[str] = None,
299
attributes: Optional[Attributes] = None,
300
) -> EventLogger:
301
"""Returns a no-op event logger."""
302
303
class ProxyEventLoggerProvider(EventLoggerProvider):
304
"""Proxy event logger provider for late binding of real providers."""
305
306
def get_event_logger(
307
self,
308
name: str,
309
instrumenting_library_version: Optional[str] = None,
310
schema_url: Optional[str] = None,
311
attributes: Optional[Attributes] = None,
312
) -> EventLogger:
313
"""Returns an event logger from the real provider or proxy event logger."""
314
315
class NoOpEventLogger(EventLogger):
316
"""No-op implementation of EventLogger."""
317
318
def emit(self, event: Event) -> None:
319
"""No-op emit implementation."""
320
321
class ProxyEventLogger(EventLogger):
322
"""Proxy event logger for late binding of real event loggers."""
323
324
def emit(self, event: Event) -> None:
325
"""Emit using real event logger or no-op."""
326
```
327
328
## Usage Examples
329
330
### Basic Structured Logging
331
332
```python
333
from opentelemetry._logs import get_logger, LogRecord, SeverityNumber
334
from opentelemetry import trace
335
import time
336
337
# Get a logger
338
logger = get_logger(__name__)
339
340
# Create and emit log records
341
def log_user_action(user_id: str, action: str):
342
# Get current trace context for correlation
343
current_span = trace.get_current_span()
344
span_context = current_span.get_span_context()
345
346
log_record = LogRecord(
347
timestamp=int(time.time() * 1_000_000_000), # nanoseconds
348
severity_text="INFO",
349
severity_number=SeverityNumber.INFO,
350
body=f"User {user_id} performed action: {action}",
351
attributes={
352
"user.id": user_id,
353
"action": action,
354
"service.name": "user-service",
355
},
356
trace_id=span_context.trace_id if span_context.is_valid else None,
357
span_id=span_context.span_id if span_context.is_valid else None,
358
trace_flags=span_context.trace_flags if span_context.is_valid else None,
359
)
360
361
logger.emit(log_record)
362
363
# Usage
364
log_user_action("12345", "login")
365
```
366
367
### Event-Based Logging
368
369
```python
370
from opentelemetry._events import get_event_logger, Event
371
from opentelemetry import trace, baggage
372
import time
373
374
# Get an event logger
375
event_logger = get_event_logger(__name__)
376
377
def record_purchase_event(user_id: str, product_id: str, amount: float):
378
"""Record a structured purchase event."""
379
380
# Create event with rich attributes
381
event = Event(
382
name="user.purchase",
383
timestamp=int(time.time() * 1_000_000_000),
384
attributes={
385
"user.id": user_id,
386
"product.id": product_id,
387
"purchase.amount": amount,
388
"currency": "USD",
389
"event.category": "commerce",
390
}
391
)
392
393
# Emit the event
394
event_logger.emit(event)
395
396
def record_system_event(event_name: str, **attributes):
397
"""Record a generic system event."""
398
event = Event(
399
name=event_name,
400
attributes=attributes
401
)
402
event_logger.emit(event)
403
404
# Usage examples
405
record_purchase_event("12345", "prod-789", 29.99)
406
record_system_event("service.startup", service="user-service", version="1.2.3")
407
```
408
409
### Logging with Trace Correlation
410
411
```python
412
from opentelemetry import trace
413
from opentelemetry._logs import get_logger, LogRecord, SeverityNumber
414
415
logger = get_logger(__name__)
416
tracer = trace.get_tracer(__name__)
417
418
def process_order_with_logging(order_id: str):
419
"""Process an order with comprehensive logging."""
420
421
with tracer.start_as_current_span("process-order") as span:
422
span.set_attribute("order.id", order_id)
423
424
try:
425
# Log start of processing
426
log_info("Order processing started", {
427
"order.id": order_id,
428
"stage": "start"
429
})
430
431
# Simulate processing steps
432
validate_order(order_id)
433
log_info("Order validated", {"order.id": order_id, "stage": "validation"})
434
435
charge_payment(order_id)
436
log_info("Payment charged", {"order.id": order_id, "stage": "payment"})
437
438
ship_order(order_id)
439
log_info("Order shipped", {"order.id": order_id, "stage": "shipping"})
440
441
# Log successful completion
442
log_info("Order processing completed", {
443
"order.id": order_id,
444
"stage": "complete",
445
"result": "success"
446
})
447
448
except Exception as e:
449
# Log error with exception details
450
log_error(f"Order processing failed: {e}", {
451
"order.id": order_id,
452
"error.type": type(e).__name__,
453
"error.message": str(e)
454
})
455
span.record_exception(e)
456
raise
457
458
def log_info(message: str, attributes: dict = None):
459
"""Helper function for info logging with trace correlation."""
460
current_span = trace.get_current_span()
461
span_context = current_span.get_span_context()
462
463
log_record = LogRecord(
464
timestamp=int(time.time() * 1_000_000_000),
465
severity_text="INFO",
466
severity_number=SeverityNumber.INFO,
467
body=message,
468
attributes=attributes or {},
469
trace_id=span_context.trace_id if span_context.is_valid else None,
470
span_id=span_context.span_id if span_context.is_valid else None,
471
trace_flags=span_context.trace_flags if span_context.is_valid else None,
472
)
473
474
logger.emit(log_record)
475
476
def log_error(message: str, attributes: dict = None):
477
"""Helper function for error logging with trace correlation."""
478
current_span = trace.get_current_span()
479
span_context = current_span.get_span_context()
480
481
log_record = LogRecord(
482
timestamp=int(time.time() * 1_000_000_000),
483
severity_text="ERROR",
484
severity_number=SeverityNumber.ERROR,
485
body=message,
486
attributes=attributes or {},
487
trace_id=span_context.trace_id if span_context.is_valid else None,
488
span_id=span_context.span_id if span_context.is_valid else None,
489
trace_flags=span_context.trace_flags if span_context.is_valid else None,
490
)
491
492
logger.emit(log_record)
493
```
494
495
### Structured Logging with Baggage Context
496
497
```python
498
from opentelemetry import baggage, context
499
from opentelemetry._logs import get_logger, LogRecord, SeverityNumber
500
import time
501
502
logger = get_logger(__name__)
503
504
def enhanced_logging_example():
505
"""Demonstrate logging with baggage context."""
506
507
# Set baggage context
508
ctx = baggage.set_baggage("user.id", "12345")
509
ctx = baggage.set_baggage("request.id", "req-abc-123", ctx)
510
ctx = baggage.set_baggage("service.version", "1.2.3", ctx)
511
512
token = context.attach(ctx)
513
514
try:
515
# Log with baggage automatically included
516
log_with_baggage("Processing user request", {
517
"operation": "user.profile.update",
518
"timestamp": time.time()
519
})
520
521
# Simulate some processing
522
process_user_update()
523
524
log_with_baggage("User request completed successfully", {
525
"operation": "user.profile.update",
526
"result": "success"
527
})
528
529
except Exception as e:
530
log_with_baggage(f"User request failed: {e}", {
531
"operation": "user.profile.update",
532
"result": "error",
533
"error.type": type(e).__name__
534
}, SeverityNumber.ERROR)
535
536
finally:
537
context.detach(token)
538
539
def log_with_baggage(message: str, attributes: dict = None, severity: SeverityNumber = SeverityNumber.INFO):
540
"""Log with automatic baggage inclusion."""
541
542
# Get all baggage and include in attributes
543
all_baggage = baggage.get_all()
544
combined_attributes = dict(all_baggage)
545
546
if attributes:
547
combined_attributes.update(attributes)
548
549
log_record = LogRecord(
550
timestamp=int(time.time() * 1_000_000_000),
551
severity_text=severity.name,
552
severity_number=severity,
553
body=message,
554
attributes=combined_attributes,
555
)
556
557
logger.emit(log_record)
558
```
559
560
### Custom Event Types
561
562
```python
563
from opentelemetry._events import get_event_logger, Event
564
from opentelemetry import trace
565
import time
566
from typing import Dict, Any
567
568
event_logger = get_event_logger(__name__)
569
570
class SecurityEvent(Event):
571
"""Custom event type for security-related events."""
572
573
def __init__(self, event_type: str, user_id: str, source_ip: str, **kwargs):
574
super().__init__(
575
name=f"security.{event_type}",
576
attributes={
577
"security.event_type": event_type,
578
"user.id": user_id,
579
"source.ip": source_ip,
580
"event.category": "security",
581
**kwargs
582
}
583
)
584
585
class BusinessEvent(Event):
586
"""Custom event type for business-related events."""
587
588
def __init__(self, event_type: str, entity_id: str, **kwargs):
589
super().__init__(
590
name=f"business.{event_type}",
591
attributes={
592
"business.event_type": event_type,
593
"entity.id": entity_id,
594
"event.category": "business",
595
**kwargs
596
}
597
)
598
599
# Usage examples
600
def record_security_events():
601
# Failed login attempt
602
security_event = SecurityEvent(
603
event_type="login_failed",
604
user_id="user123",
605
source_ip="192.168.1.100",
606
reason="invalid_password",
607
attempt_count=3
608
)
609
event_logger.emit(security_event)
610
611
# Successful login
612
security_event = SecurityEvent(
613
event_type="login_success",
614
user_id="user123",
615
source_ip="192.168.1.100",
616
session_id="sess-abc-456"
617
)
618
event_logger.emit(security_event)
619
620
def record_business_events():
621
# Order placed
622
business_event = BusinessEvent(
623
event_type="order_placed",
624
entity_id="order-789",
625
customer_id="cust-123",
626
order_total=99.99,
627
currency="USD"
628
)
629
event_logger.emit(business_event)
630
631
# Payment processed
632
business_event = BusinessEvent(
633
event_type="payment_processed",
634
entity_id="payment-456",
635
order_id="order-789",
636
amount=99.99,
637
payment_method="credit_card"
638
)
639
event_logger.emit(business_event)
640
```
641
642
### Integration with Standard Python Logging
643
644
```python
645
import logging
646
from opentelemetry._logs import get_logger, LogRecord, SeverityNumber
647
from opentelemetry import trace
648
import time
649
650
# Custom handler to bridge Python logging to OpenTelemetry
651
class OpenTelemetryLogHandler(logging.Handler):
652
def __init__(self):
653
super().__init__()
654
self.otel_logger = get_logger(__name__)
655
656
def emit(self, record: logging.LogRecord):
657
# Convert Python log level to OpenTelemetry severity
658
severity_map = {
659
logging.DEBUG: SeverityNumber.DEBUG,
660
logging.INFO: SeverityNumber.INFO,
661
logging.WARNING: SeverityNumber.WARN,
662
logging.ERROR: SeverityNumber.ERROR,
663
logging.CRITICAL: SeverityNumber.FATAL,
664
}
665
666
# Get current trace context
667
current_span = trace.get_current_span()
668
span_context = current_span.get_span_context()
669
670
# Create OpenTelemetry log record
671
otel_record = LogRecord(
672
timestamp=int(record.created * 1_000_000_000), # Convert to nanoseconds
673
severity_text=record.levelname,
674
severity_number=severity_map.get(record.levelno, SeverityNumber.INFO),
675
body=record.getMessage(),
676
attributes={
677
"python.logger.name": record.name,
678
"python.module": record.module,
679
"python.function": record.funcName,
680
"python.lineno": record.lineno,
681
},
682
trace_id=span_context.trace_id if span_context.is_valid else None,
683
span_id=span_context.span_id if span_context.is_valid else None,
684
trace_flags=span_context.trace_flags if span_context.is_valid else None,
685
)
686
687
self.otel_logger.emit(otel_record)
688
689
# Set up Python logging with OpenTelemetry handler
690
def setup_logging():
691
# Create and configure handler
692
handler = OpenTelemetryLogHandler()
693
handler.setLevel(logging.INFO)
694
695
# Add to root logger
696
root_logger = logging.getLogger()
697
root_logger.addHandler(handler)
698
root_logger.setLevel(logging.INFO)
699
700
# Usage
701
setup_logging()
702
703
# Now standard Python logging will be captured by OpenTelemetry
704
logger = logging.getLogger(__name__)
705
706
def example_function():
707
tracer = trace.get_tracer(__name__)
708
with tracer.start_as_current_span("example-operation"):
709
logger.info("Starting operation")
710
711
try:
712
# Some operation that might fail
713
result = risky_operation()
714
logger.info(f"Operation completed successfully: {result}")
715
716
except Exception as e:
717
logger.error(f"Operation failed: {e}", exc_info=True)
718
raise
719
```