Skip to content

Commit

Permalink
Merge pull request #852 from roflcoopter/feature/log-file
Browse files Browse the repository at this point in the history
add logging to file
  • Loading branch information
roflcoopter authored Dec 11, 2024
2 parents 7aacc99 + 3e09787 commit 37b5a1e
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 11 deletions.
59 changes: 55 additions & 4 deletions viseron/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import tracemalloc
from collections.abc import Callable
from functools import partial
from logging.handlers import RotatingFileHandler
from timeit import default_timer as timer
from typing import TYPE_CHECKING, Any, Literal, overload

Expand Down Expand Up @@ -47,22 +48,27 @@
DOMAIN_LOADING,
DOMAIN_SETUP_TASKS,
DOMAINS_TO_SETUP,
ENV_LOG_BACKUP_COUNT,
ENV_LOG_MAX_BYTES,
ENV_PROFILE_MEMORY,
EVENT_DOMAIN_REGISTERED,
FAILED,
LOADED,
LOADING,
REGISTERED_DOMAINS,
VISERON_LOG_PATH,
VISERON_SIGNAL_LAST_WRITE,
VISERON_SIGNAL_SHUTDOWN,
VISERON_SIGNAL_STOPPING,
)
from viseron.domains.camera.const import DOMAIN as CAMERA_DOMAIN
from viseron.events import Event, EventData
from viseron.exceptions import DataStreamNotLoaded, DomainNotRegisteredError
from viseron.helpers import memory_usage_profiler, utcnow
from viseron.helpers import memory_usage_profiler, parse_size_to_bytes, utcnow
from viseron.helpers.json import JSONEncoder
from viseron.helpers.logs import (
LOG_DATE_FORMAT,
LOG_FORMAT,
DuplicateFilter,
SensitiveInformationFilter,
ViseronLogFormat,
Expand Down Expand Up @@ -100,16 +106,61 @@
LOGGER = logging.getLogger(f"{__name__}.core")


def _get_rotation_rules() -> tuple[int, int]:
env_max_bytes = os.getenv(ENV_LOG_MAX_BYTES)
env_backup_count = os.getenv(ENV_LOG_BACKUP_COUNT)

max_bytes = 0
if env_max_bytes is not None:
try:
max_bytes = parse_size_to_bytes(env_max_bytes)
except ValueError as error:
LOGGER.error(
f"Failed to parse {ENV_LOG_MAX_BYTES} as int, using default value",
exc_info=error,
)

backup_count = 1
if env_backup_count is not None:
try:
backup_count = parse_size_to_bytes(env_backup_count)
except ValueError as error:
LOGGER.error(
f"Failed to parse {ENV_LOG_BACKUP_COUNT} as int, using default value",
exc_info=error,
)

return max_bytes, backup_count


def enable_logging() -> None:
"""Enable logging."""
root_logger = logging.getLogger()
root_logger.propagate = False
handler = logging.StreamHandler()
formatter = ViseronLogFormat()
duplicate_filter = DuplicateFilter()
sensitive_information_filter = SensitiveInformationFilter()

handler = logging.StreamHandler()
handler.setFormatter(formatter)
handler.addFilter(DuplicateFilter())
handler.addFilter(SensitiveInformationFilter())
handler.addFilter(duplicate_filter)
handler.addFilter(sensitive_information_filter)
root_logger.addHandler(handler)

max_bytes, backup_count = _get_rotation_rules()
file_handler = RotatingFileHandler(
VISERON_LOG_PATH,
maxBytes=max_bytes,
backupCount=backup_count,
delay=True,
)
file_handler.setFormatter(
logging.Formatter(fmt=LOG_FORMAT, datefmt=LOG_DATE_FORMAT)
)
file_handler.addFilter(sensitive_information_filter)
file_handler.doRollover()
root_logger.addHandler(file_handler)

root_logger.setLevel(logging.INFO)

# Silence noisy loggers
Expand Down
4 changes: 4 additions & 0 deletions viseron/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
CONFIG_PATH = "/config/config.yaml"
SECRETS_PATH = "/config/secrets.yaml"
STORAGE_PATH = "/config/.viseron"
VISERON_LOG_PATH = "/config/viseron.log"
TEMP_DIR = "/tmp/viseron"
DEFAULT_CONFIG = """# Thanks for trying out Viseron!
# This is a small walkthrough of the configuration to get you started.
Expand Down Expand Up @@ -88,13 +89,16 @@
CAMERA_SEGMENT_DURATION = 5


# Environment variables
ENV_CUDA_SUPPORTED = "VISERON_CUDA_SUPPORTED"
ENV_VAAPI_SUPPORTED = "VISERON_VAAPI_SUPPORTED"
ENV_OPENCL_SUPPORTED = "VISERON_OPENCL_SUPPORTED"
ENV_RASPBERRYPI3 = "VISERON_RASPBERRYPI3"
ENV_RASPBERRYPI4 = "VISERON_RASPBERRYPI4"
ENV_JETSON_NANO = "VISERON_JETSON_NANO"
ENV_PROFILE_MEMORY = "VISERON_PROFILE_MEMORY"
ENV_LOG_MAX_BYTES = "VISERON_LOG_MAX_BYTES"
ENV_LOG_BACKUP_COUNT = "VISERON_LOG_BACKUP_COUNT"


FONT = cv2.FONT_HERSHEY_SIMPLEX
Expand Down
31 changes: 31 additions & 0 deletions viseron/helpers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,37 @@ def escape_string(string: str) -> str:
return urllib.parse.quote(string, safe="")


def parse_size_to_bytes(size_str: str) -> int:
"""Convert human-readable size strings to bytes (e.g. '10mb' -> 10485760)."""

units = {
"tb": 1024**4,
"gb": 1024**3,
"mb": 1024**2,
"kb": 1024,
"b": 1,
}

size_str = str(size_str).strip().lower()

# If it's just a number, assume bytes
if size_str.isdigit():
return int(size_str)

# Extract number and unit
for unit in units:
if size_str.endswith(unit):
try:
number = float(size_str[: -len(unit)])
return int(number * units[unit])
except ValueError as err:
raise ValueError(f"Invalid size format: {size_str}") from err

raise ValueError(
f"Invalid size unit in {size_str}. Must be one of: {', '.join(units.keys())}"
)


def memory_usage_profiler(logger, key_type="lineno", limit=5) -> None:
"""Print a table with the lines that are using the most memory."""
snapshot = tracemalloc.take_snapshot()
Expand Down
18 changes: 11 additions & 7 deletions viseron/helpers/logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@

from colorlog import ColoredFormatter

LOG_FORMAT = "%(asctime)s.%(msecs)03d [%(levelname)-8s] [%(name)s] - %(message)s"
STREAM_LOG_FORMAT = "%(log_color)s" + LOG_FORMAT
LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"


class DuplicateFilter(logging.Filter):
"""Formats identical log entries to overwrite the last."""
Expand Down Expand Up @@ -103,13 +107,13 @@ def filter(self, record) -> bool:


class ViseronLogFormat(ColoredFormatter):
"""Log formatter."""
"""Log formatter.
Used only by the StreamHandler logs.
"""

# pylint: disable=protected-access
base_format = (
"%(log_color)s[%(asctime)s] [%(levelname)-8s] [%(name)s] - %(message)s"
)
overwrite_fmt = "\x1b[80D\x1b[1A\x1b[K" + base_format
overwrite_fmt = "\x1b[80D\x1b[1A\x1b[K" + STREAM_LOG_FORMAT

def __init__(self) -> None:
log_colors = {
Expand All @@ -121,8 +125,8 @@ def __init__(self) -> None:
}

super().__init__(
fmt=self.base_format,
datefmt="%Y-%m-%d %H:%M:%S",
fmt=STREAM_LOG_FORMAT,
datefmt=LOG_DATE_FORMAT,
style="%",
reset=True,
log_colors=log_colors,
Expand Down

0 comments on commit 37b5a1e

Please sign in to comment.