Skip to content

Conversation

@Pijukatel
Copy link

@Pijukatel Pijukatel commented Oct 22, 2025

Description

  • Add convenience methods and arguments for redirecting the streamed actor log to another log.
  • This changes the default behavior in non non-breaking way. By default, the logs will be redirected now.
  • Update log endpoints calls to accept raw parameter.
  • Parity with Python implementation feat: Add redirected actor logs apify-client-python#403

Example usage:

Actor.call - default

This will redirect logs by default. Example default redirected line:

2025-10-22T13:06:39.476Z redirect-log-tester runId:SoK1VRJxG61tVrgNz -> 2025-10-22T13:06:06.686Z ACTOR: Pulling container image of build Gh9yAJtjw0rHpc1NU from registry.

...
await client.actor('redirect-actor-id').call();

Actor.call - custom Log

This will redirect logs using custom log and logger. Example default redirected line:

2025-10-22T13:06:39.476Z customPrefix 2025-10-22T13:06:06.686Z ACTOR: Pulling container image of build Gh9yAJtjw0rHpc1NU from registry.

...
await client.actor('redirect-actor-id').call(someInputs, {
    log: new Log({ level: LEVELS.DEBUG, prefix: 'customPrefix', logger: new LoggerActorRedirect()
    }),

Actor.call - no log redirection

This will disable all log redirection (same as current behavior)

...
await client.actor('redirect-actor-id').call(someInputs, {log: null}),

Actor.run - attaching to already running actor and redirecting new logs

A typical use case is redirecting logs from an Actor that runs in standby. We do not want all the logs; we only want the new logs generated from the moment of connection.

...
const streamedLog = await client.run('someActorRunId').getStreamedLog({ fromStart: false });
streamedLog.start();
// Do some stuff while also redirecting logs from another Actor
await streamedLog.stop();

Example actor with recursive redirection:

https://console.apify.com/actors/IcfIKTIvbmujMmovj/source

Issues

@github-actions github-actions bot added this to the 126th sprint - Tooling team milestone Oct 22, 2025
@github-actions github-actions bot added t-tooling Issues with this label are in the ownership of the tooling team. tested Temporary label used only programatically for some analytics. labels Oct 22, 2025
@Pijukatel Pijukatel requested a review from B4nan October 23, 2025 13:24
"url": "https://github.com/apify/apify-client-js/issues"
},
"homepage": "https://docs.apify.com/api/client/js/",
"files": [
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just temp for testing

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, what are you testing this way?

Copy link
Author

@Pijukatel Pijukatel Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have an Actor that just points to this branch and I can test the tip of it on the Apify platform as it compiles on the fly. It takes time, but it does not require me to do any manual actions.

The actor has in pacakge.json

	"dependencies": {
        "apify-client": "github:apify/apify-client-js#log-redirection",
        ...
	},
	"scripts": {
        ...
        "postinstall": "cd node_modules/apify-client && npm install && npm run build:node"
	},

...
    "overrides": {
        "apify-client": "github:apify/apify-client-js#log-redirection"
    },

and I removed --omit=dev from npm install in the docker file

If there is a more convenient way for TypeScript-based code, I would be very happy to learn about it.

@B4nan
Copy link
Member

B4nan commented Oct 23, 2025

@barjin @janbuchar can you guys please take a look at this and provide some early feedback? I'll be off tomorrow+monday, so it would have to wait a bit for me.

@barjin barjin self-requested a review October 23, 2025 14:37

export interface ActorCallOptions extends Omit<ActorStartOptions, 'waitForFinish'> {
waitSecs?: number;
log?: Log | null;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The difference from Python is the name of the argument. While in Python it is called logger and can be of type Logger, here it is log and can be of type Log.

In JS tooling, the Log is a kind of Logger from Python, as it (implicitly) includes level, formatting, and handler.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for accepting null here? Wouldn't log?: Log work as well? If there are any semantics that I'm failing to see, please document them.

Copy link
Author

@Pijukatel Pijukatel Nov 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undefined -> use default
null -> explicitly requesting not to use any redirect logger
Log -> use whatever log is passed

In Python, it is similar, but since there is no undefined, there it is like
"default" -> use default
None -> explicitly requesting not to use any redirect logger
Logger -> use whatever Logger is passed

Added description

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't using a string literal like in Python work as well? I'd consider that more readable, too.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't that going to cause more confusion? Aren't the default values in JS commonly applied when the key is undefined in options?

Using default would allow to define the default value through both undefined and default literal

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried in the last commit. It feels very redundant to me, but I can live with it
3e48c60

Copy link
Member

@barjin barjin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a brief look, I don't see any obvious JS problems. Good job!

Here are some first ideas I had reading this:

@Pijukatel Pijukatel force-pushed the log-redirection branch 3 times, most recently from c1a034a to a54e71f Compare October 29, 2025 14:24
@Pijukatel Pijukatel marked this pull request as ready for review October 29, 2025 15:22
@Pijukatel Pijukatel requested a review from barjin October 31, 2025 09:02
Copy link
Member

@barjin barjin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good! I have some points to discuss, but most of them are questions rather than apparent problems:

console.log(line); // eslint-disable-line no-console
}

override _log(level: LogLevel, message: string, data?: any, exception?: unknown, opts: Record<string, any> = {}) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering, what's the biggest difference between this implementation and the _log from LoggerText (link)? Could we subclass LoggerText instead of abstract Logger and reduce the amount of code here?

Copy link
Author

@Pijukatel Pijukatel Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logger in our codebase is kind of a combination of a formater and a handler in Python terms. While LoggerActorRedirect has the same handler as LoggerText (they are both logging to console), they have completely different formater. So there is no point in inheriting from LoggerText as none of its methods would be reused in LoggerActorRedirect.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we need to adjust the shared logger to support this use case?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not really needed for this change. This change works with the current version of the codebase. Changing Log and Logger from the upstream repo to be more similar to the Python counterpart would be quite a refactoring.


let maybeDate = '';
if (!this.options.skipTime) {
maybeDate = `${new Date().toISOString().replace('Z', '').replace('T', ' ')} `;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to repeat the timestamps?

Copy link
Author

@Pijukatel Pijukatel Nov 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those timestamps will not be the same. One is the timestamp of origin, and the other is the timestamp of redirection.

For example>
2025-10-31T09:04:03.380Z redirect-log-tester runId:V4OVPjPbmjflPDeMP -> 2025-10-31T09:03:26.899Z ACTOR: Pulling container image of build KNlNPqgcBBivdtNCr from registry.

It is important to

  • keep logs formatted in the same way. (Otherwise, redirected logs would be offset by not having the timestamp) - which is used to detect the actual log lines separations.
  • to keep it clear what the time of log redirection is and what the time of the actual log origin is (for redirected logs from actors that were started before we started redirection and to which we just attached)

raw?: boolean;
}

// Temp create it here and ask Martin where to put it
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is where the apify logger is defined, so maybe there?

https://github.com/apify/apify-shared-js/tree/master/packages/log

it doesnt feel right to have this in the client

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about that. I can't imagine a scenario where this would be used outside of the client. It is very client-specific code. Maybe I can put it in to separate file? Something like redirection_utils or logging_utils?

@Pijukatel Pijukatel requested review from B4nan and barjin November 4, 2025 07:13
"url": "https://github.com/apify/apify-client-js/issues"
},
"homepage": "https://docs.apify.com/api/client/js/",
"files": [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, what are you testing this way?


export interface ActorCallOptions extends Omit<ActorStartOptions, 'waitForFinish'> {
waitSecs?: number;
log?: Log | null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for accepting null here? Wouldn't log?: Log work as well? If there are any semantics that I'm failing to see, please document them.

Copy link
Contributor

@janbuchar janbuchar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only a couple of nits. Also, some of my previous comments remain unanswered, it seems 🙂

I assume you tested this against Apify already?


export interface ActorCallOptions extends Omit<ActorStartOptions, 'waitForFinish'> {
waitSecs?: number;
log?: Log | null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't using a string literal like in Python work as well? I'd consider that more readable, too.

@Pijukatel Pijukatel requested a review from janbuchar November 11, 2025 10:17
@Pijukatel
Copy link
Author

Pijukatel commented Nov 11, 2025

I assume you tested this against Apify already?

Tested for example, here:
https://console.apify.com/actors/IcfIKTIvbmujMmovj/runs/gqj5UDlFeQ6QvZlxz#log

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

t-tooling Issues with this label are in the ownership of the tooling team. tested Temporary label used only programatically for some analytics.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants