From 8bdf4990a20427097e143c49544cf276bffca8f7 Mon Sep 17 00:00:00 2001 From: linmianxun <634841751@qq.com> Date: Sun, 13 Sep 2026 21:47:05 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=BC=BA=208910=20=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E7=9B=AF=E7=9B=98=E4=B8=8E=E8=81=94=E8=B0=83=E5=8F=AF=E8=AF=BB?= =?UTF-8?q?=E6=80=A7=EF=BC=9A=E6=9C=AC=E6=9C=BA=E8=90=BD=E7=9B=98=20info/e?= =?UTF-8?q?rror=E3=80=81bat=20=E5=85=A5=E5=8F=A3=EF=BC=8C=E5=87=BA?= =?UTF-8?q?=E7=AB=99=E4=B8=8E=20echo=20=E6=89=93=20reply=20=E9=A2=84?= =?UTF-8?q?=E8=A7=88=EF=BC=8C=E9=83=A8=E7=BD=B2=E5=BC=BA=E5=88=B6=20UTF-8?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- .gitignore | 2 + deploy/Watch-Agent8910-Once.bat | 28 +++ deploy/Watch-Agent8910.bat | 31 +++ deploy/Watch-Agent8910.ps1 | 197 +++++++++++++++---- deploy/Watch-Agent8910.同事说明.txt | 38 ++++ deploy/_remote_safe_deploy_agent.ps1 | 4 + inquiry-agent/agent/channel/outbox/sender.py | 3 +- inquiry-agent/agent/handlers/echo_text.py | 5 +- 8 files changed, 263 insertions(+), 45 deletions(-) create mode 100644 deploy/Watch-Agent8910-Once.bat create mode 100644 deploy/Watch-Agent8910.bat create mode 100644 deploy/Watch-Agent8910.同事说明.txt diff --git a/.gitignore b/.gitignore index 1056abd..fc8affc 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,8 @@ spool/ _trash_*/ *.zip agent-release.zip +# 本机 Watch-Agent8910 落盘目录 +/logs/ # 本机工具(Maven 等,不进仓) .tools/ diff --git a/deploy/Watch-Agent8910-Once.bat b/deploy/Watch-Agent8910-Once.bat new file mode 100644 index 0000000..8c58ecb --- /dev/null +++ b/deploy/Watch-Agent8910-Once.bat @@ -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% diff --git a/deploy/Watch-Agent8910.bat b/deploy/Watch-Agent8910.bat new file mode 100644 index 0000000..77818f2 --- /dev/null +++ b/deploy/Watch-Agent8910.bat @@ -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% diff --git a/deploy/Watch-Agent8910.ps1 b/deploy/Watch-Agent8910.ps1 index a57fe4c..39eee25 100644 --- a/deploy/Watch-Agent8910.ps1 +++ b/deploy/Watch-Agent8910.ps1 @@ -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 diff --git a/deploy/Watch-Agent8910.同事说明.txt b/deploy/Watch-Agent8910.同事说明.txt new file mode 100644 index 0000000..790cae3 --- /dev/null +++ b/deploy/Watch-Agent8910.同事说明.txt @@ -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 diff --git a/deploy/_remote_safe_deploy_agent.ps1 b/deploy/_remote_safe_deploy_agent.ps1 index 52358e2..f036b8c 100644 --- a/deploy/_remote_safe_deploy_agent.ps1 +++ b/deploy/_remote_safe_deploy_agent.ps1 @@ -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 + '"'), diff --git a/inquiry-agent/agent/channel/outbox/sender.py b/inquiry-agent/agent/channel/outbox/sender.py index 89c12b7..efe0a83 100644 --- a/inquiry-agent/agent/channel/outbox/sender.py +++ b/inquiry-agent/agent/channel/outbox/sender.py @@ -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: diff --git a/inquiry-agent/agent/handlers/echo_text.py b/inquiry-agent/agent/handlers/echo_text.py index c16617c..940b479 100644 --- a/inquiry-agent/agent/handlers/echo_text.py +++ b/inquiry-agent/agent/handlers/echo_text.py @@ -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, )