Skip to content

Commit

Permalink
Fix logger implementation errors and standardize on new loguru system…
Browse files Browse the repository at this point in the history
… and expectations
  • Loading branch information
markurtz committed Jul 19, 2024
1 parent 7aed12e commit 663b3e7
Show file tree
Hide file tree
Showing 3 changed files with 232 additions and 0 deletions.
8 changes: 8 additions & 0 deletions src/guidellm/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""
Guidellm is a package that provides an easy and intuitive interface for
evaluating and benchmarking large language models (LLMs).
"""

from .logger import LoggerConfig, configure_logger, logger

__all__ = ["logger", "configure_logger", "LoggerConfig"]
119 changes: 119 additions & 0 deletions src/guidellm/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"""
Logger configuration for GuideLLM.
This module provides a flexible logging configuration using the loguru library.
It supports console and file logging with options to configure via environment
variables or direct function calls.
Environment Variables:
- GUIDELLM_LOG_DISABLED: Disable logging (default: false).
- GUIDELLM_CLEAR_LOGGERS: Clear existing loggers from loguru (default: true).
- GUIDELLM_LOG_LEVEL: Log level for console logging
(default: none, options: DEBUG, INFO, WARNING, ERROR, CRITICAL).
- GUIDELLM_LOG_FILE: Path to the log file for file logging
(default: guidellm.log if log file level set else none)
- GUIDELLM_LOG_FILE_LEVEL: Log level for file logging
(default: INFO if log file set else none).
Usage:
from guidellm import logger, configure_logger, LoggerConfig
# Configure metrics with default settings
configure_logger(
config=LoggerConfig(
disabled=False,
clear_loggers=True,
console_log_level="DEBUG",
log_file=None,
log_file_level=None,
)
)
logger.debug("This is a debug message")
logger.info("This is an info message")
"""

import os
import sys
from dataclasses import dataclass
from typing import Optional

from loguru import logger

__all__ = ["LoggerConfig", "configure_logger", "logger"]


@dataclass
class LoggerConfig:
disabled: bool = False
clear_loggers: bool = True
console_log_level: Optional[str] = "INFO"
log_file: Optional[str] = None
log_file_level: Optional[str] = None


def configure_logger(config: Optional[LoggerConfig] = None):
"""
Configure the metrics for LLM Compressor.
This function sets up the console and file logging
as per the specified or default parameters.
Note: Environment variables take precedence over the function parameters.
:param config: The configuration for the logger to use.
:type config: LoggerConfig
"""

_ENV_CONFIG = LoggerConfig(
disabled=os.getenv("GUIDELLM_LOG_DISABLED") == "true",
clear_loggers=os.getenv("GUIDELLM_CLEAR_LOGGERS") == "true",
console_log_level=os.getenv("GUIDELLM_LOG_LEVEL"),
log_file=os.getenv("GUIDELLM_LOG_FILE"),
log_file_level=os.getenv("GUIDELLM_LOG_FILE_LEVEL"),
)

if not config:
config = LoggerConfig()
# override from environment variables, if set
logger_config = LoggerConfig(
disabled=_ENV_CONFIG.disabled or config.disabled,
console_log_level=_ENV_CONFIG.console_log_level or config.console_log_level,
log_file=_ENV_CONFIG.log_file or config.log_file,
log_file_level=_ENV_CONFIG.log_file_level or config.log_file_level,
)

if logger_config.disabled:
logger.disable("guidellm")
return

logger.enable("guidellm")

if logger_config.clear_loggers:
logger.remove()

if logger_config.console_log_level:
# log as a human readable string with the time, function, level, and message
logger.add(
sys.stdout,
level=logger_config.console_log_level.upper(),
format="{time} | {function} | {level} - {message}",
)

if logger_config.log_file or logger_config.log_file_level:
log_file = logger_config.log_file or "guidellm.log"
log_file_level = logger_config.log_file_level or "INFO"
# log as json to the file for easier parsing
logger.add(log_file, level=log_file_level.upper(), serialize=True)


# invoke logger setup on import with default values enabling console logging with INFO
# and disabling file logging
configure_logger(
config=LoggerConfig(
disabled=False,
clear_loggers=True,
console_log_level="INFO",
log_file=None,
log_file_level=None,
)
)
105 changes: 105 additions & 0 deletions tests/unit/test_logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import pytest

from guidellm import LoggerConfig, configure_logger, logger


@pytest.fixture(autouse=True)
def reset_logger():
# Ensure logger is reset before each test
logger.remove()
yield
logger.remove()


def test_default_logger_settings(capsys):
configure_logger()

# Default settings should log to console with INFO level and no file logging
logger.info("Info message")
logger.debug("Debug message")

captured = capsys.readouterr()
assert captured.out.count("Info message") == 1
assert "Debug message" not in captured.out


def test_configure_logger_console_settings(capsys):
# Test configuring the logger to change console log level
config = LoggerConfig(console_log_level="DEBUG")
configure_logger(config=config)
logger.info("Info message")
logger.debug("Debug message")

captured = capsys.readouterr()
assert captured.out.count("Info message") == 1
assert captured.out.count("Debug message") == 1


def test_configure_logger_file_settings(tmp_path):
# Test configuring the logger to log to a file
log_file = tmp_path / "test.log"
config = LoggerConfig(log_file=str(log_file), log_file_level="DEBUG")
configure_logger(config=config)
logger.info("Info message")
logger.debug("Debug message")

with open(log_file, "r") as f:
log_contents = f.read()
assert log_contents.count('"message": "Info message"') == 1
assert log_contents.count('"message": "Debug message"') == 1


def test_configure_logger_console_and_file(capsys, tmp_path):
# Test configuring the logger to change both console and file settings
log_file = tmp_path / "test.log"
config = LoggerConfig(
console_log_level="ERROR", log_file=str(log_file), log_file_level="INFO"
)
configure_logger(config=config)
logger.info("Info message")
logger.error("Error message")

captured = capsys.readouterr()
assert "Info message" not in captured.out
assert captured.out.count("Error message") == 1

with open(log_file, "r") as f:
log_contents = f.read()
assert log_contents.count('"message": "Info message"') == 1
assert log_contents.count('"message": "Error message"') == 1


def test_environment_variable_override(monkeypatch, capsys, tmp_path):
# Test environment variables override settings
monkeypatch.setenv("GUIDELLM_LOG_LEVEL", "ERROR")
monkeypatch.setenv("GUIDELLM_LOG_FILE", str(tmp_path / "env_test.log"))
monkeypatch.setenv("GUIDELLM_LOG_FILE_LEVEL", "DEBUG")

configure_logger(config=LoggerConfig())
logger.info("Info message")
logger.error("Error message")
logger.debug("Debug message")

captured = capsys.readouterr()
assert "Info message" not in captured.out
assert captured.out.count("Error message") == 1
assert "Debug message" not in captured.out

with open(tmp_path / "env_test.log", "r") as f:
log_contents = f.read()
assert log_contents.count('"message": "Error message"') == 1
assert log_contents.count('"message": "Info message"') == 1
assert log_contents.count('"message": "Debug message"') == 1


def test_environment_variable_disable_logging(monkeypatch, capsys):
# Test environment variable to disable logging
monkeypatch.setenv("GUIDELLM_LOG_DISABLED", "true")

configure_logger(config=LoggerConfig())
logger.info("Info message")
logger.error("Error message")

captured = capsys.readouterr()
assert captured.out == ""
assert captured.err == ""

0 comments on commit 663b3e7

Please sign in to comment.