diff --git a/README.md b/README.md index aebb138..ad2b1da 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,8 @@ GET /api/v1/config GET /api/v1/services ``` +日志接口支持 `since`、`until` Unix 时间戳,`severity`、`event`、`search` 筛选,`timestamp` / `severity` / `event` / `message` 排序,以及 `limit` / `offset` 分页。Web 提供今天、昨天、本周、本月和全部快捷范围。日志直接读取 systemd journal 的现有保留数据,不建立重复的 SQLite 日志副本。 + 配置写入接口: ```text diff --git a/SHA256SUMS b/SHA256SUMS index d433416..387ed20 100644 --- a/SHA256SUMS +++ b/SHA256SUMS @@ -1,10 +1,10 @@ -8e3d96c25cf6d6cd7d6dbbdf22829226ae29db5844a6c6d5867d81863538b3b0 README.md +ce6123a9d39b1c552774a4b02062abbf4e223ff8f4c93212c9ea6e0bef412c85 README.md 72c46162c33f9587c6ccb01ad53fbaaf4ecec8cd21014b909e87e8707e253e36 app/oled_font_5x7.bin d71d949643d31461f2e62ded3818e1a0f1711d1420756ad0cc32eb04cde0fad3 app/pigway_pi_control.py -7dc6c796febc012fa02e3ba9d66f151f585d8f77d4dabf9ca62c2b82423f92da app/pigway_pi_control_api.py +93aff38477658c1517eac6a2d6a0bdf23b9fb372fa0b1b578ab32b649be1f1f2 app/pigway_pi_control_api.py 75fa662ed21138a8e394de83e46281dab039433cc0d7a8f787e1a049f4b47ec8 config/pigway-pi-control.conf f34eac1df180a3da3a6998d650c06435fd89728c10b090f6162df82e53986dd0 install.sh 4f6e8db3698a62f57823d3298a0e6814593de6fa3d31e4751b7ea4a76e553e35 systemd/pigway-pi-control.service aa21cef9a06593371ccff005335b6c0c826fbf4b5c48e4255b5f4b75ce6e9217 systemd/pigway-pi-control-api.service 9f59e7313ee58e687ae6dd29d175c67082674254ac5c5f54168b2735b64c20a4 uninstall.sh -08f3e276b44d615c18c394bf0508d7134f69d272e9f0e48ce5572cb5d4515dcd web/index.html +63a5e7a21d16f7f0e72845798be3b858b641f9d886dc06a013821100ccb1f3f4 web/index.html diff --git a/app/pigway_pi_control_api.py b/app/pigway_pi_control_api.py index b41f7ca..60ed53b 100644 --- a/app/pigway_pi_control_api.py +++ b/app/pigway_pi_control_api.py @@ -91,18 +91,39 @@ def update_ini(updates): temporary.write_text(text); temporary.chmod(0o644); temporary.replace(CFG) return str(backup) -def journal(limit): - result=subprocess.run(["journalctl","-u","pigway-pi-control.service","-n",str(limit), - "--no-pager","-o","json"],capture_output=True,text=True,timeout=5) +def journal_query(since=None,until=None,severity="",event="",search="",sort="timestamp",order="desc",limit=100,offset=0): + command=["journalctl","-u","pigway-pi-control.service","--no-pager","-o","json"] + if since is not None: command.extend(["--since",f"@{since}"]) + if until is not None: command.extend(["--until",f"@{until}"]) + result=subprocess.run(command,capture_output=True,text=True,timeout=15) if result.returncode: raise RuntimeError(result.stderr.strip() or "journalctl failed") rows=[] for line in result.stdout.splitlines(): try: item=json.loads(line) - rows.append({"timestamp":item.get("__REALTIME_TIMESTAMP"),"priority":item.get("PRIORITY"), - "message":item.get("MESSAGE","")}) + message=item.get("MESSAGE","") + level_match=re.search(r"(?:^|\s)level=([A-Z]+)(?:\s|$)",message) + event_match=re.search(r"(?:^|\s)event=([^ ]+)",message) + priority=str(item.get("PRIORITY","6")) + level=(level_match.group(1) if level_match else + {"0":"EMERGENCY","1":"ALERT","2":"CRITICAL","3":"ERROR","4":"WARN","5":"NOTICE","6":"INFO","7":"DEBUG"}.get(priority,"INFO")) + rows.append({"timestamp":int(item.get("__REALTIME_TIMESTAMP",0)),"priority":priority, + "severity":level,"event":event_match.group(1) if event_match else "SYSTEM", + "message":message}) except json.JSONDecodeError: pass - return rows + severities={x.strip().upper() for x in severity.split(",") if x.strip()} + if severities: rows=[x for x in rows if x["severity"] in severities] + event_types=sorted({x["event"] for x in rows}) + if event: rows=[x for x in rows if x["event"]==event] + if search: + needle=search.casefold(); rows=[x for x in rows if needle in x["message"].casefold()] + severity_rank={"EMERGENCY":0,"ALERT":1,"CRITICAL":2,"ERROR":3,"WARN":4,"NOTICE":5,"INFO":6,"DEBUG":7} + keys={"timestamp":lambda x:x["timestamp"],"severity":lambda x:severity_rank.get(x["severity"],99), + "event":lambda x:x["event"],"message":lambda x:x["message"]} + rows.sort(key=keys[sort],reverse=order=="desc") + total=len(rows) + return {"logs":rows[offset:offset+limit],"total":total,"offset":offset,"limit":limit, + "event_types":event_types,"retention":"systemd-journal"} def service_inventory(): cfg=load_cfg() @@ -214,13 +235,25 @@ class Handler(BaseHTTPRequestHandler): if not STATUS.exists(): self.send_json(503,{"error":"agent status unavailable"}); return self.send_json(200,json.loads(STATUS.read_text())); return if parsed.path=="/api/v1/logs": - limit=min(max(int(parse_qs(parsed.query).get("limit",[api_setting("log_limit",200,int)])[0]),1),1000) - self.send_json(200,{"logs":journal(limit)}); return + query=parse_qs(parsed.query) + value=lambda key,default="":query.get(key,[default])[0] + limit=min(max(int(value("limit",api_setting("log_limit",200,int))),1),500) + offset=max(int(value("offset",0)),0) + since=int(value("since")) if value("since") else None + until=int(value("until")) if value("until") else None + if since is not None and until is not None and since>until: raise ValueError("since must not be after until") + sort=value("sort","timestamp"); order=value("order","desc") + if sort not in {"timestamp","severity","event","message"}: raise ValueError("invalid log sort") + if order not in {"asc","desc"}: raise ValueError("invalid log order") + severity=value("severity").upper(); event=value("event"); search=value("search") + if len(event)>80 or len(search)>120: raise ValueError("log filter is too long") + self.send_json(200,journal_query(since,until,severity,event,search,sort,order,limit,offset)); return if parsed.path=="/api/v1/config": self.send_json(200,{"config":config_json(),"write_requires_token":True}); return if parsed.path=="/api/v1/services": self.send_json(200,{"services":service_inventory()}); return self.send_json(404,{"error":"not found"}) + except ValueError as e: self.send_json(400,{"error":str(e)}) except Exception as e: self.send_json(500,{"error":str(e)}) def do_PUT(self): diff --git a/web/index.html b/web/index.html index a66352c..3fbc415 100644 --- a/web/index.html +++ b/web/index.html @@ -6,6 +6,9 @@ +

PIGWay Pi Control

正在连接树莓派…
离线
@@ -13,7 +16,7 @@
处理器负载
--
--
处理器温度
--
--
内存占用
--
磁盘占用
--
主网络
--
--
显示状态
--
--

当前告警

无告警

服务与进程监控

- +