Skip to content

Distributed Tracing - Laravel InertiaJS Vue3 Sentry #11362

@RChutchev

Description

@RChutchev

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/vue

SDK Version

@sentry/[email protected] , @sentry/[email protected]

Framework Version

Laravel Framework 10.48.4

Link to Sentry event

No response

SDK Setup

/*

                    IMPORTANT!

     Some comments may be inappropriate for someone
   or may contain private project / corp information

                   DON'T FORGET
     TO ENABLE OPTION WHICH REMOVES ALL COMMENTS
         FOR PRODUCTION BEFORE PRODUCTION!


Last edit: RChutchev, 4/1/2024
*/
require("./bootstrap");
// Import modules...
import { createApp, h } from "vue";
import { createInertiaApp, router } from "@inertiajs/vue3";
import * as Sentry from "@sentry/vue";
import * as SentryIntegrations from "@sentry/integrations";

import Filters from "./filters";

createInertiaApp({
    id:'app',
    progress: {
        color: '#8d2618',
    },
    resolve: (name) => require(`./Pages/${name}`).default,
    setup({ el, App, props, plugin }) {

        const app = createApp({ render: () => h(App, props) })
            .use(plugin)
            .mixin({methods: {route}});
        Sentry.init({
            app,
            dsn: process.env.MIX_APP_SENTRY_DSN_PUBLIC,
            environment: process.env.MIX_APP_ENV,
            release: process.env.MIX_SENTRY_RELEASE,
            integrations: [
                // NOTE: Migrate to entry.browserTracingIntegration, 
                // but this will not work with routingInstrumentation 
                // and we'll unable to use inertiaRoutingInstrumentation function... 
                // temporally workaround to use old new Sentry.BrowserTracing
                // new Sentry.browserTracingIntegration(
                //    enableInp: true,
                // ),
                new Sentry.BrowserTracing({
                    // Helps to send pageload and navigation OPs to Sentry for InertiaJS
                    routingInstrumentation: inertiaRoutingInstrumentation,
                    enableInp: true,
                    tracePropagationTargets: [
                        "/http:\/\/subdomain\.domain\.local\/",
                        "/https:\/\/subdomain\.domain\.local\/",
                        "/https:\/\/subdomain\.domain\.net\/",
                        "/http:\/\/subdomain\.domain\.net\/",
                    ],
                    tracingOrigins: [
                        "localhost",
                        "subdomain.domain.local",
                        "subdomain.domain.net",
                    ]
                }),
                Sentry.replayIntegration({
                    maskAllText: false,
                    blockAllMedia: false,
                }),
                Sentry.replayCanvasIntegration(),
                SentryIntegrations.captureConsoleIntegration({
                    // Add 'warn', 'error', 'debug', 'assert' console output types to Sentry
                    // and additionally possible to add 'log' and 'info' console output types to Sentry if required
                    levels: ['log', 'info', 'warn', 'error', 'debug', 'assert'],
                }),
                SentryIntegrations.contextLinesIntegration({
                    frameContextLines: 50,
                }),
                SentryIntegrations.httpClientIntegration(),
            ],
            //Now not required to use beforeSend and modify event, but we're able to do that if req.
            //beforeSend: function(event, hint) {
            //    if (hint && hint.originalException instanceof Event) {
            //        event.extra.isTrusted = hint.originalException.isTrusted;
            //        event.extra.detail = hint.originalException.detail;
            //        event.extra.type = hint.originalException.type;
            //    }
            //    return event;
            //},
            sendDefaultPii: true,
            tracesSampleRate: process.env.MIX_SENTRY_TRACES_SAMPLE_RATE,
            replaysSessionSampleRate: 0.1,
            replaysOnErrorSampleRate: 1.0,
            autoSessionTracking: true,
            // Disable for Dev/Prod?
            telemetry: true,
            denyUrls:
                [
                    // All browser extensions
                    /extensions\//i,
                    /^safari-extension:\/\//i,
                    /^safari-web-extension:\/\//i,
                    /^moz-extension:\/\//i,
                    /^chrome:\/\//i,
                    /^chrome-extension:\/\//i,
                    /moz-extension/i,
                ],
            ignoreErrors:
                [
                    // Let's ignore some output as unnecessary for us now
                    '@webkit-masked-url',
                    'debugger eval',
                    'Not implemented',
                    //'Failed to fetch',
                ],
        });
        Sentry.attachErrorHandler(app, {
            logErrors: true,
        });
        app.config.globalProperties.$filters = Filters;
        app.mixin(
            Sentry.createTracingMixins({ 
                trackComponents: true 
            })
        );
        app.mount(el);


        /*

            Some code which not working or unnecessary now, starts here after this comment
                        Please, KEEP IT HERE and DON'T DELETE
                            or angry developer kill you (:

        */

        /*
        router.on('start', (event) => {

            //inertiaRoutingInstrumentation(Sentry.browserTracingIntegration);
            console.info(`Начинается переход к ${event.detail.visit.url}`);
            let sentryDataOnStart = props.initialPage.props.sentry || {};
            console.info(sentryDataOnStart);

            if (sentryDataOnStart.baggage) {
                event.detail.visit.headers['baggage'] = sentryDataOnStart.baggage;
            }
            if (sentryDataOnStart.sentryTrace) {
                event.detail.visit.headers['sentry-trace'] = sentryDataOnStart.sentryTrace;
            }
        });
        */

        /*
        router.on('navigate', (event) => {
            console.info(`Navigated to ${event.detail.page.url}`)
            let sentryData = event.detail.page.props || {};
            console.info(sentryData);

            if (sentryData.baggage) {
                // event.detail.visit.headers unavalible here let's try window.axios.defaults.headers.common
                // event.detail.visit.headers["X-Socket-ID"] = echo.socketId();
                window.axios.defaults.headers.common['baggage'] = sentryData.baggage;
            }
            if (sentryDataOnNavigate.sentryTrace) {
                window.axios.defaults.headers.common['sentry-trace'] = sentryDataOnNavigate.sentryTrace;
            }
        }); 
        */

        router.on('navigate', (event) => {
            console.info(`Navigate in router.on`);
            console.info(props.initialPage.props.sentry);
        });
        
        /*
        router.on('before', (event) => {
            // Let's try to set sentry headers from props on Before 
            console.info(`Navigated to ${event.detail.page.url}`)
            let sentryData = props.initialPage.props.sentry || {};
            console.log(sentryData);

            if (sentryData.baggage) {
                event.detail.visit.headers['baggage'] = sentryData.baggage;
            }
            if (sentryData.sentryTrace) {
                event.detail.visit.headers['sentry-trace'] = sentryData.sentryTrace;
            }

        })
        */
    },
});


