_structured.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2019 The Matrix.org Foundation C.I.C.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import os.path
  16. from typing import Any, Dict, Generator, Optional, Tuple
  17. from constantly import NamedConstant, Names
  18. from synapse.config._base import ConfigError
  19. class DrainType(Names):
  20. CONSOLE = NamedConstant()
  21. CONSOLE_JSON = NamedConstant()
  22. CONSOLE_JSON_TERSE = NamedConstant()
  23. FILE = NamedConstant()
  24. FILE_JSON = NamedConstant()
  25. NETWORK_JSON_TERSE = NamedConstant()
  26. DEFAULT_LOGGERS = {"synapse": {"level": "info"}}
  27. def parse_drain_configs(
  28. drains: dict,
  29. ) -> Generator[Tuple[str, Dict[str, Any]], None, None]:
  30. """
  31. Parse the drain configurations.
  32. Args:
  33. drains (dict): A list of drain configurations.
  34. Yields:
  35. dict instances representing a logging handler.
  36. Raises:
  37. ConfigError: If any of the drain configuration items are invalid.
  38. """
  39. for name, config in drains.items():
  40. if "type" not in config:
  41. raise ConfigError("Logging drains require a 'type' key.")
  42. try:
  43. logging_type = DrainType.lookupByName(config["type"].upper())
  44. except ValueError:
  45. raise ConfigError(
  46. "%s is not a known logging drain type." % (config["type"],)
  47. )
  48. # Either use the default formatter or the tersejson one.
  49. if logging_type in (DrainType.CONSOLE_JSON, DrainType.FILE_JSON,):
  50. formatter = "json" # type: Optional[str]
  51. elif logging_type in (
  52. DrainType.CONSOLE_JSON_TERSE,
  53. DrainType.NETWORK_JSON_TERSE,
  54. ):
  55. formatter = "tersejson"
  56. else:
  57. # A formatter of None implies using the default formatter.
  58. formatter = None
  59. if logging_type in [
  60. DrainType.CONSOLE,
  61. DrainType.CONSOLE_JSON,
  62. DrainType.CONSOLE_JSON_TERSE,
  63. ]:
  64. location = config.get("location")
  65. if location is None or location not in ["stdout", "stderr"]:
  66. raise ConfigError(
  67. (
  68. "The %s drain needs the 'location' key set to "
  69. "either 'stdout' or 'stderr'."
  70. )
  71. % (logging_type,)
  72. )
  73. yield name, {
  74. "class": "logging.StreamHandler",
  75. "formatter": formatter,
  76. "stream": "ext://sys." + location,
  77. }
  78. elif logging_type in [DrainType.FILE, DrainType.FILE_JSON]:
  79. if "location" not in config:
  80. raise ConfigError(
  81. "The %s drain needs the 'location' key set." % (logging_type,)
  82. )
  83. location = config.get("location")
  84. if os.path.abspath(location) != location:
  85. raise ConfigError(
  86. "File paths need to be absolute, '%s' is a relative path"
  87. % (location,)
  88. )
  89. yield name, {
  90. "class": "logging.FileHandler",
  91. "formatter": formatter,
  92. "filename": location,
  93. }
  94. elif logging_type in [DrainType.NETWORK_JSON_TERSE]:
  95. host = config.get("host")
  96. port = config.get("port")
  97. maximum_buffer = config.get("maximum_buffer", 1000)
  98. yield name, {
  99. "class": "synapse.logging.RemoteHandler",
  100. "formatter": formatter,
  101. "host": host,
  102. "port": port,
  103. "maximum_buffer": maximum_buffer,
  104. }
  105. else:
  106. raise ConfigError(
  107. "The %s drain type is currently not implemented."
  108. % (config["type"].upper(),)
  109. )
  110. def setup_structured_logging(log_config: dict,) -> dict:
  111. """
  112. Convert a legacy structured logging configuration (from Synapse < v1.23.0)
  113. to one compatible with the new standard library handlers.
  114. """
  115. if "drains" not in log_config:
  116. raise ConfigError("The logging configuration requires a list of drains.")
  117. new_config = {
  118. "version": 1,
  119. "formatters": {
  120. "json": {"class": "synapse.logging.JsonFormatter"},
  121. "tersejson": {"class": "synapse.logging.TerseJsonFormatter"},
  122. },
  123. "handlers": {},
  124. "loggers": log_config.get("loggers", DEFAULT_LOGGERS),
  125. "root": {"handlers": []},
  126. }
  127. for handler_name, handler in parse_drain_configs(log_config["drains"]):
  128. new_config["handlers"][handler_name] = handler
  129. # Add each handler to the root logger.
  130. new_config["root"]["handlers"].append(handler_name)
  131. return new_config