task-logging 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- task_logging/__init__.py +20 -0
- task_logging/models.py +45 -0
- task_logging/py.typed +0 -0
- task_logging/task_logger.py +438 -0
- task_logging/task_logging_database_interface.py +19 -0
- task_logging-0.0.2.dist-info/METADATA +26 -0
- task_logging-0.0.2.dist-info/RECORD +9 -0
- task_logging-0.0.2.dist-info/WHEEL +4 -0
- task_logging-0.0.2.dist-info/licenses/LICENSE +21 -0
task_logging/__init__.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from .models import ContextLogMessage, ExceptionLogMessage, OneTaskLog, TaskLogIn
|
|
2
|
+
from .task_logger import (
|
|
3
|
+
ClassFunctionLogger,
|
|
4
|
+
FunctionLogger,
|
|
5
|
+
TaskLogger,
|
|
6
|
+
TaskLoggerFactory,
|
|
7
|
+
)
|
|
8
|
+
from .task_logging_database_interface import TaskLoggingDatabaseInterface
|
|
9
|
+
|
|
10
|
+
__all__: list[str] = [
|
|
11
|
+
"ClassFunctionLogger",
|
|
12
|
+
"ContextLogMessage",
|
|
13
|
+
"ExceptionLogMessage",
|
|
14
|
+
"FunctionLogger",
|
|
15
|
+
"OneTaskLog",
|
|
16
|
+
"TaskLogIn",
|
|
17
|
+
"TaskLogger",
|
|
18
|
+
"TaskLoggerFactory",
|
|
19
|
+
"TaskLoggingDatabaseInterface",
|
|
20
|
+
]
|
task_logging/models.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
# should be same with database_service.models.task_logging
|
|
8
|
+
# otherwise you will have to write a lot of code to convert between them
|
|
9
|
+
class ExceptionLogMessage(BaseModel):
|
|
10
|
+
name: str = Field(..., title="Exception Name", description="Exception Name")
|
|
11
|
+
details: str = Field(
|
|
12
|
+
..., title="Exception Details", description="Exception Details"
|
|
13
|
+
)
|
|
14
|
+
stack_trace: str = Field(..., title="Stack Trace", description="Stack Trace")
|
|
15
|
+
locals_dict: dict[str, Any] = Field(
|
|
16
|
+
..., title="Locals Dict", description="Locals Dict"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ContextLogMessage(BaseModel):
|
|
21
|
+
hostname: str = Field(..., title="Hostname", description="Hostname")
|
|
22
|
+
process_id: int = Field(..., title="Process ID", description="Process ID")
|
|
23
|
+
thread_name: str = Field(..., title="Thread Name", description="Thread Name")
|
|
24
|
+
module_name: str = Field(..., title="Module Name", description="Module Name")
|
|
25
|
+
function_name: str = Field(..., title="Function Name", description="Function Name")
|
|
26
|
+
line_no: int = Field(..., title="Line Number", description="Line Number")
|
|
27
|
+
filename: str = Field(..., title="Filename", description="Filename")
|
|
28
|
+
# function_args: str = Field(..., title="Function Args", description="Function Args")
|
|
29
|
+
thread_id: int = Field(..., title="Thread ID", description="Thread ID")
|
|
30
|
+
stack_depth: int = Field(..., title="Stack Depth", description="Stack Depth")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TaskLogIn(BaseModel):
|
|
34
|
+
level: str = Field(..., title="Log Level", description="Log Level")
|
|
35
|
+
message: str = Field(..., title="Message", description="Message")
|
|
36
|
+
ctx_msg: ContextLogMessage = Field(
|
|
37
|
+
..., title="Context Message", description="Context Message"
|
|
38
|
+
)
|
|
39
|
+
exc_msg: ExceptionLogMessage | None = Field(
|
|
40
|
+
title="Exception Message", description="Exception Message"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class OneTaskLog(TaskLogIn):
|
|
45
|
+
logged_at: datetime = Field(default=..., title="Timestamp", description="Timestamp")
|
task_logging/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import inspect
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import socket
|
|
6
|
+
import sys
|
|
7
|
+
import threading
|
|
8
|
+
|
|
9
|
+
# 就像一个门卫一样,守在函数的入口和出口,记录函数的执行时间和返回值
|
|
10
|
+
import time
|
|
11
|
+
import traceback
|
|
12
|
+
from collections.abc import Callable
|
|
13
|
+
from logging import Logger
|
|
14
|
+
from types import TracebackType
|
|
15
|
+
from typing import Any, ParamSpec, TypeVar, cast
|
|
16
|
+
|
|
17
|
+
from .models import ContextLogMessage, ExceptionLogMessage, TaskLogIn
|
|
18
|
+
from .task_logging_database_interface import TaskLoggingDatabaseInterface
|
|
19
|
+
|
|
20
|
+
P = ParamSpec("P") # Represents all parameters
|
|
21
|
+
R = TypeVar("R") # Represents return value
|
|
22
|
+
|
|
23
|
+
# 感觉这个不好使啊
|
|
24
|
+
# 因为每次都要在参数里面加上一个logger
|
|
25
|
+
# 应该可以实现一个 FunctionLogger
|
|
26
|
+
# 然后它的参数是一个logger
|
|
27
|
+
# 然后它有一个log的装饰器
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class FunctionLogger:
|
|
31
|
+
def __init__(self, logger: Logger) -> None:
|
|
32
|
+
self._logger: Logger = logger
|
|
33
|
+
|
|
34
|
+
# 必须定义在这个模块里面,才能正常工作!
|
|
35
|
+
# TODO:日志级别
|
|
36
|
+
def log_func(
|
|
37
|
+
self, level: int = logging.INFO
|
|
38
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
|
39
|
+
def log_func_impl(func: Callable[P, R]) -> Callable[P, R]:
|
|
40
|
+
"""
|
|
41
|
+
A decorator that logs the input parameters and return value of a function.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
@functools.wraps(wrapped=func)
|
|
45
|
+
def wrapper(*args, **kwargs) -> Any: # type: ignore
|
|
46
|
+
# Log function call with parameters
|
|
47
|
+
# The stacklevel parameter is designed to specify how many levels up the stack to look to find the source location of the logging call. By setting stacklevel=2, the logger will report the location of the call to the decorated function, not the location inside the decorator.
|
|
48
|
+
# 日志一定要选择既精简,又遍于阅读 搜索的方式
|
|
49
|
+
# 比如,这里有几种风格
|
|
50
|
+
# ENTER fund_name
|
|
51
|
+
# [ENTER] func_name
|
|
52
|
+
# ENTER [func_name]
|
|
53
|
+
# 额外的那个括号不仅没有什么用,还会让搜索变得复杂,比如我只想搜索ENTER 我只想搜索func_name
|
|
54
|
+
# 但是如果我想同时搜索 ENTER func_name就会变的复杂,因为[]在regex中是特殊字符,
|
|
55
|
+
# 单纯的大写已经足够醒目了,不需要额外的符号
|
|
56
|
+
|
|
57
|
+
# 现在要根据level来选择调用不同的日志函数,哦 fuck
|
|
58
|
+
# 这个要怎么写
|
|
59
|
+
# 这个函数可以写在 先不急
|
|
60
|
+
|
|
61
|
+
self._logger.log(
|
|
62
|
+
level=level,
|
|
63
|
+
msg=f"Enter {func.__name__}, args: {args}, kwargs: {kwargs}.",
|
|
64
|
+
# stacklevel=2,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
start_time = time.time() # Capture start time
|
|
68
|
+
result = func(*args, **kwargs)
|
|
69
|
+
end_time = time.time()
|
|
70
|
+
|
|
71
|
+
# Log function return value
|
|
72
|
+
execution_ms = (end_time - start_time) * 1000
|
|
73
|
+
self._logger.log(
|
|
74
|
+
level=level,
|
|
75
|
+
msg=f"EXIT {func.__name__}, return: {result}, cost: {execution_ms:.3f} ms.",
|
|
76
|
+
# stacklevel=2,
|
|
77
|
+
)
|
|
78
|
+
return result
|
|
79
|
+
|
|
80
|
+
return wrapper
|
|
81
|
+
|
|
82
|
+
return log_func_impl
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class ClassFunctionLogger:
|
|
86
|
+
"""
|
|
87
|
+
A decorator factory for logging class methods that have access to a class logger instance.
|
|
88
|
+
This decorator will use the class's logger attribute to log method entry and exit.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def __init__(self, logger_attr: str = "_logger") -> None:
|
|
92
|
+
"""
|
|
93
|
+
Initialize with the name of the logger attribute in the class.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
logger_attr: The attribute name of the logger in the class (default: "_logger")
|
|
97
|
+
"""
|
|
98
|
+
self._logger_attr = logger_attr
|
|
99
|
+
|
|
100
|
+
def log_func(
|
|
101
|
+
self, level: int = logging.INFO
|
|
102
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
|
103
|
+
"""
|
|
104
|
+
Create a decorator that logs method calls using the class's logger.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
level: The logging level to use
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
A decorator function
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
def decorator(func: Callable[P, R]) -> Callable[P, R]:
|
|
114
|
+
"""
|
|
115
|
+
Decorator that logs the input parameters and return value of a class method.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
@functools.wraps(wrapped=func)
|
|
119
|
+
def wrapper(self_obj: Any, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
120
|
+
# Get the logger from the class instance
|
|
121
|
+
# If not found, just call the function normally
|
|
122
|
+
if not hasattr(self_obj, self._logger_attr):
|
|
123
|
+
return func(self_obj, *args, **kwargs)
|
|
124
|
+
|
|
125
|
+
logger = getattr(self_obj, self._logger_attr)
|
|
126
|
+
|
|
127
|
+
# Log method entry
|
|
128
|
+
logger.log(
|
|
129
|
+
level=level,
|
|
130
|
+
msg=f"ENTER {func.__name__}, args: {args}, kwargs: {kwargs}.",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Execute the method and measure execution time
|
|
134
|
+
start_time = time.time()
|
|
135
|
+
result = func(self_obj, *args, **kwargs)
|
|
136
|
+
execution_ms = (time.time() - start_time) * 1000
|
|
137
|
+
|
|
138
|
+
# Log method exit
|
|
139
|
+
logger.log(
|
|
140
|
+
level=level,
|
|
141
|
+
msg=f"EXIT {func.__name__}, return: {result}, cost: {execution_ms:.3f} ms.",
|
|
142
|
+
)
|
|
143
|
+
return result
|
|
144
|
+
|
|
145
|
+
return wrapper # type: ignore
|
|
146
|
+
|
|
147
|
+
return decorator
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# TODO: 咱们把service给改成module名字?
|
|
151
|
+
class TaskLogger(logging.Logger):
|
|
152
|
+
# python的logger本来就有名字
|
|
153
|
+
def __init__(
|
|
154
|
+
self,
|
|
155
|
+
task_logging_db: TaskLoggingDatabaseInterface,
|
|
156
|
+
task_id: str,
|
|
157
|
+
service_name: str,
|
|
158
|
+
level: int = logging.NOTSET,
|
|
159
|
+
) -> None:
|
|
160
|
+
super().__init__(name=service_name, level=level)
|
|
161
|
+
self._service_name: str = service_name
|
|
162
|
+
self._task_id: str = task_id
|
|
163
|
+
self._db = task_logging_db
|
|
164
|
+
# 插入日志到数据库
|
|
165
|
+
# 标准库有关于日志级别的定义,那咱们就用这个了,也不需要自己来定义
|
|
166
|
+
|
|
167
|
+
def debug(self, msg, *args, **kwargs) -> None: # type: ignore
|
|
168
|
+
super().debug(msg, *args, **kwargs)
|
|
169
|
+
self._append_task_log(level=logging.DEBUG, message=msg)
|
|
170
|
+
|
|
171
|
+
def info(self, msg, *args, **kwargs) -> None: # type: ignore
|
|
172
|
+
super().info(msg, *args, **kwargs)
|
|
173
|
+
self._append_task_log(level=logging.INFO, message=msg)
|
|
174
|
+
|
|
175
|
+
def warning(self, msg, *args, **kwargs) -> None: # type: ignore
|
|
176
|
+
super().warning(msg, *args, **kwargs)
|
|
177
|
+
self._append_task_log(level=logging.WARNING, message=msg)
|
|
178
|
+
|
|
179
|
+
# warn is deprecated
|
|
180
|
+
|
|
181
|
+
def error(self, msg, *args, **kwargs) -> None: # type: ignore
|
|
182
|
+
super().error(msg, *args, **kwargs)
|
|
183
|
+
self._append_task_log(level=logging.ERROR, message=msg)
|
|
184
|
+
|
|
185
|
+
def exception(self, msg, *args, exc_info=True, **kwargs) -> None: # type: ignore
|
|
186
|
+
"""
|
|
187
|
+
Convenience method for logging an ERROR with exception information.
|
|
188
|
+
"""
|
|
189
|
+
self.error(msg, *args, exc_info=exc_info, **kwargs)
|
|
190
|
+
|
|
191
|
+
def critical(self, msg, *args, **kwargs) -> None: # type: ignore
|
|
192
|
+
super().critical(msg, *args, **kwargs)
|
|
193
|
+
self._append_task_log(level=logging.CRITICAL, message=msg)
|
|
194
|
+
|
|
195
|
+
def fatal(self, msg, *args, **kwargs) -> None: # type: ignore
|
|
196
|
+
"""
|
|
197
|
+
Don't use this method, use critical() instead.
|
|
198
|
+
"""
|
|
199
|
+
self.critical(msg, *args, **kwargs)
|
|
200
|
+
|
|
201
|
+
def log(self, level, msg, *args, **kwargs) -> None: # type: ignore
|
|
202
|
+
super().log(level, msg, *args, **kwargs)
|
|
203
|
+
self._append_task_log(level=level, message=msg)
|
|
204
|
+
|
|
205
|
+
# 其实都这样了,不如直接用一个函数来实现
|
|
206
|
+
# 但是不行,我们的函数必须是本模块的
|
|
207
|
+
# 不然get context就无法正常工作了
|
|
208
|
+
|
|
209
|
+
# 如果我把迭代器定义在这里怎么样?
|
|
210
|
+
|
|
211
|
+
# 咱们要不用一个Model来定义一下Exception
|
|
212
|
+
# 防止以后还想加东西,结果就要改函数签名
|
|
213
|
+
|
|
214
|
+
# 现在咱们可以去掉这两个函数
|
|
215
|
+
#
|
|
216
|
+
# def error_with_exception(
|
|
217
|
+
# self, msg: str, exc_msg: ExceptionLogMessage, *args, **kwargs
|
|
218
|
+
# ) -> None:
|
|
219
|
+
# self._append_exceptional_task_log(
|
|
220
|
+
# level=logging.ERROR,
|
|
221
|
+
# msg=msg,
|
|
222
|
+
# exc_msg=exc_msg,
|
|
223
|
+
# )
|
|
224
|
+
# super().critical(msg, *args, **kwargs)
|
|
225
|
+
|
|
226
|
+
# def critical_with_exception(
|
|
227
|
+
# self, msg: str, esc_msg: ExceptionLogMessage, *args, **kwargs
|
|
228
|
+
# ) -> None:
|
|
229
|
+
# self._append_exceptional_task_log(
|
|
230
|
+
# level=logging.CRITICAL,
|
|
231
|
+
# msg=msg,
|
|
232
|
+
# exc_msg=esc_msg,
|
|
233
|
+
# )
|
|
234
|
+
# super().critical(msg, *args, **kwargs)
|
|
235
|
+
|
|
236
|
+
def _append_task_log(self, level: int, message: str) -> None:
|
|
237
|
+
ctx_msg = self._get_context()
|
|
238
|
+
exc_msg = self._get_exception_log_message()
|
|
239
|
+
|
|
240
|
+
if self.isEnabledFor(level=level):
|
|
241
|
+
self._db.append_task_log(
|
|
242
|
+
service_name=self._service_name,
|
|
243
|
+
task_id=self._task_id,
|
|
244
|
+
task_log=TaskLogIn(
|
|
245
|
+
level=logging.getLevelName(level=level),
|
|
246
|
+
message=message,
|
|
247
|
+
ctx_msg=ctx_msg,
|
|
248
|
+
exc_msg=exc_msg,
|
|
249
|
+
),
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# def _append_exceptional_task_log(
|
|
253
|
+
# self, level: int, msg: str, exc_msg: ExceptionLogMessage
|
|
254
|
+
# ) -> None:
|
|
255
|
+
# self._db.append_exception_task_log(
|
|
256
|
+
# service_name=self._service_name,
|
|
257
|
+
# task_id=self._task_id,
|
|
258
|
+
# log_level=logging.getLevelName(level=level),
|
|
259
|
+
# message=msg,
|
|
260
|
+
# exc_msg=exc_msg,
|
|
261
|
+
# )
|
|
262
|
+
|
|
263
|
+
# 这里应该返回异常信息的类就行了
|
|
264
|
+
def _get_exception_log_message(self) -> ExceptionLogMessage | None:
|
|
265
|
+
"""
|
|
266
|
+
在 except 块中调用时,返回包含异常实例、类名和完整错误堆栈的字典。
|
|
267
|
+
若不在异常上下文中调用,抛出 ValueError。
|
|
268
|
+
"""
|
|
269
|
+
# # 获取当前异常信息
|
|
270
|
+
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
271
|
+
|
|
272
|
+
if not exc_type:
|
|
273
|
+
return None
|
|
274
|
+
|
|
275
|
+
# # 确保在异常上下文中调用
|
|
276
|
+
# if exc_type is None:
|
|
277
|
+
# # 倒也不用
|
|
278
|
+
# # 如果不是在异常上下文中,那么就不管这个了
|
|
279
|
+
# # raise ValueError("log_error() must be called within an except block")
|
|
280
|
+
# return None
|
|
281
|
+
|
|
282
|
+
# # 生成完整的错误堆栈字符串
|
|
283
|
+
stack_trace = "".join(
|
|
284
|
+
traceback.format_exception(exc_type, exc_value, exc_traceback)
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
"""
|
|
288
|
+
获取包含异常信息及触发点上下文的日志消息
|
|
289
|
+
返回包含以下信息的对象:
|
|
290
|
+
- 异常类名
|
|
291
|
+
- 异常详细信息
|
|
292
|
+
- 完整堆栈跟踪
|
|
293
|
+
- 异常触发点的全局变量(快照)
|
|
294
|
+
- 异常触发点的局部变量(快照)
|
|
295
|
+
"""
|
|
296
|
+
# exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
297
|
+
|
|
298
|
+
# 获取最底层的异常堆栈帧
|
|
299
|
+
deepest_traceback = exc_traceback
|
|
300
|
+
while cast(TracebackType, deepest_traceback).tb_next:
|
|
301
|
+
deepest_traceback = cast(TracebackType, deepest_traceback).tb_next
|
|
302
|
+
|
|
303
|
+
# 获取异常触发点的帧信息
|
|
304
|
+
exception_frame = cast(TracebackType, deepest_traceback).tb_frame
|
|
305
|
+
|
|
306
|
+
# 获取触发点的变量信息(转换为字典避免引用问题)
|
|
307
|
+
# frame_globals = (
|
|
308
|
+
# dict(exception_frame.f_globals) if exception_frame.f_globals else {}
|
|
309
|
+
# )
|
|
310
|
+
frame_locals: dict[str, Any] = (
|
|
311
|
+
dict(exception_frame.f_locals) if exception_frame.f_locals else {}
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# 把这个frame全部变成str
|
|
315
|
+
# 因为有些变量是无法json化的 直接写入 pydantic model 话,在执行json化的时候会报错
|
|
316
|
+
frame_locals_str = {k: repr(v) for k, v in frame_locals.items()}
|
|
317
|
+
|
|
318
|
+
# print("-----------------------------------------")
|
|
319
|
+
# # globals几乎没用,而且很长很长
|
|
320
|
+
# print("frame_globals:", frame_globals)
|
|
321
|
+
# # frame_locals: {'a': 1, 'b': 2} 这个非常有用了
|
|
322
|
+
# print("frame_locals:", frame_locals)
|
|
323
|
+
# print("-----------------------------------------")
|
|
324
|
+
|
|
325
|
+
# return ExceptionLogMessage(
|
|
326
|
+
# name=exc_type.__name__,
|
|
327
|
+
# details=str(exc_value),
|
|
328
|
+
# stack_trace="".join(traceback.format_exception(exc_type, exc_value, exc_traceback)),
|
|
329
|
+
# globals=frame_globals,
|
|
330
|
+
# locals=frame_locals
|
|
331
|
+
# )
|
|
332
|
+
|
|
333
|
+
return ExceptionLogMessage(
|
|
334
|
+
name=exc_type.__name__,
|
|
335
|
+
details=str(exc_value),
|
|
336
|
+
stack_trace=stack_trace,
|
|
337
|
+
# locals_dict=frame_locals,
|
|
338
|
+
locals_dict=frame_locals_str,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
# 这个函数不容易测试,因为只有从log进行测试才能成功
|
|
342
|
+
# 这个设计如果可以改进一下就好了
|
|
343
|
+
# 我知道了,应该实现一个函数
|
|
344
|
+
# 教过 get context
|
|
345
|
+
def _get_context(self) -> ContextLogMessage:
|
|
346
|
+
# 不断的向上寻找
|
|
347
|
+
# 知道找到
|
|
348
|
+
stacks = inspect.stack(context=0)
|
|
349
|
+
# 从上往下找 调用栈是随着调用顺序从下往上的
|
|
350
|
+
# 所以我们从上往下找
|
|
351
|
+
# 不管怎么说
|
|
352
|
+
# 调用我们的函数就是logger里面的那几个
|
|
353
|
+
# 哪些接口是固定的
|
|
354
|
+
|
|
355
|
+
ctx_msg = ContextLogMessage(
|
|
356
|
+
hostname=socket.gethostname(),
|
|
357
|
+
process_id=os.getpid(),
|
|
358
|
+
thread_name=threading.current_thread().name,
|
|
359
|
+
filename="",
|
|
360
|
+
module_name="",
|
|
361
|
+
function_name="",
|
|
362
|
+
line_no=-1,
|
|
363
|
+
# Thread identifier of this thread or None if it has not been started.
|
|
364
|
+
#
|
|
365
|
+
# This is a nonzero integer. See the get_ident() function. Thread
|
|
366
|
+
# identifiers may be recycled when a thread exits and another thread is
|
|
367
|
+
# created. The identifier is available even after the thread has exited.
|
|
368
|
+
thread_id=threading.current_thread().ident or 0,
|
|
369
|
+
stack_depth=len(stacks),
|
|
370
|
+
# function_args="",
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
logger_module = self.__class__.__module__ # 获取Logger所在模块名
|
|
374
|
+
|
|
375
|
+
# 遍历调用栈,寻找第一个非Logger模块的栈帧
|
|
376
|
+
for frame_info in inspect.stack(context=0):
|
|
377
|
+
frame_module = frame_info.frame.f_globals.get("__name__", "")
|
|
378
|
+
if frame_module == logger_module:
|
|
379
|
+
continue # 跳过Logger自身模块的帧
|
|
380
|
+
|
|
381
|
+
# 找到调用者帧,填充信息
|
|
382
|
+
ctx_msg.filename = frame_info.filename
|
|
383
|
+
ctx_msg.module_name = frame_module
|
|
384
|
+
ctx_msg.function_name = frame_info.function
|
|
385
|
+
ctx_msg.line_no = frame_info.lineno
|
|
386
|
+
break
|
|
387
|
+
|
|
388
|
+
# for i, stack in enumerate(stacks):
|
|
389
|
+
# if stack.function in [
|
|
390
|
+
# "info",
|
|
391
|
+
# "error",
|
|
392
|
+
# "warning",
|
|
393
|
+
# "debug",
|
|
394
|
+
# "critical",
|
|
395
|
+
# ]:
|
|
396
|
+
# # 如果下一层是wrapper 那就就下去两层
|
|
397
|
+
|
|
398
|
+
# # 这个时候下一层就是
|
|
399
|
+
# if i + 1 >= len(stacks):
|
|
400
|
+
# break
|
|
401
|
+
|
|
402
|
+
# frame = stacks[i + 1]
|
|
403
|
+
# if frame.function == "wrapper" and i + 2 < len(stacks):
|
|
404
|
+
# frame = stacks[i + 2]
|
|
405
|
+
# # # print(frame)
|
|
406
|
+
# # print(frame.filename)
|
|
407
|
+
# # print(frame.function)
|
|
408
|
+
# # print(frame.lineno)
|
|
409
|
+
# # print(frame.frame.f_globals["__name__"])
|
|
410
|
+
# # # 我保留可以获得全部局部变量的能力,就先不写了
|
|
411
|
+
# # # 等0.2.0 写成 enter exit log
|
|
412
|
+
# # # 这个其实也不重要了
|
|
413
|
+
# # print(frame.frame.f_locals)
|
|
414
|
+
# ctx_msg.module_name = frame.frame.f_globals["__name__"]
|
|
415
|
+
# ctx_msg.function_name = frame.function
|
|
416
|
+
# ctx_msg.line_number = frame.lineno
|
|
417
|
+
# ctx_msg.filename = frame.filename
|
|
418
|
+
|
|
419
|
+
# break
|
|
420
|
+
# # else:
|
|
421
|
+
# # 找不到函数的调用信息
|
|
422
|
+
# # 那么就只天蝎hostnane pid tid 即可
|
|
423
|
+
|
|
424
|
+
return ctx_msg
|
|
425
|
+
|
|
426
|
+
# 这个函数大概是通过栈来实现的
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
class TaskLoggerFactory:
|
|
430
|
+
def __init__(self, task_logging_db: TaskLoggingDatabaseInterface) -> None:
|
|
431
|
+
self._task_logging_db = task_logging_db
|
|
432
|
+
|
|
433
|
+
def new(self, service_name: str, task_id: str) -> TaskLogger:
|
|
434
|
+
return TaskLogger(
|
|
435
|
+
task_logging_db=self._task_logging_db,
|
|
436
|
+
service_name=service_name,
|
|
437
|
+
task_id=task_id,
|
|
438
|
+
)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
from .models import OneTaskLog, TaskLogIn
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TaskLoggingDatabaseInterface(ABC):
|
|
7
|
+
@abstractmethod
|
|
8
|
+
def append_task_log(
|
|
9
|
+
self, service_name: str, task_id: str, task_log: TaskLogIn
|
|
10
|
+
) -> None:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def get_all_logs(self, service_name: str, task_id: str) -> list[OneTaskLog]:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def delete_all_logs(self, service_name: str, task_id: str) -> None:
|
|
19
|
+
pass
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: task-logging
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Project-URL: Homepage, https://github.com/im-zhong/task-logging
|
|
5
|
+
Project-URL: Repository, https://github.com/im-zhong/task-logging
|
|
6
|
+
Project-URL: Issues, https://github.com/im-zhong/task-logging/issues
|
|
7
|
+
Author-email: zhangzhong <im.zhong@outlook.com>
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: logging,task
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Requires-Python: <4.0,>=3.12
|
|
15
|
+
Requires-Dist: pydantic<3.0.0,>=2.10.6
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# Task Logging
|
|
19
|
+
|
|
20
|
+
## Introduction
|
|
21
|
+
|
|
22
|
+
Task logging is a library for logging in the distributed task-based system.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
`pip install task-logging`
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
task_logging/__init__.py,sha256=HB27U14nngJiITyqxPgiCDzCU_zjv2kbgkLedUBTczQ,510
|
|
2
|
+
task_logging/models.py,sha256=cwrF6jKs36FsW87kif0hCJNwNq-_xgCAdWVDcvmiaIE,2007
|
|
3
|
+
task_logging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
task_logging/task_logger.py,sha256=IK2grj69ogCmCHjQQigi2pSKaUeQao3JYCWYn5Aicy0,16629
|
|
5
|
+
task_logging/task_logging_database_interface.py,sha256=NurssvKIMjsTaMTgsdfNjCoZ7G0StWeUO5AWhVvMcko,483
|
|
6
|
+
task_logging-0.0.2.dist-info/METADATA,sha256=_-1A8JDFa67qGUCaBdufMdqhGcAmSXFSoGWRqLWAAE8,808
|
|
7
|
+
task_logging-0.0.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
8
|
+
task_logging-0.0.2.dist-info/licenses/LICENSE,sha256=IeR4_rPBHfdP9ZKD2E6_5lsQwl8ugMUtIA2YVdVmGVY,1065
|
|
9
|
+
task_logging-0.0.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 im-zhong
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|