// Uses https://inertiajs.com/events
function inertiaRoutingInstrumentation(
    customStartTransaction,
    startTransactionOnPageLoad = true,
    startTransactionOnLocationChange = true,
) {
    console.info('inertiaRoutingInstrumentation Started');

    let activeTransaction;
    let name;
    if (startTransactionOnPageLoad) {
        console.info('Start transaction on page load');
        name = '/'+route().current();

        activeTransaction = customStartTransaction({
            name,
            op: 'pageload',
            metadata: {
                source: 'route',
            },
        });
    }

    if (startTransactionOnLocationChange) {
        console.info('Start transaction on location change');
        console.info(propsForSentry);
        router.on('before', (_to, _from) => {
            if (activeTransaction) {
                activeTransaction.finish();
            }

            const newName = '/'+route().current();
            console.info('Old name: '+name+'. New name: '+newName)

            if (newName !== name) {
                console.info('Old name is not equal to new name!');
                activeTransaction = customStartTransaction({
                    name: newName,
                    op: 'navigation',
                    metadata: {
                        source: 'route',
                    },
                });
            }
        });

        router.on('finish', () => {
            console.info('Router on finish. Route: '+'/'+route().current())
            activeTransaction.setName('/'+route().current(), 'route');
        });
    }
    console.info('inertiaRoutingInstrumentation Finished');
}

Steps to Reproduce

  1. I've an application in production on Laravel 10 (Vue 3, InertiaJS - users portal, and Filament - admin portal)
    based on Github but of course, modified and working with new ver. of Laravel etc, but anyway code is based on the repo code and you can check it if it's required for understanding.
  2. Sentry integration added to Filament and works well, and added to InertiaJS, but InertiaJS works only with page-load. For all navigation events not working at all.
  3. I've created a new Sentry.BrowserTracing({*}); entity for integrations in Sentry.init, etc check in SDK Setup here.
  4. I've added config for webpack.mix.js which requires webpackConfig with devtool: "source-map" and sentryWebpackPlugin with ORG, PROJECT, and authToken configs. Everything transferred to Sentry successfully and works with Filament and Inertia fine.
  5. Opening a new page or full reload will cause an update tag for Sentry and send correct data to Sentry, but without page reload incorrect baggage and sentry-trace added on the page and no data will be sent to Sentry for Inertia navigation.
  6. Yes, i imported Laravel integration via use Sentry\Laravel\Integration;
  7. And added {!! Integration::sentryMeta() !!} in app.blade.php (main file for Inertia app) where application loading ( etc), and that's the reason why incorrect meta adds, but I didn't find another way to add bagger and sentry-trace to pages with Inertia.
  8. I am unable to find any good docs for Distributed Tracing with Vue and Inertia.
    Maybe I'm dumb, but it's going me crazy.
  9. I've also tried to add
    event.detail.visit.headers['baggage'] = sentryDataOnStart.baggage;
    and
    event.detail.visit.headers['sentry-trace'] = sentryDataOnStart.sentryTrace;
    to my js file from
    props.initialPage.props.sentry
    and added this code to the public function share in HandleInertiaRequests in the Middleware file.
$data['sentry'] = [
    'baggage' => $baggage,
    'sentryTrace' => $sentryTrace,
]; 
  1. but still unable to set a proper header for the inertia app in setup for all outgoing requests b/c when I'm getting sentryData from event.detail.page.props it returns the same baggage and sentry-trace as for pageload.
  2. IDK how to set up Distributed Tracing and BrowserTracing properly with the Inertia app, do you have any idea how to fix that?

Expected Result

  1. I'm able to add BrowserTracing and Distributed Tracing Sentry future by reading Docs.
  2. Distributed Tracing and BrowserTracing for Laravel app with InertiaJS works and I'm getting correct data in Sentry for both navigation and page load events.
  3. Like on those screenshots for the Event when the page was reloaded and the event was recorded successfully and fully.
    Screenshot 2024-04-02 at 12 51 32 AM
    Screenshot 2024-04-02 at 12 52 02 AM

Actual Result

I'm getting this (no page view)
Screenshot 2024-04-02 at 12 37 37 AM

Screenshot 2024-04-02 at 12 38 36 AM

or this (multiply events from different pages to one Event)
Screenshot 2024-04-02 at 12 49 21 AM

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Status

    Waiting for: Community

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions