Skip to content

Commit c2d603d

Browse files
committed
feat(core): Introduce processEvent hook on Integration
1 parent 7fc2225 commit c2d603d

File tree

7 files changed

+125
-91
lines changed

7 files changed

+125
-91
lines changed

packages/core/src/baseclient.ts

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
Event,
1313
EventDropReason,
1414
EventHint,
15+
EventProcessor,
1516
Integration,
1617
IntegrationClass,
1718
Outcome,
@@ -43,6 +44,7 @@ import {
4344

4445
import { getEnvelopeEndpointWithUrlEncodedAuth } from './api';
4546
import { createEventEnvelope, createSessionEnvelope } from './envelope';
47+
import { notifyEventProcessors } from './eventProcessors';
4648
import type { IntegrationIndex } from './integration';
4749
import { setupIntegration, setupIntegrations } from './integration';
4850
import type { Scope } from './scope';
@@ -107,6 +109,8 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
107109
// eslint-disable-next-line @typescript-eslint/ban-types
108110
private _hooks: Record<string, Function[]>;
109111

112+
private _eventProcessors: EventProcessor[];
113+
110114
/**
111115
* Initializes this client instance.
112116
*
@@ -119,6 +123,7 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
119123
this._numProcessing = 0;
120124
this._outcomes = {};
121125
this._hooks = {};
126+
this._eventProcessors = [];
122127

123128
if (options.dsn) {
124129
this._dsn = makeDsn(options.dsn);
@@ -280,6 +285,11 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
280285
});
281286
}
282287

288+
/** @inheritDoc */
289+
public addEventProcessor(eventProcessor: EventProcessor): void {
290+
this._eventProcessors.push(eventProcessor);
291+
}
292+
283293
/**
284294
* Sets up the integrations
285295
*/
@@ -545,36 +555,41 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
545555

546556
this.emit('preprocessEvent', event, hint);
547557

548-
return prepareEvent(options, event, hint, scope).then(evt => {
549-
if (evt === null) {
550-
return evt;
551-
}
558+
return prepareEvent(options, event, hint, scope)
559+
.then(evt => {
560+
// Process client-scoped event processors
561+
return notifyEventProcessors(this._eventProcessors, evt, hint);
562+
})
563+
.then(evt => {
564+
if (evt === null) {
565+
return evt;
566+
}
552567

553-
// If a trace context is not set on the event, we use the propagationContext set on the event to
554-
// generate a trace context. If the propagationContext does not have a dynamic sampling context, we
555-
// also generate one for it.
556-
const { propagationContext } = evt.sdkProcessingMetadata || {};
557-
const trace = evt.contexts && evt.contexts.trace;
558-
if (!trace && propagationContext) {
559-
const { traceId: trace_id, spanId, parentSpanId, dsc } = propagationContext as PropagationContext;
560-
evt.contexts = {
561-
trace: {
562-
trace_id,
563-
span_id: spanId,
564-
parent_span_id: parentSpanId,
565-
},
566-
...evt.contexts,
567-
};
568+
// If a trace context is not set on the event, we use the propagationContext set on the event to
569+
// generate a trace context. If the propagationContext does not have a dynamic sampling context, we
570+
// also generate one for it.
571+
const { propagationContext } = evt.sdkProcessingMetadata || {};
572+
const trace = evt.contexts && evt.contexts.trace;
573+
if (!trace && propagationContext) {
574+
const { traceId: trace_id, spanId, parentSpanId, dsc } = propagationContext as PropagationContext;
575+
evt.contexts = {
576+
trace: {
577+
trace_id,
578+
span_id: spanId,
579+
parent_span_id: parentSpanId,
580+
},
581+
...evt.contexts,
582+
};
568583

569-
const dynamicSamplingContext = dsc ? dsc : getDynamicSamplingContextFromClient(trace_id, this, scope);
584+
const dynamicSamplingContext = dsc ? dsc : getDynamicSamplingContextFromClient(trace_id, this, scope);
570585

571-
evt.sdkProcessingMetadata = {
572-
dynamicSamplingContext,
573-
...evt.sdkProcessingMetadata,
574-
};
575-
}
576-
return evt;
577-
});
586+
evt.sdkProcessingMetadata = {
587+
dynamicSamplingContext,
588+
...evt.sdkProcessingMetadata,
589+
};
590+
}
591+
return evt;
592+
});
578593
}
579594

