Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Node-like console.log()ing #1294

Merged
merged 8 commits into from
Oct 18, 2023
Merged

Conversation

mrbbot
Copy link
Contributor

@mrbbot mrbbot commented Oct 10, 2023

Hey! 👋 This PR...

The end result is easier-to-read, detailed, coloured logs when running workerd locally. As an example, with the following script:

export default {
  async fetch(request, env, ctx) {
    console.log({ a: { b: 2 }, c: "x".repeat(50) });
    console.log(request.headers);
    console.log(new Map([["a", 1], ["b", 2], ["c", 3]]));
    console.log(new Set([1, 2, 3]));
    return new Response();
  }
}

Before:

image

After:

Screenshot 2023-10-17 at 14 43 25


Follow-up tasks:

  • Adopt util.inspect() from Node.js: the current polyfill has a few limitations. We should be able to implement the required native code to get around these.
  • Switch to util.formatWithOptions() for formatting: this will add support printf-style % strings, as required for console.log()
  • Export util.inspect(): if our util.inspect() function matches Node's, we should be able to export it from the node:util module if nodejs_compat is enabled.
  • Custom inspects: some built-in classes have internal state that would be good to log. For example, logging request.headers above should probably log something similar to the Map.
  • Unhandled exception logging: unhandled JavaScript exceptions should probably be logged in a similar way, and should definitely include the stack trace if we've got one.

@mrbbot mrbbot requested a review from jasnell October 10, 2023 14:16
src/node/internal/inspect_polyfill.d.ts Outdated Show resolved Hide resolved
src/workerd/io/worker.c++ Show resolved Hide resolved
// Lets us dump console.log()s to stdout when running test-runner with --verbose flag, to make
// it easier to debug tests. Note that when --verbose is not passed, KJ_LOG(INFO, ...) will
// not even evaluate its arguments, so `message()` will not be called at all.
KJ_LOG(INFO, "console.log()", message());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was going to remove this line completely, but there are some tests in server-test.c++ that expect logs to be written, and intercepting fprintf didn't seem like a good idea. Maybe it would be better to pass an output stream for writing to, instead of using fprintf directly? This was definitely simpler though... 😃

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, for this I'm not sure. These end up being non-ops in production. @kentonv any thoughts on this one?

src/workerd/jsg/modules.h Show resolved Hide resolved
@@ -78,6 +78,13 @@ class Worker: public kj::AtomicRefcounted {

class LockType;

enum ConsoleMode {
// Only send `console.log`s to the inspector
INSPECTOR_ONLY,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

INSPECTOR_ONLY is the previous behaviour, and the default. STDOUT must be explicitly enabled. The production runtime will continue to have the previous behaviour,

Copy link
Member

Choose a reason for hiding this comment

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

I would add this additional detail into the comment, tbh.

@mrbbot mrbbot marked this pull request as ready for review October 10, 2023 14:23
Comment on lines +658 to +663
auto setHandler = [&](const char* method, LogLevel level) {
auto methodStr = jsg::v8StrIntern(lock->v8Isolate, method);
v8::Global<v8::Function> original(
lock->v8Isolate, jsg::check(console->Get(context, methodStr)).As<v8::Function>());

auto f = lock->wrapSimpleFunction(context,
[mode, level, original = kj::mv(original)](jsg::Lock& js,
const v8::FunctionCallbackInfo<v8::Value>& info) {
handleLog(js, mode, level, original, info);
});
jsg::check(console->Set(context, methodStr, f));
};

setHandler("debug", LogLevel::DEBUG_);
setHandler("error", LogLevel::ERROR);
setHandler("info", LogLevel::INFO);
setHandler("log", LogLevel::LOG);
setHandler("warn", LogLevel::WARN);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

While I was looking into calling the original console methods, I came across https://source.chromium.org/chromium/chromium/src/+/main:v8/src/d8/d8-console.h. Is there a reason we don't use ConsoleDelegate for this custom console stuff? Is it because we wouldn't be able to link up logs with the correct worker or something like that?

Copy link
Member

Choose a reason for hiding this comment

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

Good question. This particular bit predates me so I'm not sure and I haven't looked into it yet. @kentonv might have some insight. It might be that ConsoleDelegate would just work but no one has tried? Really not sure.

@irvinebroque
Copy link
Collaborator

Amazing. Thank you @mrbbot!

src/workerd/io/worker.c++ Outdated Show resolved Hide resolved
@jasnell
Copy link
Member

jasnell commented Oct 17, 2023

Extremely happy to see this! Initial review pass looks very good but I want to take a more detailed dig through a bit later.

@jasnell
Copy link
Member

jasnell commented Oct 17, 2023

One clarifying question: You say, "Logs to the console even if an inspector is connected" ... it still logs to the inspector in that case tho, correct?

@mrbbot
Copy link
Contributor Author

mrbbot commented Oct 17, 2023

it still logs to the inspector in that case tho, correct?

Yep, it logs to both the inspector and the console. 👍

@jasnell
Copy link
Member

jasnell commented Oct 17, 2023

@mrbbot ... I'm marking this "needs-internal-pr" to ensure that we get a good internal CI run with this change before it lands. I just want to verify in CI that the default behavior on internal builds remains unchanged.

Otherwise, other than the few comments I've already left, this LGTM. Once we have the internal CI run and you're done with whatever additional edits you want to make here, I'll approve the PR.

@mrbbot mrbbot force-pushed the bcoll/pretty-console-logging branch from 50bbc20 to 0cc40a7 Compare October 18, 2023 14:26
@mrbbot mrbbot force-pushed the bcoll/pretty-console-logging branch from 0cc40a7 to 4daaf83 Compare October 18, 2023 14:33
@mrbbot
Copy link
Contributor Author

mrbbot commented Oct 18, 2023

@jasnell Internal PR #6521 is now passing tests. I've applied your suggestions, so I think this is ready to go. 👍

@jasnell jasnell merged commit 713d982 into main Oct 18, 2023
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[feature request] console output
3 participants