question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Request: Functions in Default Metadata

See original GitHub issue

The underlying issue here is somewhat complex so I’ll summarize what we’re asking for up here – we would be interested in seeing the ability for defaultMetadata to include dynamically generated values by providing a function as a field. For example:

const logger = createLogger({
  defaultMeta: {
    // Function to be evaluated every time a log is written.
    timeWritten: () => { return `Date.now()`; }
  },
  // ...
});

For a full justification see below.

Please tell us about your environment:

  • winston version?
    • winston@2
    • winston@3
  • node -v outputs: v10.15.2
  • Operating System? macOS (irrelevant)
  • Language? ES7 (irrelevant)

What is the problem?

As authors of the Stackdriver Logging Transport for Winston, we would like to support log-request correlation. The async_hooks module gives us the ability to store request-specific information such as a request ID. However, we don’t have a means of reliably retrieving it on the Transport side.

The problem is best described by example. The following code produces this output when ~100 requests are made in quick succession:

const ah = require('async_hooks');
const express = require('express');
const { createLogger } = require('winston');
const TransportStream = require('winston-transport');

// Activate async hooks.
let requestIdHighWater = 0;
const requestIdTable = {};
ah.createHook({
  init: (child, resource, parent) => {
    requestIdTable[child] = requestIdTable[parent];
  }
}).enable();

// OUR CODE

class MyTransport extends TransportStream {
  log(info, next) {
    // From the transport, the request ID is not reliable.
    const actualRequestId = requestIdTable[ah.executionAsyncId()];
    console.log(`Expected: ${info.expectedRequestId}, Actual: ${actualRequestId}`);
    setImmediate(next);
  }
}

// OUR USER'S CODE

const logger = createLogger({
  transports: [new MyTransport()]
});

const app = express();

app.get('/', async (req, res) => {
  // Store a request ID.
  const requestId = requestIdHighWater++;
  requestIdTable[ah.executionAsyncId()] = requestId;
  logger.info('hello', { expectedRequestId: requestId });
  res.sendStatus(200);
});

app.listen(3000);

Note the mistmatch in “expected” and “actual” request IDs. If we are the author of MyTransport, we would have no way of accurately getting the correct request ID by consulting the current execution ID (“actual”). Instead, we must rely on the user passing in the correct request ID as metadata (“expected”). This is because Winston 3 batches logs (via readable-stream) when a Transport defers its invocation of its log callback.

The problem is that we don’t want to rely on users passing in a request ID for us; we want it to happen automatically. After all, users would probably want to just write

app.get('/', async (req, res) => {
  logger.info('hello');
  res.sendStatus(200);
});

and leave to rest up to us.

What do you expect to happen instead?

The expected and actual request IDs (from the linked output) match up.

Other information

It’s infeasible to fix the code so that actual request IDs match up, so a solution to this problem would be to allow users to specify functions as fields on defaultMetadata that get invoked when the logger gets called. That way, the code would change to this, which allows users to write their code without any awareness of request ID, with the small caveat that they provide a thunk as a defaultMetadata field (that maybe our module would provide for them to use).

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:14
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

21reactions
Alexseycommented, Sep 26, 2019

I have found a workaround that can be used until the proposed solution would be implemented - use getters:

const logger = createLogger({
  defaultMeta: {
    get timeWritten () { return `Date.now()`; }
  },
  // ...
});

UPD: fixed the bug mentioned by @mooski

0reactions
intentionally-left-nilcommented, Jan 18, 2022

I still think that winston needs some code changes to support this feature properly, even with using the getter syntax.

The getters are captured/realized at different places depending on the codepath. This is important because the callstack is something that a logger might want to log & the frames are inconsistent.

I’ve fixed this in our fork by replacing Object.assign with the following function:

function assignWithGetters(target, ...sources) {
  // eslint-disable-next-line no-shadow
  return sources.reduce((target, source) => {
    // eslint-disable-next-line eqeqeq
    if (source != null) {
      const enumerableKeys = Object.keys(source);
      const symbolKeys = Object.getOwnPropertySymbols(source);
      // Don't use Reflect.ownKeys here because we only want to assign enumerable ones
      for (const key of enumerableKeys.concat(symbolKeys)) {
        Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
      }
    }
    return target;
  }, target);
}```
which preserves the getters when copying to the target, and then calling `Object.assign({}, msg)` in write() to consistently call the logger.

I can open a PR to address this if the maintainers feel that JS getters are the preferred approach here (rather than creating some new functionality for dynamic metadata)
Read more comments on GitHub >

github_iconTop Results From Across the Web

Default VM metadata values - Compute Engine - Google Cloud
Compute Engine defines a set of default metadata entries that provide information about your virtual machine (VM) instance or project. Default metadata is ......
Read more >
Reference Custom Metadata Type Records in Default Values
Reference a custom metadata type record in a default value. If a default field value changes, you can update it in the custom...
Read more >
get-metadata() - IBM
This extension function supports both request and response traffic. If the current traffic is from client to server, a request, the function returns ......
Read more >
modify-instance-metadata-options - AWS Documentation
If the state is required , you must send a session token with any instance metadata retrieval requests. In this state, retrieving the...
Read more >
Azure Functions HTTP trigger | Microsoft Learn
You can allow anonymous requests, which do not require keys. You can also require that the master key is used. You change the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found