1+ #!/usr/bin/env python3
2+ """
3+ 简单的 Webhook 服务器 - 使用标准库接收 Alertmanager 告警
4+ """
5+
6+ from http .server import HTTPServer , BaseHTTPRequestHandler
7+ import json
8+ from datetime import datetime
9+ import threading
10+ import time
11+
12+ # 存储接收到的告警
13+ alerts_received = []
14+ alerts_lock = threading .Lock ()
15+
16+ class WebhookHandler (BaseHTTPRequestHandler ):
17+ def do_POST (self ):
18+ """处理 POST 请求"""
19+ if self .path == '/v1/integrations/alertmanager/webhook' :
20+ try :
21+ # 读取请求体
22+ content_length = int (self .headers ['Content-Length' ])
23+ post_data = self .rfile .read (content_length )
24+
25+ # 解析 JSON
26+ data = json .loads (post_data .decode ('utf-8' ))
27+
28+ # 记录告警
29+ timestamp = datetime .now ().isoformat ()
30+ with alerts_lock :
31+ alert_record = {
32+ "timestamp" : timestamp ,
33+ "data" : data
34+ }
35+ alerts_received .append (alert_record )
36+
37+ # 只保留最近100条
38+ if len (alerts_received ) > 100 :
39+ alerts_received .pop (0 )
40+
41+ # 打印到控制台
42+ print (f"\n [{ timestamp } ] 收到告警:" )
43+ print (json .dumps (data , indent = 2 , ensure_ascii = False ))
44+
45+ # 提取并显示关键信息
46+ if 'alerts' in data :
47+ print ("\n 告警摘要:" )
48+ for alert in data ['alerts' ]:
49+ alert_name = alert .get ('labels' , {}).get ('alertname' , 'Unknown' )
50+ status = alert .get ('status' , 'Unknown' )
51+ severity = alert .get ('labels' , {}).get ('severity' , 'Unknown' )
52+ service = alert .get ('labels' , {}).get ('service' , 'N/A' )
53+ print (f" - { alert_name } : { status } (severity: { severity } , service: { service } )" )
54+
55+ print ("-" * 50 )
56+
57+ # 返回成功响应
58+ self .send_response (200 )
59+ self .send_header ('Content-Type' , 'application/json' )
60+ self .end_headers ()
61+ response = {"status" : "success" , "message" : "Alert received" }
62+ self .wfile .write (json .dumps (response ).encode ())
63+
64+ except Exception as e :
65+ print (f"Error processing alert: { e } " )
66+ self .send_response (400 )
67+ self .send_header ('Content-Type' , 'application/json' )
68+ self .end_headers ()
69+ response = {"status" : "error" , "message" : str (e )}
70+ self .wfile .write (json .dumps (response ).encode ())
71+ else :
72+ self .send_response (404 )
73+ self .end_headers ()
74+ self .wfile .write (b"Not Found" )
75+
76+ def do_GET (self ):
77+ """处理 GET 请求"""
78+ if self .path == '/alerts' :
79+ # 返回接收到的告警列表
80+ self .send_response (200 )
81+ self .send_header ('Content-Type' , 'application/json' )
82+ self .end_headers ()
83+ with alerts_lock :
84+ self .wfile .write (json .dumps (alerts_received , indent = 2 ).encode ())
85+
86+ elif self .path == '/health' :
87+ # 健康检查
88+ self .send_response (200 )
89+ self .end_headers ()
90+ self .wfile .write (b"OK" )
91+
92+ elif self .path == '/' :
93+ # 首页
94+ self .send_response (200 )
95+ self .send_header ('Content-Type' , 'text/html' )
96+ self .end_headers ()
97+ html = """
98+ <html>
99+ <head><title>Webhook Server</title></head>
100+ <body>
101+ <h1>Mock Webhook Server</h1>
102+ <p>Webhook endpoint: POST /v1/integrations/alertmanager/webhook</p>
103+ <p>View alerts: <a href="/alerts">GET /alerts</a></p>
104+ <p>Health check: <a href="/health">GET /health</a></p>
105+ <hr>
106+ <p>Alerts received: {}</p>
107+ </body>
108+ </html>
109+ """ .format (len (alerts_received ))
110+ self .wfile .write (html .encode ())
111+ else :
112+ self .send_response (404 )
113+ self .end_headers ()
114+ self .wfile .write (b"Not Found" )
115+
116+ def log_message (self , format , * args ):
117+ """自定义日志格式"""
118+ return # 禁用默认的日志输出,避免太多噪音
119+
120+ def run_server (port = 8080 ):
121+ """运行服务器"""
122+ server_address = ('' , port )
123+ httpd = HTTPServer (server_address , WebhookHandler )
124+
125+ print ("=" * 60 )
126+ print ("Mock Webhook Server 已启动" )
127+ print (f"监听地址: http://0.0.0.0:{ port } " )
128+ print (f"Webhook 端点: POST /v1/integrations/alertmanager/webhook" )
129+ print (f"查看告警: GET /alerts" )
130+ print (f"健康检查: GET /health" )
131+ print ("=" * 60 )
132+ print ("\n 等待接收告警...\n " )
133+
134+ try :
135+ httpd .serve_forever ()
136+ except KeyboardInterrupt :
137+ print ("\n 服务器已停止" )
138+ httpd .shutdown ()
139+
140+ if __name__ == '__main__' :
141+ import sys
142+
143+ # 检查是否指定端口
144+ port = 8080
145+ if len (sys .argv ) > 1 :
146+ try :
147+ port = int (sys .argv [1 ])
148+ except ValueError :
149+ print (f"无效的端口: { sys .argv [1 ]} " )
150+ sys .exit (1 )
151+
152+ # 检查端口是否被占用
153+ import socket
154+ sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
155+ result = sock .connect_ex (('127.0.0.1' , port ))
156+ sock .close ()
157+
158+ if result == 0 :
159+ print (f"警告: 端口 { port } 已被占用" )
160+ print ("你可以:" )
161+ print (f"1. 使用其他端口: python3 { sys .argv [0 ]} 8081" )
162+ print (f"2. 或者停止占用端口 { port } 的服务" )
163+ response = input (f"\n 是否继续在端口 { port } 上启动? (y/N): " )
164+ if response .lower () != 'y' :
165+ sys .exit (0 )
166+
167+ run_server (port )
0 commit comments