liblogging 0.1.16__tar.gz → 0.2.4__tar.gz
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.
- {liblogging-0.1.16 → liblogging-0.2.4}/PKG-INFO +1 -1
- liblogging-0.2.4/liblogging/collector/__init__.py +1 -0
- liblogging-0.2.4/liblogging/collector/mongo.py +118 -0
- {liblogging-0.1.16 → liblogging-0.2.4}/liblogging/logger.py +1 -1
- {liblogging-0.1.16 → liblogging-0.2.4}/liblogging/sending/kafka_service.py +10 -3
- {liblogging-0.1.16 → liblogging-0.2.4}/liblogging/sending/log_collector.py +10 -7
- {liblogging-0.1.16 → liblogging-0.2.4}/liblogging.egg-info/PKG-INFO +1 -1
- {liblogging-0.1.16 → liblogging-0.2.4}/liblogging.egg-info/SOURCES.txt +2 -0
- {liblogging-0.1.16 → liblogging-0.2.4}/liblogging.egg-info/entry_points.txt +1 -0
- {liblogging-0.1.16 → liblogging-0.2.4}/setup.py +4 -2
- {liblogging-0.1.16 → liblogging-0.2.4}/LICENSE +0 -0
- {liblogging-0.1.16 → liblogging-0.2.4}/README.md +0 -0
- {liblogging-0.1.16 → liblogging-0.2.4}/liblogging/__init__.py +0 -0
- {liblogging-0.1.16 → liblogging-0.2.4}/liblogging/sending/__init__.py +0 -0
- {liblogging-0.1.16 → liblogging-0.2.4}/liblogging/util.py +0 -0
- {liblogging-0.1.16 → liblogging-0.2.4}/liblogging.egg-info/dependency_links.txt +0 -0
- {liblogging-0.1.16 → liblogging-0.2.4}/liblogging.egg-info/requires.txt +0 -0
- {liblogging-0.1.16 → liblogging-0.2.4}/liblogging.egg-info/top_level.txt +0 -0
- {liblogging-0.1.16 → liblogging-0.2.4}/liblogging.egg-info/zip-safe +0 -0
- {liblogging-0.1.16 → liblogging-0.2.4}/setup.cfg +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
__author__ = "xi"
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from queue import Empty
|
|
9
|
+
from queue import Queue
|
|
10
|
+
from threading import Thread
|
|
11
|
+
from time import time
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from libdata import LazyMongoClient
|
|
15
|
+
from libdata.url import URL
|
|
16
|
+
from libentry import ArgumentParser
|
|
17
|
+
from pydantic import BaseModel
|
|
18
|
+
from pydantic import Field
|
|
19
|
+
|
|
20
|
+
from ..util import split_trace_id
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CollectorConfig(BaseModel):
|
|
24
|
+
mongo_url: str | URL
|
|
25
|
+
max_queue_size: int = Field(default=100_000, ge=1, le=100_000_000)
|
|
26
|
+
batch_size: int = Field(default=100, ge=1, le=10000)
|
|
27
|
+
wait_time: float = 0.2
|
|
28
|
+
max_wait_time: float = 0.5
|
|
29
|
+
|
|
30
|
+
def model_post_init(self, context: Any, /) -> None:
|
|
31
|
+
self.mongo_url = URL.ensure_url(self.mongo_url)
|
|
32
|
+
db, coll = self.mongo_url.get_database_and_table()
|
|
33
|
+
if db is None or coll is None:
|
|
34
|
+
raise ValueError("Both database and collection name should be given in the URL.")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def sending_loop(queue: Queue[dict], config: CollectorConfig):
|
|
38
|
+
client = LazyMongoClient(config.mongo_url)
|
|
39
|
+
|
|
40
|
+
batch_size = config.batch_size
|
|
41
|
+
wait_time = config.wait_time
|
|
42
|
+
max_wait_time = config.max_wait_time
|
|
43
|
+
|
|
44
|
+
while True:
|
|
45
|
+
buffer = [queue.get()]
|
|
46
|
+
|
|
47
|
+
t = time()
|
|
48
|
+
for _ in range(batch_size):
|
|
49
|
+
try:
|
|
50
|
+
buffer.append(queue.get(timeout=wait_time))
|
|
51
|
+
except Empty:
|
|
52
|
+
break
|
|
53
|
+
if time() - t > max_wait_time:
|
|
54
|
+
break
|
|
55
|
+
|
|
56
|
+
if buffer[-1] is not None:
|
|
57
|
+
client.insert_many(buffer)
|
|
58
|
+
else:
|
|
59
|
+
client.insert_many(buffer[:-1])
|
|
60
|
+
break
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def process_message(line):
|
|
64
|
+
message = json.loads(line)
|
|
65
|
+
trace_id = message.get("trace_id", "")
|
|
66
|
+
if trace_id:
|
|
67
|
+
_, chat_dict = split_trace_id(trace_id)
|
|
68
|
+
message.update({
|
|
69
|
+
"uid": chat_dict.get("uid", ""),
|
|
70
|
+
"session_id": chat_dict.get("session_id", ""),
|
|
71
|
+
"turn": chat_dict.get("turn", 0)
|
|
72
|
+
})
|
|
73
|
+
create_time = message.get("create_time")
|
|
74
|
+
create_date = datetime.strptime(create_time, "%Y-%m-%d %H:%M:%S.%f").strftime("%Y-%m-%d")
|
|
75
|
+
message.update({"create_date": create_date})
|
|
76
|
+
return trace_id, message
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def main():
|
|
80
|
+
parser = ArgumentParser()
|
|
81
|
+
parser.add_schema("config", CollectorConfig)
|
|
82
|
+
config: CollectorConfig = parser.parse_args().config
|
|
83
|
+
|
|
84
|
+
queue = Queue(config.max_queue_size)
|
|
85
|
+
|
|
86
|
+
sending_thread = Thread(
|
|
87
|
+
target=sending_loop,
|
|
88
|
+
kwargs=dict(queue=queue, config=config)
|
|
89
|
+
)
|
|
90
|
+
sending_thread.start()
|
|
91
|
+
|
|
92
|
+
while True:
|
|
93
|
+
try:
|
|
94
|
+
line = sys.stdin.readline()
|
|
95
|
+
if not line:
|
|
96
|
+
break
|
|
97
|
+
line = line.strip()
|
|
98
|
+
try:
|
|
99
|
+
trace_id, message = process_message(line)
|
|
100
|
+
queue.put(message)
|
|
101
|
+
print(message)
|
|
102
|
+
except ValueError:
|
|
103
|
+
print(line)
|
|
104
|
+
except EOFError:
|
|
105
|
+
print("End of input reached. Exiting...", file=sys.stderr)
|
|
106
|
+
break
|
|
107
|
+
except Exception as e:
|
|
108
|
+
print(f"Unexpected error: {e}", file=sys.stderr)
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
queue.put(None)
|
|
112
|
+
sending_thread.join()
|
|
113
|
+
print("Mongo collector exited.", file=sys.stderr)
|
|
114
|
+
return 0
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
raise SystemExit(main())
|
|
@@ -167,7 +167,7 @@ class ContextJSONFormatter(Formatter):
|
|
|
167
167
|
except TypeError:
|
|
168
168
|
log_data["message"] = str(log_data["message"])
|
|
169
169
|
output_log = json.dumps(log_data, ensure_ascii=False)
|
|
170
|
-
return output_log
|
|
170
|
+
return f"\n{output_log}\n"
|
|
171
171
|
|
|
172
172
|
def formatTime(self, record):
|
|
173
173
|
"""to match mysql 'datetime(3)' format"""
|
|
@@ -18,6 +18,8 @@ class KafkaService:
|
|
|
18
18
|
def __init__(self, config):
|
|
19
19
|
bootstrap_servers = config["bootstrap_servers"].split(",")
|
|
20
20
|
print(f"kafka config: {config}")
|
|
21
|
+
self.default_send_timeout = config.get("send_timeout_seconds", 2)
|
|
22
|
+
self.send_timeout_increment = config.get("send_timeout_increment", 1)
|
|
21
23
|
self.producer = KafkaProducer(
|
|
22
24
|
bootstrap_servers=bootstrap_servers,
|
|
23
25
|
key_serializer=lambda k: json.dumps(k).encode(),
|
|
@@ -44,22 +46,27 @@ class KafkaService:
|
|
|
44
46
|
message: Dict,
|
|
45
47
|
source: str,
|
|
46
48
|
key: str = None,
|
|
47
|
-
topic: str = None
|
|
49
|
+
topic: str = None,
|
|
50
|
+
timeout: float = None
|
|
48
51
|
):
|
|
49
52
|
if topic is None:
|
|
50
53
|
topic = self.topic
|
|
51
54
|
message["source"] = source
|
|
52
|
-
|
|
55
|
+
current_timeout = timeout or self.default_send_timeout
|
|
56
|
+
for attempt in range(1, 4):
|
|
53
57
|
try:
|
|
54
58
|
future = self.producer.send(
|
|
55
59
|
topic,
|
|
56
60
|
value=message,
|
|
57
61
|
key=key
|
|
58
62
|
)
|
|
59
|
-
future.get(timeout=
|
|
63
|
+
future.get(timeout=current_timeout)
|
|
64
|
+
print(f"Kafka send succeeded on attempt {attempt} (timeout={current_timeout}s)")
|
|
60
65
|
return True
|
|
61
66
|
except Exception as e:
|
|
62
67
|
print(traceback.format_exc())
|
|
68
|
+
current_timeout += self.send_timeout_increment
|
|
69
|
+
print(f"Kafka send failed after retries (final timeout={current_timeout}s)")
|
|
63
70
|
return False
|
|
64
71
|
|
|
65
72
|
|
|
@@ -10,7 +10,6 @@ import json
|
|
|
10
10
|
from datetime import datetime
|
|
11
11
|
import time
|
|
12
12
|
|
|
13
|
-
from .kafka_service import KafkaServiceFactory
|
|
14
13
|
from ..util import split_trace_id, thread_pool_manager
|
|
15
14
|
|
|
16
15
|
|
|
@@ -26,11 +25,13 @@ def process_message(line):
|
|
|
26
25
|
trace_id = message.get("trace_id", "")
|
|
27
26
|
if trace_id:
|
|
28
27
|
_, chat_dict = split_trace_id(trace_id)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
# 新日志直接携带 uid/session_id/turn;旧日志仍可从复合 trace_id
|
|
29
|
+
# 中解析。显式传入的非空字段始终优先,避免被兼容逻辑覆盖。
|
|
30
|
+
for field in ("uid", "session_id", "turn"):
|
|
31
|
+
if message.get(field) in (None, ""):
|
|
32
|
+
legacy_value = chat_dict.get(field)
|
|
33
|
+
if legacy_value is not None and legacy_value != "":
|
|
34
|
+
message[field] = legacy_value
|
|
34
35
|
create_time = message.get("create_time")
|
|
35
36
|
message.update({"create_date": get_date_from_time(create_time)})
|
|
36
37
|
return trace_id, message
|
|
@@ -39,6 +40,8 @@ def process_message(line):
|
|
|
39
40
|
class LogCollector:
|
|
40
41
|
|
|
41
42
|
def __init__(self, args):
|
|
43
|
+
from .kafka_service import KafkaServiceFactory
|
|
44
|
+
|
|
42
45
|
start_time = time.time()
|
|
43
46
|
self.kafka_service = KafkaServiceFactory.create_kafka_service(
|
|
44
47
|
config_path=args.config_path,
|
|
@@ -110,4 +113,4 @@ def main():
|
|
|
110
113
|
|
|
111
114
|
|
|
112
115
|
if __name__ == "__main__":
|
|
113
|
-
raise SystemExit(main())
|
|
116
|
+
raise SystemExit(main())
|
|
@@ -11,6 +11,8 @@ liblogging.egg-info/entry_points.txt
|
|
|
11
11
|
liblogging.egg-info/requires.txt
|
|
12
12
|
liblogging.egg-info/top_level.txt
|
|
13
13
|
liblogging.egg-info/zip-safe
|
|
14
|
+
liblogging/collector/__init__.py
|
|
15
|
+
liblogging/collector/mongo.py
|
|
14
16
|
liblogging/sending/__init__.py
|
|
15
17
|
liblogging/sending/kafka_service.py
|
|
16
18
|
liblogging/sending/log_collector.py
|
|
@@ -12,13 +12,15 @@ if __name__ == '__main__':
|
|
|
12
12
|
packages=[
|
|
13
13
|
'liblogging',
|
|
14
14
|
'liblogging.sending',
|
|
15
|
+
'liblogging.collector',
|
|
15
16
|
],
|
|
16
17
|
entry_points={
|
|
17
18
|
'console_scripts': [
|
|
18
|
-
'liblogging_collector = liblogging.sending.log_collector:main'
|
|
19
|
+
'liblogging_collector = liblogging.sending.log_collector:main',
|
|
20
|
+
"liblogging_mongo_collector = liblogging.collector.mongo:main"
|
|
19
21
|
]
|
|
20
22
|
},
|
|
21
|
-
version='0.
|
|
23
|
+
version='0.2.4',
|
|
22
24
|
description='Utilities for logging and sending logs.',
|
|
23
25
|
long_description_content_type='text/markdown',
|
|
24
26
|
long_description=long_description,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|