增强 8910 日志盯盘与联调可读性:本机落盘 info/error、bat 入口,出站与 echo 打 reply 预览,部署强制 UTF-8。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+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
|
||||
|
||||
Reference in New Issue
Block a user