Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalize the RotatingLogger; size, time or first of the two trigger the rollover #1365

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5c23248
Generalize the RotatingLogger; size, time or both constraint
j-c-cook Aug 7, 2022
7970ce1
Merge branch 'develop' into issue1364_TimedRotatingLogger
j-c-cook Aug 11, 2022
019e41c
Update test_log_virtual_sizedlogger to have a file path
j-c-cook Aug 11, 2022
e091ba0
Update logger doc string
j-c-cook Aug 11, 2022
5be3a81
Move rollover time reset to capture when file_size triggers
j-c-cook Aug 11, 2022
89bf921
Merge branch 'develop' into issue1364_TimedRotatingLogger
Sep 29, 2022
0cf8383
Change `delta_t` to `max_seconds`
Sep 29, 2022
cd36995
Run logger test on `RotatingLogger` rather than `SizedRotatingLogger`
Sep 29, 2022
c9ba894
Modify timeout period name and call Rotating if `file_time`
Sep 29, 2022
dc75109
Add test for the timed logger given `file_time` input
Sep 29, 2022
22bf537
Update logger help statement
Sep 29, 2022
83b901e
Merge branch 'develop' into issue1364_TimedRotatingLogger
j-c-cook Dec 20, 2022
0a89a6c
Merge branch 'develop' into issue1364_TimedRotatingLogger
j-c-cook Dec 27, 2022
c3d8715
Update can/logger.py
j-c-cook Dec 28, 2022
b6b75b9
Adjust the example in docstring and update documentation
j-c-cook Dec 28, 2022
a830c7a
Update the deprecation warning message
j-c-cook Dec 28, 2022
2862573
Fix doc string build warning
j-c-cook Dec 28, 2022
78f777c
Merge branch 'develop' into issue1364_TimedRotatingLogger
j-c-cook Dec 28, 2022
b8a7ec5
Update API order to be like develop and add formats set
j-c-cook Dec 28, 2022
ce815d6
Fix mypy workflow build error check
j-c-cook Dec 28, 2022
842dcbb
Include the previous changes made to _default_name
j-c-cook Dec 28, 2022
2a466b9
Fix spacing in _default_name
j-c-cook Dec 28, 2022
49649e7
Update logconvert to use the generalized RotatingLogger
j-c-cook Dec 28, 2022
35d25c7
Fix comment to reference RotatingLogger
j-c-cook Dec 28, 2022
b763f4a
Format code with black
j-c-cook Dec 28, 2022
29717f4
Add test for time rollover condition
j-c-cook Dec 28, 2022
ad382b5
Merge branch 'issue1364_TimedRotatingLogger' of https://github.com/j-…
j-c-cook Dec 28, 2022
8aff574
Add note to preserve size rotating test functions at v5.0
j-c-cook Dec 28, 2022
cabafb4
Merge branch 'develop' into issue1364_TimedRotatingLogger
j-c-cook Jan 11, 2023
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
9 changes: 8 additions & 1 deletion can/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,14 @@
from .interface import Bus, detect_available_configs
from .bit_timing import BitTiming

from .io import Logger, SizedRotatingLogger, Printer, LogReader, MessageSync
from .io import (
Logger,
SizedRotatingLogger,
Printer,
LogReader,
MessageSync,
RotatingLogger,
)
felixdivo marked this conversation as resolved.
Show resolved Hide resolved
from .io import ASCWriter, ASCReader
from .io import BLFReader, BLFWriter
from .io import CanutilsLogReader, CanutilsLogWriter
Expand Down
2 changes: 1 addition & 1 deletion can/io/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""

# Generic
from .logger import Logger, BaseRotatingLogger, SizedRotatingLogger
from .logger import Logger, BaseRotatingLogger, SizedRotatingLogger, RotatingLogger
from .player import LogReader, MessageSync

# Format specific
Expand Down
119 changes: 105 additions & 14 deletions can/io/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import pathlib
from abc import ABC, abstractmethod
from datetime import datetime
import time
import warnings
import gzip
from typing import Any, Optional, Callable, Type, Tuple, cast, Dict

Expand Down Expand Up @@ -265,11 +267,14 @@ def do_rollover(self) -> None:
"""Perform rollover."""


class SizedRotatingLogger(BaseRotatingLogger):
"""Log CAN messages to a sequence of files with a given maximum size.
class RotatingLogger(BaseRotatingLogger):
"""Log CAN messages to a sequence of files with a given maximum size,
a specified amount of time or a combination of both. When both max size
(bytes) and rollover time (seconds) are provided, the rollover trigger
is caused by the first to occur.

The logger creates a log file with the given `base_filename`. When the
size threshold is reached the current log file is closed and renamed
The logger creates a log file with the given `base_filename`. When a size
or time threshold is reached the current log file is closed and renamed
by adding a timestamp and the rollover count. A new log file is then
created and written to.

