-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathintegration.ts
More file actions
172 lines (140 loc) · 6.26 KB
/
integration.ts
File metadata and controls
172 lines (140 loc) · 6.26 KB
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import type { Client } from './client';
import { getClient } from './currentScopes';
import { DEBUG_BUILD } from './debug-build';
import type { Event, EventHint } from './types-hoist/event';
import type { Integration, IntegrationFn } from './types-hoist/integration';
import type { CoreOptions } from './types-hoist/options';
import type { StreamedSpanJSON } from './types-hoist/span';
import { debug } from './utils/debug-logger';
export const installedIntegrations: string[] = [];
/** Map of integrations assigned to a client */
export type IntegrationIndex = {
[key: string]: Integration;
};
type IntegrationWithDefaultInstance = Integration & { isDefaultInstance?: true };
/**
* Remove duplicates from the given array, preferring the last instance of any duplicate. Not guaranteed to
* preserve the order of integrations in the array.
*
* @private
*/
function filterDuplicates(integrations: Integration[]): Integration[] {
const integrationsByName: { [key: string]: Integration } = {};
integrations.forEach((currentInstance: IntegrationWithDefaultInstance) => {
const { name } = currentInstance;
const existingInstance: IntegrationWithDefaultInstance | undefined = integrationsByName[name];
// We want integrations later in the array to overwrite earlier ones of the same type, except that we never want a
// default instance to overwrite an existing user instance
if (existingInstance && !existingInstance.isDefaultInstance && currentInstance.isDefaultInstance) {
return;
}
integrationsByName[name] = currentInstance;
});
return Object.values(integrationsByName);
}
/** Gets integrations to install */
export function getIntegrationsToSetup(
options: Pick<CoreOptions, 'defaultIntegrations' | 'integrations'>,
): Integration[] {
const defaultIntegrations = options.defaultIntegrations || [];
const userIntegrations = options.integrations;
// We flag default instances, so that later we can tell them apart from any user-created instances of the same class
defaultIntegrations.forEach((integration: IntegrationWithDefaultInstance) => {
integration.isDefaultInstance = true;
});
let integrations: Integration[];
if (Array.isArray(userIntegrations)) {
integrations = [...defaultIntegrations, ...userIntegrations];
} else if (typeof userIntegrations === 'function') {
const resolvedUserIntegrations = userIntegrations(defaultIntegrations);
integrations = Array.isArray(resolvedUserIntegrations) ? resolvedUserIntegrations : [resolvedUserIntegrations];
} else {
integrations = defaultIntegrations;
}
return filterDuplicates(integrations);
}
/**
* Given a list of integration instances this installs them all. When `withDefaults` is set to `true` then all default
* integrations are added unless they were already provided before.
* @param integrations array of integration instances
* @param withDefault should enable default integrations
*/
export function setupIntegrations(client: Client, integrations: Integration[]): IntegrationIndex {
const integrationIndex: IntegrationIndex = {};
integrations.forEach((integration: Integration | undefined) => {
if (integration?.beforeSetup) {
integration.beforeSetup(client);
}
});
integrations.forEach((integration: Integration | undefined) => {
// guard against empty provided integrations
if (integration) {
setupIntegration(client, integration, integrationIndex);
}
});
return integrationIndex;
}
/**
* Execute the `afterAllSetup` hooks of the given integrations.
*/
export function afterSetupIntegrations(client: Client, integrations: Integration[]): void {
for (const integration of integrations) {
// guard against empty provided integrations
if (integration?.afterAllSetup) {
integration.afterAllSetup(client);
}
}
}
/** Setup a single integration. */
export function setupIntegration(client: Client, integration: Integration, integrationIndex: IntegrationIndex): void {
if (integrationIndex[integration.name]) {
DEBUG_BUILD && debug.log(`Integration skipped because it was already installed: ${integration.name}`);
return;
}
integrationIndex[integration.name] = integration;
// `setupOnce` is only called the first time
if (!installedIntegrations.includes(integration.name) && typeof integration.setupOnce === 'function') {
integration.setupOnce();
installedIntegrations.push(integration.name);
}
// `setup` is run for each client
if (integration.setup && typeof integration.setup === 'function') {
integration.setup(client);
}
if (typeof integration.preprocessEvent === 'function') {
const callback = integration.preprocessEvent.bind(integration) as typeof integration.preprocessEvent;
client.on('preprocessEvent', (event, hint) => callback(event, hint, client));
}
if (typeof integration.processEvent === 'function') {
const callback = integration.processEvent.bind(integration) as typeof integration.processEvent;
const processor = Object.assign((event: Event, hint: EventHint) => callback(event, hint, client), {
id: integration.name,
});
client.addEventProcessor(processor);
}
(['processSpan', 'processSegmentSpan'] as const).forEach(hook => {
const callback = integration[hook];
if (typeof callback === 'function') {
// The cast is needed because TS can't resolve overloads when the discriminant is a union type.
// Both overloads have the same callback signature so this is safe.
client.on(hook as 'processSpan', (span: StreamedSpanJSON) => callback.call(integration, span, client));
}
});
DEBUG_BUILD && debug.log(`Integration installed: ${integration.name}`);
}
/** Add an integration to the current scope's client. */
export function addIntegration(integration: Integration): void {
const client = getClient();
if (!client) {
DEBUG_BUILD && debug.warn(`Cannot add integration "${integration.name}" because no SDK Client is available.`);
return;
}
client.addIntegration(integration);
}
/**
* Define an integration function that can be used to create an integration instance.
* Note that this by design hides the implementation details of the integration, as they are considered internal.
*/
export function defineIntegration<Fn extends IntegrationFn>(fn: Fn): (...args: Parameters<Fn>) => Integration {
return fn;
}