cfquant 0.2.0__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.
- LTtx/__init__.py +2 -0
- LTtx/tx/Config.txt +8 -0
- LTtx/tx/LTtx_server.py +1177 -0
- LTtx/tx/__init__.py +5 -0
- LTtx/tx/tx.py +1935 -0
- cfquant/__init__.py +26 -0
- cfquant/account_routing.py +119 -0
- cfquant/channels.py +76 -0
- cfquant/cli.py +266 -0
- cfquant/client.py +365 -0
- cfquant/config.py +86 -0
- cfquant/hub.py +304 -0
- cfquant/logging_i18n.py +348 -0
- cfquant/normal_bridge.py +676 -0
- cfquant/pipe_bridge.py +249 -0
- cfquant/pipe_client.py +223 -0
- cfquant/pipe_hub.py +602 -0
- cfquant/pipe_transport.py +493 -0
- cfquant/protocol.py +238 -0
- cfquant/qmt_bridge.py +1081 -0
- cfquant/runtime_report.py +334 -0
- cfquant/tx.py +1898 -0
- cfquant/tx_trade_bridge.py +1905 -0
- cfquant/version.py +5 -0
- cfquant/xtconstant.py +262 -0
- cfquant/xtdata.py +462 -0
- cfquant/xttrader.py +955 -0
- cfquant/xttype.py +586 -0
- cfquant-0.2.0.dist-info/LICENSE +21 -0
- cfquant-0.2.0.dist-info/METADATA +144 -0
- cfquant-0.2.0.dist-info/RECORD +90 -0
- cfquant-0.2.0.dist-info/WHEEL +5 -0
- cfquant-0.2.0.dist-info/entry_points.txt +4 -0
- cfquant-0.2.0.dist-info/top_level.txt +6 -0
- cfquant_pipe_hub.py +45 -0
- cfquant_web_server.py +12137 -0
- qmt_scripts/CFQUANT.py +507 -0
- qmt_scripts/CFQUANT_CTYPE_ALL_LOWLAT.py +830 -0
- qmt_scripts/CFQUANT_LITE.py +4928 -0
- qmt_scripts/CFQUANT_TRADE_LOWLAT.py +382 -0
- qmt_scripts/__init__.py +1 -0
- qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_CTYPE_ALL_LOWLAT_SH.py +866 -0
- qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_CTYPE_ALL_LOWLAT_SZ.py +866 -0
- qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_LITE_SH.py +5020 -0
- qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_LITE_SZ.py +5020 -0
- qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_TRADE_LOWLAT_SH.py +436 -0
- qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/CFQUANT_TRADE_LOWLAT_SZ.py +436 -0
- qmt_scripts//321/205/320/240/320/234/321/210/342/224/244/320/266/321/205/320/237/342/225/226/321/207/320/233/320/274/321/207/320/273/320/233/321/205/342/225/225/320/222/321/205/320/254/342/225/221/readme.md +95 -0
- web_dashboard/__init__.py +1 -0
- web_dashboard/app.js +9847 -0
- web_dashboard/avatars/amber-pulse.svg +15 -0
- web_dashboard/avatars/copper-grid.svg +15 -0
- web_dashboard/avatars/market-blue.svg +15 -0
- web_dashboard/avatars/rose-circuit.svg +15 -0
- web_dashboard/avatars/signal-green.svg +15 -0
- web_dashboard/avatars/slate-wave.svg +15 -0
- web_dashboard/avatars/teal-orbit.svg +16 -0
- web_dashboard/avatars/violet-node.svg +15 -0
- web_dashboard/favicon.svg +5 -0
- web_dashboard/image/qmt_deploy/image-10.png +0 -0
- web_dashboard/image/qmt_deploy/image-11.png +0 -0
- web_dashboard/image/qmt_deploy/image-12.png +0 -0
- web_dashboard/image/qmt_deploy/image-13.png +0 -0
- web_dashboard/image/qmt_deploy/image-14.png +0 -0
- web_dashboard/image/qmt_deploy/image-15.png +0 -0
- web_dashboard/image/qmt_deploy/image-16.png +0 -0
- web_dashboard/image/qmt_deploy/image-17.png +0 -0
- web_dashboard/image/qmt_deploy/image-18.png +0 -0
- web_dashboard/image/qmt_deploy/image-19.png +0 -0
- web_dashboard/image/qmt_deploy/image-20.png +0 -0
- web_dashboard/image/qmt_deploy/image-21.png +0 -0
- web_dashboard/image/qmt_deploy/image-4.png +0 -0
- web_dashboard/image/qmt_deploy/image-5.png +0 -0
- web_dashboard/image/qmt_deploy/image-6.png +0 -0
- web_dashboard/image/qmt_deploy/image-7.png +0 -0
- web_dashboard/image/qmt_deploy/image-8.png +0 -0
- web_dashboard/image/qmt_deploy/image-9.png +0 -0
- web_dashboard/image//321/211/320/220/320/252/321/207/320/244/320/270/321/206/320/270/320/261/321/205/342/225/235/320/237/321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/image-1.png +0 -0
- web_dashboard/image//321/211/320/220/320/252/321/207/320/244/320/270/321/206/320/270/320/261/321/205/342/225/235/320/237/321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/image-2.png +0 -0
- web_dashboard/image//321/211/320/220/320/252/321/207/320/244/320/270/321/206/320/270/320/261/321/205/342/225/235/320/237/321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/image-3.png +0 -0
- web_dashboard/image//321/211/320/220/320/252/321/207/320/244/320/270/321/206/320/270/320/261/321/205/342/225/235/320/237/321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/image.png +0 -0
- web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783479325179.png +0 -0
- web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783479745262.png +0 -0
- web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783480178700.png +0 -0
- web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783480428759.png +0 -0
- web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783489807217.png +0 -0
- web_dashboard/image//321/211/320/223/320/270/321/207/342/225/234/342/226/223/321/206/320/234/320/227/321/205/320/235/320/247/1783490503491.png +0 -0
- web_dashboard/index.html +2317 -0
- web_dashboard/styles.css +7817 -0
- web_dashboard/vendor/mermaid.min.js +2029 -0
cfquant/hub.py
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import socket
|
|
5
|
+
import struct
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
from .protocol import loads_message
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def default_status_file(filename):
|
|
13
|
+
runtime_dir = os.path.abspath(os.environ.get("CFQUANT_RUNTIME_DIR") or os.path.join(os.getcwd(), "runtime"))
|
|
14
|
+
return os.path.join(runtime_dir, "status", filename)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CfquantHub(object):
|
|
18
|
+
"""
|
|
19
|
+
外部 Socket Hub。
|
|
20
|
+
|
|
21
|
+
- QMT 端作为 client 连接进来,首帧发送 role:qmt
|
|
22
|
+
- 外部 API client 直接发送 cfquant request
|
|
23
|
+
- Hub 将 request 转发给 QMT,并按 request id 把 response 转回对应 API client
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, host="127.0.0.1", port=58668, show=True):
|
|
27
|
+
self.host = host
|
|
28
|
+
self.port = int(port)
|
|
29
|
+
self.show = show
|
|
30
|
+
self.running = False
|
|
31
|
+
self.server = None
|
|
32
|
+
self.qmt_sock = None
|
|
33
|
+
self.qmt_lock = threading.RLock()
|
|
34
|
+
self.send_locks = {}
|
|
35
|
+
self.send_locks_lock = threading.RLock()
|
|
36
|
+
self.pending = {}
|
|
37
|
+
self.client_by_id = {}
|
|
38
|
+
self.state_lock = threading.RLock()
|
|
39
|
+
self.status_file = os.path.abspath(
|
|
40
|
+
os.environ.get("CFQUANT_HUB_STATUS_FILE") or default_status_file("cfquant_hub_status.json")
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def start(self):
|
|
44
|
+
if self.running:
|
|
45
|
+
return self
|
|
46
|
+
self.running = True
|
|
47
|
+
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
48
|
+
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
49
|
+
self.server.bind((self.host, self.port))
|
|
50
|
+
self.server.listen(100)
|
|
51
|
+
self._log("cfquant Hub已启动:%s:%s" % (self.host, self.port))
|
|
52
|
+
self._write_status()
|
|
53
|
+
try:
|
|
54
|
+
while self.running:
|
|
55
|
+
client, address = self.server.accept()
|
|
56
|
+
self._log("Hub客户端已连接:%s" % (address,))
|
|
57
|
+
threading.Thread(target=self._client_loop, args=(client, address), daemon=True).start()
|
|
58
|
+
finally:
|
|
59
|
+
self.close()
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
def close(self):
|
|
63
|
+
self.running = False
|
|
64
|
+
try:
|
|
65
|
+
self.server.close()
|
|
66
|
+
except Exception:
|
|
67
|
+
pass
|
|
68
|
+
with self.qmt_lock:
|
|
69
|
+
qmt_sock = self.qmt_sock
|
|
70
|
+
self.qmt_sock = None
|
|
71
|
+
self._close_socket(qmt_sock)
|
|
72
|
+
|
|
73
|
+
def _client_loop(self, sock, address):
|
|
74
|
+
role = "api"
|
|
75
|
+
try:
|
|
76
|
+
while self.running:
|
|
77
|
+
raw = self._recv_frame(sock)
|
|
78
|
+
if raw is None:
|
|
79
|
+
break
|
|
80
|
+
if raw == "role:qmt":
|
|
81
|
+
role = "qmt"
|
|
82
|
+
with self.qmt_lock:
|
|
83
|
+
old = self.qmt_sock
|
|
84
|
+
self.qmt_sock = sock
|
|
85
|
+
if old is not None and old is not sock:
|
|
86
|
+
self._close_socket(old)
|
|
87
|
+
self._log("QMT桥接端已注册:%s" % (address,))
|
|
88
|
+
self._write_status()
|
|
89
|
+
continue
|
|
90
|
+
if role == "qmt":
|
|
91
|
+
self._handle_qmt_message(raw)
|
|
92
|
+
else:
|
|
93
|
+
self._handle_api_message(sock, raw)
|
|
94
|
+
except Exception as e:
|
|
95
|
+
self._log("Hub客户端循环异常 address=%s role=%s error=%s" % (address, role, e))
|
|
96
|
+
finally:
|
|
97
|
+
if role == "qmt":
|
|
98
|
+
with self.qmt_lock:
|
|
99
|
+
if self.qmt_sock is sock:
|
|
100
|
+
self.qmt_sock = None
|
|
101
|
+
self._log("QMT桥接端已断开:%s" % (address,))
|
|
102
|
+
self._write_status()
|
|
103
|
+
else:
|
|
104
|
+
self._drop_api_client(sock)
|
|
105
|
+
self._log("API客户端已断开:%s" % (address,))
|
|
106
|
+
self._write_status()
|
|
107
|
+
self._close_socket(sock)
|
|
108
|
+
|
|
109
|
+
def _handle_api_message(self, sock, raw):
|
|
110
|
+
api_received_at = time.perf_counter()
|
|
111
|
+
msg = loads_message(raw)
|
|
112
|
+
if not msg:
|
|
113
|
+
return
|
|
114
|
+
if msg.get("type") != "request":
|
|
115
|
+
return
|
|
116
|
+
request_id = msg.get("id")
|
|
117
|
+
client_id = msg.get("client_id")
|
|
118
|
+
with self.state_lock:
|
|
119
|
+
if request_id:
|
|
120
|
+
self.pending[request_id] = {
|
|
121
|
+
"sock": sock,
|
|
122
|
+
"action": msg.get("action"),
|
|
123
|
+
"api_received_at": api_received_at,
|
|
124
|
+
"forward_done_at": None,
|
|
125
|
+
}
|
|
126
|
+
if client_id:
|
|
127
|
+
self.client_by_id[client_id] = sock
|
|
128
|
+
qmt_sock = self._get_qmt_sock()
|
|
129
|
+
if qmt_sock is None:
|
|
130
|
+
with self.state_lock:
|
|
131
|
+
self.pending.pop(request_id, None)
|
|
132
|
+
self._send_error(sock, request_id, "QMT桥接端未连接")
|
|
133
|
+
return
|
|
134
|
+
forward_start = time.perf_counter()
|
|
135
|
+
self._send_frame(qmt_sock, raw)
|
|
136
|
+
forward_done = time.perf_counter()
|
|
137
|
+
with self.state_lock:
|
|
138
|
+
pending = self.pending.get(request_id)
|
|
139
|
+
if pending:
|
|
140
|
+
pending["forward_done_at"] = forward_done
|
|
141
|
+
self._log(
|
|
142
|
+
"Hub已转发请求 action=%s id=%s api_to_forward_ms=%.2f send_ms=%.2f raw_len=%s"
|
|
143
|
+
% (
|
|
144
|
+
msg.get("action"),
|
|
145
|
+
request_id,
|
|
146
|
+
self._elapsed_ms(api_received_at, forward_done),
|
|
147
|
+
self._elapsed_ms(forward_start, forward_done),
|
|
148
|
+
len(raw),
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
self._write_status()
|
|
152
|
+
|
|
153
|
+
def _handle_qmt_message(self, raw):
|
|
154
|
+
qmt_received_at = time.perf_counter()
|
|
155
|
+
msg = loads_message(raw)
|
|
156
|
+
if not msg:
|
|
157
|
+
return
|
|
158
|
+
msg_type = msg.get("type")
|
|
159
|
+
target = None
|
|
160
|
+
if msg_type == "response":
|
|
161
|
+
request_id = msg.get("id")
|
|
162
|
+
pending = None
|
|
163
|
+
with self.state_lock:
|
|
164
|
+
pending = self.pending.pop(request_id, None)
|
|
165
|
+
if isinstance(pending, dict):
|
|
166
|
+
target = pending.get("sock")
|
|
167
|
+
api_received_at = pending.get("api_received_at") or qmt_received_at
|
|
168
|
+
forward_done_at = pending.get("forward_done_at") or api_received_at
|
|
169
|
+
self._log(
|
|
170
|
+
"Hub收到QMT响应 action=%s id=%s target=%s qmt_roundtrip_ms=%.2f total_to_hub_ms=%.2f response_len=%s"
|
|
171
|
+
% (
|
|
172
|
+
pending.get("action"),
|
|
173
|
+
request_id,
|
|
174
|
+
bool(target),
|
|
175
|
+
self._elapsed_ms(forward_done_at, qmt_received_at),
|
|
176
|
+
self._elapsed_ms(api_received_at, qmt_received_at),
|
|
177
|
+
len(raw),
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
else:
|
|
181
|
+
target = pending
|
|
182
|
+
self._log("Hub收到QMT响应 id=%s target=%s response_len=%s" % (request_id, bool(target), len(raw)))
|
|
183
|
+
self._write_status()
|
|
184
|
+
elif msg_type == "event":
|
|
185
|
+
client_id = msg.get("client_id")
|
|
186
|
+
with self.state_lock:
|
|
187
|
+
target = self.client_by_id.get(client_id)
|
|
188
|
+
self._log("Hub收到QMT事件 event=%s client_id=%s target=%s" % (msg.get("event"), client_id, bool(target)))
|
|
189
|
+
if target is not None:
|
|
190
|
+
self._send_frame(target, raw)
|
|
191
|
+
|
|
192
|
+
def _send_error(self, sock, request_id, message):
|
|
193
|
+
from .protocol import pack_response
|
|
194
|
+
|
|
195
|
+
self._send_frame(sock, pack_response(request_id, ok=False, error={"type": "ConnectionError", "message": message}))
|
|
196
|
+
|
|
197
|
+
def _get_qmt_sock(self):
|
|
198
|
+
with self.qmt_lock:
|
|
199
|
+
return self.qmt_sock
|
|
200
|
+
|
|
201
|
+
def _drop_api_client(self, sock):
|
|
202
|
+
with self.state_lock:
|
|
203
|
+
for key, value in list(self.pending.items()):
|
|
204
|
+
pending_sock = value.get("sock") if isinstance(value, dict) else value
|
|
205
|
+
if pending_sock is sock:
|
|
206
|
+
self.pending.pop(key, None)
|
|
207
|
+
for key, value in list(self.client_by_id.items()):
|
|
208
|
+
if value is sock:
|
|
209
|
+
self.client_by_id.pop(key, None)
|
|
210
|
+
|
|
211
|
+
def _send_frame(self, sock, payload):
|
|
212
|
+
if isinstance(payload, str):
|
|
213
|
+
data = payload.encode("utf-8")
|
|
214
|
+
else:
|
|
215
|
+
data = payload
|
|
216
|
+
with self._get_send_lock(sock):
|
|
217
|
+
sock.sendall(struct.pack("!Q", len(data)) + data)
|
|
218
|
+
|
|
219
|
+
def _recv_frame(self, sock):
|
|
220
|
+
header = self._recv_exact(sock, 8)
|
|
221
|
+
if not header:
|
|
222
|
+
return None
|
|
223
|
+
size = struct.unpack("!Q", header)[0]
|
|
224
|
+
if size <= 0:
|
|
225
|
+
return ""
|
|
226
|
+
data = self._recv_exact(sock, size)
|
|
227
|
+
if data is None:
|
|
228
|
+
return None
|
|
229
|
+
return data.decode("utf-8", errors="replace")
|
|
230
|
+
|
|
231
|
+
def _recv_exact(self, sock, size):
|
|
232
|
+
chunks = []
|
|
233
|
+
remaining = size
|
|
234
|
+
while remaining > 0:
|
|
235
|
+
chunk = sock.recv(remaining)
|
|
236
|
+
if not chunk:
|
|
237
|
+
return None
|
|
238
|
+
chunks.append(chunk)
|
|
239
|
+
remaining -= len(chunk)
|
|
240
|
+
return b"".join(chunks)
|
|
241
|
+
|
|
242
|
+
def _get_send_lock(self, sock):
|
|
243
|
+
with self.send_locks_lock:
|
|
244
|
+
lock = self.send_locks.get(sock)
|
|
245
|
+
if lock is None:
|
|
246
|
+
lock = threading.RLock()
|
|
247
|
+
self.send_locks[sock] = lock
|
|
248
|
+
return lock
|
|
249
|
+
|
|
250
|
+
def _close_socket(self, sock):
|
|
251
|
+
if sock is None:
|
|
252
|
+
return
|
|
253
|
+
try:
|
|
254
|
+
sock.shutdown(socket.SHUT_RDWR)
|
|
255
|
+
except Exception:
|
|
256
|
+
pass
|
|
257
|
+
try:
|
|
258
|
+
sock.close()
|
|
259
|
+
except Exception:
|
|
260
|
+
pass
|
|
261
|
+
with self.send_locks_lock:
|
|
262
|
+
self.send_locks.pop(sock, None)
|
|
263
|
+
|
|
264
|
+
def _log(self, msg):
|
|
265
|
+
if self.show:
|
|
266
|
+
print("%s %s" % (self._timestamp_ms(), msg), flush=True)
|
|
267
|
+
|
|
268
|
+
def _timestamp_ms(self):
|
|
269
|
+
now = time.time()
|
|
270
|
+
local = time.localtime(now)
|
|
271
|
+
return "%s.%03d" % (time.strftime("%Y-%m-%d %H:%M:%S", local), int((now - int(now)) * 1000))
|
|
272
|
+
|
|
273
|
+
def _elapsed_ms(self, start, end=None):
|
|
274
|
+
if end is None:
|
|
275
|
+
end = time.perf_counter()
|
|
276
|
+
return (end - start) * 1000.0
|
|
277
|
+
|
|
278
|
+
def _write_status(self):
|
|
279
|
+
try:
|
|
280
|
+
with self.qmt_lock:
|
|
281
|
+
qmt_connected = self.qmt_sock is not None
|
|
282
|
+
with self.state_lock:
|
|
283
|
+
pending_ids = list(self.pending.keys())[-20:]
|
|
284
|
+
client_count = len(set(self.client_by_id.values()))
|
|
285
|
+
data = {
|
|
286
|
+
"ts": time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
287
|
+
"host": self.host,
|
|
288
|
+
"port": self.port,
|
|
289
|
+
"qmt_connected": qmt_connected,
|
|
290
|
+
"pending_count": len(pending_ids),
|
|
291
|
+
"pending_ids": pending_ids,
|
|
292
|
+
"api_client_count": client_count,
|
|
293
|
+
}
|
|
294
|
+
status_dir = os.path.dirname(self.status_file)
|
|
295
|
+
if status_dir:
|
|
296
|
+
os.makedirs(status_dir, exist_ok=True)
|
|
297
|
+
with open(self.status_file, "w", encoding="utf-8") as f:
|
|
298
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
299
|
+
except Exception:
|
|
300
|
+
pass
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def run_hub(host="127.0.0.1", port=58668, show=True):
|
|
304
|
+
return CfquantHub(host=host, port=port, show=show).start()
|
cfquant/logging_i18n.py
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import threading
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
_LANG_LOCK = threading.RLock()
|
|
8
|
+
_LOG_LANGUAGE = ""
|
|
9
|
+
_LOG_ENABLED = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
_TRANSLATIONS = [
|
|
13
|
+
(r"^tx trade bridge reload failed:(?P<error>.+)$", "交易桥模块重载失败:{error}"),
|
|
14
|
+
(r"^normal bridge reload failed:(?P<error>.+)$", "普通桥模块重载失败:{error}"),
|
|
15
|
+
(r"^QMT log output enabled=(?P<enabled>.+)$", "QMT 日志输出已切换 enabled={enabled}"),
|
|
16
|
+
(r"^pipe transport reload failed:(?P<error>.+)$", "Pipe 传输模块重载失败:{error}"),
|
|
17
|
+
(r"^pipe bridge reload failed:(?P<error>.+)$", "Pipe 桥接模块重载失败:{error}"),
|
|
18
|
+
(r"^cfquant normal bridge module loaded$", "cfquant 普通桥模块已加载"),
|
|
19
|
+
(r"^cfquant entry version:(?P<version>.+)$", "cfquant 入口版本:{version}"),
|
|
20
|
+
(
|
|
21
|
+
r"^cfquant bridge id:(?P<bridge_id>[^ ]+) normal_channel:(?P<normal>[^ ]+) callback_channel:(?P<callback>.+)$",
|
|
22
|
+
"cfquant 桥接ID={bridge_id} 普通通道={normal} 回调通道={callback}",
|
|
23
|
+
),
|
|
24
|
+
(
|
|
25
|
+
r"^cfquant normal bridge pump max_count:(?P<count>[^ ]+) max_ms:(?P<ms>.+)$",
|
|
26
|
+
"cfquant 普通桥泵处理上限 条数={count} 耗时={ms}ms",
|
|
27
|
+
),
|
|
28
|
+
(
|
|
29
|
+
r"^cfquant normal bridge timer scheduled key:(?P<key>[^ ]+) interval_ms:(?P<interval>.+)$",
|
|
30
|
+
"cfquant 普通桥定时器已注册 key={key} 间隔={interval}ms",
|
|
31
|
+
),
|
|
32
|
+
(
|
|
33
|
+
r"^cfquant normal bridge timer schedule failed:(?P<error>.+)$",
|
|
34
|
+
"cfquant 普通桥定时器注册失败:{error}",
|
|
35
|
+
),
|
|
36
|
+
(
|
|
37
|
+
r"^cfquant normal bridge context ready version:(?P<version>.+)$",
|
|
38
|
+
"cfquant 普通桥 ContextInfo 已就绪 版本={version}",
|
|
39
|
+
),
|
|
40
|
+
(
|
|
41
|
+
r"^cfquant normal bridge timer cancel failed:(?P<error>.+)$",
|
|
42
|
+
"cfquant 普通桥定时器取消失败:{error}",
|
|
43
|
+
),
|
|
44
|
+
(r"^cfquant normal bridge stopped$", "cfquant 普通桥已停止"),
|
|
45
|
+
(
|
|
46
|
+
r"^cfquant callback publish failed event=(?P<event>[^ ]+) error=(?P<error>.+)$",
|
|
47
|
+
"cfquant 回调事件发布失败 event={event} 错误={error}",
|
|
48
|
+
),
|
|
49
|
+
(r"^cfquant lowlat trade bridge module loaded$", "cfquant 极速交易桥模块已加载"),
|
|
50
|
+
(r"^cfquant lowlat entry version:(?P<version>.+)$", "cfquant 极速交易入口版本:{version}"),
|
|
51
|
+
(
|
|
52
|
+
r"^cfquant bridge id:(?P<bridge_id>[^ ]+) trade_channel:(?P<trade>.+)$",
|
|
53
|
+
"cfquant 桥接ID={bridge_id} 交易通道={trade}",
|
|
54
|
+
),
|
|
55
|
+
(
|
|
56
|
+
r"^cfquant lowlat trade context ready version:(?P<version>.+)$",
|
|
57
|
+
"cfquant 极速交易桥 ContextInfo 已就绪 版本={version}",
|
|
58
|
+
),
|
|
59
|
+
(r"^cfquant lowlat trade bridge stopped$", "cfquant 极速交易桥已停止"),
|
|
60
|
+
(r"^cfquant ctypes all-in-one lowlat bridge module loaded$", "cfquant ctypes 单文件低延迟桥模块已加载"),
|
|
61
|
+
(r"^cfquant ctypes all-in-one lowlat entry version:(?P<version>.+)$", "cfquant ctypes 单文件低延迟入口版本:{version}"),
|
|
62
|
+
(
|
|
63
|
+
r"^cfquant ctypes bridge id:(?P<bridge_id>[^ ]+) pipe:(?P<pipe>[^ ]+) normal_channel:(?P<normal>[^ ]+) trade_channel:(?P<trade>[^ ]+) callback_channel:(?P<callback>.+)$",
|
|
64
|
+
"cfquant ctypes 桥接ID={bridge_id} pipe={pipe} 普通通道={normal} 交易通道={trade} 回调通道={callback}",
|
|
65
|
+
),
|
|
66
|
+
(
|
|
67
|
+
r"^cfquant ctypes trade loop in thread:(?P<thread>[^ ]+) sleep_seconds:(?P<sleep>.+)$",
|
|
68
|
+
"cfquant ctypes 交易循环后台线程={thread} 轮询间隔={sleep}秒",
|
|
69
|
+
),
|
|
70
|
+
(
|
|
71
|
+
r"^cfquant ctypes lowlat trade loop error:(?P<error>.+)$",
|
|
72
|
+
"cfquant ctypes 低延迟交易循环异常:{error}",
|
|
73
|
+
),
|
|
74
|
+
(r"^cfquant ctypes lowlat trade loop started in worker thread$", "cfquant ctypes 低延迟交易循环已在后台线程启动"),
|
|
75
|
+
(r"^cfquant ctypes lowlat trade loop entering current QMT thread$", "cfquant ctypes 低延迟交易循环进入当前 QMT 线程"),
|
|
76
|
+
(
|
|
77
|
+
r"^cfquant ctypes lowlat trade dispatch error source=(?P<source>[^ ]+) error=(?P<error>.+)$",
|
|
78
|
+
"cfquant ctypes 低延迟交易请求派发异常 来源={source} 错误={error}",
|
|
79
|
+
),
|
|
80
|
+
(
|
|
81
|
+
r"^cfquant ctypes lowlat trade timer scheduled key:(?P<key>[^ ]+) interval_ms:(?P<interval>.+)$",
|
|
82
|
+
"cfquant ctypes 低延迟交易定时器已注册 key={key} 间隔={interval}ms",
|
|
83
|
+
),
|
|
84
|
+
(
|
|
85
|
+
r"^cfquant ctypes lowlat trade timer schedule failed:(?P<error>.+)$",
|
|
86
|
+
"cfquant ctypes 低延迟交易定时器注册失败:{error}",
|
|
87
|
+
),
|
|
88
|
+
(
|
|
89
|
+
r"^cfquant ctypes lowlat trade timer cancel failed:(?P<error>.+)$",
|
|
90
|
+
"cfquant ctypes 低延迟交易定时器取消失败:{error}",
|
|
91
|
+
),
|
|
92
|
+
(
|
|
93
|
+
r"^cfquant ctypes normal context ready version:(?P<version>.+)$",
|
|
94
|
+
"cfquant ctypes 普通桥 ContextInfo 已就绪 版本={version}",
|
|
95
|
+
),
|
|
96
|
+
(
|
|
97
|
+
r"^cfquant ctypes lowlat trade context ready version:(?P<version>.+)$",
|
|
98
|
+
"cfquant ctypes 低延迟交易桥 ContextInfo 已就绪 版本={version}",
|
|
99
|
+
),
|
|
100
|
+
(r"^cfquant ctypes lowlat trade bridge stopped$", "cfquant ctypes 低延迟交易桥已停止"),
|
|
101
|
+
(r"^cfquant ctypes normal bridge stopped$", "cfquant ctypes 普通桥已停止"),
|
|
102
|
+
(
|
|
103
|
+
r"^cfquant ctypes lowlat callback publish failed event=(?P<event>[^ ]+) error=(?P<error>.+)$",
|
|
104
|
+
"cfquant ctypes 回调事件发布失败 event={event} 错误={error}",
|
|
105
|
+
),
|
|
106
|
+
(
|
|
107
|
+
r"^stage=request_dequeued raw=(?P<raw>.+)$",
|
|
108
|
+
"阶段=请求出队 raw={raw}",
|
|
109
|
+
),
|
|
110
|
+
(
|
|
111
|
+
r"^stage=parse_invalid parse_ms=(?P<parse_ms>[^ ]+) raw=(?P<raw>.+)$",
|
|
112
|
+
"阶段=解析失败 解析耗时={parse_ms}ms raw={raw}",
|
|
113
|
+
),
|
|
114
|
+
(
|
|
115
|
+
r"^stage=request_enqueued_qmt_thread action=(?P<action>[^ ]+) id=(?P<id>.+)$",
|
|
116
|
+
"阶段=请求转入QMT线程 action={action} id={id}",
|
|
117
|
+
),
|
|
118
|
+
(
|
|
119
|
+
r"^stage=request_received action=(?P<action>[^ ]+) id=(?P<id>[^ ]+) parse_ms=(?P<parse_ms>[^ ]+) params=(?P<params>.+)$",
|
|
120
|
+
"阶段=收到请求 action={action} id={id} 解析耗时={parse_ms}ms 参数={params}",
|
|
121
|
+
),
|
|
122
|
+
(
|
|
123
|
+
r"^stage=response_ready action=(?P<action>[^ ]+) id=(?P<id>[^ ]+) dispatch_ms=(?P<dispatch_ms>[^ ]+) result=(?P<result>.+)$",
|
|
124
|
+
"阶段=响应已生成 action={action} id={id} 处理耗时={dispatch_ms}ms 结果={result}",
|
|
125
|
+
),
|
|
126
|
+
(
|
|
127
|
+
r"^stage=response_sent action=(?P<action>[^ ]+) id=(?P<id>[^ ]+) client_id=(?P<client_id>[^ ]+) total_ms=(?P<ms>.+)$",
|
|
128
|
+
"阶段=响应已发送 action={action} id={id} 客户端={client_id} 总耗时={ms}ms",
|
|
129
|
+
),
|
|
130
|
+
(r"^tx trade bridge context ready$", "交易桥 ContextInfo 已就绪"),
|
|
131
|
+
(r"^tx trade bridge stopped$", "交易桥已停止"),
|
|
132
|
+
(
|
|
133
|
+
r"^tx trade bridge started LTtx=(?P<endpoint>[^ ]+) request_channel=(?P<channel>.+)$",
|
|
134
|
+
"交易桥已启动 LTtx={endpoint} 请求通道={channel}",
|
|
135
|
+
),
|
|
136
|
+
(
|
|
137
|
+
r"^tx trade response_ready action=(?P<action>[^ ]+) id=(?P<id>.+)$",
|
|
138
|
+
"交易请求已生成响应 action={action} id={id}",
|
|
139
|
+
),
|
|
140
|
+
(
|
|
141
|
+
r"^tx trade request_error action=(?P<action>[^ ]+) id=(?P<id>[^ ]+) error=(?P<error>.+)$",
|
|
142
|
+
"交易请求处理失败 action={action} id={id} 错误={error}",
|
|
143
|
+
),
|
|
144
|
+
(
|
|
145
|
+
r"^tx trade response_sent action=(?P<action>[^ ]+) id=(?P<id>[^ ]+) client_id=(?P<client_id>[^ ]+) total_ms=(?P<ms>.+)$",
|
|
146
|
+
"交易响应已发送 action={action} id={id} 客户端={client_id} 总耗时={ms}ms",
|
|
147
|
+
),
|
|
148
|
+
(
|
|
149
|
+
r"^query_trade_detail start account=(?P<account>[^ ]+) account_type=(?P<account_type>[^ ]+) detail_type=(?P<detail_type>.+)$",
|
|
150
|
+
"交易明细查询开始 账号={account} 账号类型={account_type} 明细类型={detail_type}",
|
|
151
|
+
),
|
|
152
|
+
(
|
|
153
|
+
r"^query_trade_detail call failed account=(?P<account>[^ ]+) detail_type=(?P<detail_type>[^ ]+) error=(?P<error>.+)$",
|
|
154
|
+
"交易明细查询调用失败 账号={account} 明细类型={detail_type} 错误={error}",
|
|
155
|
+
),
|
|
156
|
+
(
|
|
157
|
+
r"^query_trade_detail format failed detail_type=(?P<detail_type>[^ ]+) index=(?P<index>[^ ]+) type=(?P<type>[^ ]+) error=(?P<error>.+)$",
|
|
158
|
+
"交易明细格式化失败 明细类型={detail_type} 序号={index} 数据类型={type} 错误={error}",
|
|
159
|
+
),
|
|
160
|
+
(
|
|
161
|
+
r"^query_trade_detail done detail_type=(?P<detail_type>[^ ]+) count=(?P<count>.+)$",
|
|
162
|
+
"交易明细查询完成 明细类型={detail_type} 数量={count}",
|
|
163
|
+
),
|
|
164
|
+
(
|
|
165
|
+
r"^trade detail getattr failed type=(?P<type>[^ ]+) field=(?P<field>[^ ]+) error=(?P<error>.+)$",
|
|
166
|
+
"交易明细读取属性失败 数据类型={type} 字段={field} 错误={error}",
|
|
167
|
+
),
|
|
168
|
+
(
|
|
169
|
+
r"^trade detail get failed type=(?P<type>[^ ]+) field=(?P<field>[^ ]+) error=(?P<error>.+)$",
|
|
170
|
+
"交易明细 get 读取失败 数据类型={type} 字段={field} 错误={error}",
|
|
171
|
+
),
|
|
172
|
+
(
|
|
173
|
+
r"^qmt userdata log cleanup log_dir=(?P<dir>.+) retention_days=(?P<days>[^ ]+) deleted=(?P<deleted>[^ ]+) failed=(?P<failed>[^ ]+) dry_run=(?P<dry_run>.+)$",
|
|
174
|
+
"QMT userdata 日志清理完成 目录={dir} 保留天数={days} 删除={deleted} 失败={failed} dry_run={dry_run}",
|
|
175
|
+
),
|
|
176
|
+
(
|
|
177
|
+
r"^account subscribed account=(?P<account>[^ ]+) client_id=(?P<client_id>.+)$",
|
|
178
|
+
"账号回调已订阅 账号={account} 客户端={client_id}",
|
|
179
|
+
),
|
|
180
|
+
(
|
|
181
|
+
r"^account unsubscribed account=(?P<account>[^ ]+) client_id=(?P<client_id>.+)$",
|
|
182
|
+
"账号回调已取消订阅 账号={account} 客户端={client_id}",
|
|
183
|
+
),
|
|
184
|
+
(
|
|
185
|
+
r"^normal bridge started LTtx=(?P<endpoint>[^ ]+) request_channel=(?P<channel>.+)$",
|
|
186
|
+
"普通桥已启动 LTtx={endpoint} 请求通道={channel}",
|
|
187
|
+
),
|
|
188
|
+
(r"^normal bridge worker is released by quote/timer/handlebar callbacks$", "普通桥 worker 由行情/定时器/handlebar 回调唤醒"),
|
|
189
|
+
(r"^normal bridge context ready$", "普通桥 ContextInfo 已就绪"),
|
|
190
|
+
(r"^normal bridge worker thread started in init context$", "普通桥 worker 线程已在 init context 中启动"),
|
|
191
|
+
(
|
|
192
|
+
r"^normal bridge recv error: (?P<error>.+)$",
|
|
193
|
+
"普通桥接收请求异常:{error}",
|
|
194
|
+
),
|
|
195
|
+
(
|
|
196
|
+
r"^normal bridge request queued action=(?P<action>[^ ]+) id=(?P<id>[^ ]+) queue_size=(?P<size>[^ ]+) coalesced=(?P<coalesced>.+)$",
|
|
197
|
+
"普通桥请求已入队 action={action} id={id} 队列长度={size} 合并查询={coalesced}",
|
|
198
|
+
),
|
|
199
|
+
(
|
|
200
|
+
r"^normal bridge request queued action=(?P<action>[^ ]+) id=(?P<id>[^ ]+) queue_size=(?P<size>.+)$",
|
|
201
|
+
"普通桥请求已入队 action={action} id={id} 队列长度={size}",
|
|
202
|
+
),
|
|
203
|
+
(
|
|
204
|
+
r"^normal bridge request coalesced action=(?P<action>[^ ]+) id=(?P<id>[^ ]+) waiters=(?P<waiters>.+)$",
|
|
205
|
+
"普通桥请求已合并 action={action} id={id} 等待方={waiters}",
|
|
206
|
+
),
|
|
207
|
+
(
|
|
208
|
+
r"^normal bridge quote subscribed id=(?P<id>[^ ]+) kind=(?P<kind>.+)$",
|
|
209
|
+
"行情订阅已建立 id={id} 类型={kind}",
|
|
210
|
+
),
|
|
211
|
+
(
|
|
212
|
+
r"^normal bridge whole quote publish enabled id=(?P<id>[^ ]+) internal_id=(?P<internal>.+)$",
|
|
213
|
+
"全推行情发布已开启 id={id} 内部订阅={internal}",
|
|
214
|
+
),
|
|
215
|
+
(
|
|
216
|
+
r"^normal bridge quote unsubscribed id=(?P<id>.+)$",
|
|
217
|
+
"行情订阅已取消 id={id}",
|
|
218
|
+
),
|
|
219
|
+
(
|
|
220
|
+
r"^normal bridge worker error source=(?P<source>[^ ]+) error=(?P<error>.+)$",
|
|
221
|
+
"普通桥 worker 异常 来源={source} 错误={error}",
|
|
222
|
+
),
|
|
223
|
+
(
|
|
224
|
+
r"^normal bridge worker response source=(?P<source>[^ ]+) action=(?P<action>[^ ]+) id=(?P<id>[^ ]+) total_ms=(?P<ms>.+)$",
|
|
225
|
+
"普通桥响应完成 来源={source} action={action} id={id} 总耗时={ms}ms",
|
|
226
|
+
),
|
|
227
|
+
(
|
|
228
|
+
r"^normal bridge worker coalesced_response source=(?P<source>[^ ]+) action=(?P<action>[^ ]+) id=(?P<id>[^ ]+) waiters=(?P<waiters>[^ ]+) total_ms=(?P<ms>.+)$",
|
|
229
|
+
"普通桥合并响应完成 来源={source} action={action} id={id} 等待方={waiters} 总耗时={ms}ms",
|
|
230
|
+
),
|
|
231
|
+
(
|
|
232
|
+
r"^normal bridge worker coalesced_error source=(?P<source>[^ ]+) action=(?P<action>[^ ]+) id=(?P<id>[^ ]+) waiters=(?P<waiters>[^ ]+) error=(?P<error>.+)$",
|
|
233
|
+
"普通桥合并请求处理失败 来源={source} action={action} id={id} 等待方={waiters} 错误={error}",
|
|
234
|
+
),
|
|
235
|
+
(
|
|
236
|
+
r"^normal bridge worker request_error source=(?P<source>[^ ]+) action=(?P<action>[^ ]+) id=(?P<id>[^ ]+) error=(?P<error>.+)$",
|
|
237
|
+
"普通桥请求处理失败 来源={source} action={action} id={id} 错误={error}",
|
|
238
|
+
),
|
|
239
|
+
(
|
|
240
|
+
r"^normal bridge internal whole quote subscribed id=(?P<id>.+)$",
|
|
241
|
+
"普通桥内部全推行情订阅成功 id={id}",
|
|
242
|
+
),
|
|
243
|
+
(
|
|
244
|
+
r"^normal bridge internal whole quote subscribe failed: (?P<error>.+)$",
|
|
245
|
+
"普通桥内部全推行情订阅失败:{error}",
|
|
246
|
+
),
|
|
247
|
+
(
|
|
248
|
+
r"^normal bridge timer scheduled key=(?P<key>.+)$",
|
|
249
|
+
"普通桥定时器已注册 key={key}",
|
|
250
|
+
),
|
|
251
|
+
(
|
|
252
|
+
r"^normal bridge timer schedule failed: (?P<error>.+)$",
|
|
253
|
+
"普通桥定时器注册失败:{error}",
|
|
254
|
+
),
|
|
255
|
+
(
|
|
256
|
+
r"^normal bridge send_error action=(?P<action>[^ ]+) id=(?P<id>[^ ]+) client_id=(?P<client_id>[^ ]+) error=(?P<error>.+)$",
|
|
257
|
+
"普通桥错误响应已发送 action={action} id={id} 客户端={client_id} 错误={error}",
|
|
258
|
+
),
|
|
259
|
+
(
|
|
260
|
+
r"^normal bridge callback event sent event=(?P<event>[^ ]+) account=(?P<account>.+)$",
|
|
261
|
+
"普通桥交易回调事件已发送 event={event} 账号={account}",
|
|
262
|
+
),
|
|
263
|
+
(
|
|
264
|
+
r"^pipe normal bridge started pipe=(?P<pipe>[^ ]+) request_channel=(?P<channel>.+)$",
|
|
265
|
+
"Pipe 普通桥已启动 pipe={pipe} 请求通道={channel}",
|
|
266
|
+
),
|
|
267
|
+
(r"^pipe normal bridge stopped$", "Pipe 普通桥已停止"),
|
|
268
|
+
(
|
|
269
|
+
r"^pipe trade bridge started pipe=(?P<pipe>[^ ]+) request_channel=(?P<channel>.+)$",
|
|
270
|
+
"Pipe 交易桥已启动 pipe={pipe} 请求通道={channel}",
|
|
271
|
+
),
|
|
272
|
+
(r"^pipe trade bridge stopped$", "Pipe 交易桥已停止"),
|
|
273
|
+
(
|
|
274
|
+
r"^pipe connected pipe=(?P<pipe>[^ ]+) request_channel=(?P<channel>[^ ]+) bridge_id=(?P<bridge_id>.+)$",
|
|
275
|
+
"Pipe 已连接 pipe={pipe} 请求通道={channel} 桥接ID={bridge_id}",
|
|
276
|
+
),
|
|
277
|
+
(
|
|
278
|
+
r"^pipe connect/read failed: (?P<error>.+)$",
|
|
279
|
+
"Pipe 连接或读取失败:{error}",
|
|
280
|
+
),
|
|
281
|
+
(
|
|
282
|
+
r"^pipe push failed: (?P<error>.+)$",
|
|
283
|
+
"Pipe 推送失败:{error}",
|
|
284
|
+
),
|
|
285
|
+
]
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def normalize_log_language(value=None):
|
|
289
|
+
value = str(value or "").strip().lower()
|
|
290
|
+
if value in ("en", "english"):
|
|
291
|
+
return "en"
|
|
292
|
+
return "zh"
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def normalize_log_enabled(value=None):
|
|
296
|
+
if isinstance(value, bool):
|
|
297
|
+
return value
|
|
298
|
+
if value is None:
|
|
299
|
+
return True
|
|
300
|
+
text = str(value).strip().lower()
|
|
301
|
+
if text in ("0", "false", "no", "off", "disable", "disabled", "closed", "close"):
|
|
302
|
+
return False
|
|
303
|
+
return True
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def get_log_language():
|
|
307
|
+
with _LANG_LOCK:
|
|
308
|
+
if _LOG_LANGUAGE:
|
|
309
|
+
return _LOG_LANGUAGE
|
|
310
|
+
return normalize_log_language(os.environ.get("CFQUANT_QMT_LOG_LANGUAGE") or os.environ.get("CFQUANT_LOG_LANGUAGE") or "zh")
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def set_log_language(value):
|
|
314
|
+
global _LOG_LANGUAGE
|
|
315
|
+
lang = normalize_log_language(value)
|
|
316
|
+
with _LANG_LOCK:
|
|
317
|
+
_LOG_LANGUAGE = lang
|
|
318
|
+
return lang
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def get_log_enabled():
|
|
322
|
+
with _LANG_LOCK:
|
|
323
|
+
if _LOG_ENABLED is not None:
|
|
324
|
+
return bool(_LOG_ENABLED)
|
|
325
|
+
return normalize_log_enabled(os.environ.get("CFQUANT_QMT_LOG_ENABLED") or os.environ.get("CFQUANT_LOG_ENABLED") or "1")
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def set_log_enabled(value):
|
|
329
|
+
global _LOG_ENABLED
|
|
330
|
+
enabled = normalize_log_enabled(value)
|
|
331
|
+
with _LANG_LOCK:
|
|
332
|
+
_LOG_ENABLED = enabled
|
|
333
|
+
return enabled
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def translate_log(message, language=None):
|
|
337
|
+
text = str(message)
|
|
338
|
+
if normalize_log_language(language or get_log_language()) == "en":
|
|
339
|
+
return text
|
|
340
|
+
for pattern, template in _TRANSLATIONS:
|
|
341
|
+
match = re.match(pattern, text)
|
|
342
|
+
if not match:
|
|
343
|
+
continue
|
|
344
|
+
try:
|
|
345
|
+
return template.format(**match.groupdict())
|
|
346
|
+
except Exception:
|
|
347
|
+
return text
|
|
348
|
+
return text
|