提交 b89186a9 authored 作者: 陈泽健's avatar 陈泽健

feat(nginx): 实现容器化环境 Nginx 错误日志导出功能

- 新增 Export-NginxErrorLogFromContainer 函数处理容器日志导出
- 支持从 ujava 和 upython 容器导出 nginx_error.log 日志文件
- 实现 docker cp 命令将容器内日志拷贝到宿主机临时目录
- 添加 pscp 下载功能将远端日志文件传输到本地
- 支持按容器名区分本地日志文件名避免冲突
- 实现远端临时文件自动清理机制
- 添加完整的错误处理和日志记录机制
上级 610497f8
......@@ -185,6 +185,9 @@
- 步骤2:再将宿主机上的这份error.log日志文件命名改为nginx_error.log导出到本地
如果有upython容器:
1、将/var/www/html/log目录下的error.log、uinfo.log和uwsgi.log日志文件导出来命名都增加前缀:运维集控_error.log、运维集控_uinfo.log和运维集控_uwsgi.log
2、将nginx日志导出:
- 步骤1:通过docker cp将/usr/local/nginx/logs路径下的error.log日志文件拷贝到宿主机上
- 步骤2:再将宿主机上的这份error.log日志文件命名改为nginx_error.log导出到本地
新统一平台:
......
......@@ -1840,6 +1840,147 @@ function Test-ContainerInformation {
}
function Export-NginxErrorLogFromContainer {
param(
[Parameter(Mandatory=$true)] [hashtable]$Server,
[Parameter(Mandatory=$true)] [string]$ContainerName,
[Parameter(Mandatory=$true)] [string]$ExportDir,
[ref]$ExportedFiles,
[ref]$FailedFiles
)
if (-not $script:PSCP_PATH -or -not (Test-Path $script:PSCP_PATH)) {
Write-Log -Level "ERROR" -Message "[Nginx] pscp.exe 未找到,无法导出 nginx_error.log"
$FailedFiles.Value += @{
Name = "${ContainerName}_nginx_error.log" # ← 这里
RemotePath = "/usr/local/nginx/logs/error.log (in-container)"
Reason = "pscp.exe 未找到"
}
return
}
Write-Host ""
Write-Log -Level "INFO" -Message "[Nginx] 开始导出 Nginx error.log (容器: $ContainerName) ..."
$remoteTmpDir = "/tmp"
$remoteTmpFile = "$remoteTmpDir/nginx_error.log"
# 本地文件名按容器名区分
$localFileName = "${ContainerName}_nginx_error.log" # ← 新增变量
$localNginxLog = Join-Path $ExportDir $localFileName # ← 使用新文件名
# 1) 用单引号 here-string 构造远端脚本,避免 PowerShell 解析 $retCode/$?
$remoteCmd = @'
if docker ps --format "{{.Names}}" 2>/dev/null | grep -w "__CONTAINER__" >/dev/null 2>&1; then \
mkdir -p "__TMPDIR__" && \
echo "[Nginx] docker cp __CONTAINER__:/usr/local/nginx/logs/error.log -> __TMPFILE__" && \
docker cp "__CONTAINER__:/usr/local/nginx/logs/error.log" "__TMPFILE__" 2>/tmp/nginx_cp_err.log; \
retCode=$?; \
if [ $retCode -ne 0 ]; then \
echo "[Nginx] docker cp 失败,详情:"; \
if [ -f /tmp/nginx_cp_err.log ]; then cat /tmp/nginx_cp_err.log; fi; \
exit $retCode; \
fi; \
echo "[Nginx] 已拷贝到宿主机: __TMPFILE__"; \
else \
echo "[Nginx] 容器未运行或不存在: __CONTAINER__"; \
exit 1; \
fi
'@
# 占位符替换为实际值
$remoteCmd = $remoteCmd.Replace("__CONTAINER__", $ContainerName).
Replace("__TMPDIR__", $remoteTmpDir).
Replace("__TMPFILE__", $remoteTmpFile)
# 去掉 Windows 回车,避免 bash: $'\r' 未找到命令
$remoteCmd = $remoteCmd -replace "`r", ""
# 2) 远端执行 docker cp 脚本
$cpRes = Invoke-SSHCommand -HostName $Server.IP `
-User $Server.User `
-Pass $Server.Pass `
-Port $Server.Port `
-Command $remoteCmd
$cpOutput = ($cpRes.Output | Out-String).Trim()
if ($cpOutput) {
Write-Log -Level "INFO" -Message ("[Nginx] 远端输出: {0}" -f ($cpOutput -replace '\s+', ' '))
}
if ($cpRes.ExitCode -ne 0) {
Write-Log -Level "ERROR" -Message "[Nginx] 远端 docker cp 执行失败,终止 Nginx 日志导出"
$FailedFiles.Value += @{
Name = "nginx_error.log"
RemotePath = "/usr/local/nginx/logs/error.log (in-container)"
Reason = "docker cp 失败,详见远端输出"
}
return
}
# 3) 用 pscp 下载 /tmp/nginx_error.log -> 本地
Write-Log -Level "INFO" -Message "[Nginx] 下载: $remoteTmpFile -> $localNginxLog"
$pscpArgs = @(
"-scp",
"-batch",
"-P", $Server.Port,
"-l", $Server.User,
"-pw", $Server.Pass,
"$($Server.User)@$($Server.IP):$remoteTmpFile",
$localNginxLog
)
try {
$dlOut = & $script:PSCP_PATH @pscpArgs 2>&1
$dlCode = $LASTEXITCODE
if ($dlCode -ne 0 -and ($dlOut -match "host key" -or $dlOut -match "Cannot confirm")) {
$cmdLine = "echo y | `"$($script:PSCP_PATH)`" -scp -batch -P $($Server.Port) -l $($Server.User) -pw `"$($Server.Pass)`" `"$($Server.User)@$($Server.IP):$remoteTmpFile`" `"$localNginxLog`""
Write-Log -Level "WARN" -Message "[Nginx] 主机密钥提示,自动接受并重试: $cmdLine"
$dlOut = cmd /c $cmdLine 2>&1
$dlCode = $LASTEXITCODE
}
if ($dlCode -eq 0 -and (Test-Path $localNginxLog)) {
$sz = (Get-Item $localNginxLog).Length
$szKB = [math]::Round($sz / 1024, 2)
Write-Log -Level "SUCCESS" -Message "[Nginx] $localFileName 导出成功 ($szKB KB): $localNginxLog"
$ExportedFiles.Value += @{
Name = $localFileName # ← 使用容器区分名
RemotePath = "/usr/local/nginx/logs/error.log (via docker cp)"
LocalPath = $localNginxLog
Size = $sz
}
}
else {
Write-Log -Level "ERROR" -Message ("[Nginx] 下载失败,ExitCode={0}" -f $dlCode)
if ($dlOut) {
$oneLine = ($dlOut -join " ") -replace '\s+', ' '
Write-Log -Level "ERROR" -Message ("[Nginx] pscp 输出: {0}" -f $oneLine)
}
$FailedFiles.Value += @{
Name = $localFileName # ← 同样改成容器名版本
RemotePath = $remoteTmpFile
Reason = "下载失败: ExitCode=$dlCode"
}
}
}
catch {
Write-Log -Level "ERROR" -Message "[Nginx] 下载异常: $($_.Exception.Message)"
$FailedFiles.Value += @{
Name = $localFileName
RemotePath = $remoteTmpFile
Reason = "异常: $($_.Exception.Message)"
}
}
finally {
$cleanCmd = "rm -f '$remoteTmpFile' 2>/dev/null || true"
[void](Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $cleanCmd)
}
}
# ================================
# 日志导出
# ================================
......@@ -2013,102 +2154,20 @@ function Export-ServiceLogs {
}
# =============================
# 传统平台 nginx 日志导出 (PRD 183-185)
# 传统平台 nginx 日志导出(需求文档第7点:ujava / upython 都需要)
# =============================
if ($PlatformType -eq "old" -and $SystemInfo.HasUjava) {
Write-Host ""
Write-Log -Level "INFO" -Message "[Nginx] 开始执行传统平台 Nginx 日志导出 (docker cp → nginx_error.log) ..."
if ($PlatformType -eq "old") {
# 远端临时目录和临时文件路径
$remoteTmpDir = "/tmp"
$remoteTmpFile = "$remoteTmpDir/nginx_error.log"
$localNginxLog = Join-Path $exportDir "nginx_error.log"
# 步骤1:在远端通过 docker cp 将容器内 /usr/local/nginx/logs/error.log 拷贝到宿主机
# 注意:这里用单引号 here-string,避免 PowerShell 解析 $ 和 $(...) 等
$remoteScript = @'
HAS_UJAVA=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -E 'ujava' | head -n 1); if [ -z "$HAS_UJAVA" ]; then echo "[Nginx] 未检测到 ujava 容器,跳过 Nginx 日志导出。"; exit 0; fi; mkdir -p /tmp; if docker ps --format '{{.Names}}' 2>/dev/null | grep -w 'ujava2' >/dev/null 2>&1; then UJAVA_CONTAINER="ujava2"; else UJAVA_CONTAINER=$(docker ps --format '{{.Names}}' 2>/dev/null | grep 'ujava' | head -n 1); fi; if [ -z "$UJAVA_CONTAINER" ]; then echo "[Nginx] 未找到可用的 ujava 容器名称,无法导出 Nginx 日志。"; exit 1; fi; echo "[Nginx] 使用容器 $UJAVA_CONTAINER 导出 /usr/local/nginx/logs/error.log ..."; docker cp "$UJAVA_CONTAINER:/usr/local/nginx/logs/error.log" "/tmp/nginx_error.log" 2>/tmp/nginx_cp_err.log; RET=$?; if [ $RET -ne 0 ]; then echo "[Nginx] docker cp 失败,无法从容器内拷贝 error.log,详情如下:"; if [ -f /tmp/nginx_cp_err.log ]; then cat /tmp/nginx_cp_err.log; fi; exit $RET; fi; echo "[Nginx] 已将容器内 /usr/local/nginx/logs/error.log 拷贝到宿主机: /tmp/nginx_error.log"
'@
$cpRes = Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $remoteScript
$cpOutput = ($cpRes.Output | Out-String).Trim()
if ($cpOutput) {
Write-Log -Level "INFO" -Message ("[Nginx] 远端执行输出: {0}" -f ($cpOutput -replace '\s+', ' '))
}
# 如果 docker cp 脚本异常退出,则记录失败
if ($cpRes.ExitCode -ne 0) {
Write-Log -Level "ERROR" -Message "[Nginx] 远端 docker cp 执行失败,Nginx 日志导出终止。"
$failedFiles += @{
Name = "nginx_error.log"
RemotePath = "/usr/local/nginx/logs/error.log (in-container)"
Reason = "docker cp 失败,详见远端输出"
}
}
elseif ($cpOutput -match "未检测到 ujava 容器,跳过 Nginx 日志导出") {
Write-Log -Level "WARN" -Message "[Nginx] 未检测到 ujava 容器,本次跳过 Nginx 日志导出。"
# ujava 场景:从 ujava 容器导出 nginx error.log
if ($SystemInfo.HasUjava -and $SystemInfo.UjavaContainer) {
Export-NginxErrorLogFromContainer -Server $Server -ContainerName $SystemInfo.UjavaContainer -ExportDir $exportDir `
-ExportedFiles ([ref]$exportedFiles) -FailedFiles ([ref]$failedFiles)
}
else {
# 步骤2:使用 pscp 将远端临时文件下载到本地,并命名为 nginx_error.log
Write-Log -Level "INFO" -Message "[Nginx] 准备将 $remoteTmpFile 下载到本地: $localNginxLog"
$pscpArgs2 = @(
"-scp",
"-batch",
"-P", $Server.Port,
"-l", $Server.User,
"-pw", $Server.Pass,
"$($Server.User)@$($Server.IP):$remoteTmpFile",
$localNginxLog
)
try {
$dlOut = & $script:PSCP_PATH @pscpArgs2 2>&1
$dlCode = $LASTEXITCODE
if ($dlCode -ne 0 -and ($dlOut -match "host key" -or $dlOut -match "Cannot confirm")) {
$cmdLine2 = "echo y | `"$($script:PSCP_PATH)`" -scp -batch -P $($Server.Port) -l $($Server.User) -pw `"$($Server.Pass)`" `"$($Server.User)@$($Server.IP):$remoteTmpFile`" `"$localNginxLog`""
Write-Log -Level "WARN" -Message "[Nginx] 检测到主机密钥提示,自动接受并重试: $cmdLine2"
$dlOut = cmd /c $cmdLine2 2>&1
$dlCode = $LASTEXITCODE
}
if ($dlCode -eq 0 -and (Test-Path $localNginxLog)) {
$sz = (Get-Item $localNginxLog).Length
$szKB = [math]::Round($sz / 1024, 2)
Write-Log -Level "SUCCESS" -Message "[Nginx] nginx_error.log 导出成功 ($szKB KB): $localNginxLog"
$exportedFiles += @{
Name = "nginx_error.log"
RemotePath = "/usr/local/nginx/logs/error.log (via docker cp)"
LocalPath = $localNginxLog
Size = $sz
}
}
else {
Write-Log -Level "ERROR" -Message ("[Nginx] nginx_error.log 下载失败,ExitCode={0}" -f $dlCode)
if ($dlOut) {
$oneLine2 = ($dlOut -join " ") -replace '\s+', ' '
Write-Log -Level "ERROR" -Message ("[Nginx] pscp 输出: {0}" -f $oneLine2)
}
$failedFiles += @{
Name = "nginx_error.log"
RemotePath = $remoteTmpFile
Reason = "下载失败: ExitCode=$dlCode; Output=$dlOut"
}
}
}
catch {
Write-Log -Level "ERROR" -Message "[Nginx] nginx_error.log 下载异常: $($_.Exception.Message)"
$failedFiles += @{
Name = "nginx_error.log"
RemotePath = $remoteTmpFile
Reason = "异常: $($_.Exception.Message)"
}
}
# 远端清理临时文件(最佳实践,可失败不影响主流程)
$cleanCmd = "rm -f '$remoteTmpFile' 2>/dev/null || true"
[void](Invoke-SSHCommand -HostName $Server.IP -User $Server.User -Pass $Server.Pass -Port $Server.Port -Command $cleanCmd)
# upython 场景:从 upython 容器导出 nginx error.log(逻辑完全一致)
if ($SystemInfo.HasUpython -and $SystemInfo.UpythonContainer) {
Export-NginxErrorLogFromContainer -Server $Server -ContainerName $SystemInfo.UpythonContainer -ExportDir $exportDir `
-ExportedFiles ([ref]$exportedFiles) -FailedFiles ([ref]$failedFiles)
}
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论