580595
/**

packages/core/src/eventProcessors.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { Event, EventHint, EventProcessor } from '@sentry/types';
2+
import { getGlobalSingleton, isThenable, logger, SyncPromise } from '@sentry/utils';
3+
4+
/**
5+
* Returns the global event processors.
6+
*/
7+
export function getGlobalEventProcessors(): EventProcessor[] {
8+
return getGlobalSingleton<EventProcessor[]>('globalEventProcessors', () => []);
9+
}
10+
11+
/**
12+
* Add a EventProcessor to be kept globally.
13+
* @param callback EventProcessor to add
14+
*/
15+
export function addGlobalEventProcessor(callback: EventProcessor): void {
16+
getGlobalEventProcessors().push(callback);
17+
}
18+
19+
/**
20+
* Process an array of event processors, returning the processed event (or `null` if the event was dropped).
21+
*/
22+
export function notifyEventProcessors(
23+
processors: EventProcessor[],
24+
event: Event | null,
25+
hint: EventHint,
26+
index: number = 0,
27+
): PromiseLike<Event | null> {
28+
return new SyncPromise<Event | null>((resolve, reject) => {
29+
const processor = processors[index];
30+
if (event === null || typeof processor !== 'function') {
31+
resolve(event);
32+
} else {
33+
const result = processor({ ...event }, hint) as Event | null;
34+
35+
__DEBUG_BUILD__ &&
36+
processor.id &&
37+
result === null &&
38+
logger.log(`Event processor "${processor.id}" dropped event`);
39+
40+
if (isThenable(result)) {
41+
void result
42+
.then(final => notifyEventProcessors(processors, final, hint, index + 1).then(resolve))
43+
.then(null, reject);
44+
} else {
45+
void notifyEventProcessors(processors, result, hint, index + 1)
46+
.then(resolve)
47+
.then(null, reject);
48+
}
49+
}
50+
});
51+
}

packages/core/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ export {
3636
} from './hub';
3737
export { makeSession, closeSession, updateSession } from './session';
3838
export { SessionFlusher } from './sessionflusher';
39-
export { addGlobalEventProcessor, Scope } from './scope';
39+
export { Scope } from './scope';
40+
export { addGlobalEventProcessor } from './eventProcessors';
4041
export { getEnvelopeEndpointWithUrlEncodedAuth, getReportDialogEndpoint } from './api';
4142
export { BaseClient } from './baseclient';
4243
export { ServerRuntimeClient } from './server-runtime-client';

packages/core/src/integration.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import type { Client, Integration, Options } from '@sentry/types';
1+
import type { Client, Event, EventHint, Integration, Options } from '@sentry/types';
22
import { arrayify, logger } from '@sentry/utils';
33

4+
import { addGlobalEventProcessor } from './eventProcessors';
45
import { getCurrentHub } from './hub';
5-
import { addGlobalEventProcessor } from './scope';
66

