From 1696eb570a387e36e442ba022acd4f3a122d1f67 Mon Sep 17 00:00:00 2001 From: Caleb Siebach Date: Fri, 11 Jul 2025 12:50:12 -0600 Subject: [PATCH 1/2] feat: adding in python logging configuration Signed-off-by: Caleb Siebach --- docker-compose.yaml | 21 +++++++ logging/config.yml | 18 ++++++ logging/jsonLog.py | 26 +++++++++ socketdock/__main__.py | 34 +++++++++++- socketdock/loadlogger.py | 115 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 docker-compose.yaml create mode 100644 logging/config.yml create mode 100644 logging/jsonLog.py create mode 100644 socketdock/loadlogger.py diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..a8e9623 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,21 @@ +version: '3' + +services: + socket-gateway: + build: . + environment: + SANIC_REQUEST_TIMEOUT: 700 + SANIC_RESPONSE_TIMEOUT: 700 + ports: + - "8765:8765" + volumes: + - ./postdock:/usr/src/app/postdock:z + - ./logging/jsonLog.py:/usr/local/lib/python3.10/jsonLog.py + - ./logging/config.yml:/usr/src/app/postdock/config.yml + command: > + --bindip 0.0.0.0 + --backend http + --forward-uri 0.0.0.0 + --endpoint http://localhost:8765 + --log-level DEBUG + --log-config "/usr/src/app/postdock/config.yml" \ No newline at end of file diff --git a/logging/config.yml b/logging/config.yml new file mode 100644 index 0000000..97f23c1 --- /dev/null +++ b/logging/config.yml @@ -0,0 +1,18 @@ +version: 1 +formatters: + jsonFormatter: + (): jsonLog.JsonFormatter +handlers: + stdout: + class: logging.StreamHandler + level: DEBUG + formatter: jsonFormatter + stream: ext://sys.stdout +loggers: + jsonLogger: + level: DEBUG + handlers: [stdout] + propagate: no +root: + level: DEBUG + handlers: [stdout] \ No newline at end of file diff --git a/logging/jsonLog.py b/logging/jsonLog.py new file mode 100644 index 0000000..86f9f7c --- /dev/null +++ b/logging/jsonLog.py @@ -0,0 +1,26 @@ +import json, logging +import socket +import uuid +from datetime import datetime + +hostname = socket.gethostname() + +class JsonFormatter(logging.Formatter): + + def format(self, record): + jsonLog = { + "timestamp": datetime.fromtimestamp(record.created).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z', # Only 3 Milliseconds + # "time": datetime.fromtimestamp(record.created).isoformat(), + "level": record.levelname, + "logId": str(uuid.uuid4()), + "service": "postdock", + "hostname": hostname, + "pid": record.process, + "file": record.filename, + "function": record.funcName, + "lineNumber": record.lineno, + "message": record.msg, + } + + return json.dumps(jsonLog) + \ No newline at end of file diff --git a/socketdock/__main__.py b/socketdock/__main__.py index 2292670..901f581 100644 --- a/socketdock/__main__.py +++ b/socketdock/__main__.py @@ -5,6 +5,20 @@ from sanic import Sanic from .api import api, backend_var +from .loadlogger import LoggingConfigurator + + +def configure_logging(args): + """Perform common app configuration.""" + # Set up logging + log_config = args.log_config + log_level = args.log_level + log_file = args.log_file + LoggingConfigurator.configure( + log_config_path=log_config, + log_level=log_level, + log_file=log_file, + ) def config() -> argparse.Namespace: @@ -21,9 +35,27 @@ def config() -> argparse.Namespace: parser.add_argument("--connect-uri") parser.add_argument( "--log-level", + dest="log_level", default="INFO", choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], ) + parser.add_argument( + "--log-file", + dest="log_file", + default=None, + help=( + "--log-file enables writing of logs to file, if a value is " + "provided then it uses that as log file location, otherwise " + "the default location in log config file is used." + ), + ) + parser.add_argument( + "--log-config", + dest="log_config", + default=None, + help="Specifies a custom logging configuration file", + ) + return parser.parse_args() @@ -46,7 +78,7 @@ def main(): backend_var.set(backend) - logging.basicConfig(level=args.log_level) + configure_logging(args) app = Sanic("SocketDock") app.config.WEBSOCKET_MAX_SIZE = 2**22 diff --git a/socketdock/loadlogger.py b/socketdock/loadlogger.py new file mode 100644 index 0000000..115271d --- /dev/null +++ b/socketdock/loadlogger.py @@ -0,0 +1,115 @@ +"""Logging Configurator for aca-py agent.""" + +import io +import logging +from importlib import resources +from logging.config import ( + dictConfigClass, +) +from typing import Optional + +import yaml + +LOGGER = logging.getLogger(__name__) + +def load_resource(path: str, encoding: Optional[str] = None): + """Open a resource file located in a python package or the local filesystem. + + Args: + path (str): The resource path in the form of `dir/file` or `package:dir/file` + encoding (str, optional): The encoding to use when reading the resource file. + Defaults to None. + + Returns: + file-like object: A file-like object representing the resource + """ + components = path.rsplit(":", 1) + try: + if len(components) == 1: + # Local filesystem resource + return open(components[0], encoding=encoding) + else: + # Package resource + package, resource = components + bstream = resources.files(package).joinpath(resource).open("rb") + if encoding: + return io.TextIOWrapper(bstream, encoding=encoding) + return bstream + except IOError: + LOGGER.warning("Resource not found: %s", path) + return None + + +def dictConfig(config, new_file_path=None): + """Custom dictConfig, https://github.com/python/cpython/blob/main/Lib/logging/config.py.""" + if new_file_path: + config["handlers"]["rotating_file"]["filename"] = f"{new_file_path}" + dictConfigClass(config).configure() + + +class LoggingConfigurator: + """Utility class used to configure logging and print an informative start banner.""" + + @classmethod + def configure( + cls, + log_config_path: Optional[str] = None, + log_level: Optional[str] = None, + log_file: Optional[str] = None, + ): + """Configure logger. + + :param logging_config_path: str: (Default value = None) Optional path to + custom logging config + + :param log_level: str: (Default value = None) + + :param log_file: str: (Default value = None) Optional file name to write logs to + """ + + write_to_log_file = log_file is not None or log_file == "" + + # This is a check that requires a log file path to be provided if + # --log-file is specified on startup and a config file is not. + if not log_config_path and write_to_log_file and not log_file: + raise ValueError( + "log_file (--log-file) must be provided in single-tenant mode " + "using the default config since a log file path is not set." + ) + + cls._configure_logging( + log_config_path=log_config_path, + log_level=log_level, + log_file=log_file, + ) + + @classmethod + def _configure_logging(cls, log_config_path, log_level, log_file): + # Setup log config and log file if provided + cls._setup_log_config_file(log_config_path, log_file) + + # Set custom file handler + if log_file: + logging.root.handlers.append(logging.FileHandler(log_file, encoding="utf-8")) + + # Set custom log level + if log_level: + logging.root.setLevel(log_level.upper()) + + @classmethod + def _setup_log_config_file(cls, log_config_path, log_file): + log_config, is_dict_config = cls._load_log_config(log_config_path) + + # Setup config + if not log_config: + logging.basicConfig(level=logging.WARNING) + logging.root.warning(f"Logging config file not found: {log_config_path}") + elif is_dict_config: + dictConfig(log_config, new_file_path=log_file or None) + + @classmethod + def _load_log_config(cls, log_config_path): + if ".yml" in log_config_path or ".yaml" in log_config_path: + with open(log_config_path, "r") as stream: + return yaml.safe_load(stream), True + return load_resource(log_config_path, "utf-8"), False From 4c737d4d4e738f477b298a373ea920b4cf20e344 Mon Sep 17 00:00:00 2001 From: Caleb Siebach <46693467+whyriod@users.noreply.github.com> Date: Mon, 14 Jul 2025 13:55:09 -0600 Subject: [PATCH 2/2] Delete docker-compose.yaml Signed-off-by: Caleb Siebach <46693467+whyriod@users.noreply.github.com> --- docker-compose.yaml | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 docker-compose.yaml diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index a8e9623..0000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,21 +0,0 @@ -version: '3' - -services: - socket-gateway: - build: . - environment: - SANIC_REQUEST_TIMEOUT: 700 - SANIC_RESPONSE_TIMEOUT: 700 - ports: - - "8765:8765" - volumes: - - ./postdock:/usr/src/app/postdock:z - - ./logging/jsonLog.py:/usr/local/lib/python3.10/jsonLog.py - - ./logging/config.yml:/usr/src/app/postdock/config.yml - command: > - --bindip 0.0.0.0 - --backend http - --forward-uri 0.0.0.0 - --endpoint http://localhost:8765 - --log-level DEBUG - --log-config "/usr/src/app/postdock/config.yml" \ No newline at end of file