(
router: VueRouter,
options: {
/**
* What to use for route labels.
* By default, we use route.name (if set) and else the path.
*
* Default: 'name'
*/
routeLabel: 'name' | 'path';
instrumentPageLoad: boolean;
instrumentNavigation: boolean;
},
startNavigationSpanFn: (context: StartSpanOptions) => void,
)
| 39 | * Instrument the Vue router to create navigation spans. |
| 40 | */ |
| 41 | export function instrumentVueRouter( |
| 42 | router: VueRouter, |
| 43 | options: { |
| 44 | /** |
| 45 | * What to use for route labels. |
| 46 | * By default, we use route.name (if set) and else the path. |
| 47 | * |
| 48 | * Default: 'name' |
| 49 | */ |
| 50 | routeLabel: 'name' | 'path'; |
| 51 | instrumentPageLoad: boolean; |
| 52 | instrumentNavigation: boolean; |
| 53 | }, |
| 54 | startNavigationSpanFn: (context: StartSpanOptions) => void, |
| 55 | ): void { |
| 56 | let hasHandledFirstPageLoad = false; |
| 57 | |
| 58 | // Detect Vue Router 3 by checking for the `mode` property which only exists in VR3. |
| 59 | // Vue Router 4+ uses `options.history` instead and does not expose `mode`. |
| 60 | const isLegacyRouter = 'mode' in router; |
| 61 | |
| 62 | router.onError(error => captureException(error, { mechanism: { handled: false } })); |
| 63 | |
| 64 | // Use rest params to capture `next` without declaring it as a named parameter. |
| 65 | // This keeps Function.length === 2, which tells Vue Router 4+/5+ to use the |
| 66 | // modern return-based resolution (no deprecation warning in Vue Router 5.0.3+). |
| 67 | router.beforeEach((to: Route, _from: Route, ...rest: [(() => void)?]) => { |
| 68 | // We avoid trying to re-fetch the page load span when we know we already handled it the first time |
| 69 | const activePageLoadSpan = !hasHandledFirstPageLoad ? getActivePageLoadSpan() : undefined; |
| 70 | |
| 71 | const attributes: SpanAttributes = {}; |
| 72 | |
| 73 | for (const key of Object.keys(to.params)) { |
| 74 | attributes[`url.path.parameter.${key}`] = to.params[key]; |
| 75 | attributes[`params.${key}`] = to.params[key]; // params.[key] is an alias |
| 76 | } |
| 77 | for (const key of Object.keys(to.query)) { |
| 78 | const value = to.query[key]; |
| 79 | if (value) { |
| 80 | attributes[`query.${key}`] = value; |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | // Determine a name for the routing transaction and where that name came from |
| 85 | let spanName: string = to.path; |
| 86 | let transactionSource: TransactionSource = 'url'; |
| 87 | if (to.name && options.routeLabel !== 'path') { |
| 88 | spanName = to.name.toString(); |
| 89 | transactionSource = 'custom'; |
| 90 | } else if (to.matched.length > 0) { |
| 91 | const lastIndex = to.matched.length - 1; |
| 92 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion |
| 93 | spanName = to.matched[lastIndex]!.path; |
| 94 | transactionSource = 'route'; |
| 95 | } |
| 96 | |
| 97 | getCurrentScope().setTransactionName(spanName); |
| 98 |
no test coverage detected