From 1cffc5e1944a1e4fdcf4cb57a3d7367b2f6901ee Mon Sep 17 00:00:00 2001 From: Josef Prochazka Date: Mon, 8 Dec 2025 15:47:03 +0100 Subject: [PATCH 1/4] Update types to match the latest API and add docs --- docs/guides/log_redirection.mdx | 64 +++++++++++++++++++++++++++++++++ package-lock.json | 57 ++++++++++------------------- package.json | 2 +- packages/apify/src/actor.ts | 44 +++++++++++------------ website/sidebars.js | 1 + 5 files changed, 105 insertions(+), 63 deletions(-) create mode 100644 docs/guides/log_redirection.mdx diff --git a/docs/guides/log_redirection.mdx b/docs/guides/log_redirection.mdx new file mode 100644 index 0000000000..0e7eeeee99 --- /dev/null +++ b/docs/guides/log_redirection.mdx @@ -0,0 +1,64 @@ +--- +id: log-redirection +title: Log redirection +description: Redirect logs from another Actor run +--- + +import ApiLink from '@site/src/components/ApiLink'; + +In some situations, one Actor is going to start one or more other Actors and wait for them to finish and produce some results. In such cases, you might want to redirect the logs of the started Actors' runs back to the parent Actor run, so that you can see the progress of the started Actors' runs in the parent Actor's logs. This guide will show possibilities on how to do it. + +### Redirecting logs from Actor.call + +Typical use case for log redirection is to call another Actor using the [`Actor.call()`](/reference/class/Actor#call) method. This method has an optional logger argument, which is by default set to the `default` literal. This means that the logs of the called Actor will be automatically redirected to the parent Actor's logs with default formatting and filtering. If you set the logger argument to `null`, then no log redirection happens. The third option is to pass your own `Log` instance with the possibility to define your specific logging. Below you can see those three possible ways of log redirection when starting another Actor run through Actor.call. + +```javascript +import { Actor } from 'apify'; +import {LoggerActorRedirect} from 'apify-client'; +import { LEVELS, Log } from '@apify/log'; + +const input = {}; // Some Actor input +const actorId= 'someActorId'; // ID of actor you want to call + +await Actor.init(); + +// Default log redirection - implicitly +await Actor.call(actorId, input); + +// Default log redirection - explicitly +await Actor.call(actorId, input, {log:'default'}); + +// No log redirection +await Actor.call(actorId, input, {log:null}); + +// Custom log redirection +await Actor.call(actorId, input, {log: new Log({ level: LEVELS.DEBUG, prefix: 'customPrefix', logger: new LoggerActorRedirect()}); + +await Actor.exit(); +``` + +Each default redirect log entry will have a specific format. After the timestamp, it will contain cyan colored text that will contain the redirect information - the other Actor's name and the run ID. The rest of the log message will be printed in the same manner as the parent Actor's log is configured. + +The log redirection can be deep, meaning that if the other Actor also starts another actor and is redirecting logs from it, then in the top-level Actor, you can see it as well. See the following example screenshot of the Apify log console when one Actor recursively starts itself (there are 2 levels of recursion in the example). + +### Redirecting logs from already running Actor run + +In some cases, you might want to connect to an already running Actor run and redirect its logs to your current Actor run. This can be done using the `ApifyClient` and getting the streamed log from a specific Actor run. You can then control the log redirection manually by explicitly calling start and stop methods. + +You can further decide whether you want to redirect just new logs of the ongoing Actor run, or if you also want to redirect historical logs from that Actor's run, so all logs it has produced since it was started. This can be controlled by the `fromStart` option of the `getStreamedLog()` method. If you set it to `true`, all historical logs will be redirected first, and then new logs will be redirected as they are produced. If you set it to `false`, only new logs will be redirected. + +```javascript +import { Actor } from 'apify'; + +const actorRunId = 'someRunId'; // ID of actor you want to call + +await Actor.init(); + +// Redirect only new logs from the other Actor run, due to `fromStart: false` +await Actor.newClient().run(actorRunId).getStreamedLog({ fromStart: false }); +streamedLog.start(); +// Do some stuff while also redirecting logs from another Actor +await streamedLog.stop(); + +await Actor.exit(); +``` diff --git a/package-lock.json b/package-lock.json index 2aac8db2c0..0c4f1a9ba7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@types/semver": "^7.5.8", "@types/tough-cookie": "^4.0.5", "@types/ws": "^8.5.12", - "apify-client": "^2.17.0", + "apify-client": "^2.20.0", "commitlint": "^20.0.0", "crawlee": "^3.13.5", "eslint": "^9.23.0", @@ -55,9 +55,9 @@ "peer": true }, "node_modules/@apify/consts": { - "version": "2.47.1", - "resolved": "https://registry.npmjs.org/@apify/consts/-/consts-2.47.1.tgz", - "integrity": "sha512-psuf1uZ8E/9YV6cQXWGX18D7k9yCJPOSCdWI3Qgy0cx6yjckgafMBEtIK+o4BlGid58sAwxBC2dtcH7iIYj5uw==", + "version": "2.48.0", + "resolved": "https://registry.npmjs.org/@apify/consts/-/consts-2.48.0.tgz", + "integrity": "sha512-a0HeYDxAbbkRxc9z2N6beMFAmAJSgBw8WuKUwV+KmCuPyGUVLp54fYzjQ63p9Gv5IVFC88/HMXpAzI29ARgO5w==", "license": "Apache-2.0" }, "node_modules/@apify/datastructures": { @@ -105,12 +105,12 @@ } }, "node_modules/@apify/log": { - "version": "2.5.26", - "resolved": "https://registry.npmjs.org/@apify/log/-/log-2.5.26.tgz", - "integrity": "sha512-o/Ciki7UwaXwdJ+tvTg7WG8Q3C+hZXYUpo2FrTG7Upr1PQW45PGfwLDuJxteIBsEXjIHtWitlLi4AZwG7mtRMQ==", + "version": "2.5.28", + "resolved": "https://registry.npmjs.org/@apify/log/-/log-2.5.28.tgz", + "integrity": "sha512-jU8qIvU+Crek8glBjFl3INjJQWWDR9n2z9Dr0WvUI8KJi0LG9fMdTvV+Aprf9z1b37CbHXgiZkA1iPlNYxKOEQ==", "license": "Apache-2.0", "dependencies": { - "@apify/consts": "^2.47.0", + "@apify/consts": "^2.48.0", "ansi-colors": "^4.1.1" } }, @@ -156,13 +156,13 @@ "license": "Apache-2.0" }, "node_modules/@apify/utilities": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/@apify/utilities/-/utilities-2.20.3.tgz", - "integrity": "sha512-zh2/pT8J7JKnAX8egR1qaCVvyHuLy8xrx5rWGuCqWqPhLfwpD+eqhsriE726mSaf8YURNxPT52Y/MjrVdmc8rg==", + "version": "2.24.1", + "resolved": "https://registry.npmjs.org/@apify/utilities/-/utilities-2.24.1.tgz", + "integrity": "sha512-e1spKyuRINF3wNfkRLp4Op/0VlDGsQ/oyalIm4Hf1HDVrYQoiFPsJb73O37ojWRtTbWn5hlApKf4aSrNhsRfag==", "license": "Apache-2.0", "dependencies": { - "@apify/consts": "^2.47.0", - "@apify/log": "^2.5.26" + "@apify/consts": "^2.48.0", + "@apify/log": "^2.5.28" } }, "node_modules/@asamuzakjp/css-color": { @@ -5263,18 +5263,6 @@ "node": ">= 14" } }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "license": "MIT", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -5368,16 +5356,16 @@ "link": true }, "node_modules/apify-client": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/apify-client/-/apify-client-2.19.0.tgz", - "integrity": "sha512-cDYwbygx/OplyF9MXTeb70nKwZHQQIp5OodsPeikWrh8sHmfeKWUu9jUUzeiWpHIPcwroYrP7KxA6UDSBGY3kQ==", + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/apify-client/-/apify-client-2.20.0.tgz", + "integrity": "sha512-oEMTImVVRZ5n8JkFV6dgbBFL3Xqz+GTwjUCjn/hwSNkow31Q8VNGk4qYDfRjkoqNQJ3ZirhtCwTnhkSXn1Tf+g==", "license": "Apache-2.0", "dependencies": { "@apify/consts": "^2.42.0", "@apify/log": "^2.2.6", - "@apify/utilities": "^2.18.0", + "@apify/utilities": "^2.23.2", "@crawlee/types": "^3.3.0", - "agentkeepalive": "^4.2.1", + "ansi-colors": "^4.1.1", "async-retry": "^1.3.3", "axios": "^1.6.7", "content-type": "^1.0.5", @@ -11111,15 +11099,6 @@ "node": ">=10.17.0" } }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "license": "MIT", - "dependencies": { - "ms": "^2.0.0" - } - }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", diff --git a/package.json b/package.json index 7489c86576..84abebccf9 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "@types/semver": "^7.5.8", "@types/tough-cookie": "^4.0.5", "@types/ws": "^8.5.12", - "apify-client": "^2.17.0", + "apify-client": "^2.20.0", "commitlint": "^20.0.0", "crawlee": "^3.13.5", "eslint": "^9.23.0", diff --git a/packages/apify/src/actor.ts b/packages/apify/src/actor.ts index 3a2e3eb5b4..45edc0f960 100644 --- a/packages/apify/src/actor.ts +++ b/packages/apify/src/actor.ts @@ -26,6 +26,7 @@ import type { import { sleep, snakeCaseToCamelCase } from '@crawlee/utils'; import type { ActorCallOptions, + ActorStartOptions, ApifyClientOptions, RunAbortOptions, TaskCallOptions, @@ -227,40 +228,37 @@ export interface ApifyEnv { export type UserFunc = () => Awaitable; -export interface CallOptions extends Omit { +export interface Token { /** - * User API token that is used to run the Actor. By default, it is taken from the `APIFY_TOKEN` environment variable. + * User Apify API token that is used for API calls. By default, it is taken from the `APIFY_TOKEN` environment variable. */ token?: string; - /** - * Timeout for the Actor run in seconds, or `'inherit'`. - * - * Using `inherit` will set timeout of the newly started Actor run to the time - * remaining until this Actor run times out so that the new run does not outlive this one. - */ - timeout?: number | 'inherit'; } -export interface CallTaskOptions extends Omit { +export interface Timeout { /** - * User API token that is used to run the Actor. By default, it is taken from the `APIFY_TOKEN` environment variable. - */ - token?: string; - /** - * Timeout for the Actor task in seconds, or `'inherit'`. + * Timeout for the Actor run in seconds, or `'inherit'`. * - * Using `inherit` will set timeout of the newly started Actor task to the time + * Using `inherit` will set timeout of the newly started Actor run to the time * remaining until this Actor run times out so that the new run does not outlive this one. */ timeout?: number | 'inherit'; } -export interface AbortOptions extends RunAbortOptions { - /** - * User API token that is used to run the Actor. By default, it is taken from the `APIFY_TOKEN` environment variable. - */ - token?: string; - +export interface CallOptions + extends Omit, + Token, + Timeout {} +export interface StartOptions + extends Omit, + Token, + Timeout {} +export interface CallTaskOptions + extends Omit, + Token, + Timeout {} + +export interface AbortOptions extends RunAbortOptions, Token { /** Exit with given status message */ statusMessage?: string; } @@ -718,7 +716,7 @@ export class Actor { async start( actorId: string, input?: unknown, - options: CallOptions = {}, + options: StartOptions = {}, ): Promise { const timeout = options.timeout === 'inherit' diff --git a/website/sidebars.js b/website/sidebars.js index 41956a1478..3a07eb318d 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -24,6 +24,7 @@ module.exports = { 'guides/pay-per-event', 'guides/type-script-actor', 'guides/docker-images', + 'guides/log-redirection', ], }, { From 496d67f1b2ef45b6a973728f584981b8a90daf3a Mon Sep 17 00:00:00 2001 From: Josef Prochazka Date: Tue, 9 Dec 2025 14:47:41 +0100 Subject: [PATCH 2/4] Minor text tweaks --- docs/guides/log_redirection.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/guides/log_redirection.mdx b/docs/guides/log_redirection.mdx index 0e7eeeee99..840325b047 100644 --- a/docs/guides/log_redirection.mdx +++ b/docs/guides/log_redirection.mdx @@ -8,7 +8,7 @@ import ApiLink from '@site/src/components/ApiLink'; In some situations, one Actor is going to start one or more other Actors and wait for them to finish and produce some results. In such cases, you might want to redirect the logs of the started Actors' runs back to the parent Actor run, so that you can see the progress of the started Actors' runs in the parent Actor's logs. This guide will show possibilities on how to do it. -### Redirecting logs from Actor.call +### Redirecting logs from `Actor.call` Typical use case for log redirection is to call another Actor using the [`Actor.call()`](/reference/class/Actor#call) method. This method has an optional logger argument, which is by default set to the `default` literal. This means that the logs of the called Actor will be automatically redirected to the parent Actor's logs with default formatting and filtering. If you set the logger argument to `null`, then no log redirection happens. The third option is to pass your own `Log` instance with the possibility to define your specific logging. Below you can see those three possible ways of log redirection when starting another Actor run through Actor.call. @@ -39,11 +39,11 @@ await Actor.exit(); Each default redirect log entry will have a specific format. After the timestamp, it will contain cyan colored text that will contain the redirect information - the other Actor's name and the run ID. The rest of the log message will be printed in the same manner as the parent Actor's log is configured. -The log redirection can be deep, meaning that if the other Actor also starts another actor and is redirecting logs from it, then in the top-level Actor, you can see it as well. See the following example screenshot of the Apify log console when one Actor recursively starts itself (there are 2 levels of recursion in the example). +The log redirection can be deep, meaning that if the other Actor also starts another actor and is redirecting logs from it, then in the top-level Actor, you can see it as well. ### Redirecting logs from already running Actor run -In some cases, you might want to connect to an already running Actor run and redirect its logs to your current Actor run. This can be done using the `ApifyClient` and getting the streamed log from a specific Actor run. You can then control the log redirection manually by explicitly calling start and stop methods. +In some cases, you might want to connect to an already running Actor run and redirect its logs to your current Actor run. This can be done using the `ApifyClient` and getting the streamed log from a specific Actor run. You can then control the log redirection manually by explicitly calling `start` and `stop` methods. You can further decide whether you want to redirect just new logs of the ongoing Actor run, or if you also want to redirect historical logs from that Actor's run, so all logs it has produced since it was started. This can be controlled by the `fromStart` option of the `getStreamedLog()` method. If you set it to `true`, all historical logs will be redirected first, and then new logs will be redirected as they are produced. If you set it to `false`, only new logs will be redirected. From eb0b83476ce956b815c4c5c5b888ab99442f1673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josef=20Proch=C3=A1zka?= Date: Wed, 10 Dec 2025 15:23:05 +0100 Subject: [PATCH 3/4] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jindřich Bär --- docs/guides/log_redirection.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/log_redirection.mdx b/docs/guides/log_redirection.mdx index 840325b047..c631dbcd7f 100644 --- a/docs/guides/log_redirection.mdx +++ b/docs/guides/log_redirection.mdx @@ -14,7 +14,7 @@ Typical use case for log redirection is to call another Actor using the [`Actor. ```javascript import { Actor } from 'apify'; -import {LoggerActorRedirect} from 'apify-client'; +import { LoggerActorRedirect } from 'apify-client'; import { LEVELS, Log } from '@apify/log'; const input = {}; // Some Actor input @@ -50,7 +50,7 @@ You can further decide whether you want to redirect just new logs of the ongoing ```javascript import { Actor } from 'apify'; -const actorRunId = 'someRunId'; // ID of actor you want to call +const actorRunId = 'someRunId'; // ID of the run you want to connect to await Actor.init(); From 9c9e1c1269132fb0786af098dead47de225b1a56 Mon Sep 17 00:00:00 2001 From: Josef Prochazka Date: Wed, 10 Dec 2025 15:46:17 +0100 Subject: [PATCH 4/4] Rephrase and format the guide --- docs/guides/log_redirection.mdx | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/docs/guides/log_redirection.mdx b/docs/guides/log_redirection.mdx index c631dbcd7f..ee72e73598 100644 --- a/docs/guides/log_redirection.mdx +++ b/docs/guides/log_redirection.mdx @@ -18,7 +18,7 @@ import { LoggerActorRedirect } from 'apify-client'; import { LEVELS, Log } from '@apify/log'; const input = {}; // Some Actor input -const actorId= 'someActorId'; // ID of actor you want to call +const actorId = 'someActorId'; // ID of actor you want to call await Actor.init(); @@ -26,18 +26,24 @@ await Actor.init(); await Actor.call(actorId, input); // Default log redirection - explicitly -await Actor.call(actorId, input, {log:'default'}); +await Actor.call(actorId, input, { log: 'default' }); // No log redirection -await Actor.call(actorId, input, {log:null}); +await Actor.call(actorId, input, { log: null }); // Custom log redirection -await Actor.call(actorId, input, {log: new Log({ level: LEVELS.DEBUG, prefix: 'customPrefix', logger: new LoggerActorRedirect()}); +await Actor.call(actorId, input, { + log: new Log({ + level: LEVELS.DEBUG, + prefix: 'customPrefix', + logger: new LoggerActorRedirect(), + }), +}); await Actor.exit(); ``` -Each default redirect log entry will have a specific format. After the timestamp, it will contain cyan colored text that will contain the redirect information - the other Actor's name and the run ID. The rest of the log message will be printed in the same manner as the parent Actor's log is configured. +Each default redirect log entry has a specific format. First part is a cyan-colored text with the redirect information (the other Actor's name and the run ID), the rest of the log message is printed in the same manner as the parent Actor's log is configured. The log redirection can be deep, meaning that if the other Actor also starts another actor and is redirecting logs from it, then in the top-level Actor, you can see it as well. @@ -55,7 +61,10 @@ const actorRunId = 'someRunId'; // ID of the run you want to connect to await Actor.init(); // Redirect only new logs from the other Actor run, due to `fromStart: false` -await Actor.newClient().run(actorRunId).getStreamedLog({ fromStart: false }); +const streamedLog = await Actor.newClient() + .run(actorRunId) + .getStreamedLog({ fromStart: false }); + streamedLog.start(); // Do some stuff while also redirecting logs from another Actor await streamedLog.stop();