fix: keep pod log streams alive

This commit is contained in:
2026-09-27 11:37:02 +00:00 Unverified
parent 5f7509a9c5
commit d3e9753c4b
8 changed files with 32 additions and 9 deletions
+10
View File
@@ -0,0 +1,10 @@
# 2.5.2-rc.1
## Fixed
- Keep server-side Kubernetes log streams active with a 5-second idle heartbeat, below common intermediary idle timeouts.
- Retry transient Kubernetes log socket and stream closures; permanent TLS certificate errors remain terminal.
## Changed
- Prefix CLI log lines with the originating pod name instead of the target service name.
+1 -1
View File
@@ -86,7 +86,7 @@ export async function runLogs(
write(service ?? event.pod ?? project, event.message, true);
continue;
}
write(event.targetName || service || project, event.message, false);
write(event.pod || event.targetName || service || project, event.message, false);
}
}
+1 -1
View File
@@ -37,7 +37,7 @@ function cloneCommand<T extends object>(command: T): T {
export const main = defineCommand({
meta: {
name: "kuber",
version: "2.5.1",
version: "2.5.2-rc.1",
description: "Docker Compose -> K8s translation layer",
},
args: {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@dmgnr/kuber",
"version": "2.5.1",
"version": "2.5.2-rc.1",
"description": "Docker Compose to Kubernetes translation layer",
"bin": {
"kuber": "dist/index.js"
+11 -2
View File
@@ -40,10 +40,19 @@ function logError(error: unknown): KubernetesLogError {
error && typeof error === "object" && "code" in error
? error.code
: undefined;
const transientNetworkCodes = new Set([
"ECONNRESET", "ETIMEDOUT", "EPIPE", "ECONNREFUSED", "EHOSTUNREACH",
"ENETUNREACH", "ECONNABORTED", "EAI_AGAIN", "UND_ERR_SOCKET",
"UND_ERR_CONNECT_TIMEOUT",
]);
const message = error instanceof Error ? error.message : "";
return new KubernetesLogError(
error instanceof Error ? error.message : "Kubernetes log request failed",
code === undefined &&
(status === undefined || status === 408 || status === 429 || status >= 500),
typeof code === "string"
? transientNetworkCodes.has(code)
: status === undefined && /socket hang up|socket closed/i.test(message)
? true
: status === undefined || status === 408 || status === 429 || status >= 500,
);
}
+6 -2
View File
@@ -438,7 +438,7 @@ export class LogService {
const discoveryInterval = positiveInteger(input.discoveryIntervalMs, 2_000);
const heartbeatInterval = positiveInteger(
input.heartbeatIntervalMs,
15_000,
5_000,
);
const retryInterval = positiveInteger(input.retryIntervalMs, 1_000);
const queue = new BoundedAsyncQueue<LogEvent>(capacity);
@@ -489,7 +489,11 @@ export class LogService {
signal,
(event) => queue.push(event),
);
if (!signal.aborted) completed.add(key);
if (!signal.aborted)
throw new KubernetesLogError(
"Kubernetes log stream ended unexpectedly",
true,
);
} catch (error) {
if (!isAbort(signal)) {
await pushError(error, source);
+1 -1
View File
@@ -58,7 +58,7 @@ describe("logs API command runner", () => {
},
]);
expect(output).toEqual([
["web", "ready", false],
["web-1", "ready", false],
["web api", "disconnected", true],
]);
});
+1 -1
View File
@@ -650,7 +650,7 @@ describe("LogService", () => {
expect(error).toMatchObject({
pod: "web-abc",
container: "web",
retryable: false,
retryable: true,
});
expect(error?.message).toMatch(
/socket.*connection.*closed|socket hang up|fetch failed/i,