164 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			164 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Python
		
	
	
# 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: Optional[str] = "json"
 | 
						|
        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
 |