Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
Fix for structured logging tests stomping on logs (#6023)
Browse files Browse the repository at this point in the history
  • Loading branch information
hawkowl authored Sep 12, 2019
1 parent 3d882a7 commit b617864
Show file tree
Hide file tree
Showing 13 changed files with 154 additions and 41 deletions.
12 changes: 7 additions & 5 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,16 @@ exclude sytest-blacklist
include pyproject.toml
recursive-include changelog.d *

prune .github
prune demo/etc
prune docker
prune .buildkite
prune .circleci
prune .codecov.yml
prune .coveragerc
prune .github
prune debian
prune .codecov.yml
prune .buildkite
prune demo/etc
prune docker
prune mypy.ini
prune stubs

exclude jenkins*
recursive-exclude jenkins *.sh
1 change: 1 addition & 0 deletions changelog.d/6023.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix the structured logging tests stomping on the global log configuration for subsequent tests.
54 changes: 54 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
[mypy]
namespace_packages=True
plugins=mypy_zope:plugin
follow_imports=skip
mypy_path=stubs

[mypy-synapse.config.homeserver]
# this is a mess because of the metaclass shenanigans
ignore_errors = True

[mypy-zope]
ignore_missing_imports = True

[mypy-constantly]
ignore_missing_imports = True

[mypy-twisted.*]
ignore_missing_imports = True

[mypy-treq.*]
ignore_missing_imports = True

[mypy-hyperlink]
ignore_missing_imports = True

[mypy-h11]
ignore_missing_imports = True

[mypy-opentracing]
ignore_missing_imports = True

[mypy-OpenSSL]
ignore_missing_imports = True

[mypy-netaddr]
ignore_missing_imports = True

[mypy-saml2.*]
ignore_missing_imports = True

[mypy-unpaddedbase64]
ignore_missing_imports = True

[mypy-canonicaljson]
ignore_missing_imports = True

[mypy-jaeger_client]
ignore_missing_imports = True

[mypy-jsonschema]
ignore_missing_imports = True

[mypy-signedjson.*]
ignore_missing_imports = True
33 changes: 25 additions & 8 deletions synapse/config/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@

import yaml

from twisted.logger import STDLibLogObserver, globalLogBeginner
from twisted.logger import (
ILogObserver,
LogBeginner,
STDLibLogObserver,
globalLogBeginner,
)

import synapse
from synapse.app import _base as appbase
Expand Down Expand Up @@ -124,7 +129,7 @@ def generate_files(self, config, config_dir_path):
log_config_file.write(DEFAULT_LOG_CONFIG.substitute(log_file=log_file))


def _setup_stdlib_logging(config, log_config):
def _setup_stdlib_logging(config, log_config, logBeginner: LogBeginner):
"""
Set up Python stdlib logging.
"""
Expand Down Expand Up @@ -165,12 +170,12 @@ def _log(event):

return observer(event)

globalLogBeginner.beginLoggingTo(
[_log], redirectStandardIO=not config.no_redirect_stdio
)
logBeginner.beginLoggingTo([_log], redirectStandardIO=not config.no_redirect_stdio)
if not config.no_redirect_stdio:
print("Redirected stdout/stderr to logs")

return observer


def _reload_stdlib_logging(*args, log_config=None):
logger = logging.getLogger("")
Expand All @@ -181,7 +186,9 @@ def _reload_stdlib_logging(*args, log_config=None):
logging.config.dictConfig(log_config)


def setup_logging(hs, config, use_worker_options=False):
def setup_logging(
hs, config, use_worker_options=False, logBeginner: LogBeginner = globalLogBeginner
) -> ILogObserver:
"""
Set up the logging subsystem.
Expand All @@ -191,6 +198,12 @@ def setup_logging(hs, config, use_worker_options=False):
use_worker_options (bool): True to use the 'worker_log_config' option
instead of 'log_config'.
logBeginner: The Twisted logBeginner to use.
Returns:
The "root" Twisted Logger observer, suitable for sending logs to from a
Logger instance.
"""
log_config = config.worker_log_config if use_worker_options else config.log_config

Expand All @@ -210,14 +223,18 @@ def read_config(*args, callback=None):
log_config_body = read_config()

if log_config_body and log_config_body.get("structured") is True:
setup_structured_logging(hs, config, log_config_body)
logger = setup_structured_logging(
hs, config, log_config_body, logBeginner=logBeginner
)
appbase.register_sighup(read_config, callback=reload_structured_logging)
else:
_setup_stdlib_logging(config, log_config_body)
logger = _setup_stdlib_logging(config, log_config_body, logBeginner=logBeginner)
appbase.register_sighup(read_config, callback=_reload_stdlib_logging)

# make sure that the first thing we log is a thing we can grep backwards
# for
logging.warn("***** STARTING SERVER *****")
logging.warn("Server %s version %s", sys.argv[0], get_version_string(synapse))
logging.info("Server hostname: %s", config.server_name)

return logger
8 changes: 4 additions & 4 deletions synapse/logging/_structured.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import sys
import typing
import warnings
from typing import List

import attr
from constantly import NamedConstant, Names, ValueConstant, Values
Expand All @@ -33,7 +34,6 @@
LogLevelFilterPredicate,
LogPublisher,
eventAsText,
globalLogBeginner,
jsonFileLogObserver,
)

