From 431c42838ed60c4582a33055c1b1d570db89679b Mon Sep 17 00:00:00 2001 From: Josef Prochazka Date: Thu, 26 Mar 2026 13:16:05 +0100 Subject: [PATCH 1/2] Suppress any log streaming related errors. Just warn. --- src/resource_clients/log.ts | 14 +++++++++----- test/mock_server/server.ts | 12 ++++++++++++ test/runs.test.ts | 14 ++++++++++++++ 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/resource_clients/log.ts b/src/resource_clients/log.ts index 68905673..35a5adef 100644 --- a/src/resource_clients/log.ts +++ b/src/resource_clients/log.ts @@ -197,11 +197,15 @@ export class StreamedLog { if (!logStream) { return; } - const lastChunkRemainder = await this.logStreamChunks(logStream); - // Process whatever is left when exiting. Maybe it is incomplete, maybe it is last log without EOL. - const lastMessage = Buffer.from(lastChunkRemainder).toString().trim(); - if (lastMessage.length) { - this.destinationLog.info(lastMessage); + try { + const lastChunkRemainder = await this.logStreamChunks(logStream); + // Process whatever is left when exiting. Maybe it is incomplete, maybe it is last log without EOL. + const lastMessage = Buffer.from(lastChunkRemainder).toString().trim(); + if (lastMessage.length) { + this.destinationLog.info(lastMessage); + } + } catch (err) { + this.destinationLog.warning(`Log redirection stopped due to error: ${JSON.stringify(err)}`); } } diff --git a/test/mock_server/server.ts b/test/mock_server/server.ts index dc857810..a6df9a55 100644 --- a/test/mock_server/server.ts +++ b/test/mock_server/server.ts @@ -106,6 +106,18 @@ export function createDefaultApp(v2Router = express.Router()) { res.json({ data: { id: 'redirect-run-id', actId: 'redirect-actor-id', status: 'SUCCEEDED' } }); }); + v2Router.use('/actor-runs/econnreset-run-id/log', async (req: express.Request, res: express.Response) => { + res.write(MOCKED_ACTOR_LOGS[0]); + (res as any).flush(); + await new Promise((resolve) => { + setTimeout(resolve, 10); + }); + req.socket.destroy(); + }); + v2Router.use('/actor-runs/econnreset-run-id', async (_, res) => { + res.json({ data: { id: 'econnreset-run-id', actId: 'redirect-actor-id', status: 'SUCCEEDED' } }); + }); + v2Router.use('/actor-runs', runs); v2Router.use('/actor-tasks', tasks); v2Router.use('/users', users); diff --git a/test/runs.test.ts b/test/runs.test.ts index c051e0d6..2c4c4370 100644 --- a/test/runs.test.ts +++ b/test/runs.test.ts @@ -428,6 +428,20 @@ describe('Redirect run logs', () => { { fromStart: false, expected: MOCKED_ACTOR_LOGS_PROCESSED.slice(1) }, ]; + describe('run.getStreamedLog ECONNRESET', () => { + test('logs warning instead of throwing on error', async () => { + const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); + const streamedLog = await client.run('econnreset-run-id').getStreamedLog({ fromStart: true }); + streamedLog?.start(); + await setTimeoutNode(500); + await expect(streamedLog?.stop()).resolves.not.toThrow(); + expect( + logSpy.mock.calls.some(([msg]: [string]) => msg?.includes('Log redirection stopped due to error')), + ).toBe(true); + logSpy.mockRestore(); + }); + }); + describe('run.getStreamedLog', () => { test.each(testCases)('getStreamedLog fromStart:$fromStart', async ({ fromStart, expected }) => { const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); From 0b879f0323c883f2e3937e85d8c682b95cfb2041 Mon Sep 17 00:00:00 2001 From: Josef Prochazka Date: Thu, 26 Mar 2026 15:15:48 +0100 Subject: [PATCH 2/2] Log error with regular logger, not the redirect logger as it is not coming from the other actor log --- src/resource_clients/log.ts | 4 ++-- test/runs.test.ts | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/resource_clients/log.ts b/src/resource_clients/log.ts index 35a5adef..497373ba 100644 --- a/src/resource_clients/log.ts +++ b/src/resource_clients/log.ts @@ -4,7 +4,7 @@ import type { Readable } from 'node:stream'; import c from 'ansi-colors'; import type { Log } from '@apify/log'; -import { Logger, LogLevel } from '@apify/log'; +import log, { Logger, LogLevel } from '@apify/log'; import type { ApifyApiError } from '../apify_api_error'; import type { ApiClientSubResourceOptions } from '../base/api_client'; @@ -205,7 +205,7 @@ export class StreamedLog { this.destinationLog.info(lastMessage); } } catch (err) { - this.destinationLog.warning(`Log redirection stopped due to error: ${JSON.stringify(err)}`); + log.warning(`Log redirection stopped due to error`, err as Error); } } diff --git a/test/runs.test.ts b/test/runs.test.ts index 2c4c4370..f4d4bc08 100644 --- a/test/runs.test.ts +++ b/test/runs.test.ts @@ -428,20 +428,6 @@ describe('Redirect run logs', () => { { fromStart: false, expected: MOCKED_ACTOR_LOGS_PROCESSED.slice(1) }, ]; - describe('run.getStreamedLog ECONNRESET', () => { - test('logs warning instead of throwing on error', async () => { - const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); - const streamedLog = await client.run('econnreset-run-id').getStreamedLog({ fromStart: true }); - streamedLog?.start(); - await setTimeoutNode(500); - await expect(streamedLog?.stop()).resolves.not.toThrow(); - expect( - logSpy.mock.calls.some(([msg]: [string]) => msg?.includes('Log redirection stopped due to error')), - ).toBe(true); - logSpy.mockRestore(); - }); - }); - describe('run.getStreamedLog', () => { test.each(testCases)('getStreamedLog fromStart:$fromStart', async ({ fromStart, expected }) => { const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {}); @@ -461,4 +447,18 @@ describe('Redirect run logs', () => { logSpy.mockRestore(); }); }); + + describe('run.getStreamedLog ECONNRESET', () => { + test('logs warning instead of throwing on error', async () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const streamedLog = await client.run('econnreset-run-id').getStreamedLog({ fromStart: true }); + streamedLog?.start(); + await setTimeoutNode(500); + await expect(streamedLog?.stop()).resolves.not.toThrow(); + expect( + warnSpy.mock.calls.some(([msg]: [string]) => msg?.includes('Log redirection stopped due to error')), + ).toBe(true); + warnSpy.mockRestore(); + }); + }); });