logging-config.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. """
  2. Production logging configuration for Python applications.
  3. Usage:
  4. from logging_config import configure_logging
  5. configure_logging()
  6. """
  7. import logging
  8. import sys
  9. from typing import Literal
  10. import structlog
  11. def configure_logging(
  12. log_level: str = "INFO",
  13. format: Literal["json", "console"] = "json",
  14. service_name: str = "app",
  15. ):
  16. """
  17. Configure structured logging for production.
  18. Args:
  19. log_level: Logging level (DEBUG, INFO, WARNING, ERROR)
  20. format: Output format - 'json' for production, 'console' for development
  21. service_name: Service name to include in logs
  22. """
  23. # Timestamper
  24. timestamper = structlog.processors.TimeStamper(fmt="iso")
  25. # Shared processors for structlog and stdlib
  26. shared_processors = [
  27. structlog.contextvars.merge_contextvars,
  28. structlog.stdlib.add_log_level,
  29. structlog.stdlib.add_logger_name,
  30. structlog.stdlib.PositionalArgumentsFormatter(),
  31. timestamper,
  32. structlog.processors.StackInfoRenderer(),
  33. structlog.processors.UnicodeDecoder(),
  34. ]
  35. # Add service name
  36. def add_service_name(_, __, event_dict):
  37. event_dict["service"] = service_name
  38. return event_dict
  39. shared_processors.insert(0, add_service_name)
  40. # Choose renderer based on format
  41. if format == "json":
  42. renderer = structlog.processors.JSONRenderer()
  43. else:
  44. renderer = structlog.dev.ConsoleRenderer(
  45. colors=True,
  46. exception_formatter=structlog.dev.plain_traceback,
  47. )
  48. # Configure structlog
  49. structlog.configure(
  50. processors=shared_processors + [
  51. structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
  52. ],
  53. logger_factory=structlog.stdlib.LoggerFactory(),
  54. wrapper_class=structlog.stdlib.BoundLogger,
  55. cache_logger_on_first_use=True,
  56. )
  57. # Configure stdlib logging
  58. formatter = structlog.stdlib.ProcessorFormatter(
  59. foreign_pre_chain=shared_processors,
  60. processors=[
  61. structlog.stdlib.ProcessorFormatter.remove_processors_meta,
  62. renderer,
  63. ],
  64. )
  65. handler = logging.StreamHandler(sys.stdout)
  66. handler.setFormatter(formatter)
  67. # Configure root logger
  68. root_logger = logging.getLogger()
  69. root_logger.handlers = []
  70. root_logger.addHandler(handler)
  71. root_logger.setLevel(log_level)
  72. # Quiet noisy libraries
  73. logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
  74. logging.getLogger("httpx").setLevel(logging.WARNING)
  75. logging.getLogger("httpcore").setLevel(logging.WARNING)
  76. logging.getLogger("sqlalchemy.engine").setLevel(logging.WARNING)
  77. def get_logger(name: str = None):
  78. """Get a structlog logger."""
  79. return structlog.get_logger(name)
  80. # Example usage
  81. if __name__ == "__main__":
  82. # Development
  83. configure_logging(log_level="DEBUG", format="console", service_name="demo")
  84. logger = get_logger("example")
  85. logger.info("application_started", version="1.0.0")
  86. logger.debug("debug_message", data={"key": "value"})
  87. logger.warning("rate_limit_approaching", current=95, limit=100)
  88. try:
  89. raise ValueError("Something went wrong")
  90. except Exception:
  91. logger.exception("operation_failed")