Skip to content

Commit 0fae30e

Browse files
committed
1. 增加错误信息推钉钉群机器人
2. 增加备份脚本
1 parent 22ee1ff commit 0fae30e

7 files changed

Lines changed: 272 additions & 140 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,4 +298,6 @@ cython_debug/
298298

299299
*.db
300300

301-
config.yaml
301+
config.yaml
302+
303+
/tools/syncer/

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ gateway等网关插件的高扩展性
99
### 通过docker运行
1010

1111
```bash
12-
docker run anjia0532/discovery-syncer-python:v2.2.3
12+
docker run anjia0532/discovery-syncer-python:v2.3.0
1313
```
1414

1515
特别的,`-c ` 支持配置远端http[s]的地址,比如读取静态资源的,比如读取nacos的
@@ -21,6 +21,8 @@ docker run anjia0532/discovery-syncer-python:v2.2.3
2121

2222
### Api接口
2323

24+
**注意:** 请勿将此服务暴露到公网,否则对于引发的一切安全事故概不负责。
25+
2426
| 路径 | 返回值 | 用途 |
2527
|--------------------------------------------------|:-----------|:-------------------------------------------------------|
2628
| `GET /` | `OK` | 服务是否启动 |
@@ -35,7 +37,7 @@ docker run anjia0532/discovery-syncer-python:v2.2.3
3537

3638
#### `GET /-/reload` 重新加载配置文件,加载成功返回OK
3739

38-
主要是cicd场景或者k8s的configmap reload 场景使用
40+
主要是 cicd 场景或者 k8s 的 configmap reload 场景使用
3941

4042
#### `GET /health` 判断服务是否健康,可以配合k8s等容器服务的健康检查使用
4143

app/service/gateway/apisix.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ def fetch_admin_api_to_file(self, file_name: str):
170170
VersionComment=apisix_config_version_comment.get(self.VERSION),
171171
Version=self.VERSION)
172172
file_name = file_name or tempfile.gettempdir() + "/apisix.yaml"
173-
pathlib.Path(file_name).mkdir(parents=True, exist_ok=True)
173+
pathlib.Path(file_name).parent.mkdir(parents=True, exist_ok=True)
174174
with open(file_name, "w") as f:
175175
f.write(content)
176176
return content, file_name
@@ -254,13 +254,17 @@ def translate(self, target_version: str, data: dict) -> dict:
254254
return data
255255

256256
def apisix_execute(self, method, uri, params, data=None):
257-
resp_txt = httpx.request(method, f"{self._config.admin_url}{self._config.prefix}{uri}",
258-
params=params, data=data, timeout=10,
259-
headers={"X-API-KEY": self._config.config.get("X-API-KEY"),
260-
"Content-Type": "application/json", "Accept": "application/json"}).text
261-
262-
logger.info(
263-
f"请求 apisix 接口,version: {self._config.config.get('version')}, method: {method}, url: {self._config.admin_url}{self._config.prefix}{uri}, 请求参数: {params}, 请求数据: {data}, 响应结果: {resp_txt}")
257+
http_resp = httpx.request(method, f"{self._config.admin_url}{self._config.prefix}{uri}",
258+
params=params, data=data, timeout=10,
259+
headers={"X-API-KEY": self._config.config.get("X-API-KEY"),
260+
"Content-Type": "application/json", "Accept": "application/json"})
261+
resp_txt = http_resp.text
262+
if http_resp.status_code >= 400:
263+
logger.warning(
264+
f"请求 apisix 接口,version: {self._config.config.get('version')}, method: {method}, url: {self._config.admin_url}{self._config.prefix}{uri}, 请求参数: {params}, 请求数据: {data}, 响应结果: {resp_txt}")
265+
else:
266+
logger.info(
267+
f"请求 apisix 接口,version: {self._config.config.get('version')}, method: {method}, url: {self._config.admin_url}{self._config.prefix}{uri}, 请求参数: {params}, 请求数据: {data}, 响应结果: {resp_txt}")
264268

265269
resp = json.loads(resp_txt)
266270

@@ -274,5 +278,5 @@ def apisix_execute(self, method, uri, params, data=None):
274278
if APISIX_V3 != self._config.config.get('version') and "list" not in resp:
275279
resp["list"] = resp.get("node", {}).get("nodes", [])
276280
if "value" in resp.get("node", {}):
277-
resp["list"].append(resp.get("node"))
281+
resp["list"].append(resp.get("node", {}))
278282
return resp

