Skip to content
Merged
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
17 changes: 16 additions & 1 deletion agentrun/utils/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,20 @@

import logging
import os
import time

from dotenv import load_dotenv

load_dotenv()

UTC8_OFFSET_SECONDS = 8 * 60 * 60


def _utc8_converter(timestamp: float) -> time.struct_time:
"""Return a UTC+8 ``struct_time`` for logging.Formatter."""

return time.gmtime(timestamp + UTC8_OFFSET_SECONDS)


class CustomFormatter(logging.Formatter):
"""自定义日志格式化器 / Custom Log Formatter
Expand Down Expand Up @@ -48,11 +57,17 @@ def __init__(self) -> None:
f" {self.DIM}%(pathname)s:%(lineno)s{self.RESET}"
"\n%(message)s"
)
self._formatters[level] = logging.Formatter(fmt)
self._formatters[level] = self._create_formatter(fmt)
self._default = logging.Formatter(
"\n%(levelname)s [%(name)s] %(asctime)s"
" %(pathname)s:%(lineno)s\n%(message)s"
)
self._default.converter = _utc8_converter

def _create_formatter(self, fmt: str) -> logging.Formatter:
formatter = logging.Formatter(fmt)
formatter.converter = _utc8_converter
return formatter

def format(self, record: logging.LogRecord) -> str:
return self._formatters.get(record.levelname, self._default).format(
Expand Down
66 changes: 66 additions & 0 deletions tests/unittests/test_log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"""Unit tests for AgentRun SDK logging."""

import logging
import time

import pytest

from agentrun.utils.log import CustomFormatter, _utc8_converter


def make_record(level: int, level_name: str) -> logging.LogRecord:
record = logging.LogRecord(
name="agentrun-logger",
level=level,
pathname=__file__,
lineno=1,
msg="hello",
args=(),
exc_info=None,
)
record.created = 0
record.levelname = level_name
return record


def test_utc8_converter_is_independent_from_local_timezone():
assert _utc8_converter(0) == time.gmtime(8 * 60 * 60)


def test_custom_formatter_uses_utc8_for_all_inner_formatters():
formatter = CustomFormatter()
expected = time.gmtime(8 * 60 * 60)

for inner in formatter._formatters.values():
assert inner.converter(0) == expected
assert formatter._default.converter(0) == expected


@pytest.mark.parametrize(
("level", "level_name"),
[
(logging.DEBUG, "DEBUG"),
(logging.INFO, "INFO"),
(logging.WARNING, "WARNING"),
(logging.ERROR, "ERROR"),
(logging.CRITICAL, "CRITICAL"),
],
)
def test_custom_formatter_formats_known_levels_in_utc8(
level: int, level_name: str
):
formatter = CustomFormatter()

output = formatter.format(make_record(level, level_name))

assert "1970-01-01 08:00:00" in output
assert "1970-01-01 00:00:00" not in output


def test_custom_formatter_formats_fallback_level_in_utc8():
formatter = CustomFormatter()

output = formatter.format(make_record(5, "TRACE"))

assert "1970-01-01 08:00:00" in output
assert "1970-01-01 00:00:00" not in output
Loading