feat(utils): add universal timer decorator with loguru integration

This commit is contained in:
jinli.yl 2025-12-30 17:38:58 +08:00
parent da8260e5c0
commit cb907ea201
4 changed files with 144 additions and 0 deletions

9
docs/deprecated.txt Normal file
View file

@ -0,0 +1,9 @@
from loguru import logger
用英文注释完善module/class/function docstring要一句话简洁不要变更代码
C0114: Missing module docstring (missing-module-docstring)
C0115: Missing class docstring (missing-class-docstring)
C0116: Missing function or method docstring (missing-function-docstring)
done: { for f in ./*.py; do [[ "$f" != "./__init__.py" ]] && grep -v '^[[:space:]]*#' "$f"; done; } | pbcopy
然后是一个完整的tests但是不要用其他的包只是test开头的函数或者类要求from loguru import logger

View file

@ -0,0 +1,5 @@
"""utils"""
from .timer import timer
__all__ = ["timer"]

View file

@ -0,0 +1,66 @@
"""
Utility module for timing function execution with log metadata preservation.
"""
import functools
import inspect
import time
from typing import Any, Callable, TypeVar, cast
from loguru import logger
# Type variable to preserve the signature of the decorated callable
F = TypeVar("F", bound=Callable[..., Any])
def timer(func: F) -> F:
"""
Decorator that logs execution time and patches log records with original function metadata.
"""
# Extract original function metadata to ensure logs point to the correct source
func_name = func.__name__
try:
# Retrieve the source file path and the starting line number
file_path = inspect.getsourcefile(func) or "unknown"
_, line_no = inspect.getsourcelines(func)
except Exception:
file_path = "unknown"
line_no = 0
def patcher(record):
"""Modifies the log record to reflect the decorated function's location."""
record["function"] = func_name
record["file"].name = file_path.split("/")[-1]
record["file"].path = file_path
record["line"] = line_no
@functools.wraps(func)
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
"""Timer wrapper for asynchronous functions."""
start_time = time.perf_counter()
try:
return await func(*args, **kwargs)
finally:
duration = time.perf_counter() - start_time
# Use patch to inject metadata instead of relying on stack depth
logger.patch(patcher).info(
"========== cost={:.6f}s ==========",
duration
)
@functools.wraps(func)
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
"""Timer wrapper for synchronous functions."""
start_time = time.perf_counter()
try:
return func(*args, **kwargs)
finally:
duration = time.perf_counter() - start_time
logger.patch(patcher).info(
"========== cost={:.6f}s ==========",
duration
)
if inspect.iscoroutinefunction(func):
return cast(F, async_wrapper)
return cast(F, sync_wrapper)

64
tests/test_timer.py Normal file
View file

@ -0,0 +1,64 @@
"""
This module provides a suite of tests to verify universal timer decorator functionality using loguru.
"""
import asyncio
import time
from loguru import logger
from reme_ai.core.utils import timer
@timer
def test_sync_function(seconds: float) -> str:
"""Tests timing of a standard synchronous function."""
time.sleep(seconds)
return "sync done"
@timer
async def test_async_function(seconds: float) -> str:
"""Tests timing of an asynchronous function."""
await asyncio.sleep(seconds)
return "async done"
class TestMemberMethods:
"""Container class to test class method decoration."""
@timer
def test_sync_method(self, seconds: float) -> None:
"""Tests a synchronous instance method."""
time.sleep(seconds)
@timer
async def test_async_method(self, seconds: float) -> None:
"""Tests an asynchronous instance method."""
await asyncio.sleep(seconds)
def run_all_tests() -> None:
"""
Manual test runner.
Notice that the logs will now point to the line numbers below
(where the function is actually called).
"""
logger.info("Starting tests and verifying stack trace...")
# 1. Test Sync Function
test_sync_function(0.1)
# 2. Test Async Function
asyncio.run(test_async_function(0.1))
# 3. Test Class Methods
tester = TestMemberMethods()
tester.test_sync_method(0.05)
asyncio.run(tester.test_async_method(0.05))
logger.success("All tests completed.")
if __name__ == "__main__":
run_all_tests()