Expand Down Expand Up @@ -134,7 +134,7 @@ def emit(self, record: logging.LogRecord) -> None:
)


def SynapseFileLogObserver(outFile: typing.io.TextIO) -> FileLogObserver:
def SynapseFileLogObserver(outFile: typing.IO[str]) -> FileLogObserver:
"""
A log observer that formats events like the traditional log formatter and
sends them to `outFile`.
Expand Down Expand Up @@ -265,7 +265,7 @@ def setup_structured_logging(
hs,
config,
log_config: dict,
logBeginner: LogBeginner = globalLogBeginner,
logBeginner: LogBeginner,
redirect_stdlib_logging: bool = True,
) -> LogPublisher:
"""
Expand All @@ -286,7 +286,7 @@ def setup_structured_logging(
if "drains" not in log_config:
raise ConfigError("The logging configuration requires a list of drains.")

observers = []
observers = [] # type: List[ILogObserver]

for observer in parse_drain_configs(log_config["drains"]):
# Pipe drains
Expand Down
8 changes: 5 additions & 3 deletions synapse/logging/_terse_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@
from collections import deque
from ipaddress import IPv4Address, IPv6Address, ip_address
from math import floor
from typing.io import TextIO
from typing import IO

import attr
from simplejson import dumps
from zope.interface import implementer

from twisted.application.internet import ClientService
from twisted.internet.endpoints import (
Expand All @@ -33,7 +34,7 @@
TCP6ClientEndpoint,
)
from twisted.internet.protocol import Factory, Protocol
from twisted.logger import FileLogObserver, Logger
from twisted.logger import FileLogObserver, ILogObserver, Logger
from twisted.python.failure import Failure


Expand Down Expand Up @@ -129,7 +130,7 @@ def flatten_event(event: dict, metadata: dict, include_time: bool = False):
return new_event


def TerseJSONToConsoleLogObserver(outFile: TextIO, metadata: dict) -> FileLogObserver:
def TerseJSONToConsoleLogObserver(outFile: IO[str], metadata: dict) -> FileLogObserver:
"""
A log observer that formats events to a flattened JSON representation.
Expand All @@ -146,6 +147,7 @@ def formatEvent(_event: dict) -> str:


@attr.s
@implementer(ILogObserver)
class TerseJSONToTCPLogObserver(object):
"""
An IObserver that writes JSON logs to a TCP target.
Expand Down
4 changes: 2 additions & 2 deletions synapse/logging/opentracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,8 @@ class _DummyTagNames(object):
from jaeger_client import Config as JaegerConfig
from synapse.logging.scopecontextmanager import LogContextScopeManager
except ImportError:
JaegerConfig = None
LogContextScopeManager = None
JaegerConfig = None # type: ignore
LogContextScopeManager = None # type: ignore


logger = logging.getLogger(__name__)
Expand Down
5 changes: 2 additions & 3 deletions synapse/metrics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import platform
import threading
import time
from typing import Dict, Union

import six

Expand All @@ -42,9 +43,7 @@
METRICS_PREFIX = "/_synapse/metrics"

running_on_pypy = platform.python_implementation() == "PyPy"
all_metrics = []
all_collectors = []
all_gauges = {}
all_gauges = {} # type: Dict[str, Union[LaterGauge, InFlightGauge, BucketCollector]]

HAVE_PROC_SELF_STAT = os.path.exists("/proc/self/stat")

Expand Down
4 changes: 3 additions & 1 deletion synapse/metrics/_exposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@
try:
from prometheus_client.samples import Sample
except ImportError:
Sample = namedtuple("Sample", ["name", "labels", "value", "timestamp", "exemplar"])
Sample = namedtuple(
"Sample", ["name", "labels", "value", "timestamp", "exemplar"]
) # type: ignore


CONTENT_TYPE_LATEST = str("text/plain; version=0.0.4; charset=utf-8")
Expand Down
7 changes: 4 additions & 3 deletions synapse/python_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# limitations under the License.

import logging
from typing import Set

from pkg_resources import (
DistributionNotFound,
Expand Down Expand Up @@ -97,7 +98,7 @@
"jwt": ["pyjwt>=1.6.4"],
}

ALL_OPTIONAL_REQUIREMENTS = set()
ALL_OPTIONAL_REQUIREMENTS = set() # type: Set[str]

for name, optional_deps in CONDITIONAL_REQUIREMENTS.items():
# Exclude systemd as it's a system-based requirement.
Expand Down Expand Up @@ -174,8 +175,8 @@ def check_requirements(for_feature=None):
pass

if deps_needed:
for e in errors:
logging.error(e)
for err in errors:
logging.error(err)

raise DependencyException(deps_needed)

Expand Down
25 changes: 21 additions & 4 deletions tests/logging/test_structured.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
import os
import os.path
import shutil
Expand All @@ -33,7 +34,20 @@ def beginLoggingTo(self, observers, **kwargs):
self.observers = observers


class StructuredLoggingTestCase(HomeserverTestCase):
class StructuredLoggingTestBase(object):
"""
Test base that registers a cleanup handler to reset the stdlib log handler
to 'unset'.
"""

def prepare(self, reactor, clock, hs):
def _cleanup():
logging.getLogger("synapse").setLevel(logging.NOTSET)

self.addCleanup(_cleanup)


class StructuredLoggingTestCase(StructuredLoggingTestBase, HomeserverTestCase):
"""
Tests for Synapse's structured logging support.
"""
Expand Down Expand Up @@ -139,7 +153,9 @@ def test_collects_logcontext(self):
self.assertEqual(logs[0]["request"], "somereq")


class StructuredLoggingConfigurationFileTestCase(HomeserverTestCase):
class StructuredLoggingConfigurationFileTestCase(
StructuredLoggingTestBase, HomeserverTestCase
):
def make_homeserver(self, reactor, clock):

tempdir = self.mktemp()
Expand Down Expand Up @@ -179,10 +195,11 @@ def test_log_output(self):
"""
When a structured logging config is given, Synapse will use it.
"""
setup_logging(self.hs, self.hs.config)
beginner = FakeBeginner()
publisher = setup_logging(self.hs, self.hs.config, logBeginner=beginner)

# Make a logger and send an event
logger = Logger(namespace="tests.logging.test_structured")
logger = Logger(namespace="tests.logging.test_structured", observer=publisher)

with LoggingContext("testcontext", request="somereq"):
logger.info("Hello there, {name}!", name="steve")
Expand Down
4 changes: 2 additions & 2 deletions tests/logging/test_terse_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@
from tests.server import connect_client
from tests.unittest import HomeserverTestCase

from .test_structured import FakeBeginner
from .test_structured import FakeBeginner, StructuredLoggingTestBase


class TerseJSONTCPTestCase(HomeserverTestCase):
class TerseJSONTCPTestCase(StructuredLoggingTestBase, HomeserverTestCase):
def test_log_output(self):
"""
The Terse JSON outputter delivers simplified structured logs over TCP.
Expand Down
Loading

0 comments on commit b617864

Please sign in to comment.