Skip to content

Commit

Permalink
Merge branch 'logging-improvements' into develop
Browse files Browse the repository at this point in the history
Conflicts:
	src/sensai/util/logging.py
  • Loading branch information
opcode81 committed Nov 29, 2024
2 parents 382248c + 7ffb9aa commit 5bb38eb
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 29 deletions.
7 changes: 7 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[*]
charset = utf-8
indent_size = 4
indent_style = space
insert_final_newline = false
max_line_length = 140
tab_width = 4
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,23 @@
is well-tested.
* `util`:
* `util.version`: Add methods `Version.is_at_most` and `Version.is_equal`
* `util.logging`:
* `add_memory_logger` now returns the logger instance, which can be queried to retrieve the log (see breacking
change below)
* Add class `MemoryLoggerContext`, which be used in conjunction with Python's `with` statement to record logs
* `evaluation`:
* `EvaluationResultCollector`: Add method `is_plot_creation_enabled`
* `data`:
* `InputOutputData`: Add method `to_df`
* Add module `data.dataset` containing sample datasets (mainly for demonstration purposes)
* `tracking`:
* `mlflow_tracking`: Option `add_log_to_all_contexts` now stores only the logs of each model's training process (instead of the entire
process beginning with the instantiation of the experiment)
### Breaking Changes:

* `util.logging`: Change `add_memory_logger` to no longer define a global logger, but return the handler (an instance of
`MemoryStramHandler`) instead. Consequently removed method `get_memory_log` as it is no longer needed (use the handler's method
`get_log` instead).

### Fixes:

Expand Down
14 changes: 7 additions & 7 deletions src/sensai/tracking/mlflow_tracking.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import re
from typing import Dict, Any
from typing import Dict, Any, Optional

