增强 8910 日志盯盘与联调可读性:本机落盘 info/error、bat 入口,出站与 echo 打 reply 预览,部署强制 UTF-8。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
linmianxun
2026-09-13 21:47:05 +08:00
co-authored by Cursor
parent 4d90bba99d
commit 8bdf4990a2
8 changed files with 263 additions and 45 deletions
+2
View File
@@ -59,6 +59,8 @@ spool/
_trash_*/
*.zip
agent-release.zip
# 本机 Watch-Agent8910 落盘目录
/logs/
# 本机工具(Maven 等,不进仓)
.tools/
+28
View File
@@ -0,0 +1,28 @@
@echo off
setlocal EnableExtensions
cd /d "%~dp0"
REM Double-click: fetch recent remote agent 8910 logs once, then pause.
where powershell.exe >nul 2>&1
if errorlevel 1 (
echo ERROR: powershell.exe not found.
goto :end
)
if not exist "%~dp0Watch-Agent8910.ps1" (
echo ERROR: Watch-Agent8910.ps1 missing next to this bat.
goto :end
)
echo Starting Watch-Agent8910 -Once ...
echo.
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%~dp0Watch-Agent8910.ps1" -Once %*
set ERR=%ERRORLEVEL%
echo.
echo Finished. ExitCode=%ERR%
:end
echo.
pause
exit /b %ERR%
+31
View File
@@ -0,0 +1,31 @@
@echo off
setlocal EnableExtensions
cd /d "%~dp0"
REM Double-click: follow remote agent 8910 logs. Ctrl+C to stop.
REM Colleague setup: see Watch-Agent8910.colleague.txt (Chinese guide also available).
REM Pass-through args, e.g. Watch-Agent8910.bat -Once
where powershell.exe >nul 2>&1
if errorlevel 1 (
echo ERROR: powershell.exe not found.
goto :end
)
if not exist "%~dp0Watch-Agent8910.ps1" (
echo ERROR: Watch-Agent8910.ps1 missing next to this bat.
goto :end
)
echo Starting Watch-Agent8910 ...
echo Local logs: repo\logs\YYYYMMDD_info.log and _error.log
echo.
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%~dp0Watch-Agent8910.ps1" %*
set ERR=%ERRORLEVEL%
echo.
echo Finished. ExitCode=%ERR%
:end
echo.
pause
exit /b %ERR%
+154 -43
View File
@@ -1,58 +1,120 @@
# 只读:拉服务器新系统 8910 智能体日志中的企微相关行与错误行。
# 用法(仓库根或任意目录):
# .\deploy\Watch-Agent8910.ps1 # 持续监听(Ctrl+C 结束)
# .\deploy\Watch-Agent8910.ps1 -Once # 只看最近一段
# .\deploy\Watch-Agent8910.ps1 -All # 不过滤关键字,整行刷出
# 只读:拉服务器新系统 8910 的「业务/错误」日志,并同时落到本机 logs/。
# 用法:
# powershell -NoProfile -ExecutionPolicy Bypass -File .\deploy\Watch-Agent8910.ps1
# powershell -NoProfile -ExecutionPolicy Bypass -File .\deploy\Watch-Agent8910.ps1 -Once
# 双击 Watch-Agent8910.bat / Watch-Agent8910-Once.bat
#
# 说明:
# - 盯的是 E:\wwwroot\logs\agent-http.*.log(新系统 8910),不碰现网 8810/8811。
# - 切流前公网 /wecom/callback 仍进现网;本脚本主要看到 inject、出站、本机调 8910 的错误。
# - 依赖本机 SSH 密钥 ~/.ssh/id_ed25519_ytd。
# 本机落盘(仓库根 logs/,按当天日期):
# logs/20260913_info.log — 业务信息(入队、reply=、发送成功等)
# logs/20260913_error.log — 告警/异常(WARNING、ERROR、Traceback、errcode 等)
#
# 乱码:默认 UTF-8;仍乱码可加 -Gbk。
# SSH:默认密钥 ~/.ssh/id_ed25519_ytd;可用 YTD_SSH_KEY / YTD_SSH_HOST 覆盖。
[CmdletBinding()]
param(
[switch]$Once,
[switch]$All,
[int]$Tail = 80
[switch]$Access,
[switch]$Gbk,
[switch]$NoLocalLog,
[int]$Tail = 120
)
$ErrorActionPreference = 'Stop'
$SshKey = Join-Path $env:USERPROFILE '.ssh\id_ed25519_ytd'
$SshHost = 'ytd_extdev_0903@129.211.181.80'
$SshKey = if ($env:YTD_SSH_KEY) { $env:YTD_SSH_KEY } else { Join-Path $env:USERPROFILE '.ssh\id_ed25519_ytd' }
$SshHost = if ($env:YTD_SSH_HOST) { $env:YTD_SSH_HOST } else { 'ytd_extdev_0903@129.211.181.80' }
$LogOut = 'E:\wwwroot\logs\agent-http.out.log'
$LogErr = 'E:\wwwroot\logs\agent-http.err.log'
$IncomingRemotePath = 'C:/Users/ytd_extdev_0903/incoming/_watch_agent8910.ps1'
$IncomingLocalRun = 'C:\Users\ytd_extdev_0903\incoming\_watch_agent8910.ps1'
# 企微回调 / 出站 / 注入 / 失败;故意不含纯 /health 探活行,避免刷屏
$Pattern = '/wecom/callback|inject-text|inject:|outbox|inbox|errcode|errmsg|60020|ERROR|WARNING|Traceback|Exception|ModuleNotFoundError|sender=|touser=|crypto|ChannelRuntime|dispatch intent'
$Pattern = '回调|inject-text|inject:|outbox|inbox|errcode|errmsg|60020|ERROR|WARNING|Traceback|Exception|ModuleNotFoundError|sender=|touser=|crypto|ChannelRuntime|dispatch intent|企微发送|验签|解密|reply='
if (-not (Test-Path $SshKey)) {
throw "缺少 SSH 密钥: $SshKey"
}
Write-Host "目标: $SshHost"
Write-Host "日志: $LogOut"
Write-Host " $LogErr"
if ($All) {
Write-Host '过滤: 关闭(全部输出)'
} else {
Write-Host "过滤: $Pattern"
try {
chcp 65001 | Out-Null
[Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false)
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
$OutputEncoding = [System.Text.UTF8Encoding]::new($false)
} catch {
Write-Host 'warn: 无法设置本机控制台 UTF-8'
}
Write-Host '---'
# 仓库根/logs(本脚本在 deploy/ 下)
$RepoRoot = Split-Path -Parent $PSScriptRoot
$LocalLogDir = Join-Path $RepoRoot 'logs'
$Day = Get-Date -Format 'yyyyMMdd'
$LocalInfoLog = Join-Path $LocalLogDir ($Day + '_info.log')
$LocalErrorLog = Join-Path $LocalLogDir ($Day + '_error.log')
$Utf8NoBom = New-Object System.Text.UTF8Encoding $false
if (-not $NoLocalLog) {
New-Item -ItemType Directory -Force -Path $LocalLogDir | Out-Null
$sessionHdr = '===== watch start ' + (Get-Date).ToString('yyyy-MM-dd HH:mm:ss') + ' host=' + $SshHost + ' once=' + $Once.IsPresent + ' ====='
[System.IO.File]::AppendAllText($LocalInfoLog, $sessionHdr + [Environment]::NewLine, $Utf8NoBom)
[System.IO.File]::AppendAllText($LocalErrorLog, $sessionHdr + [Environment]::NewLine, $Utf8NoBom)
}
# 写入本地 *_error.log 的判定(其余进 *_info.log)
# 注意:不能用裸 ERROR(-match 不区分大小写,会误伤 “error.log” 说明行)
function Write-LocalLine([string]$line) {
Write-Host $line
if ($NoLocalLog) { return }
# 脚本自身说明行一律进 info,避免关键字误伤
if ($line -match '^(目标:|主日志|访问日志|日志编码|过滤:|本机落盘:|说明:|---+|=====)') {
$isErr = $false
} else {
$isErr = ($line -cmatch '\] WARNING |\] ERROR |Traceback|Exception|ModuleNotFoundError') -or
($line -match 'errcode=\d+|errmsg=.+|health_fail|MISSING |解密失败|验签/解密失败|relation "agent_inbox"|not allow to access from your ip')
}
$target = if ($isErr) { $LocalErrorLog } else { $LocalInfoLog }
try {
[System.IO.File]::AppendAllText($target, $line + [Environment]::NewLine, $Utf8NoBom)
} catch {
Write-Host ('warn: 写本地日志失败: ' + $_.Exception.Message)
}
}
Write-LocalLine ("目标: $SshHost")
Write-LocalLine ("主日志(业务/错误): $LogErr")
if ($Access -or $All) { Write-LocalLine ("访问日志: $LogOut") }
Write-LocalLine ("日志编码: " + $(if ($Gbk) { 'Default/GBK' } else { 'UTF-8' }))
if ($All) {
Write-LocalLine '过滤: 关闭'
} else {
Write-LocalLine ("过滤: $Pattern")
}
if (-not $NoLocalLog) {
Write-LocalLine ("本机落盘: $LocalInfoLog")
Write-LocalLine ("本机落盘: $LocalErrorLog")
}
Write-LocalLine '说明: 企微气泡里的 [test] 回复若已出现=发送成功;日志里看 reply= 字段'
Write-LocalLine '---'
$waitLiteral = if ($Once) { '$false' } else { '$true' }
$allLiteral = if ($All) { '$true' } else { '$false' }
$accessLiteral = if ($Access) { '$true' } else { '$false' }
$encName = if ($Gbk) { 'Default' } else { 'UTF8' }
# 远程执行体:先 health,再 Tail;持续模式用 -Wait
$remoteScript = @"
`$ErrorActionPreference = 'Continue'
try {
chcp 65001 | Out-Null
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new(`$false)
`$OutputEncoding = [System.Text.UTF8Encoding]::new(`$false)
} catch {}
`$out = '$LogOut'
`$err = '$LogErr'
`$tail = $Tail
`$wait = $waitLiteral
`$all = $allLiteral
`$wantAccess = $accessLiteral
`$pat = '$Pattern'
`$enc = '$encName'
Write-Output '=== remote health 8910 ==='
try {
@@ -61,41 +123,90 @@ try {
} catch {
Write-Output ('health_fail: ' + `$_.Exception.Message)
}
Write-Output '=== log tail ==='
Write-Output '=== business/error log (err) ==='
`$files = @()
if (Test-Path `$out) { `$files += `$out } else { Write-Output ('MISSING ' + `$out) }
if (Test-Path `$err) { `$files += `$err } else { Write-Output ('MISSING ' + `$err) }
if (`$files.Count -eq 0) { throw 'no agent-http log files' }
function Show-Err([string]`$line) {
if (`$all) { Write-Output ('[err] ' + `$line); return }
if (`$line -match `$pat) { Write-Output ('[err] ' + `$line) }
}
function Show-Line([string]`$line) {
if (`$all) {
Write-Output `$line
return
}
if (`$line -match `$pat) {
Write-Output `$line
function Show-Out([string]`$line) {
if (-not `$wantAccess -and -not `$all) { return }
if (`$all) { Write-Output ('[out] ' + `$line); return }
if (`$line -match '/wecom/callback|inject-text') {
Write-Output ('[out] ' + `$line)
}
}
if (-not (Test-Path `$err)) { Write-Output ('MISSING ' + `$err) }
if (-not `$wait) {
foreach (`$f in `$files) {
Write-Output ('--- ' + `$f + ' ---')
Get-Content -Path `$f -Tail `$tail -ErrorAction SilentlyContinue | ForEach-Object { Show-Line `$_ }
if (Test-Path `$err) {
Get-Content -Path `$err -Tail `$tail -Encoding `$enc -ErrorAction SilentlyContinue | ForEach-Object { Show-Err `$_ }
}
if (`$wantAccess -or `$all) {
Write-Output '=== access log (out) ==='
if (Test-Path `$out) {
Get-Content -Path `$out -Tail `$tail -Encoding `$enc -ErrorAction SilentlyContinue | ForEach-Object { Show-Out `$_ }
}
}
exit 0
}
Write-Output 'following... (Ctrl+C to stop)'
Get-Content -Path `$files -Tail `$tail -Wait -ErrorAction SilentlyContinue | ForEach-Object { Show-Line `$_ }
Write-Output 'following err.log ... (Ctrl+C to stop)'
`$watch = @()
if (Test-Path `$err) { `$watch += `$err }
if ((`$wantAccess -or `$all) -and (Test-Path `$out)) { `$watch += `$out }
if (`$watch.Count -eq 0) { throw 'no log files' }
Get-Content -Path `$watch -Tail `$tail -Wait -Encoding `$enc -ErrorAction SilentlyContinue | ForEach-Object {
`$line = `$_
if (`$line -match '^\s*INFO:\s+\S+\s+-\s+"(GET|POST|PUT|DELETE)') {
Show-Out `$line
} else {
Show-Err `$line
}
}
"@
$localTemp = Join-Path $env:TEMP 'ytd-watch-agent8910-remote.ps1'
Set-Content -Path $localTemp -Value $remoteScript -Encoding UTF8
[System.IO.File]::WriteAllText($localTemp, $remoteScript, $Utf8NoBom)
& scp.exe -o BatchMode=yes -o ConnectTimeout=12 -i $SshKey $localTemp "${SshHost}:${IncomingRemotePath}"
if ($LASTEXITCODE -ne 0) { throw "scp failed: $LASTEXITCODE" }
& ssh.exe -o BatchMode=yes -o ConnectTimeout=12 -i $SshKey $SshHost `
# 用 Process + UTF-8 读 SSH 输出,避免管道把中文解错再写入本地日志
$sshArgs = @(
'-o', 'BatchMode=yes',
'-o', 'ConnectTimeout=12',
'-i', $SshKey,
$SshHost,
"powershell -NoProfile -ExecutionPolicy Bypass -File `"$IncomingLocalRun`""
exit $LASTEXITCODE
)
$psi = New-Object System.Diagnostics.ProcessStartInfo
$psi.FileName = 'ssh.exe'
$psi.Arguments = ($sshArgs | ForEach-Object {
if ($_ -match '\s') { '"' + $_ + '"' } else { $_ }
}) -join ' '
$psi.UseShellExecute = $false
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
$psi.CreateNoWindow = $true
$psi.StandardOutputEncoding = [System.Text.Encoding]::UTF8
$psi.StandardErrorEncoding = [System.Text.Encoding]::UTF8
$proc = New-Object System.Diagnostics.Process
$proc.StartInfo = $psi
[void]$proc.Start()
while (-not $proc.StandardOutput.EndOfStream) {
$line = $proc.StandardOutput.ReadLine()
if ($null -ne $line) { Write-LocalLine $line }
}
$errTail = $proc.StandardError.ReadToEnd()
if ($errTail) {
foreach ($eline in ($errTail -split "`r?`n")) {
if ($eline) { Write-LocalLine $eline }
}
}
$proc.WaitForExit()
exit $proc.ExitCode
+38
View File
@@ -0,0 +1,38 @@
同事如何使用「Watch-Agent8910」(只读盯服务器 8910 日志)
一、需要具备
1. Windows + 已安装 OpenSSH 客户端(Win10/11 一般自带 ssh/scp)
2. 仓库里这两个文件(或单独拷贝 deploy 目录):
- Watch-Agent8910.ps1
- Watch-Agent8910.bat(可选 Once 版:Watch-Agent8910-Once.bat)
3. 能登录测试机的 SSH 私钥,且账号已加入服务器授权
二、本机配置密钥(二选一)
A. 默认路径(与现网脚本一致)
把私钥放到:%USERPROFILE%\.ssh\id_ed25519_ytd
(无扩展名;权限保持仅自己可读)
B. 自定义路径
运行前设置环境变量,例如:
set YTD_SSH_KEY=D:\keys\my_ytd_ed25519
set YTD_SSH_HOST=ytd_extdev_0903@129.211.181.80
三、怎么跑
- 双击 Watch-Agent8910.bat → 持续监听(Ctrl+C 结束)
- 双击 Watch-Agent8910-Once.bat → 只看最近一段
- 或命令行:
Watch-Agent8910.bat
Watch-Agent8910.bat -Once
Watch-Agent8910.bat -Access
本机同时落盘(仓库根目录 logs/,按当天日期追加写入):
logs/20260913_info.log
logs/20260913_error.log
不要提交 logs/ 到 Git。
四、注意
- 这是只读拉日志,不会改 Nginx / 不会切流
- 私钥不要发到微信群或提交 Git;向负责人单独要密钥或加自己的公钥到服务器
- 若提示禁止运行脚本:用 bat 即可(已带 Bypass),不要直接右键用受限策略跑 ps1
- 乱码时用:Watch-Agent8910.bat -Gbk
- 只要屏幕不要文件:Watch-Agent8910.bat -NoLocalLog
+4
View File
@@ -75,6 +75,8 @@ $runHttp = @(
('cd /d "' + $AgentCurrent + '"'),
'set YTD_ENV=test',
'set AGENT_HTTP_PORT=8910',
'set PYTHONUTF8=1',
'set PYTHONIOENCODING=utf-8',
':loop',
('"' + $venvPy + '" -m agent.http_app >> "' + $logHttpOut + '" 2>> "' + $logHttpErr + '"'),
('echo agent exited, restart in 3s >> "' + $logHttpErr + '"'),
@@ -87,6 +89,8 @@ $runWorker = @(
'@echo off',
('cd /d "' + $AgentCurrent + '"'),
'set YTD_ENV=test',
'set PYTHONUTF8=1',
'set PYTHONIOENCODING=utf-8',
':loop',
('"' + $venvPy + '" -m agent.worker_app >> "' + $logWorkerOut + '" 2>> "' + $logWorkerErr + '"'),
('echo worker exited, restart in 3s >> "' + $logWorkerErr + '"'),
+2 -1
View File
@@ -98,7 +98,8 @@ class WeComAppClient:
self._token_expire_at = 0.0
logger.warning("企微发送失败 errcode=%s errmsg=%s", errcode, errmsg)
return WeComSendResult(ok=False, errcode=errcode, errmsg=errmsg, raw=data)
logger.info("企微发送成功 touser=%s", touser)
preview = content if len(content) <= 200 else (content[:200] + "…")
logger.info("企微发送成功 touser=%s reply=%s", touser, preview)
return WeComSendResult(ok=True, errcode=0, errmsg=errmsg, raw=data)
def _get_token(self) -> str:
+4 -1
View File
@@ -50,9 +50,12 @@ def handle_echo_text(
content=body,
dedupe_key=dedupe,
)
# 联调需要能在 err 日志里对上企微气泡;正文截断避免超长刷屏
preview = body if len(body) <= 200 else (body[:200] + "…")
logger.info(
"echo 已排队 outbox ok=%s info=%s sender=%s",
"echo 已排队 outbox ok=%s info=%s sender=%s reply=%s",
ok,
info,
message.sender_id,
preview,
)