-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
Copy pathtracing.ts
136 lines (117 loc) · 4.82 KB
/
tracing.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getActiveSpan, startInactiveSpan } from '@sentry/browser';
import type { Span } from '@sentry/types';
import { logger, timestampInSeconds } from '@sentry/utils';
import { DEFAULT_HOOKS } from './constants';
import { DEBUG_BUILD } from './debug-build';
import type { Hook, Operation, TracingOptions, ViewModel, Vue } from './types';
import { formatComponentName } from './vendor/components';
const VUE_OP = 'ui.vue';
type Mixins = Parameters<Vue['mixin']>[0];
interface VueSentry extends ViewModel {
readonly $root: VueSentry;
$_sentrySpans?: {
[key: string]: Span | undefined;
};
$_sentryRootSpan?: Span;
$_sentryRootSpanTimer?: ReturnType<typeof setTimeout>;
}
// Mappings from operation to corresponding lifecycle hook.
const HOOKS: { [key in Operation]: Hook[] } = {
activate: ['activated', 'deactivated'],
create: ['beforeCreate', 'created'],
// Vue 3
unmount: ['beforeUnmount', 'unmounted'],
// Vue 2
destroy: ['beforeDestroy', 'destroyed'],
mount: ['beforeMount', 'mounted'],
update: ['beforeUpdate', 'updated'],
};
/** Finish top-level span and activity with a debounce configured using `timeout` option */
function finishRootSpan(vm: VueSentry, timestamp: number, timeout: number): void {
if (vm.$_sentryRootSpanTimer) {
clearTimeout(vm.$_sentryRootSpanTimer);
}
vm.$_sentryRootSpanTimer = setTimeout(() => {
if (vm.$root && vm.$root.$_sentryRootSpan) {
vm.$root.$_sentryRootSpan.end(timestamp);
vm.$root.$_sentryRootSpan = undefined;
}
}, timeout);
}
export const createTracingMixins = (options: TracingOptions): Mixins => {
const hooks = (options.hooks || [])
.concat(DEFAULT_HOOKS)
// Removing potential duplicates
.filter((value, index, self) => self.indexOf(value) === index);
const mixins: Mixins = {};
for (const operation of hooks) {
// Retrieve corresponding hooks from Vue lifecycle.
// eg. mount => ['beforeMount', 'mounted']
const internalHooks = HOOKS[operation];
if (!internalHooks) {
DEBUG_BUILD && logger.warn(`Unknown hook: ${operation}`);
continue;
}
for (const internalHook of internalHooks) {
mixins[internalHook] = function (this: VueSentry) {
const isRoot = this.$root === this;
if (isRoot) {
const activeSpan = getActiveSpan();
if (activeSpan) {
this.$_sentryRootSpan =
this.$_sentryRootSpan ||
startInactiveSpan({
name: 'Application Render',
op: `${VUE_OP}.render`,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.vue',
},
});
}
}
// Skip components that we don't want to track to minimize the noise and give a more granular control to the user
const name = formatComponentName(this, false);
const shouldTrack = Array.isArray(options.trackComponents)
? options.trackComponents.indexOf(name) > -1
: options.trackComponents;
// We always want to track root component
if (!isRoot && !shouldTrack) {
return;
}
this.$_sentrySpans = this.$_sentrySpans || {};
// Start a new span if current hook is a 'before' hook.
// Otherwise, retrieve the current span and finish it.
if (internalHook == internalHooks[0]) {
const activeSpan = (this.$root && this.$root.$_sentryRootSpan) || getActiveSpan();
if (activeSpan) {
// Cancel old span for this hook operation in case it didn't get cleaned up. We're not actually sure if it
// will ever be the case that cleanup hooks re not called, but we had users report that spans didn't get
// finished so we finish the span before starting a new one, just to be sure.
const oldSpan = this.$_sentrySpans[operation];
if (oldSpan) {
oldSpan.end();
}
this.$_sentrySpans[operation] = startInactiveSpan({
name: `Vue <${name}>`,
op: `${VUE_OP}.${operation}`,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.vue',
},
// UI spans should only be created if there is an active root span (transaction)
onlyIfParent: true,
});
}
} else {
// The span should already be added via the first handler call (in the 'before' hook)
const span = this.$_sentrySpans[operation];
// The before hook did not start the tracking span, so the span was not added.
// This is probably because it happened before there is an active transaction
if (!span) return;
span.end();
finishRootSpan(this, timestampInSeconds(), options.timeout);
}
};
}
}
return mixins;
};