import mlflow
from matplotlib import pyplot as plt
Expand All @@ -17,6 +17,7 @@ def __init__(self, name: str, experiment: "MLFlowExperiment", run_id=None, descr
else:
run = mlflow.start_run(run_name=name, description=description)
self.run = run
self.log_handler: Optional[logging.MemoryStreamHandler] = None

@staticmethod
def _metric_name(name: str):
Expand Down Expand Up @@ -51,16 +52,13 @@ def __init__(self, experiment_name: str, tracking_uri: str, additional_logging_v
:param additional_logging_values_dict:
:param context_prefix: a prefix to add to all contexts that are created within the experiment. This can be used to add
an identifier of a certain execution/run, such that the actual context name passed to `begin_context` can be concise (e.g. just model name).
:param add_log_to_all_contexts: whether to enable in-memory logging and add the resulting log file to all tracking contexts that
are generated for this experiment upon context exit (or process termination if it is not cleanly closed)
:param add_log_to_all_contexts: whether to enable in-memory logging and add the respective log to each context
"""
mlflow.set_tracking_uri(tracking_uri)
mlflow.set_experiment(experiment_name=experiment_name)
super().__init__(context_prefix=context_prefix, additional_logging_values_dict=additional_logging_values_dict)
self._run_name_to_id = {}
self.add_log_to_all_contexts = add_log_to_all_contexts
if self.add_log_to_all_contexts:
logging.add_memory_logger()

def _track_values(self, values_dict: Dict[str, Any]):
with mlflow.start_run():
Expand All @@ -75,11 +73,13 @@ def _create_tracking_context(self, name: str, description: str) -> MLFlowTrackin

def begin_context_for_model(self, model: VectorModelBase):
context = super().begin_context_for_model(model)
if self.add_log_to_all_contexts:
context.log_handler = logging.add_memory_logger()
context.track_tag("ModelClass", model.__class__.__name__)
return context

def end_context(self, instance: MLFlowTrackingContext):
print(f"end {instance}")
if self.add_log_to_all_contexts:
instance.track_text("log", logging.get_memory_log())
if instance.log_handler is not None:
instance.track_text("log", instance.log_handler.get_log())
super().end_context(instance)
76 changes: 54 additions & 22 deletions src/sensai/util/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging as lg
import sys
import time
from abc import ABC, abstractmethod
from datetime import datetime
from io import StringIO
from logging import *
Expand Down Expand Up @@ -142,15 +143,14 @@ def datetime_tag() -> str:

_fileLoggerPaths: List[str] = []
_isAtExitReportFileLoggerRegistered = False
_memoryLogStream: Optional[StringIO] = None


def _at_exit_report_file_logger():
for path in _fileLoggerPaths:
print(f"A log file was saved to {path}")


def add_file_logger(path, append=True, register_atexit=True):
def add_file_logger(path, append=True, register_atexit=True) -> FileHandler:
global _isAtExitReportFileLoggerRegistered
log.info(f"Logging to {path} ...")
mode = "a" if append else "w"
Expand All @@ -164,22 +164,24 @@ def add_file_logger(path, append=True, register_atexit=True):
return handler


def add_memory_logger() -> None:
class MemoryStreamHandler(StreamHandler):
def __init__(self, stream: StringIO):
super().__init__(stream)

def get_log(self) -> str:
stream: StringIO = self.stream
return stream.getvalue()


def add_memory_logger() -> MemoryStreamHandler:
"""
Enables in-memory logging (if it is not already enabled), i.e. all log statements are written to a memory buffer and can later be
read via function `get_memory_log()`
Adds an in-memory logger, i.e. all log statements are written to a memory buffer which can be retrieved
using the handler's `get_log` method.
"""
global _memoryLogStream
if _memoryLogStream is not None:
return
_memoryLogStream = StringIO()
handler = StreamHandler(_memoryLogStream)
handler = MemoryStreamHandler(StringIO())
handler.setFormatter(Formatter(_logFormat))
Logger.root.addHandler(handler)


def get_memory_log():
return _memoryLogStream.getvalue()
return handler


class StopWatch:
Expand Down Expand Up @@ -335,31 +337,61 @@ def __enter__(self):
return self


class FileLoggerContext:
class LoggerContext(ABC):
"""
A context handler to be used in conjunction with Python's `with` statement which enables file-based logging.
Base class for context handlers to be used in conjunction with Python's `with` statement.
"""
def __init__(self, path: str, append=True, enabled=True):

def __init__(self, enabled=True):
"""
:param path: the path to the log file
:param append: whether to append in case the file already exists; if False, always create a new file.
:param enabled: whether to actually perform any logging.
This switch allows the with statement to be applied regardless of whether logging shall be enabled.
"""
self.enabled = enabled
self.path = path
self.append = append
self._log_handler = None

@abstractmethod
def _create_log_handler(self) -> Handler:
pass

def __enter__(self):
if self.enabled:
self._log_handler = add_file_logger(self.path, append=self.append, register_atexit=False)
self._log_handler = self._create_log_handler()

def __exit__(self, exc_type, exc_value, traceback):
if self._log_handler is not None:
remove_log_handler(self._log_handler)


class FileLoggerContext(LoggerContext):
"""
A context handler to be used in conjunction with Python's `with` statement which enables file-based logging.
"""

def __init__(self, path: str, append=True, enabled=True):
"""
:param path: the path to the log file
:param append: whether to append in case the file already exists; if False, always create a new file.
:param enabled: whether to actually perform any logging.
This switch allows the with statement to be applied regardless of whether logging shall be enabled.
"""
self.path = path
self.append = append
super().__init__(enabled=enabled)

def _create_log_handler(self) -> Handler:
return add_file_logger(self.path, append=self.append, register_atexit=False)


class MemoryLoggerContext(LoggerContext):
"""
A context handler to be used in conjunction with Python's `with` statement which enables in-memory logging.
"""

def _create_log_handler(self) -> MemoryStreamHandler:
return add_memory_logger()


class LoggingDisabledContext:
"""
A context manager that will temporarily disable logging
Expand Down

0 comments on commit 5bb38eb

Please sign in to comment.