77
declare module '@sentry/types' {
88
interface Integration {
@@ -107,10 +107,20 @@ export function setupIntegration(client: Client, integration: Integration, integ
107107
}
108108

109109
if (client.on && typeof integration.preprocessEvent === 'function') {
110-
const callback = integration.preprocessEvent.bind(integration);
110+
const callback = integration.preprocessEvent.bind(integration) as typeof integration.preprocessEvent;
111111
client.on('preprocessEvent', (event, hint) => callback(event, hint, client));
112112
}
113113

114+
if (client.addEventProcessor && typeof integration.processEvent === 'function') {
115+
const callback = integration.processEvent.bind(integration) as typeof integration.processEvent;
116+
117+
const processor = Object.assign((event: Event, hint: EventHint) => callback(event, hint, client), {
118+
id: integration.name,
119+
});
120+
121+
client.addEventProcessor(processor);
122+
}
123+
114124
__DEBUG_BUILD__ && logger.log(`Integration installed: ${integration.name}`);
115125
}
116126

packages/core/src/scope.ts

Lines changed: 3 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,9 @@ import type {
2222
Transaction,
2323
User,
2424
} from '@sentry/types';
25-
import {
26-
arrayify,
27-
dateTimestampInSeconds,
28-
getGlobalSingleton,
29-
isPlainObject,
30-
isThenable,
31-
logger,
32-
SyncPromise,
33-
uuid4,
34-
} from '@sentry/utils';
25+
import { arrayify, dateTimestampInSeconds, isPlainObject, uuid4 } from '@sentry/utils';
3526

27+
import { getGlobalEventProcessors, notifyEventProcessors } from './eventProcessors';
3628
import { updateSession } from './session';
3729

3830
/**
@@ -525,7 +517,7 @@ export class Scope implements ScopeInterface {
525517
propagationContext: this._propagationContext,
526518
};
527519

528-
return this._notifyEventProcessors([...getGlobalEventProcessors(), ...this._eventProcessors], event, hint);
520+
return notifyEventProcessors([...getGlobalEventProcessors(), ...this._eventProcessors], event, hint);
529521
}
530522

531523
/**
@@ -559,40 +551,6 @@ export class Scope implements ScopeInterface {
559551
return this._breadcrumbs;
560552
}
561553

562-
/**
563-
* This will be called after {@link applyToEvent} is finished.
564-
*/
565-
protected _notifyEventProcessors(
566-
processors: EventProcessor[],
567-
event: Event | null,
568-
hint: EventHint,
569-
index: number = 0,
570-
): PromiseLike<Event | null> {
571-
return new SyncPromise<Event | null>((resolve, reject) => {
572-
const processor = processors[index];
573-
if (event === null || typeof processor !== 'function') {
574-
resolve(event);
575-
} else {
576-
const result = processor({ ...event }, hint) as Event | null;
577-
578-
__DEBUG_BUILD__ &&
579-
processor.id &&
580-
result === null &&
581-
logger.log(`Event processor "${processor.id}" dropped event`);
582-
583-
if (isThenable(result)) {
584-
void result
585-
.then(final => this._notifyEventProcessors(processors, final, hint, index + 1).then(resolve))
586-
.then(null, reject);
587-
} else {
588-
void this._notifyEventProcessors(processors, result, hint, index + 1)
589-
.then(resolve)
590-
.then(null, reject);
591-
}
592-
}
593-
});
594-
}
595-
596554
/**
597555
* This will be called on every set call.
598556
*/
@@ -629,21 +587,6 @@ export class Scope implements ScopeInterface {
629587
}
630588
}
631589

632-
/**
633-
* Returns the global event processors.
634-
*/
635-
function getGlobalEventProcessors(): EventProcessor[] {
636-
return getGlobalSingleton<EventProcessor[]>('globalEventProcessors', () => []);
637-
}
638-
639-
/**
640-
* Add a EventProcessor to be kept globally.
641-
* @param callback EventProcessor to add
642-
*/
643-
export function addGlobalEventProcessor(callback: EventProcessor): void {
644-
getGlobalEventProcessors().push(callback);
645-
}
646-
647590
function generatePropagationContext(): PropagationContext {
648591
return {
649592
traceId: uuid4(),

packages/types/src/client.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { DataCategory } from './datacategory';
55
import type { DsnComponents } from './dsn';
66
import type { DynamicSamplingContext, Envelope } from './envelope';
77
import type { Event, EventHint } from './event';
8+
import type { EventProcessor } from './eventprocessor';
89
import type { Integration, IntegrationClass } from './integration';
910
import type { ClientOptions } from './options';
1011
import type { Scope } from './scope';
@@ -120,6 +121,13 @@ export interface Client<O extends ClientOptions = ClientOptions> {
120121
*/
121122
flush(timeout?: number): PromiseLike<boolean>;
122123

124+
/**
125+
* Adds an event processor that applies to any event processed by this client.
126+
*
127+
* TODO (v8): Make this a required method.
128+
*/
129+
addEventProcessor?(eventProcessor: EventProcessor): void;
130+
123131
/** Returns the client's instance of the given integration class, it any. */
124132
getIntegration<T extends Integration>(integration: IntegrationClass<T>): T | null;
125133

packages/types/src/integration.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,10 @@ export interface Integration {
3030
* An optional hook that allows to preprocess an event _before_ it is passed to all other event processors.
3131
*/
3232
preprocessEvent?(event: Event, hint: EventHint | undefined, client: Client): void;
33+
34+
/**
35+
* An optional hook that allows to process an event.
36+
* Return `null` to drop the event, or mutate the event & return it.
37+
*/
38+
processEvent?(event: Event, hint: EventHint | undefined, client: Client): Event | null | PromiseLike<Event | null>;
3339
}

0 commit comments

Comments
 (0)