From 8ee97dd559d58a7fbdec9e87685b5715a170291f Mon Sep 17 00:00:00 2001 From: vegano1 Date: Fri, 13 Oct 2023 17:21:26 -0400 Subject: [PATCH] feat(server-utils): add logging configuration to server_utils to be used by servers. --- api/src/opentrons/__init__.py | 1 + robot-server/robot_server/app_setup.py | 5 +- robot-server/robot_server/service/logging.py | 142 -------- robot-server/robot_server/settings.py | 5 + .../robot_server/system/time_utils.py | 3 +- server-utils/Makefile | 4 +- server-utils/server_utils/__init__.py | 10 + server-utils/server_utils/config/__init__.py | 65 ++++ server-utils/server_utils/constants.py | 12 + server-utils/server_utils/logging/__init__.py | 26 ++ server-utils/server_utils/logging/config.py | 333 ++++++++++++++++++ system-server/api_log_file | 0 system-server/api_serial_log_file | 0 system-server/system_server/__main__.py | 6 +- system-server/system_server/app_setup.py | 6 +- .../system_server/settings/settings.py | 5 + system-server/system_server/systemd.py | 51 +-- update-server/Pipfile | 2 + update-server/Pipfile.lock | 32 ++ update-server/otupdate/__init__.py | 4 +- update-server/otupdate/buildroot/__main__.py | 7 +- update-server/otupdate/common/systemd.py | 45 +-- .../otupdate/openembedded/__main__.py | 7 +- 23 files changed, 521 insertions(+), 250 deletions(-) delete mode 100644 robot-server/robot_server/service/logging.py create mode 100644 server-utils/server_utils/config/__init__.py create mode 100644 server-utils/server_utils/constants.py create mode 100644 server-utils/server_utils/logging/__init__.py create mode 100644 server-utils/server_utils/logging/config.py create mode 100644 system-server/api_log_file create mode 100644 system-server/api_serial_log_file diff --git a/api/src/opentrons/__init__.py b/api/src/opentrons/__init__.py index 7240dcfbb76..f8830985610 100755 --- a/api/src/opentrons/__init__.py +++ b/api/src/opentrons/__init__.py @@ -140,6 +140,7 @@ async def initialize() -> ThreadManagedHardware: """ Initialize the Opentrons hardware returning a hardware instance. """ + # TODO (ba, 2023-10-12): Remove log init once the opentrons_hardware process is up and running robot_conf = robot_configs.load() logging_config.log_init(robot_conf.log_level) diff --git a/robot-server/robot_server/app_setup.py b/robot-server/robot_server/app_setup.py index 818b66f492a..3d26e1d3877 100644 --- a/robot-server/robot_server/app_setup.py +++ b/robot-server/robot_server/app_setup.py @@ -6,11 +6,12 @@ from opentrons import __version__ +from server_utils import PackageName, log_init + from .errors import exception_handlers from .hardware import start_initializing_hardware, clean_up_hardware from .persistence import start_initializing_persistence, clean_up_persistence from .router import router -from .service import initialize_logging from .service.task_runner import ( initialize_task_runner, clean_up_task_runner, @@ -55,7 +56,7 @@ async def on_startup() -> None: """Handle app startup.""" settings = get_settings() - initialize_logging() + log_init(PackageName.ROBOT_SERVER, settings.log_level) initialize_task_runner(app_state=app.state) start_initializing_hardware( app_state=app.state, diff --git a/robot-server/robot_server/service/logging.py b/robot-server/robot_server/service/logging.py deleted file mode 100644 index 1f109bb96ab..00000000000 --- a/robot-server/robot_server/service/logging.py +++ /dev/null @@ -1,142 +0,0 @@ -import logging -from logging.config import dictConfig -from typing import Any, Dict -from opentrons.config import IS_ROBOT, robot_configs - - -def initialize_logging() -> None: - """Initialize logging""" - # TODO Amit 2019/04/08 Move the logging level from robot configs to - # robot server mutable configs. - robot_conf = robot_configs.load() - level = logging._nameToLevel.get(robot_conf.log_level.upper(), logging.INFO) - if IS_ROBOT: - c = _robot_log_config(level) - else: - c = _dev_log_config(level) - dictConfig(c) - - -class _LogBelow: - def __init__(self, level: int) -> None: - self._log_below_level = level - - def __call__(self, record: logging.LogRecord) -> bool: - return record.levelno < self._log_below_level - - -def _robot_log_config(log_level: int) -> Dict[str, Any]: - """Logging configuration for robot deployment""" - return { - "version": 1, - "disable_existing_loggers": False, - "formatters": { - "message_only": {"format": "%(message)s"}, - }, - "filters": {"records_below_warning": {"()": _LogBelow, "level": logging.WARN}}, - "handlers": { - "unit_only": { - "class": "systemd.journal.JournalHandler", - "level": logging.DEBUG, - "formatter": "message_only", - }, - "syslog_plus_unit": { - "class": "systemd.journal.JournalHandler", - "level": logging.DEBUG, - "formatter": "message_only", - "SYSLOG_IDENTIFIER": "uvicorn", - }, - "syslog_plus_unit_above_warn": { - "class": "systemd.journal.JournalHandler", - "level": logging.WARN, - "formatter": "message_only", - "SYSLOG_IDENTIFIER": "uvicorn", - }, - "unit_only_below_warn": { - "class": "systemd.journal.JournalHandler", - "level": logging.DEBUG, - "formatter": "message_only", - "filters": ["records_below_warning"], - "SYSLOG_IDENTIFIER": "uvicorn", - }, - }, - "loggers": { - "robot_server": { - "handlers": ["syslog_plus_unit"], - "level": log_level, - "propagate": False, - }, - "uvicorn.error": { - "handlers": ["syslog_plus_unit"], - "level": log_level, - "propagate": False, - }, - "uvicorn": { - "handlers": ["syslog_plus_unit"], - "level": log_level, - "propagate": False, - }, - "fastapi": { - "handlers": ["syslog_plus_unit"], - "level": log_level, - "propagate": False, - }, - "starlette": { - "handlers": ["syslog_plus_unit"], - "level": log_level, - "propagate": False, - }, - "sqlalchemy": { - "handlers": ["syslog_plus_unit_above_warn", "unit_only_below_warn"], - # SQLAlchemy's logging is slightly unusual: - # they set up their logger with a default level of WARN by itself, - # so even if we enabled propagation, we'd have to override the level - # to see things below WARN. - # docs.sqlalchemy.org/en/14/core/engines.html#configuring-logging - "level": log_level, - "propagate": False, - }, - }, - } - - -def _dev_log_config(log_level: int) -> Dict[str, Any]: - """Logging configuration for local dev deployment""" - return { - "version": 1, - "disable_existing_loggers": False, - "formatters": { - "basic": { - "format": "%(asctime)s %(name)s %(levelname)s [Line %(lineno)s] %(message)s" - }, - }, - "handlers": { - "robot_server": { - "class": "logging.StreamHandler", - "level": logging.DEBUG, - "formatter": "basic", - } - }, - "loggers": { - "robot_server": { - "handlers": ["robot_server"], - "level": log_level, - "propagate": False, - }, - "uvicorn": { - "handlers": ["robot_server"], - "level": log_level, - "propagate": False, - }, - "sqlalchemy": { - "handlers": ["robot_server"], - # SQLAlchemy's logging is slightly unusual: - # they set up their logger with a default level of WARN by itself, - # so even if we enabled propagation, we'd have to override the level - # to see things below WARN. - # docs.sqlalchemy.org/en/14/core/engines.html#configuring-logging - "level": log_level, - "propagate": False, - }, - }, - } diff --git a/robot-server/robot_server/settings.py b/robot-server/robot_server/settings.py index 1e7b0fa4533..77e7f6e40de 100644 --- a/robot-server/robot_server/settings.py +++ b/robot-server/robot_server/settings.py @@ -94,5 +94,10 @@ class RobotServerSettings(BaseSettings): ), ) + log_level: str = Field( + default="INFO", + description="The log level for the robot server logs.", + ) + class Config: env_prefix = "OT_ROBOT_SERVER_" diff --git a/robot-server/robot_server/system/time_utils.py b/robot-server/robot_server/system/time_utils.py index 367f084adab..d6446902599 100644 --- a/robot-server/robot_server/system/time_utils.py +++ b/robot-server/robot_server/system/time_utils.py @@ -4,7 +4,8 @@ from typing import Dict, Tuple, Union, cast from datetime import datetime, timezone from opentrons.util.helpers import utc_now -from opentrons.config import IS_ROBOT + +from server_utils.config import IS_ROBOT from robot_server.system import errors from robot_server.service.errors import CommonErrorDef diff --git a/server-utils/Makefile b/server-utils/Makefile index 12c61573049..8268094251a 100755 --- a/server-utils/Makefile +++ b/server-utils/Makefile @@ -116,4 +116,6 @@ push: wheel .PHONY: push-ot3 push-ot3: sdist $(call push-python-sdist,$(host),$(ssh_key),$(ssh_opts),$(sdist_file),"/opt/opentrons-system-server","server_utils",,,$(version_file)) - $(call push-python-sdist,$(host),$(ssh_key),$(ssh_opts),$(sdist_file),"/opt/opentrons-robot-server","server_utils") + $(call push-python-sdist,$(host),$(ssh_key),$(ssh_opts),$(sdist_file),"/opt/opentrons-robot-server","server_utils",,,$(version_file)) + $(call push-python-sdist,$(host),$(ssh_key),$(ssh_opts),$(sdist_file),"/opt/opentrons-update-server","server_utils",,,$(version_file)) + $(call push-python-sdist,$(host),$(ssh_key),$(ssh_opts),$(sdist_file),"/opt/ot3usb","server_utils",,,$(version_file)) diff --git a/server-utils/server_utils/__init__.py b/server-utils/server_utils/__init__.py index eabc2c47453..d4da0c7de7b 100644 --- a/server-utils/server_utils/__init__.py +++ b/server-utils/server_utils/__init__.py @@ -3,3 +3,13 @@ This package provides common utilities to be shared across any Opentrons servers intended to run on Opentrons robots. """ + +from .constants import * +from .logging import log_init + +print("Im importing server_utils") + +__all__ = [ + "PackageName", + "log_init", +] diff --git a/server-utils/server_utils/config/__init__.py b/server-utils/server_utils/config/__init__.py new file mode 100644 index 00000000000..a2676a85e40 --- /dev/null +++ b/server-utils/server_utils/config/__init__.py @@ -0,0 +1,65 @@ +"""Common environment data about the robot.""" + +import os +import sys + +from enum import Enum, auto +from typing import Optional +from pathlib import Path + +IS_WIN = sys.platform.startswith("win") +IS_OSX = sys.platform == "darwin" +IS_LINUX = sys.platform.startswith("linux") +IS_ROBOT = bool( + IS_LINUX + and (os.environ.get("RUNNING_ON_PI") or os.environ.get("RUNNING_ON_VERDIN")) +) +#: This is the correct thing to check to see if we’re running on a robot +IS_VIRTUAL = bool(os.environ.get("ENABLE_VIRTUAL_SMOOTHIE")) + +class SystemArchitecture(Enum): + HOST = auto() + BUILDROOT = auto() + YOCTO = auto() + + +class SystemOS(Enum): + WIN = auto() + OSX = auto() + LINUX = auto() + + +OS: SystemOS = SystemOS.LINUX +if IS_WIN: + OS = SystemOS.WIN +elif IS_OSX: + OS = SystemOS.OSX + +ARCHITECTURE: SystemArchitecture = SystemArchitecture.HOST +#: The system architecture running + +OT_SYSTEM_VERSION = "0.0.0" +#: The semver string of the system + +if IS_ROBOT: + if "OT_SYSTEM_VERSION" in os.environ: + OT_SYSTEM_VERSION = os.environ["OT_SYSTEM_VERSION"] + ARCHITECTURE = SystemArchitecture.YOCTO + else: + try: + with open("/etc/VERSION.json") as vj: + contents = json.load(vj) + OT_SYSTEM_VERSION = contents["buildroot_version"] + ARCHITECTURE = SystemArchitecture.BUILDROOT + except Exception: + log.exception("Could not find version file in /etc/VERSION.json") + + +__all__ = [ + "OS", + "SystemOS", + "IS_ROBOT", + "ARCHITECTURE", + "SystemArchitecture", + "OT_SYSTEM_VERSION", +] diff --git a/server-utils/server_utils/constants.py b/server-utils/server_utils/constants.py new file mode 100644 index 00000000000..82b2c450547 --- /dev/null +++ b/server-utils/server_utils/constants.py @@ -0,0 +1,12 @@ +from enum import Enum + +VERBOSE = 10 + +class PackageName(Enum): + """The types of packages to configure logging""" + ROBOT_SERVER = "robot_server" + SYSTEM_SERVER = "system_server" + UPDATE_SERVER = "otupdate" + HARDWARE_SERVER = "opentrons_hardware" + OT3USBBridge = "ot3usb" + diff --git a/server-utils/server_utils/logging/__init__.py b/server-utils/server_utils/logging/__init__.py new file mode 100644 index 00000000000..85a48ec0ce7 --- /dev/null +++ b/server-utils/server_utils/logging/__init__.py @@ -0,0 +1,26 @@ +"""Logging module used accross servers.""" + +import logging + +from ..constants import VERBOSE +from .config import log_init + + +logging.addLevelName(VERBOSE, "VERBOSE") + + +class Logging(logging.Logger): + def __init__(self, *args, **kwargs): + """initializer""" + super().__init__(*args, **lwargs) + + def verbose(self, msg: str, *args, **kwargs) -> None: + """Custom log level for very chatty messages like can logs.""" + if self.isEnabledFor(VERBOSE): + self._log(VERBOSE, msg, args, **kwargs) + + +__all__ = [ + "Logging", + "log_init", +] diff --git a/server-utils/server_utils/logging/config.py b/server-utils/server_utils/logging/config.py new file mode 100644 index 00000000000..f6cb155b61e --- /dev/null +++ b/server-utils/server_utils/logging/config.py @@ -0,0 +1,333 @@ +import sys +import logging + +from dataclasses import dataclass +from logging.config import dictConfig +from typing import Any, Dict, Optional +from typing_extensions import TypedDict +from pydantic import BaseSettings + +from ..constants import PackageName +from ..config import IS_ROBOT + + +class _LogBelow: + def __init__(self, level: int) -> None: + self._log_below_level = level + + def __call__(self, record: logging.LogRecord) -> bool: + return record.levelno < self._log_below_level + + +def _host_config(settings: BaseSettings, level_value: int) -> Dict[str, Any]: + return { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "basic": { + "format": ( + "%(asctime)s %(name)s %(levelname)s [Line %(lineno)s] %(message)s" + ) + } + }, + "handlers": { + "debug": { + "class": "logging.StreamHandler", + "formatter": "basic", + "level": level_value, + }, + "serial": { + "class": "logging.handlers.RotatingFileHandler", + "formatter": "basic", + "filename": "api_serial_log_file", + "maxBytes": 5000000, + "level": logging.DEBUG, + "backupCount": 3, + }, + "api": { + "class": "logging.handlers.RotatingFileHandler", + "formatter": "basic", + "filename": "api_log_file", + "maxBytes": 1000000, + "level": logging.DEBUG, + "backupCount": 5, + }, + }, + "loggers": { + "opentrons": { + "handlers": ["debug", "api"], + "level": level_value, + }, + "opentrons.deck_calibration": { + "handlers": ["debug", "api"], + "level": level_value, + }, + "opentrons.drivers.asyncio.communication.serial_connection": { + "handlers": ["serial"], + "level": logging.DEBUG, + "propagate": False, + }, + "opentrons_hardware.drivers.can_bus.can_messenger": { + "handlers": ["serial"], + "level": logging.DEBUG, + "propagate": False, + }, + "opentrons_hardware.drivers.binary_usb.bin_serial": { + "handlers": ["serial"], + "level": logging.DEBUG, + "propagate": False, + }, + "__main__": {"handlers": ["api"], "level": level_value}, + }, + } + + +def _robot_server_config(log_level: int) -> Dict[str, Any]: + """Logging configuration for the robot_server.""" + return { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "message_only": {"format": "%(message)s"}, + }, + "filters": {"records_below_warning": {"()": _LogBelow, "level": logging.WARN}}, + "handlers": { + "unit_only": { + "class": "systemd.journal.JournalHandler", + "level": logging.DEBUG, + "formatter": "message_only", + }, + "syslog_plus_unit": { + "class": "systemd.journal.JournalHandler", + "level": logging.DEBUG, + "formatter": "message_only", + "SYSLOG_IDENTIFIER": "uvicorn", + }, + "syslog_plus_unit_above_warn": { + "class": "systemd.journal.JournalHandler", + "level": logging.WARN, + "formatter": "message_only", + "SYSLOG_IDENTIFIER": "uvicorn", + }, + "unit_only_below_warn": { + "class": "systemd.journal.JournalHandler", + "level": logging.DEBUG, + "formatter": "message_only", + "filters": ["records_below_warning"], + "SYSLOG_IDENTIFIER": "uvicorn", + }, + }, + "loggers": { + "robot_server": { + "handlers": ["syslog_plus_unit"], + "level": log_level, + "propagate": False, + }, + "uvicorn.error": { + "handlers": ["syslog_plus_unit"], + "level": log_level, + "propagate": False, + }, + "uvicorn": { + "handlers": ["syslog_plus_unit"], + "level": log_level, + "propagate": False, + }, + "fastapi": { + "handlers": ["syslog_plus_unit"], + "level": log_level, + "propagate": False, + }, + "starlette": { + "handlers": ["syslog_plus_unit"], + "level": log_level, + "propagate": False, + }, + "sqlalchemy": { + "handlers": ["syslog_plus_unit_above_warn", "unit_only_below_warn"], + # SQLAlchemy's logging is slightly unusual: + # they set up their logger with a default level of WARN by itself, + # so even if we enabled propagation, we'd have to override the level + # to see things below WARN. + # docs.sqlalchemy.org/en/14/core/engines.html#configuring-logging + "level": log_level, + "propagate": False, + }, + }, + } + + +def _system_server_config(log_level: int) -> Dict[str, Any]: + """Logging configuration for the system_server.""" + return { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "basic": {"format": "%(name)s %(levelname)s %(message)s"}, + "message_only": {"format": "%(message)s"}, + }, + "filters": {"records_below_warning": {"()": _LogBelow, "level": logging.WARN}}, + "handlers": { + "journald": { + "class": "systemd.journal.JournalHandler", + "level": logging.DEBUG, + "formatter": "message_only", + "SYSLOG_IDENTIFIER": "opentrons-system-server", + }, + }, + "loggers": { + "system_server": { + "handlers": ["journald"], + "level": log_level, + "propagate": False, + }, + "__main__": { + "handlers": ["journald"], + "level": log_level, + "propagate": False, + }, + }, + "root": {"handlers": ["journald"], "level": level} + } + + +def _update_server_config(log_level: int) -> Dict[str, Any]: + """Logging configuration for the update_server.""" + return { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "basic": {"format": "%(name)s %(levelname)s %(message)s"}, + "message_only": {"format": "%(message)s"}, + }, + "handlers": { + "journald": { + "class": "systemd.journal.JournalHandler", + "level": log_level, + "formatter": "message_only", + "SYSLOG_IDENTIFIER": "opentrons-update", + }, + }, + "loggers": { + "otupdate": { + "handlers": ["journald"], + "level": log_level, + "propagate": False, + }, + "__main__": { + "handlers": ["journald"], + "level": log_level, + "propagate": False, + }, + }, + "root": {"handlers": ["journald"], "level": level}, + } + + +def _hardware_server_config(level_value: int) -> Dict[str, Any]: + return { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "message_only": {"format": "%(message)s"}, + }, + "handlers": { + "api": { + "class": "systemd.journal.JournalHandler", + "level": logging.DEBUG, + "formatter": "message_only", + "SYSLOG_IDENTIFIER": "opentrons-api", + }, + "serial": { + "class": "systemd.journal.JournalHandler", + "level": logging.DEBUG, + "formatter": "message_only", + "SYSLOG_IDENTIFIER": "opentrons-api-serial", + }, + }, + "loggers": { + "opentrons.drivers.asyncio.communication.serial_connection": { + "handlers": ["serial"], + "level": logging.DEBUG, + "propagate": False, + }, + "opentrons": { + "handlers": ["api"], + "level": level_value, + }, + "opentrons_hardware": { + "handlers": ["api"], + "level": level_value, + }, + "opentrons_hardware.drivers.can_bus.can_messenger": { + "handlers": ["serial"], + "level": logging.DEBUG, + "propagate": False, + }, + "opentrons_hardware.drivers.binary_usb.bin_serial": { + "handlers": ["serial"], + "level": logging.DEBUG, + "propagate": False, + }, + "__main__": {"handlers": ["api"], "level": level_value}, + }, + } + + +def _ot3usb_config(log_level: int) -> Dict[str, Any]: + """Logging configuration for the ot3usb bridge.""" + return { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "basic": {"format": "%(name)s %(levelname)s %(message)s"}, + "message_only": {"format": "%(message)s"}, + }, + "handlers": { + "journald": { + "class": "systemd.journal.JournalHandler", + "level": logging.DEBUG, + "formatter": "message_only", + "SYSLOG_IDENTIFIER": "ot3usb", + }, + }, + "loggers": { + "ot3usb": { + "handlers": ["journald"], + "level": log_level, + "propagate": False, + }, + }, + "root": {"handlers": ["journald"], "level": level}, + } + + +def _config(package: PackageName, log_level: int, settings: BaseSettings) -> Dict[str, Any]: + settings = settings or BaseSettings() + if IS_ROBOT: + return { + PackageName.ROBOT_SERVER: _robot_server_config, + PackageName.SYSTEM_SERVER: _system_server_config, + PackageName.UPDATE_SERVER: _update_server_config, + PackageName.HARDWARE_SERVER: _hardware_server_config, + PackageName.OT3USBBridge: _ot3usb_config, + }[package](log_level) + return _host_config(settings, log_level) + + +def log_init(package: PackageName, level_name: str, settings: Optional[BaseSettings] = None) -> None: + """ + Function that sets log levels and format strings. Checks for the + OT_API_LOG_LEVEL environment variable otherwise defaults to INFO + """ + fallback_log_level = "INFO" + ot_log_level = level_name.upper() + if ot_log_level not in logging._nameToLevel: + sys.stderr.write( + f"OT Log Level {ot_log_level} not found. " + f"Defaulting to {fallback_log_level}\n" + ) + ot_log_level = fallback_log_level + level_value = logging._nameToLevel[ot_log_level] + logging_config = _config(package, level_value, settings) + dictConfig(logging_config) diff --git a/system-server/api_log_file b/system-server/api_log_file new file mode 100644 index 00000000000..e69de29bb2d diff --git a/system-server/api_serial_log_file b/system-server/api_serial_log_file new file mode 100644 index 00000000000..e69de29bb2d diff --git a/system-server/system_server/__main__.py b/system-server/system_server/__main__.py index df3596249b2..a294c7853a0 100644 --- a/system-server/system_server/__main__.py +++ b/system-server/system_server/__main__.py @@ -4,13 +4,13 @@ from .cli import build_root_parser import uvicorn # type: ignore[import] -LOG = logging.getLogger(__name__) + +log = logging.getLogger(__name__) if __name__ == "__main__": args = build_root_parser().parse_args() - systemd.configure_logging(level=args.log_level.upper()) - LOG.info(f"Starting system server on {args.host}:{args.port}") + log.info(f"Starting system server on {args.host}:{args.port}") systemd.notify_up() uvicorn.run( app="system_server:app", diff --git a/system-server/system_server/app_setup.py b/system-server/system_server/app_setup.py index e6fa9a20e84..342ef4874c7 100644 --- a/system-server/system_server/app_setup.py +++ b/system-server/system_server/app_setup.py @@ -8,6 +8,9 @@ from system_server.settings import get_settings from system_server.router import router +from server_utils import PackageName +from server_utils.logging import log_init + log = logging.getLogger(__name__) @@ -40,7 +43,8 @@ async def on_startup() -> None: """Handle app startup.""" # Load settings and (throw away the result) so that we detect errors early # on in startup, instead of the first time someone happens to use a setting. - get_settings() + settings = get_settings() + log_init(PackageName.SYSTEM_SERVER, settings.log_level) @app.on_event("shutdown") diff --git a/system-server/system_server/settings/settings.py b/system-server/system_server/settings/settings.py index 0019cd9e3b6..fbdd9c729c9 100644 --- a/system-server/system_server/settings/settings.py +++ b/system-server/system_server/settings/settings.py @@ -55,6 +55,11 @@ class SystemServerSettings(BaseSettings): ), ) + log_level: str = Field( + default="INFO", + description="The log level for the system server logs", + ) + class Config: """Prefix configuration for environment variables.""" diff --git a/system-server/system_server/systemd.py b/system-server/system_server/systemd.py index 787d744dc23..a4af38addd3 100644 --- a/system-server/system_server/systemd.py +++ b/system-server/system_server/systemd.py @@ -1,22 +1,9 @@ """Systemd bindings with fallbacks for test.""" -import logging.config -from typing import Dict, Union - try: - # systemd journal is available, we can use its handler - import systemd.journal + # systemd is available, we can use its handler import systemd.daemon - def log_handler(topic_name: str, log_level: int) -> Dict[str, Union[int, str]]: - """Initialize log handler.""" - return { - "class": "systemd.journal.JournalHandler", - "formatter": "message_only", - "level": log_level, - "SYSLOG_IDENTIFIER": topic_name, - } - # By using sd_notify # (https://www.freedesktop.org/software/systemd/man/sd_notify.html) # and type=notify in the unit file, we can prevent systemd from starting @@ -32,14 +19,6 @@ def notify_up() -> None: except ImportError: # systemd journal isn't available, probably running tests - def log_handler(topic_name: str, log_level: int) -> Dict[str, Union[int, str]]: - """Initialize log handler.""" - return { - "class": "logging.StreamHandler", - "formatter": "basic", - "level": log_level, - } - def notify_up() -> None: """Notify systemd that the service is up.""" pass @@ -47,30 +26,4 @@ def notify_up() -> None: SOURCE = "dummy" -def configure_logging(level: int) -> None: - """Configure logging and set hostname.""" - config = { - "version": 1, - "formatters": { - "basic": {"format": "%(name)s %(levelname)s %(message)s"}, - "message_only": {"format": "%(message)s"}, - }, - "handlers": {"journald": log_handler("opentrons-system-server", level)}, - "loggers": { - "system_server": { - "handlers": ["journald"], - "level": level, - "propagate": False, - }, - "__main__": { - "handlers": ["journald"], - "level": level, - "propagate": False, - }, - }, - "root": {"handlers": ["journald"], "level": level}, - } - logging.config.dictConfig(config) - - -__all__ = ["notify_up", "configure_logging"] +__all__ = ["notify_up"] diff --git a/update-server/Pipfile b/update-server/Pipfile index 50ff797de6c..adc4f43a417 100644 --- a/update-server/Pipfile +++ b/update-server/Pipfile @@ -6,6 +6,8 @@ name = "pypi" [packages] aiohttp = "==3.4.4" typing-extensions = "==3.10.0.0" +pydantic = "==1.8.2" +server-utils = {path = "./../server-utils", editable = true} [dev-packages] otupdate = {path = ".", editable = true} diff --git a/update-server/Pipfile.lock b/update-server/Pipfile.lock index cced41f3d19..47e25778dfb 100644 --- a/update-server/Pipfile.lock +++ b/update-server/Pipfile.lock @@ -44,6 +44,34 @@ "index": "pypi", "version": "==3.4.4" }, + "pydantic": { + "hashes": [ + "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd", + "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739", + "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f", + "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840", + "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23", + "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287", + "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62", + "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b", + "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb", + "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820", + "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3", + "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b", + "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e", + "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3", + "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316", + "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b", + "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4", + "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20", + "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e", + "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505", + "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1", + "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833" + ], + "index": "pypi", + "version": "==1.8.2" + }, "async-timeout": { "hashes": [ "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", @@ -466,6 +494,10 @@ "editable": true, "path": "." }, + "server-utils": { + "editable": true, + "path": "./../server-utils" + }, "packaging": { "hashes": [ "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", diff --git a/update-server/otupdate/__init__.py b/update-server/otupdate/__init__.py index ac46017cd90..b804abeb73b 100644 --- a/update-server/otupdate/__init__.py +++ b/update-server/otupdate/__init__.py @@ -2,6 +2,6 @@ Opentrons Update Server This is a python application that serves HTTP requests to update the Opentrons -OT2 it is running on. It should not be run (except for testing purposes) -outside of an OT2 robot. +Robot it is running on. It should not be run (except for testing purposes) +outside of an Opentrons robot. """ diff --git a/update-server/otupdate/buildroot/__main__.py b/update-server/otupdate/buildroot/__main__.py index 6557bb4f7f8..d0d65ba64dd 100644 --- a/update-server/otupdate/buildroot/__main__.py +++ b/update-server/otupdate/buildroot/__main__.py @@ -7,9 +7,11 @@ from . import get_app -from otupdate.common import name_management, cli, systemd, constants +from otupdate.common import name_management, cli, constants from otupdate.common.run_application import run_and_notify_up +from server_utils import PackageName +from server_utils.logging import log_init LOG = logging.getLogger(__name__) @@ -17,8 +19,7 @@ async def main() -> NoReturn: parser = cli.build_root_parser() args = parser.parse_args() - - systemd.configure_logging(getattr(logging, args.log_level.upper())) + log_init(PackageName.UPDATE_SERVER, args.log_level) # Because this involves restarting Avahi, this must happen early, # before the NameSynchronizer starts up and connects to Avahi. diff --git a/update-server/otupdate/common/systemd.py b/update-server/otupdate/common/systemd.py index 2df77f853fa..b9726167c1b 100644 --- a/update-server/otupdate/common/systemd.py +++ b/update-server/otupdate/common/systemd.py @@ -2,22 +2,9 @@ systemd bindings with fallbacks for test """ -import logging.config -from typing import Dict, Union - try: - # systemd journal is available, we can use its handler - import systemd.journal import systemd.daemon - def log_handler(topic_name: str, log_level: int) -> Dict[str, Union[int, str]]: - return { - "class": "systemd.journal.JournalHandler", - "formatter": "message_only", - "level": log_level, - "SYSLOG_IDENTIFIER": topic_name, - } - # By using sd_notify # (https://www.freedesktop.org/software/systemd/man/sd_notify.html) # and type=notify in the unit file, we can prevent systemd from starting @@ -30,14 +17,7 @@ def notify_up() -> None: SOURCE: str = "systemd" except ImportError: - # systemd journal isn't available, probably running tests - - def log_handler(topic_name: str, log_level: int) -> Dict[str, Union[int, str]]: - return { - "class": "logging.StreamHandler", - "formatter": "basic", - "level": log_level, - } + # systemd isn't available, probably running tests def notify_up() -> None: pass @@ -45,25 +25,4 @@ def notify_up() -> None: SOURCE = "dummy" -def configure_logging(level: int) -> None: - config = { - "version": 1, - "formatters": { - "basic": {"format": "%(name)s %(levelname)s %(message)s"}, - "message_only": {"format": "%(message)s"}, - }, - "handlers": {"journald": log_handler("opentrons-update", level)}, - "loggers": { - "otupdate": {"handlers": ["journald"], "level": level, "propagate": False}, - "__main__": { - "handlers": ["journald"], - "level": level, - "propagate": False, - }, - }, - "root": {"handlers": ["journald"], "level": level}, - } - logging.config.dictConfig(config) - - -__all__ = ["notify_up", "configure_logging"] +__all__ = ["notify_up"] diff --git a/update-server/otupdate/openembedded/__main__.py b/update-server/otupdate/openembedded/__main__.py index 4d897a2faea..248669ea473 100644 --- a/update-server/otupdate/openembedded/__main__.py +++ b/update-server/otupdate/openembedded/__main__.py @@ -7,9 +7,11 @@ from . import get_app -from otupdate.common import name_management, cli, systemd, constants +from otupdate.common import name_management, cli, constants from otupdate.common.run_application import run_and_notify_up +from server_utils import PackageName +from server_utils.logging import log_init LOG = logging.getLogger(__name__) @@ -17,8 +19,7 @@ async def main() -> NoReturn: parser = cli.build_root_parser() args = parser.parse_args() - - systemd.configure_logging(getattr(logging, args.log_level.upper())) + log_init(PackageName.UPDATE_SERVER, args.log_level) # Because this involves restarting Avahi, this must happen early, # before the NameSynchronizer starts up and connects to Avahi.