JSON formatter for structured logging. Outputs logs as JSON lines for easy parsing by log aggregation tools like ELK, Datadog, CloudWatch, etc. Enable via JSON_LOGS=true environment variable. Format: {"timestamp": "2024-12-15T15:30:00.123Z", "level": "ERROR", "source": "A
| 601 | |
| 602 | |
| 603 | class JSONFormatter(logging.Formatter): |
| 604 | """ |
| 605 | JSON formatter for structured logging. |
| 606 | |
| 607 | Outputs logs as JSON lines for easy parsing by log aggregation tools |
| 608 | like ELK, Datadog, CloudWatch, etc. |
| 609 | |
| 610 | Enable via JSON_LOGS=true environment variable. |
| 611 | |
| 612 | Format: |
| 613 | {"timestamp": "2024-12-15T15:30:00.123Z", "level": "ERROR", "source": "API", |
| 614 | "request_id": "abc1234", "message": "...", "exception": "..."} |
| 615 | """ |
| 616 | |
| 617 | def format(self, record: logging.LogRecord) -> str: |
| 618 | """Format log record as JSON.""" |
| 619 | import json |
| 620 | from datetime import timezone |
| 621 | |
| 622 | # Get context variables |
| 623 | try: |
| 624 | req_id = request_id_var.get() |
| 625 | except LookupError: |
| 626 | req_id = "" |
| 627 | |
| 628 | try: |
| 629 | source = source_var.get() |
| 630 | except LookupError: |
| 631 | source = "SYS" |
| 632 | |
| 633 | source_normalized = normalize_source(source) |
| 634 | |
| 635 | # Build timestamp in ISO 8601 format with milliseconds |
| 636 | now = datetime.now(timezone.utc) |
| 637 | timestamp = ( |
| 638 | now.strftime("%Y-%m-%dT%H:%M:%S.") + f"{int(now.microsecond / 1000):03d}Z" |
| 639 | ) |
| 640 | |
| 641 | # Base log entry |
| 642 | log_entry: dict[str, Any] = { |
| 643 | "timestamp": timestamp, |
| 644 | "level": record.levelname, |
| 645 | "source": source_normalized.strip(), |
| 646 | "message": record.getMessage(), |
| 647 | } |
| 648 | |
| 649 | # Add request ID if present |
| 650 | if req_id and req_id.strip(): |
| 651 | log_entry["request_id"] = req_id.strip() |
| 652 | |
| 653 | # Add logger name |
| 654 | if record.name: |
| 655 | log_entry["logger"] = record.name |
| 656 | |
| 657 | # Add exception info if present |
| 658 | if record.exc_info and record.exc_info[1] is not None: |
| 659 | import traceback as tb |
| 660 |
no outgoing calls