awslabs / llrt

LLRT (Low Latency Runtime) is an experimental, lightweight JavaScript runtime designed to address the growing demand for fast and efficient Serverless applications.
Apache License 2.0
7.73k stars 342 forks source link

Console.log does not to print header values #341

Closed nabetti1720 closed 4 weeks ago

nabetti1720 commented 2 months ago

llrt cannot display fetch() response headers in console.log().

Reproduction procedure:

// fetch.js
const main = async () => {
  try {
    const response = await fetch('https://www.google.com');
    console.log(response.status);
    console.log(response.headers.get('date'));
    console.log(response.headers);
  } catch(e) {
    console.log(e);
  }
}

main();

Execution results in llrt:

shinya@MBA2022M2 llrt-test % ./llrt fetch.js 
200
Sat, 20 Apr 2024 01:11:21 GMT
Headers { Symbol.iterator: undefined }

Execution results in bun:

shinya@MBA2022M2 llrt-test % bun fetch.js
200
Sat, 20 Apr 2024 01:11:28 GMT
Headers {
  "date": "Sat, 20 Apr 2024 01:11:28 GMT",
  "expires": "-1",
  // Other headers are also displayed.
}
richarddavison commented 2 months ago

Thanks for the bug report @nabetti1720! Keep 'em coming 🎉 We should fix so console prints iterators as well. Also we should fix so we don't print all keys/values for excessively large objects like we currently do.

nabetti1720 commented 2 months ago

Hi @richarddavison , I have checked all the sources and it seems difficult for me to solve the problem.

https://github.com/awslabs/llrt/blob/d05da26103acc1fdf45afe15965ef05013e5f488/src/console.rs#L437-L449

In this case, we thought that L437 expected to get the list of keys in Headers, but in fact keys.len() returns zero. Therefore, the decision in L440 is true, and the process of getting the list of methods appears to be working incorrectly.

In any case, stringify_value() is too complicated for me to fix...

richarddavison commented 2 months ago

Hi @nabetti1720.

Thanks for looking into this. The console implementation is quite complex and we should probably refactor this. I spent of lot of time building it to avoid recursion but since we often don't have so "deep" objects it has just complicated things.

I'm looking into this, I need to investigate out how node is handing this, for instance if all iterators have their values printed or just a few specific ones.

nabetti1720 commented 2 months ago

I don't know if this will be helpful...

It seems that node.js outputs all key/value without omission.

On the other hand, the implementation of deno is interesting.

shinya@MBA2022M2 llrt-test % deno run fetch.js
✅ Granted all net access.
200
null
Headers {
  "alt-svc": 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000',
  "cache-control": "private, max-age=0",
  "content-security-policy-report-only": "object-src 'none';base-uri 'self';script-src 'nonce-A2I2IXOAYXGmHVY3-LUnfQ' 'strict-dynamic' 'report"... 106 more characters,
  "content-type": "text/html; charset=ISO-8859-1",
  date: "Tue, 23 Apr 2024 08:25:18 GMT",
  expires: "-1",
  p3p: 'CP="This is not a P3P policy! See g.co/p3phelp for more info."',
  server: "gws",
  "set-cookie": "NID=513=UYiAlngaonY0hQvYNTBdelHrd3QIpHE9BzJnn9yfiqAjDg2iz99Ca98i1xa8e1aAhWN81WxpD_r-fWT4fnMKHaeGqZce"... 156 more characters,
  "x-frame-options": "SAMEORIGIN",
  "x-xss-protection": "0"
}

It appears that these two response headers are not returned in llrt either. Since both llrt and deno are implemented in rust and use hyper, it is most likely due to the hyper specification.

richarddavison commented 2 months ago

Thanks for the info! I'm curios to see how this works in a more generic way. I see now what Node does. It sets a special symbol on objects which gets executed if present on an object when running console.log: https://nodejs.org/api/util.html#utilinspectcustom So Headers has this symbol specified:

[util.inspect.custom](depth, options) {
   options.depth ??= depth;
   return `Headers ${util.formatWithOptions(options, this[kHeadersList].entries)}`;
}

We need to apply a similar mechanism in LLRT:

const customConsoleSymbol = Symbol.for('llrt.custom.console');

And then apply that to rust data structures for custom console handling

nabetti1720 commented 2 months ago

I see, with this method, the console does not need to know the contents of the object to be displayed, and the object itself can be displayed in the format expected by the console.

It also seems to simplify the implementation on the console side.

This time it was the header that was targeted, but do other objects need to add Symbol in the same way? Or will there be a common implementation that outputs all keys/values and properties, and then override them if you want to customize them individually?

richarddavison commented 2 months ago

I see, with this method, the console does not need to know the contents of the object to be displayed, and the object itself can be displayed in the format expected by the console.

It also seems to simplify the implementation on the console side.

This time it was the header that was targeted, but do other objects need to add Symbol in the same way? Or will there be a common implementation that outputs all keys/values and properties, and then override them if you want to customize them individually?

No they need to add the custom key for custom print. Otherwise it will by default display properties only