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.

Serverless Function Tracing Needs an Isolated Scope

See original GitHub issue

Package + Version

  • @sentry/node 6.3.6

Version:

6.3.6

Description

Here is my withSentry wrapper for Azure serverless Function invocations:

const Sentry = require("@sentry/node");
const Tracing = require("@sentry/tracing");
const mongodb = require("mongodb");

const config = require("../../config/sentry");

if (config.dsn) {
    Sentry.init({
        ...config,
        integrations: [
            // enable DB calls tracing
            new Tracing.Integrations.Mongo({ useMongoose: true })
            // new Tracing.Integrations.Mysql()  // No integration available for mysql2.
        ]
    });
}

/**
 * Add Sentry transaction information to outgoing messages.
 * @param {*} transaction
 * @param {*} results
 * @returns
 */
const propagateTransaction = (transaction, results) => {
    if (results && transaction) {
        for (const message of Object.values(results)) {
            Object.assign(message, {
                sentryParentSpanId: transaction.spanId,
                sentryTraceId: transaction.traceId,
                sentrySampled: transaction.sampled
            });
        }
    }
    return results;
};

/**
 * A higher order function which logs error data to Sentry.
 *
 * @param {*} callback          The actual function which might throw errors.
 */
module.exports = callback => async (context, trigger, ...args) => {
    let transaction;
    if (config.dsn) {
        // Define a transaction for performance tracking.
        const transactionContext = {
            op: "serverless-task",
            name: context?.executionContext?.functionName || "test-function"
        };
        if ("sentryParentSpanId" in trigger) {
            Object.assign(transactionContext, {
                parentSpanId: trigger.sentryParentSpanId,
                traceId: trigger.sentryTraceId,
                sampled: trigger.sentrySampled
            });
        }
        transaction = Sentry.startTransaction(transactionContext);

        Sentry.configureScope(scope => {
            scope.setSpan(transaction);
            scope.setContext(
                "executionContext",
                context?.executionContext || {
                    errorMessage:
                        "No executionContext defined.  This could be a notification from a test environment."
                }
            );
            scope.setContext("functionTrigger", trigger);
        });
    }

    try {
        return propagateTransaction(
            transaction,
            await callback(context, trigger, ...args)
        );
    } catch (error) {
        if (config.dsn) {
            Sentry.captureException(error, scope => {
                scope.setContext(
                    "executionContext",
                    context?.executionContext || {
                        errorMessage:
                            "No executionContext defined.  This could be a notification from a test environment."
                    }
                );
                scope.setContext("functionTrigger", trigger);
                scope.setContext(
                    "theFullContext",
                    Object.assign(
                        {},
                        ...Object.keys(context).map(k => ({
                            [k]: JSON.stringify(context[k])
                        }))
                    )
                );
            });
            scope.setSpan(transaction);
            await Sentry.flush();
        }

        throw error;
    } finally {
        await transaction?.finish();
    }
};

Defining an Azure Function like this:

module.exports = withSentry(processLogFile);

This logs everything correctly for exceptions since the scope is set immediately before sending the log, but performance tracing associates DB children with the wrong span and other context. This appears to be because the DB integrations use the span set on the scope. Since we have a high volume application (on the order of 3400 invocations per minute), many function invocations run concurrently and, since Sentry.configureScope() configures the span in a global scope, logged child DB transactions are associated not with the invocation they actually ran during but instead with context of the function invocation that last called Sentry.configureScope(). In these images, there are dozens of MongoDB calls associated with the *_sessions function even though the inserts are actually all called by the parent *_timeseries function: image image

Issue Analytics

  • State:open
  • Created 2 years ago
  • Comments:18 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
derekrpricecommented, Oct 19, 2021

@richardsimko, check out #4071 for a detailed explanation of why you may not have experienced this issue yet with your web app. Basically, the problem is hidden in low traffic scenarios.

0reactions
mattfyshcommented, Dec 23, 2021

Could this not also be solved the same way the http handler works? i.e. running the wrapped handler in a new domain?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Tracing requests with AWS X-Ray - AWS Lambda
By tracing a request from the API endpoint through to the Lambda function and resulting service interactions, you can isolate which part of...
Read more >
AWS Lambda Functions
Every AWS Lambda function needs permission to interact with other AWS infrastructure resources within your account. These permissions are set via an AWS...
Read more >
Proactive Serverless Function Resource Management
Conclusion This paper proposes a new primitive to server- less language runtimes called freshen.
Read more >
Cloud Functions execution environment
Cloud Functions run in a fully-managed serverless environment where Google handles ... Each function runs in its own isolated secure execution context, ...
Read more >
Distributed Tracing for Functions
You can trace and instrument standalone functions to debug execution and performance issues. You can also use function tracing to debug issues ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

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