213 lines
7.8 KiB
PowerShell
213 lines
7.8 KiB
PowerShell
# 只读:拉服务器新系统 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
|
||
#
|
||
# 本机落盘(仓库根 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,
|
||
[switch]$Access,
|
||
[switch]$Gbk,
|
||
[switch]$NoLocalLog,
|
||
[int]$Tail = 120
|
||
)
|
||
|
||
$ErrorActionPreference = 'Stop'
|
||
$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'
|
||
|
||
$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"
|
||
}
|
||
|
||
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'
|
||
}
|
||
|
||
# 仓库根/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' }
|
||
|
||
$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 {
|
||
`$h = Invoke-WebRequest -Uri 'http://127.0.0.1:8910/health' -UseBasicParsing -TimeoutSec 5
|
||
Write-Output `$h.Content
|
||
} catch {
|
||
Write-Output ('health_fail: ' + `$_.Exception.Message)
|
||
}
|
||
Write-Output '=== business/error log (err) ==='
|
||
|
||
function Show-Err([string]`$line) {
|
||
if (`$all) { Write-Output ('[err] ' + `$line); return }
|
||
if (`$line -match `$pat) { Write-Output ('[err] ' + `$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) {
|
||
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 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'
|
||
[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" }
|
||
|
||
# 用 Process + UTF-8 读 SSH 输出,避免管道把中文解错再写入本地日志
|
||
$sshArgs = @(
|
||
'-o', 'BatchMode=yes',
|
||
'-o', 'ConnectTimeout=12',
|
||
'-i', $SshKey,
|
||
$SshHost,
|
||
"powershell -NoProfile -ExecutionPolicy Bypass -File `"$IncomingLocalRun`""
|
||
)
|
||
$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
|