Source code for ytjobs.logging.logger
"""
Text Logger for ptrack Pipeline
=================================
Provides structured human-readable logging to stderr for the ptrack pipeline.
All logs are formatted as readable text, written to stderr.
"""
import logging
import sys
from datetime import datetime
from typing import Any, Optional
class TextFormatter(logging.Formatter):
"""Formatter that outputs logs as human-readable text."""
def format(self, record: logging.LogRecord) -> str:
"""
Format log record as human-readable text.
Args:
record: Log record to format
Returns:
Formatted log string
"""
# Format timestamp
timestamp = datetime.fromtimestamp(record.created).strftime("%Y-%m-%d %H:%M:%S")
# Format level
level = record.levelname
# Get message
message = record.getMessage()
# Collect context fields
excluded_fields = {
"name",
"msg",
"args",
"created",
"filename",
"funcName",
"levelname",
"levelno",
"lineno",
"module",
"msecs",
"message",
"pathname",
"process",
"processName",
"relativeCreated",
"thread",
"threadName",
"exc_info",
"exc_text",
"stack_info",
"taskName",
}
context_parts = []
for key, value in record.__dict__.items():
if key not in excluded_fields and not key.startswith("_"):
# Format value readably
formatted_value = self._format_value(value)
context_parts.append(f"{key}={formatted_value}")
# Build log line
log_line = f"[{timestamp}] {level}: {message}"
if hasattr(record, "name"):
log_line = f"[{timestamp}] [{record.name}] {level}: {message}"
if context_parts:
log_line += " | " + " ".join(context_parts)
# Add exception info if present
if record.exc_info:
log_line += "\n" + self.formatException(record.exc_info)
return log_line
def _format_value(self, value: Any) -> str:
"""Format a value for readable output."""
if value is None:
return "None"
elif isinstance(value, (list, tuple)):
return "[" + ", ".join(str(self._format_value(v)) for v in value) + "]"
elif isinstance(value, dict):
items = ", ".join(f"{k}={self._format_value(v)}" for k, v in value.items())
return "{" + items + "}"
elif isinstance(value, str):
# Truncate very long strings
if len(value) > 100:
return value[:97] + "..."
return value
else:
return str(value)
[docs]
def get_logger(name: Optional[str] = None, level: int = logging.INFO) -> logging.Logger:
"""
Get a logger configured for human-readable text output to stderr.
Args:
name: Logger name (default: root logger)
level: Logging level (default: INFO)
Returns:
Configured logger instance
"""
logger = logging.getLogger(name)
logger.setLevel(level)
# Remove existing handlers to avoid duplicates
logger.handlers.clear()
# Create stderr handler
stderr_handler = logging.StreamHandler(sys.stderr)
stderr_handler.setLevel(level)
# Use text formatter
formatter = TextFormatter()
stderr_handler.setFormatter(formatter)
logger.addHandler(stderr_handler)
# Prevent propagation to root logger
logger.propagate = False
return logger