formatters.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import json
  2. import logging
  3. from abc import ABC, abstractmethod
  4. from typing import Any, Dict, List
  5. from ray._private.log import INTERNAL_TIMESTAMP_LOG_KEY
  6. from ray._private.ray_constants import LOGGER_FORMAT
  7. from ray._private.ray_logging.constants import (
  8. LOGGER_FLATTEN_KEYS,
  9. LOGRECORD_STANDARD_ATTRS,
  10. LogKey,
  11. )
  12. def _append_flatten_attributes(formatted_attrs: Dict[str, Any], key: str, value: Any):
  13. """Flatten the dictionary values for special keys and append the values in place.
  14. If the key is in `LOGGER_FLATTEN_KEYS`, the value will be flattened and appended
  15. to the `formatted_attrs` dictionary. Otherwise, the key-value pair will be appended
  16. directly.
  17. """
  18. if key in LOGGER_FLATTEN_KEYS:
  19. if not isinstance(value, dict):
  20. raise ValueError(
  21. f"Expected a dictionary passing into {key}, but got {type(value)}"
  22. )
  23. for k, v in value.items():
  24. if k in formatted_attrs:
  25. raise KeyError(f"Found duplicated key in the log record: {k}")
  26. formatted_attrs[k] = v
  27. else:
  28. formatted_attrs[key] = value
  29. class AbstractFormatter(logging.Formatter, ABC):
  30. def __init__(self, fmt=None, datefmt=None, style="%", validate=True) -> None:
  31. super().__init__(fmt, datefmt, style, validate)
  32. self._additional_log_standard_attrs = []
  33. def set_additional_log_standard_attrs(
  34. self, additional_log_standard_attrs: List[str]
  35. ) -> None:
  36. self._additional_log_standard_attrs = additional_log_standard_attrs
  37. @property
  38. def additional_log_standard_attrs(self) -> List[str]:
  39. return self._additional_log_standard_attrs
  40. def generate_record_format_attrs(
  41. self,
  42. record: logging.LogRecord,
  43. exclude_default_standard_attrs,
  44. ) -> dict:
  45. record_format_attrs = {}
  46. # If `exclude_default_standard_attrs` is False, include the standard attributes.
  47. # Otherwise, include only Ray and user-provided context.
  48. if not exclude_default_standard_attrs:
  49. record_format_attrs.update(
  50. {
  51. LogKey.ASCTIME.value: self.formatTime(record),
  52. LogKey.LEVELNAME.value: record.levelname,
  53. LogKey.MESSAGE.value: record.getMessage(),
  54. LogKey.FILENAME.value: record.filename,
  55. LogKey.LINENO.value: record.lineno,
  56. LogKey.PROCESS.value: record.process,
  57. }
  58. )
  59. if record.exc_info:
  60. if not record.exc_text:
  61. record.exc_text = self.formatException(record.exc_info)
  62. record_format_attrs[LogKey.EXC_TEXT.value] = record.exc_text
  63. # Add the user specified additional standard attributes.
  64. for key in self._additional_log_standard_attrs:
  65. _append_flatten_attributes(
  66. record_format_attrs, key, getattr(record, key, None)
  67. )
  68. for key, value in record.__dict__.items():
  69. # Both Ray and user-provided context are stored in `record_format`.
  70. if key not in LOGRECORD_STANDARD_ATTRS:
  71. _append_flatten_attributes(record_format_attrs, key, value)
  72. # Format the internal timestamp to the standardized `timestamp_ns` key.
  73. if INTERNAL_TIMESTAMP_LOG_KEY in record_format_attrs:
  74. record_format_attrs[LogKey.TIMESTAMP_NS.value] = record_format_attrs.pop(
  75. INTERNAL_TIMESTAMP_LOG_KEY
  76. )
  77. return record_format_attrs
  78. @abstractmethod
  79. def format(self, record: logging.LogRecord) -> str:
  80. pass
  81. class JSONFormatter(AbstractFormatter):
  82. def format(self, record: logging.LogRecord) -> str:
  83. record_format_attrs = self.generate_record_format_attrs(
  84. record, exclude_default_standard_attrs=False
  85. )
  86. return json.dumps(record_format_attrs)
  87. class TextFormatter(AbstractFormatter):
  88. def __init__(self, fmt=None, datefmt=None, style="%", validate=True) -> None:
  89. super().__init__(fmt, datefmt, style, validate)
  90. self._inner_formatter = logging.Formatter(LOGGER_FORMAT)
  91. def format(self, record: logging.LogRecord) -> str:
  92. s = self._inner_formatter.format(record)
  93. record_format_attrs = self.generate_record_format_attrs(
  94. record, exclude_default_standard_attrs=True
  95. )
  96. additional_attrs = " ".join(
  97. [f"{key}={value}" for key, value in record_format_attrs.items()]
  98. )
  99. return f"{s} {additional_attrs}"