Managing the ClientRequestContext

Asynchronous backend methods must cooperate to propagate a request's ActivityId into logging messages. The current ActivityId is held by ClientRequestContext.current. Asynchronous methods must follow several rules to maintain this context.

Promise-returning functions

Every Promise-returning functions must:

  • Take an argument of type ClientRequestContext.
  • Pass the input ClientRequestContext object to each Promise-returning method that it calls.
  • Call the ClientRequestContext.enter method on the input ClientRequestContext object:
    • On its first line.
    • Immediately following each call to await.
    • Immediately upon catching an Error thrown by a rejected await.
    • On the first line of a .then or a .catch callback that is invoked by a Promise.

A Promise-returning function must not call ClientRequestContext.current.

Note that a Promise-returning method is any method that returns a Promise, whether or not it is declared with the async keyword.

Example:


//                                Rule: A Promise-returning function takes an ClientRequestContext as an argument
async function asyncMethodExample(requestContext: ClientRequestContext): Promise<void> {
  requestContext.enter();

  try {
    await someAsync(requestContext); // Rule: Pass the ClientRequestContext to Promise-returning methods
    requestContext.enter();        // Rule: Enter the ClientRequestContext on the line after an await
    Logger.logTrace("cat", "promise resolved");
  } catch (_err) {
    requestContext.enter();        // Rule: Enter the ClientRequestContext in an async rejection
    Logger.logTrace("cat", "promise rejected");
  }

  // The same rules, using .then.catch instead of await + try/catch.
  someAsync(requestContext)          // Rule: Pass the ClientRequestContext to Promise-returning methods
    .then(() => {
      requestContext.enter();    // Rule: Enter the ClientRequestContext on the line of .then callback
      Logger.logTrace("cat", "promise resolved");
    })
    .catch((_err) => {
      requestContext.enter();    // Rule: Enter the ClientRequestContext in .catch callback
      Logger.logTrace("cat", "promise rejected");
    });

}

RpcInterface implementation methods

There is one exception to the above rule for Promise-returning functions. An RpcInterface implementation method does not take a ClientRequestContext object as an argument. Instead, it must obtain the ClientRequestContext by calling ClientRequestContext.current. An RcpInterface implementation method must follow all of the other rules listed above.

Callbacks to asynchronous functions

Examples of asynchronous functions that invoke callbacks are:

  • setTimeout and setInterval
  • XmlHttpRequest (if called in the backend)
  • fs async functions (called only in non-portable backend code)

If a callback does any logging or calls functions that do:

  • Before invoking an asynchronous function, an app must ensure that the correct ClientRequestContext is assigned to a local variable in the scope that encloses the callback.
  • The callback must, on its first line, call the ClientRequestContext.enter method on that local variable in the enclosing scope.

There are two possible cases:

1. Asynchronous function invokes an asynchronous function, passing it a callback

In this case, the calling function will take the ClientRequestContext as an argument. The callback must use that object.

Example:

//                                  Rule: A Promise-returning function takes an ClientRequestContext as an argument
async function asyncFunctionCallsAsync(requestContext: ClientRequestContext): Promise<void> {
  requestContext.enter();        // Rule: A Promise-returning function enters the ClientRequestContext on the first line.

  await new Promise<void>((resolve) => {
    setTimeout(() => {
      requestContext.enter(); // Rule: Enter the client request context of the enclosing JavaScript scope in the callback.
      Logger.logTrace("cat", "callback invoked");
      resolve();
    }, 1);
  });
}

2. Synchronous function invokes an asynchronous function, passing it a callback

In this case, the calling function must save a reference to the ClientRequestContext.current property and transmit that to the callback.

Example:

function synchronousFunctionCallsAsync() {
  // This is an example of the rare case where a synchronous function invokes an async function and
  // the async callback emits logging messages. In this case, because the caller is synchronous, it must
  // access the current ClientRequestContext and assign it to a local variable.
  const requestContext = ClientRequestContext.current;        // Must hold a local reference for callback to use.
  setTimeout(() => {
    requestContext.enter(); // Rule: Enter the client request context of the enclosing JavaScript scope in the callback.
    Logger.logTrace("cat", "callback invoked");
  }, 1);
}

A callback to an async function must never call ClientRequestContext.current.

Last Updated: 11 June, 2024