core/lib/logger.py

Lines changed: 90 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
1-
import datetime
1+
import json
2+
import logging
23
import logging
34
import os
5+
import socket
46
import sys
57
import time
68
from collections import OrderedDict
9+
# noinspection PyUnresolvedReferences
10+
from logging.handlers import WatchedFileHandler
711
from queue import Queue
8-
# noinspection PyPackageRequirements
9-
# from elasticsearch import Elasticsearch, helpers # 性能导入时间消耗2秒,实例化时候再导入。
10-
from threading import Thread
12+
from threading import Lock, Thread
1113

12-
from funboost.core.current_task import funboost_current_task
14+
import requests
1315
from funboost.core.task_id_logger import TaskIdLogger
1416
from nb_log import LogManager
17+
# noinspection PyPackageRequirements
1518
from nb_log.monkey_print import nb_print
1619

1720
from nb_log_config import IS_ADD_ELASTIC_HANDLER, \
18-
NB_LOG_FORMATER_INDEX_FOR_CONSUMER_AND_PUBLISHER, ELASTIC_HOST, computer_name
21+
NB_LOG_FORMATER_INDEX_FOR_CONSUMER_AND_PUBLISHER, ELASTIC_HOST, computer_name, IS_ADD_DING_TALK_HANDLER, \
22+
DING_TALK_TOKEN, DING_TALK_SECRET, TIME_INTERVAL, DING_TALK_MSG_TEMPLATE
1923

2024
very_nb_print = nb_print
2125

26+
host_name = socket.gethostname()
27+
2228

