增强 8910 日志盯盘与联调可读性:本机落盘 info/error、bat 入口,出站与 echo 打 reply 预览,部署强制 UTF-8。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -59,6 +59,8 @@ spool/
|
||||
_trash_*/
|
||||
*.zip
|
||||
agent-release.zip
|
||||
# 本机 Watch-Agent8910 落盘目录
|
||||
/logs/
|
||||
|
||||
# 本机工具(Maven 等,不进仓)
|
||||
.tools/
|
||||
|
||||
@@ -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%
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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 + '"'),
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user