Skip to content

Commit d03cf0c

Browse files
authored
A collection of small (stability) fixes and tweaks (#597)
1 parent 712a726 commit d03cf0c

File tree

4 files changed

+170
-69
lines changed

4 files changed

+170
-69
lines changed

matter_server/server/__main__.py

+50-9
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@
22

33
import argparse
44
import asyncio
5+
from contextlib import suppress
56
import logging
7+
from logging.handlers import RotatingFileHandler
68
import os
79
from pathlib import Path
10+
import sys
11+
import threading
12+
from typing import Final
813

914
from aiorun import run
1015
import coloredlogs
@@ -20,6 +25,11 @@
2025
DEFAULT_LISTEN_ADDRESS = None
2126
DEFAULT_STORAGE_PATH = os.path.join(Path.home(), ".matter_server")
2227

28+
FORMAT_DATE: Final = "%Y-%m-%d"
29+
FORMAT_TIME: Final = "%H:%M:%S"
30+
FORMAT_DATETIME: Final = f"{FORMAT_DATE} {FORMAT_TIME}"
31+
MAX_LOG_FILESIZE = 1000000 * 10 # 10 MB
32+
2333
# Get parsed passed in arguments.
2434
parser = argparse.ArgumentParser(
2535
description="Matter Controller Server using WebSockets."
@@ -86,6 +96,7 @@
8696

8797

8898
def _setup_logging() -> None:
99+
log_fmt = "%(asctime)s (%(threadName)s) %(levelname)s [%(name)s] %(message)s"
89100
custom_level_style = {
90101
**coloredlogs.DEFAULT_LEVEL_STYLES,
91102
"chip_automation": {"color": "green", "faint": True},
@@ -94,38 +105,68 @@ def _setup_logging() -> None:
94105
"chip_error": {"color": "red"},
95106
}
96107
# Let coloredlogs handle all levels, we filter levels in the logging module
97-
coloredlogs.install(level=logging.NOTSET, level_styles=custom_level_style)
108+
coloredlogs.install(
109+
level=logging.NOTSET, level_styles=custom_level_style, fmt=log_fmt
110+
)
111+
112+
# Capture warnings.warn(...) and friends messages in logs.
113+
# The standard destination for them is stderr, which may end up unnoticed.
114+
# This way they're where other messages are, and can be filtered as usual.
115+
logging.captureWarnings(True)
98116

99-
handlers = None
117+
logging.basicConfig(level=args.log_level.upper())
118+
logger = logging.getLogger()
119+
120+
# setup file handler
100121
if args.log_file:
101-
handlers = [logging.FileHandler(args.log_file)]
102-
logging.basicConfig(handlers=handlers, level=args.log_level.upper())
122+
log_filename = os.path.join(args.log_file)
123+
file_handler = RotatingFileHandler(
124+
log_filename, maxBytes=MAX_LOG_FILESIZE, backupCount=1
125+
)
126+
# rotate log at each start
127+
with suppress(OSError):
128+
file_handler.doRollover()
129+
file_handler.setFormatter(logging.Formatter(log_fmt, datefmt=FORMAT_DATETIME))
130+
logger.addHandler(file_handler)
103131

104132
stack.init_logging(args.log_level_sdk.upper())
105-
logging.getLogger().setLevel(args.log_level.upper())
133+
logger.setLevel(args.log_level.upper())
106134

107-
if not logging.getLogger().isEnabledFor(logging.DEBUG):
135+
if not logger.isEnabledFor(logging.DEBUG):
108136
logging.getLogger("PersistentStorage").setLevel(logging.WARNING)
109137
# Temporary disable the logger of chip.clusters.Attribute because it now logs
110138
# an error on every custom attribute that couldn't be parsed which confuses people.
111139
# We can restore the default log level again when we've patched the device controller
112140
# to handle the raw attribute data to deal with custom clusters.
113141
logging.getLogger("chip.clusters.Attribute").setLevel(logging.CRITICAL)
114-
if not logging.getLogger().isEnabledFor(logging.DEBUG):
115142
# (temporary) raise the log level of zeroconf as its a logs an annoying
116143
# warning at startup while trying to bind to a loopback IPv6 interface
117144
logging.getLogger("zeroconf").setLevel(logging.ERROR)
118145

146+
# register global uncaught exception loggers
147+
sys.excepthook = lambda *args: logger.exception(
148+
"Uncaught exception",
149+
exc_info=args,
150+
)
151+
threading.excepthook = lambda args: logger.exception(
152+
"Uncaught thread exception",
153+
exc_info=( # type: ignore[arg-type]
154+
args.exc_type,
155+
args.exc_value,
156+
args.exc_traceback,
157+
),
158+
)
159+
119160

120161
def main() -> None:
121162
"""Run main execution."""
122163

123-
_setup_logging()
124-
125164
# make sure storage path exists
126165
if not os.path.isdir(args.storage_path):
127166
os.mkdir(args.storage_path)
128167

168+
_setup_logging()
169+
129170
# Init server
130171
server = MatterServer(
131172
args.storage_path,

matter_server/server/client_handler.py

+15-3
Original file line numberDiff line numberDiff line change
@@ -193,12 +193,24 @@ async def _run_handler(
193193
result = await result
194194
self._send_message(SuccessResultMessage(msg.message_id, result))
195195
except ChipStackError as err:
196-
self._logger.exception("SDK Error during handling message: %s", msg)
196+
self._logger.error(
197+
"SDK Error during handling message: %s: %s",
198+
msg.command,
199+
str(err),
200+
# only print the full stacktrace if debug logging is enabled
201+
exc_info=err if self._logger.isEnabledFor(logging.DEBUG) else None,
202+
)
197203
self._send_message(
198204
ErrorResultMessage(msg.message_id, SDKStackError.error_code, str(err))
199205
)
200-
except Exception as err: # pylint: disable=broad-except
201-
self._logger.exception("Error handling message: %s", msg)
206+
except Exception as err: # pylint: disable=broad-except # noqa: BLE001
207+
self._logger.error(
208+
"SDK Error during handling message: %s: %s",
209+
msg.command,
210+
str(err),
211+
# only print the full stacktrace if debug logging is enabled
212+
exc_info=err if self._logger.isEnabledFor(logging.DEBUG) else None,
213+
)
202214
error_code = getattr(err, "error_code", MatterError.error_code)
203215
self._send_message(ErrorResultMessage(msg.message_id, error_code, str(err)))
204216

0 commit comments

Comments
 (0)