Expand Down Expand Up @@ -305,41 +310,61 @@ class SizedRotatingLogger(BaseRotatingLogger):
:meth:`~can.Listener.stop` is called.
"""

last_rollover_time = time.time()

def __init__(
self,
base_filename: StringPathLike,
max_bytes: int = 0,
*args: Any,
max_bytes: int = 0,
delta_t: int = 0,
**kwargs: Any,
) -> None:
"""
:param base_filename:
A path-like object for the base filename. The log file format is defined by
the suffix of `base_filename`.
A path-like object for the base filename. The log file format is
defined by the suffix of `base_filename`.
:param max_bytes:
The size threshold at which a new log file shall be created. If set to 0, no
rollover will be performed.
The size threshold at which a new log file shall be created. If set
less than or equal to 0, no rollover will be performed.
:param delta_t:
The elapsed time threshold at which a new log file shall be
created. If set less than or equal to 0, no rollover will be
performed.
"""
super().__init__(*args, **kwargs)

self.base_filename = os.path.abspath(base_filename)
self.max_bytes = max_bytes

# Rotation parameters
self.max_bytes = max_bytes # Maximum bytes for rotation (bytes)
self.delta_t = delta_t # Time difference between rotation (seconds)

self._writer = self._get_new_writer(self.base_filename)

def should_rollover(self, msg: Message) -> bool:
if self.max_bytes <= 0:
# Check to see if a file rollover should occur based on file size
# (bytes) and elapsed time (seconds) since last rollover.
if self.max_bytes <= 0 and self.delta_t <= 0:
return False

if self.writer.file_size() >= self.max_bytes:
# Check to see if the file size is greater than max bytes
if self.writer.file_size() >= self.max_bytes > 0:
return True
# Check to see if elapsed time is greater than delta_t
if time.time() - self.last_rollover_time > self.delta_t > 0:
return True

return False

def do_rollover(self) -> None:
# Perform the file rollover.
if self.writer:
self.writer.stop()

# Reset the time since last rollover
self.last_rollover_time = time.time()

sfn = self.base_filename
dfn = self.rotation_filename(self._default_name())
self.rotate(sfn, dfn)
Expand All @@ -350,11 +375,77 @@ def _default_name(self) -> StringPathLike:
"""Generate the default rotation filename."""
path = pathlib.Path(self.base_filename)
new_name = (
path.stem.split(".")[0]
path.stem
+ "_"
+ datetime.now().strftime("%Y-%m-%dT%H%M%S")
+ "_"
+ f"#{self.rollover_count:03}"
+ "".join(path.suffixes[-2:])
+ path.suffix
)
return str(path.parent / new_name)


class SizedRotatingLogger(RotatingLogger):
"""Log CAN messages to a sequence of files with a given maximum size.

The logger creates a log file with the given `base_filename`. When the
size threshold is reached the current log file is closed and renamed
by adding a timestamp and the rollover count. A new log file is then
created and written to.

This behavior can be customized by setting the :attr:`namer` and
:attr:`rotator` attribute.

Example::

from can import Notifier, SizedRotatingLogger
from can.interfaces.vector import VectorBus

bus = VectorBus(channel=[0], app_name="CANape", fd=True)

logger = SizedRotatingLogger(
base_filename="my_logfile.asc",
max_bytes=5 * 1024 ** 2, # =5MB
)
logger.rollover_count = 23 # start counter at 23

notifier = Notifier(bus=bus, listeners=[logger])

The SizedRotatingLogger currently supports the formats
* .asc: :class:`can.ASCWriter`
* .blf :class:`can.BLFWriter`
* .csv: :class:`can.CSVWriter`
* .log :class:`can.CanutilsLogWriter`
* .txt :class:`can.Printer` (if pointing to a file)

.. note::
The :class:`can.SqliteWriter` is not supported yet.

The log files on disk may be incomplete due to buffering until
:meth:`~can.Listener.stop` is called.
"""

def __init__(
self,
base_filename: StringPathLike,
max_bytes: int = 0,
*args: Any,
**kwargs: Any,
) -> None:
"""
:param base_filename:
A path-like object for the base filename. The log file format is defined by
the suffix of `base_filename`.
:param max_bytes:
The size threshold at which a new log file shall be created. If set to 0, no
rollover will be performed.
"""
# This object is deprecated as of 4.1 and will be removed in 5.0.
warnings.simplefilter("always", DeprecationWarning)
warnings.warn(
"SizedRotatingLogger is being replaced with the "
"generalized RotatingLogger in python-can 5.0.",
DeprecationWarning,
)
# Initialize self as a RotatingLogger
super().__init__(base_filename, *args, max_bytes=max_bytes, **kwargs)
16 changes: 13 additions & 3 deletions can/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import can
from can.io import BaseRotatingLogger
from can.io.generic import MessageWriter
from . import Bus, BusState, Logger, SizedRotatingLogger
from . import Bus, BusState, Logger, RotatingLogger
from .typechecking import CanFilter, CanFilters


Expand Down Expand Up @@ -196,7 +196,16 @@ def main() -> None:
help="Maximum file size in bytes (or for the case of blf, maximum "
"buffer size before compression and flush to file). Rotate log "
"file when size threshold is reached.",
default=None,
default=0,
)

parser.add_argument(
"-t",
"--time_difference",
dest="delta_t",
type=int,
help="Time difference in seconds between file rollover.",
default=0,
)

parser.add_argument(
Expand Down Expand Up @@ -239,9 +248,10 @@ def main() -> None:

logger: Union[MessageWriter, BaseRotatingLogger]
if results.file_size:
logger = SizedRotatingLogger(
logger = RotatingLogger(
base_filename=results.log_file,
max_bytes=results.file_size,
delta_t=results.delta_t,
append=results.append,
**additional_config,
)
Expand Down
2 changes: 1 addition & 1 deletion test/test_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def test_log_virtual_sizedlogger(self):
self.MockLoggerUse = self.MockLoggerSized
self.loggerToUse = self.mock_logger_sized

sys.argv = self.baseargs + ["--file_size", "1000000"]
sys.argv = self.baseargs + ["-f file.log"] + ["--file_size", "1000000"]
can.logger.main()
self.assertSuccessfullCleanup()
self.mock_logger_sized.assert_called_once()
Expand Down