# -*- coding: utf-8 -*- # Copyright 2019 The Matrix.org Foundation C.I.C. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os.path from typing import Any, Dict, Generator, Optional, Tuple from constantly import NamedConstant, Names from synapse.config._base import ConfigError class DrainType(Names): CONSOLE = NamedConstant() CONSOLE_JSON = NamedConstant() CONSOLE_JSON_TERSE = NamedConstant() FILE = NamedConstant() FILE_JSON = NamedConstant() NETWORK_JSON_TERSE = NamedConstant() DEFAULT_LOGGERS = {"synapse": {"level": "info"}} def parse_drain_configs( drains: dict, ) -> Generator[Tuple[str, Dict[str, Any]], None, None]: """ Parse the drain configurations. Args: drains (dict): A list of drain configurations. Yields: dict instances representing a logging handler. Raises: ConfigError: If any of the drain configuration items are invalid. """ for name, config in drains.items(): if "type" not in config: raise ConfigError("Logging drains require a 'type' key.") try: logging_type = DrainType.lookupByName(config["type"].upper()) except ValueError: raise ConfigError( "%s is not a known logging drain type." % (config["type"],) ) # Either use the default formatter or the tersejson one. if logging_type in (DrainType.CONSOLE_JSON, DrainType.FILE_JSON,): formatter = "json" # type: Optional[str] elif logging_type in ( DrainType.CONSOLE_JSON_TERSE, DrainType.NETWORK_JSON_TERSE, ): formatter = "tersejson" else: # A formatter of None implies using the default formatter. formatter = None if logging_type in [ DrainType.CONSOLE, DrainType.CONSOLE_JSON, DrainType.CONSOLE_JSON_TERSE, ]: location = config.get("location") if location is None or location not in ["stdout", "stderr"]: raise ConfigError( ( "The %s drain needs the 'location' key set to " "either 'stdout' or 'stderr'." ) % (logging_type,) ) yield name, { "class": "logging.StreamHandler", "formatter": formatter, "stream": "ext://sys." + location, } elif logging_type in [DrainType.FILE, DrainType.FILE_JSON]: if "location" not in config: raise ConfigError( "The %s drain needs the 'location' key set." % (logging_type,) ) location = config.get("location") if os.path.abspath(location) != location: raise ConfigError( "File paths need to be absolute, '%s' is a relative path" % (location,) ) yield name, { "class": "logging.FileHandler", "formatter": formatter, "filename": location, } elif logging_type in [DrainType.NETWORK_JSON_TERSE]: host = config.get("host") port = config.get("port") maximum_buffer = config.get("maximum_buffer", 1000) yield name, { "class": "synapse.logging.RemoteHandler", "formatter": formatter, "host": host, "port": port, "maximum_buffer": maximum_buffer, } else: raise ConfigError( "The %s drain type is currently not implemented." % (config["type"].upper(),) ) def setup_structured_logging(log_config: dict,) -> dict: """ Convert a legacy structured logging configuration (from Synapse < v1.23.0) to one compatible with the new standard library handlers. """ if "drains" not in log_config: raise ConfigError("The logging configuration requires a list of drains.") new_config = { "version": 1, "formatters": { "json": {"class": "synapse.logging.JsonFormatter"}, "tersejson": {"class": "synapse.logging.TerseJsonFormatter"}, }, "handlers": {}, "loggers": log_config.get("loggers", DEFAULT_LOGGERS), "root": {"handlers": []}, } for handler_name, handler in parse_drain_configs(log_config["drains"]): new_config["handlers"][handler_name] = handler # Add each handler to the root logger. new_config["root"]["handlers"].append(handler_name) return new_config