2329
def get(name: str = '', tag: str = '') -> logging.Logger:
2430
"""
@@ -33,10 +39,17 @@ def get(name: str = '', tag: str = '') -> logging.Logger:
3339
logger_name = name
3440
logger = LogManager(logger_name, logger_cls=TaskIdLogger).get_logger_and_add_handlers(
3541
formatter_template=NB_LOG_FORMATER_INDEX_FOR_CONSUMER_AND_PUBLISHER)
42+
3643
if IS_ADD_ELASTIC_HANDLER:
3744
handler = ElasticHandler([ELASTIC_HOST], "")
3845
handler.setLevel(10)
3946
logger.addHandler(handler)
47+
48+
if IS_ADD_DING_TALK_HANDLER:
49+
handler = DingTalkHandler(DING_TALK_TOKEN, TIME_INTERVAL, DING_TALK_SECRET)
50+
# warnings
51+
handler.setLevel(30)
52+
logger.addHandler(handler)
4053
return logger
4154

4255

@@ -85,6 +98,76 @@ def for_task(name: str) -> logging.Logger:
8598
return get('task', name)
8699

87100

101+
class DingTalkHandler(logging.Handler):
102+
_lock_for_remove_handlers = Lock()
103+
104+
def __init__(self, ding_talk_token: str = None, time_interval: int = 60, ding_talk_secret: str = None):
105+
super().__init__()
106+
self.ding_talk_token = ding_talk_token
107+
self.ding_talk_secret = ding_talk_secret
108+
self._ding_talk_url = f'https://oapi.dingtalk.com/robot/send?access_token={ding_talk_token}'
109+
self._current_time = 0
110+
self._time_interval = time_interval # 最好别频繁发。
111+
self._msg_template = r'{"msgtype":"markdown","markdown":{"title":"discovery-syncer-python","text":"**时间:** %(asctime)s\n\n**任务:** %(task_id)s\n\n**脚本:** %(pathname)s\n\n**函数:** %(funcName)s\n\n**行号:** %(lineno)s\n\n**信息:** %(msg)s"}}'
112+
self._lock = Lock()
113+
114+
def emit(self, record):
115+
# from threading import Thread
116+
with self._lock:
117+
if time.time() - self._current_time > self._time_interval:
118+
# very_nb_print(self._current_time)
119+
self._current_time = time.time()
120+
self.__emit(record)
121+
# Thread(target=self.__emit, args=(record,)).start()
122+
123+
else:
124+
very_nb_print(
125+
f'此次离上次发送钉钉消息时间间隔不足 {self._time_interval} 秒,此次不发送这个钉钉内容: {record.msg}')
126+
127+
def __emit(self, record):
128+
data = (DING_TALK_MSG_TEMPLATE or self._msg_template) % record.__dict__
129+
try:
130+
# 因为钉钉发送也是使用requests实现的,如果requests调用的urllib3命名空间也加上了钉钉日志,将会造成循环,程序卡住。一般情况是在根日志加了钉钉handler。
131+
self._remove_urllib_hanlder()
132+
resp = requests.post(self._ding_talk_url + self.sign(),
133+
json=json.loads(data.replace("\\", "\\\\").replace("\\\\n", "\\n")), timeout=(5, 5))
134+
very_nb_print(f'钉钉返回 : {resp.text}')
135+
except requests.RequestException as e:
136+
very_nb_print(f"发送消息给钉钉机器人失败 {e}")
137+
138+
def __repr__(self):
139+
level = logging.getLevelName(self.level)
140+
return '<%s (%s)>' % (self.__class__.__name__, level) + ' dingtalk token is ' + self.ding_talk_token
141+
142+
def sign(self):
143+
if not self.ding_talk_secret:
144+
return ""
145+
import time
146+
import hmac
147+
import hashlib
148+
import base64
149+
import urllib.parse
150+
timestamp = str(round(time.time() * 1000))
151+
secret_enc = self.ding_talk_secret.encode('utf-8')
152+
string_to_sign = '{}\n{}'.format(timestamp, self.ding_talk_secret)
153+
string_to_sign_enc = string_to_sign.encode('utf-8')
154+
hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()
155+
sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
156+
return "&timestamp={}&sign={}".format(timestamp, sign)
157+
158+
@classmethod
159+
def _remove_urllib_hanlder(cls):
160+
for name in ['root', 'urllib3', 'requests']:
161+
cls.__remove_urllib_hanlder_by_name(name)
162+
163+
@classmethod
164+
def __remove_urllib_hanlder_by_name(cls, logger_name):
165+
with cls._lock_for_remove_handlers:
166+
for index, hdlr in enumerate(logging.getLogger(logger_name).handlers):
167+
if 'DingTalkHandler' in str(hdlr):
168+
logging.getLogger(logger_name).handlers.pop(index)
169+
170+
88171
# noinspection PyUnresolvedReferences
89172
class ElasticHandler(logging.Handler):
90173
"""
@@ -171,8 +254,7 @@ def emit(self, record):
171254
log_info_dict['log_level'] = level_str
172255
log_info_dict['msg'] = str(record.msg)
173256
log_info_dict['script'] = self.script_name
174-
if record.task_id:
175-
log_info_dict['task_id'] = record.task_id
257+
log_info_dict['task_id'] = record.task_id
176258
self.__add_task_to_bulk({
177259
"_index": f'{self._index_prefix}{time.strftime("%Y.%m.%d")}',
178260
"_source": log_info_dict

misc/doc/README_CN.md

Lines changed: 0 additions & 114 deletions
This file was deleted.

nb_log_config.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,6 @@ def add_fields(self, log_record, record, message_dict):
102102
del log_record['for_segmentation_color']
103103

104104

105-
DING_TALK_TOKEN = '3dd0eexxxxxadab014bd604XXXXXXXXXXXX' # 钉钉报警机器人
106-
107-
IS_ADD_ELASTIC_HANDLER = False
108-
ELASTIC_HOST = 'http://127.0.0.1:9200'
109-
ELASTIC_PORT = 9200
110-
111105
RUN_ENV = 'test'
112106

113107
FORMATTER_DICT = {
@@ -153,3 +147,12 @@ def add_fields(self, log_record, record, message_dict):
153147
FORMATTER_KIND = 11 # 如果get_logger不指定日志模板,则默认选择第几个模板
154148

155149
NB_LOG_FORMATER_INDEX_FOR_CONSUMER_AND_PUBLISHER = FORMATTER_DICT[12]
150+
151+
IS_ADD_DING_TALK_HANDLER = False
152+
DING_TALK_TOKEN = '3dd0eexxxxxadab014bd604XXXXXXXXXXXX' # 钉钉报警机器人
153+
DING_TALK_SECRET = None # 钉钉报警机器人签名
154+
TIME_INTERVAL = 0
155+
DING_TALK_MSG_TEMPLATE = r'{"msgtype":"markdown","markdown":{"title":"discovery-syncer-python","text":"**时间:** %(asctime)s\n\n**任务:** %(task_id)s\n\n**脚本:** %(pathname)s\n\n**函数:** %(funcName)s\n\n**行号:** %(lineno)s\n\n**信息:** %(msg)s"}}'
156+
157+
IS_ADD_ELASTIC_HANDLER = False
158+
ELASTIC_HOST = 'http://127.0.0.1:9200'

0 commit comments

Comments
 (0)