fix: keep pod log streams alive
This commit is contained in:
@@ -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
@@ -86,7 +86,7 @@ export async function runLogs(
|
|||||||
write(service ?? event.pod ?? project, event.message, true);
|
write(service ?? event.pod ?? project, event.message, true);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
write(event.targetName || service || project, event.message, false);
|
write(event.pod || event.targetName || service || project, event.message, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ function cloneCommand<T extends object>(command: T): T {
|
|||||||
export const main = defineCommand({
|
export const main = defineCommand({
|
||||||
meta: {
|
meta: {
|
||||||
name: "kuber",
|
name: "kuber",
|
||||||
version: "2.5.1",
|
version: "2.5.2-rc.1",
|
||||||
description: "Docker Compose -> K8s translation layer",
|
description: "Docker Compose -> K8s translation layer",
|
||||||
},
|
},
|
||||||
args: {
|
args: {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@dmgnr/kuber",
|
"name": "@dmgnr/kuber",
|
||||||
"version": "2.5.1",
|
"version": "2.5.2-rc.1",
|
||||||
"description": "Docker Compose to Kubernetes translation layer",
|
"description": "Docker Compose to Kubernetes translation layer",
|
||||||
"bin": {
|
"bin": {
|
||||||
"kuber": "dist/index.js"
|
"kuber": "dist/index.js"
|
||||||
|
|||||||
@@ -40,10 +40,19 @@ function logError(error: unknown): KubernetesLogError {
|
|||||||
error && typeof error === "object" && "code" in error
|
error && typeof error === "object" && "code" in error
|
||||||
? error.code
|
? error.code
|
||||||
: undefined;
|
: 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(
|
return new KubernetesLogError(
|
||||||
error instanceof Error ? error.message : "Kubernetes log request failed",
|
error instanceof Error ? error.message : "Kubernetes log request failed",
|
||||||
code === undefined &&
|
typeof code === "string"
|
||||||
(status === undefined || status === 408 || status === 429 || status >= 500),
|
? transientNetworkCodes.has(code)
|
||||||
|
: status === undefined && /socket hang up|socket closed/i.test(message)
|
||||||
|
? true
|
||||||
|
: status === undefined || status === 408 || status === 429 || status >= 500,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -438,7 +438,7 @@ export class LogService {
|
|||||||
const discoveryInterval = positiveInteger(input.discoveryIntervalMs, 2_000);
|
const discoveryInterval = positiveInteger(input.discoveryIntervalMs, 2_000);
|
||||||
const heartbeatInterval = positiveInteger(
|
const heartbeatInterval = positiveInteger(
|
||||||
input.heartbeatIntervalMs,
|
input.heartbeatIntervalMs,
|
||||||
15_000,
|
5_000,
|
||||||
);
|
);
|
||||||
const retryInterval = positiveInteger(input.retryIntervalMs, 1_000);
|
const retryInterval = positiveInteger(input.retryIntervalMs, 1_000);
|
||||||
const queue = new BoundedAsyncQueue<LogEvent>(capacity);
|
const queue = new BoundedAsyncQueue<LogEvent>(capacity);
|
||||||
@@ -489,7 +489,11 @@ export class LogService {
|
|||||||
signal,
|
signal,
|
||||||
(event) => queue.push(event),
|
(event) => queue.push(event),
|
||||||
);
|
);
|
||||||
if (!signal.aborted) completed.add(key);
|
if (!signal.aborted)
|
||||||
|
throw new KubernetesLogError(
|
||||||
|
"Kubernetes log stream ended unexpectedly",
|
||||||
|
true,
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!isAbort(signal)) {
|
if (!isAbort(signal)) {
|
||||||
await pushError(error, source);
|
await pushError(error, source);
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ describe("logs API command runner", () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
expect(output).toEqual([
|
expect(output).toEqual([
|
||||||
["web", "ready", false],
|
["web-1", "ready", false],
|
||||||
["web api", "disconnected", true],
|
["web api", "disconnected", true],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -650,7 +650,7 @@ describe("LogService", () => {
|
|||||||
expect(error).toMatchObject({
|
expect(error).toMatchObject({
|
||||||
pod: "web-abc",
|
pod: "web-abc",
|
||||||
container: "web",
|
container: "web",
|
||||||
retryable: false,
|
retryable: true,
|
||||||
});
|
});
|
||||||
expect(error?.message).toMatch(
|
expect(error?.message).toMatch(
|
||||||
/socket.*connection.*closed|socket hang up|fetch failed/i,
|
/socket.*connection.*closed|socket hang up|fetch failed/i,
|
||||||
|
|||||||
Reference in New Issue
Block a user