Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions logging/config.yml
Original file line number Diff line number Diff line change
@@ -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]
26 changes: 26 additions & 0 deletions logging/jsonLog.py
Original file line number Diff line number Diff line change
@@ -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)

34 changes: 33 additions & 1 deletion socketdock/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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()

Expand All @@ -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
Expand Down
115 changes: 115 additions & 0 deletions socketdock/loadlogger.py
Original file line number Diff line number Diff line change
@@ -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
Loading