logger.py 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. import logging
  2. from logging.config import dictConfig
  3. import os
  4. import re
  5. from logging.handlers import RotatingFileHandler, HTTPHandler
  6. class StripAnsiFormatter(logging.Formatter):
  7. """Formatter that strips ANSI escape sequences from formatted log text."""
  8. ANSI_RE = re.compile(r"\x1B\[[0-?]*[ -/]*[@-~]")
  9. def format(self, record):
  10. text = super().format(record)
  11. return self.ANSI_RE.sub("", text)
  12. def setup_logging(app):
  13. """Configure logging: console, optional file and external HTTP handler.
  14. Controlled by env vars:
  15. - LOG_FILE: path to write rotating logs
  16. - LOG_MAX_BYTES: max bytes per file (default 10MB)
  17. - LOG_BACKUP_COUNT: number of backups (default 5)
  18. - EXTERNAL_LOG_URL: if set, an HTTPHandler will POST logs to this URL
  19. """
  20. level = logging.DEBUG if app.config.get("DEBUG") else logging.INFO
  21. handlers = {
  22. "console": {
  23. "class": "logging.StreamHandler",
  24. "formatter": "default",
  25. "level": level,
  26. }
  27. }
  28. root_handlers = ["console"]
  29. log_file = os.getenv("LOG_FILE")
  30. if log_file:
  31. # ensure parent dirs exist
  32. dirname = os.path.dirname(log_file)
  33. if dirname:
  34. os.makedirs(dirname, exist_ok=True)
  35. handlers["file"] = {
  36. "class": "logging.handlers.RotatingFileHandler",
  37. "formatter": "file",
  38. "level": level,
  39. "filename": log_file,
  40. "maxBytes": int(os.getenv("LOG_MAX_BYTES", 10 * 1024 * 1024)),
  41. "backupCount": int(os.getenv("LOG_BACKUP_COUNT", 5)),
  42. }
  43. root_handlers.append("file")
  44. external = os.getenv("EXTERNAL_LOG_URL")
  45. if external:
  46. handlers["external"] = {
  47. "class": "logging.handlers.HTTPHandler",
  48. "formatter": "default",
  49. "level": level,
  50. "host": external,
  51. "url": "/",
  52. "method": "POST",
  53. }
  54. root_handlers.append("external")
  55. config = {
  56. "version": 1,
  57. "disable_existing_loggers": False,
  58. "formatters": {
  59. "default": {
  60. "format": "%(asctime)s %(levelname)s %(name)s: %(message)s",
  61. },
  62. "file": {
  63. "()": "app.utils.logger.StripAnsiFormatter",
  64. "format": "%(asctime)s %(levelname)s %(name)s: %(message)s",
  65. },
  66. },
  67. "handlers": handlers,
  68. "root": {
  69. "handlers": root_handlers,
  70. "level": level,
  71. },
  72. }
  73. dictConfig(config)