fix: use docker ps labels to list services and fetch logs
Replace compose-file-path-dependent `docker compose -f <path>` calls with label-based `docker ps` queries so the ops log viewer works on both public cloud and private deployments regardless of compose file location. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
父节点
5e788fe26b
当前提交
26261263a0
@ -60,15 +60,16 @@ public class SystemUpdateService {
|
|||||||
// ── 公开接口 ────────────────────────────────────────────────────────────────
|
// ── 公开接口 ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回当前正在运行的服务名列表(docker compose ps --filter status=running)。
|
* 返回当前正在运行的服务名列表。
|
||||||
* 结果已过滤为 ALLOWED_LOG_SERVICES 白名单内的服务,防止枚举非预期服务。
|
* 使用 docker ps --format "{{.Label \"com.docker.compose.service\"}}" 枚举运行中容器的服务标签,
|
||||||
|
* 不依赖 compose 文件路径,公有云/私有云均可用。
|
||||||
|
* 结果已过滤为 ALLOWED_LOG_SERVICES 白名单内的服务。
|
||||||
*/
|
*/
|
||||||
public List<String> getRunningServices() {
|
public List<String> getRunningServices() {
|
||||||
String composeFile = deployRoot + "/docker-compose.yml";
|
|
||||||
try {
|
try {
|
||||||
Process p = new ProcessBuilder(
|
Process p = new ProcessBuilder(
|
||||||
"docker", "compose", "-f", composeFile,
|
"docker", "ps",
|
||||||
"ps", "--services", "--filter", "status=running"
|
"--format", "{{.Label \"com.docker.compose.service\"}}"
|
||||||
).redirectErrorStream(true).start();
|
).redirectErrorStream(true).start();
|
||||||
String out = new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8).trim();
|
String out = new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8).trim();
|
||||||
p.waitFor();
|
p.waitFor();
|
||||||
@ -76,6 +77,7 @@ public class SystemUpdateService {
|
|||||||
return Arrays.stream(out.split("\n"))
|
return Arrays.stream(out.split("\n"))
|
||||||
.map(String::trim)
|
.map(String::trim)
|
||||||
.filter(s -> !s.isEmpty() && ALLOWED_LOG_SERVICES.contains(s))
|
.filter(s -> !s.isEmpty() && ALLOWED_LOG_SERVICES.contains(s))
|
||||||
|
.distinct()
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("failed to list running services", e);
|
log.error("failed to list running services", e);
|
||||||
@ -85,23 +87,36 @@ public class SystemUpdateService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定服务最近 N 行日志(上限 1000)。
|
* 获取指定服务最近 N 行日志(上限 1000)。
|
||||||
* service 名称校验白名单,防止注入。
|
* 通过 docker ps 标签找到容器 ID 后使用 docker logs,不依赖 compose 文件路径。
|
||||||
*/
|
*/
|
||||||
public String getServiceLogs(String service, int lines) {
|
public String getServiceLogs(String service, int lines) {
|
||||||
if (!ALLOWED_LOG_SERVICES.contains(service)) {
|
if (!ALLOWED_LOG_SERVICES.contains(service)) {
|
||||||
throw new IllegalArgumentException("不允许查看此服务的日志: " + service);
|
throw new IllegalArgumentException("不允许查看此服务的日志: " + service);
|
||||||
}
|
}
|
||||||
int safeLines = Math.min(Math.max(lines, 10), 1000);
|
int safeLines = Math.min(Math.max(lines, 10), 1000);
|
||||||
String composeFile = deployRoot + "/docker-compose.yml";
|
|
||||||
try {
|
try {
|
||||||
Process p = new ProcessBuilder(
|
// 找到对应服务的容器 ID(可能有多个,取第一个)
|
||||||
"docker", "compose", "-f", composeFile,
|
Process psProc = new ProcessBuilder(
|
||||||
"logs", "--tail", String.valueOf(safeLines), "--no-color", service
|
"docker", "ps",
|
||||||
|
"--filter", "label=com.docker.compose.service=" + service,
|
||||||
|
"--format", "{{.ID}}"
|
||||||
).redirectErrorStream(true).start();
|
).redirectErrorStream(true).start();
|
||||||
String out = new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
|
String containerId = new String(psProc.getInputStream().readAllBytes(), StandardCharsets.UTF_8).trim();
|
||||||
int exitCode = p.waitFor();
|
psProc.waitFor();
|
||||||
|
|
||||||
|
if (containerId.isEmpty()) {
|
||||||
|
return "(服务 " + service + " 当前没有运行中的容器)";
|
||||||
|
}
|
||||||
|
// 取第一行(如有多个容器)
|
||||||
|
String firstId = containerId.split("\n")[0].trim();
|
||||||
|
|
||||||
|
Process logsProc = new ProcessBuilder(
|
||||||
|
"docker", "logs", "--tail", String.valueOf(safeLines), "--timestamps", firstId
|
||||||
|
).redirectErrorStream(true).start();
|
||||||
|
String out = new String(logsProc.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
int exitCode = logsProc.waitFor();
|
||||||
if (exitCode != 0 && out.isBlank()) {
|
if (exitCode != 0 && out.isBlank()) {
|
||||||
throw new RuntimeException("docker compose logs 返回非零退出码: " + exitCode);
|
throw new RuntimeException("docker logs 返回非零退出码: " + exitCode);
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户