Skip to content

Commit

Permalink
feat(core): Introduce processEvent hook on Integration
Browse files Browse the repository at this point in the history
  • Loading branch information
mydea committed Sep 13, 2023
1 parent 7fc2225 commit c2d603d
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 91 deletions.
69 changes: 42 additions & 27 deletions packages/core/src/baseclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
Event,
EventDropReason,
EventHint,
EventProcessor,
Integration,
IntegrationClass,
Outcome,
Expand Down Expand Up @@ -43,6 +44,7 @@ import {

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

private _eventProcessors: EventProcessor[];

/**
* Initializes this client instance.
*
Expand All @@ -119,6 +123,7 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
this._numProcessing = 0;
this._outcomes = {};
this._hooks = {};
this._eventProcessors = [];

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

/** @inheritDoc */
public addEventProcessor(eventProcessor: EventProcessor): void {
this._eventProcessors.push(eventProcessor);
}

/**
* Sets up the integrations
*/
Expand Down Expand Up @@ -545,36 +555,41 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {

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

return prepareEvent(options, event, hint, scope).then(evt => {
if (evt === null) {
return evt;
}
return prepareEvent(options, event, hint, scope)
.then(evt => {
// Process client-scoped event processors
return notifyEventProcessors(this._eventProcessors, evt, hint);
})
.then(evt => {
if (evt === null) {
return evt;
}

// If a trace context is not set on the event, we use the propagationContext set on the event to
// generate a trace context. If the propagationContext does not have a dynamic sampling context, we
// also generate one for it.
const { propagationContext } = evt.sdkProcessingMetadata || {};
const trace = evt.contexts && evt.contexts.trace;
if (!trace && propagationContext) {
const { traceId: trace_id, spanId, parentSpanId, dsc } = propagationContext as PropagationContext;
evt.contexts = {
trace: {
trace_id,
span_id: spanId,
parent_span_id: parentSpanId,
},
...evt.contexts,
};
// If a trace context is not set on the event, we use the propagationContext set on the event to
// generate a trace context. If the propagationContext does not have a dynamic sampling context, we
// also generate one for it.
const { propagationContext } = evt.sdkProcessingMetadata || {};
const trace = evt.contexts && evt.contexts.trace;
if (!trace && propagationContext) {
const { traceId: trace_id, spanId, parentSpanId, dsc } = propagationContext as PropagationContext;
evt.contexts = {
trace: {
trace_id,
span_id: spanId,
parent_span_id: parentSpanId,
},
...evt.contexts,
};

const dynamicSamplingContext = dsc ? dsc : getDynamicSamplingContextFromClient(trace_id, this, scope);
const dynamicSamplingContext = dsc ? dsc : getDynamicSamplingContextFromClient(trace_id, this, scope);

evt.sdkProcessingMetadata = {
dynamicSamplingContext,
...evt.sdkProcessingMetadata,
};
}
return evt;
});
evt.sdkProcessingMetadata = {
dynamicSamplingContext,
...evt.sdkProcessingMetadata,
};
}
return evt;
});
}

/**
Expand Down
51 changes: 51 additions & 0 deletions packages/core/src/eventProcessors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { Event, EventHint, EventProcessor } from '@sentry/types';
import { getGlobalSingleton, isThenable, logger, SyncPromise } from '@sentry/utils';

/**
* Returns the global event processors.
*/
export function getGlobalEventProcessors(): EventProcessor[] {
return getGlobalSingleton<EventProcessor[]>('globalEventProcessors', () => []);
}

/**
* Add a EventProcessor to be kept globally.
* @param callback EventProcessor to add
*/
export function addGlobalEventProcessor(callback: EventProcessor): void {
getGlobalEventProcessors().push(callback);
}

/**
* Process an array of event processors, returning the processed event (or `null` if the event was dropped).
*/
export function notifyEventProcessors(
processors: EventProcessor[],
event: Event | null,
hint: EventHint,
index: number = 0,
): PromiseLike<Event | null> {
return new SyncPromise<Event | null>((resolve, reject) => {
const processor = processors[index];
if (event === null || typeof processor !== 'function') {
resolve(event);
} else {
const result = processor({ ...event }, hint) as Event | null;

__DEBUG_BUILD__ &&
processor.id &&
result === null &&
logger.log(`Event processor "${processor.id}" dropped event`);

if (isThenable(result)) {
void result
.then(final => notifyEventProcessors(processors, final, hint, index + 1).then(resolve))
.then(null, reject);
} else {
void notifyEventProcessors(processors, result, hint, index + 1)
.then(resolve)
.then(null, reject);
}
}
});
}
3 changes: 2 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ export {
} from './hub';
export { makeSession, closeSession, updateSession } from './session';
export { SessionFlusher } from './sessionflusher';
export { addGlobalEventProcessor, Scope } from './scope';
export { Scope } from './scope';
export { addGlobalEventProcessor } from './eventProcessors';
export { getEnvelopeEndpointWithUrlEncodedAuth, getReportDialogEndpoint } from './api';
export { BaseClient } from './baseclient';
export { ServerRuntimeClient } from './server-runtime-client';
Expand Down
16 changes: 13 additions & 3 deletions packages/core/src/integration.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { Client, Integration, Options } from '@sentry/types';
import type { Client, Event, EventHint, Integration, Options } from '@sentry/types';
import { arrayify, logger } from '@sentry/utils';

import { addGlobalEventProcessor } from './eventProcessors';
import { getCurrentHub } from './hub';
import { addGlobalEventProcessor } from './scope';

declare module '@sentry/types' {
interface Integration {
Expand Down Expand Up @@ -107,10 +107,20 @@ export function setupIntegration(client: Client, integration: Integration, integ
}

if (client.on && typeof integration.preprocessEvent === 'function') {
const callback = integration.preprocessEvent.bind(integration);
const callback = integration.preprocessEvent.bind(integration) as typeof integration.preprocessEvent;
client.on('preprocessEvent', (event, hint) => callback(event, hint, client));
}

if (client.addEventProcessor && 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);
}

__DEBUG_BUILD__ && logger.log(`Integration installed: ${integration.name}`);
}

Expand Down
63 changes: 3 additions & 60 deletions packages/core/src/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,9 @@ import type {
Transaction,
User,
} from '@sentry/types';
import {
arrayify,
dateTimestampInSeconds,
getGlobalSingleton,
isPlainObject,
isThenable,
logger,
SyncPromise,
uuid4,
} from '@sentry/utils';
import { arrayify, dateTimestampInSeconds, isPlainObject, uuid4 } from '@sentry/utils';

import { getGlobalEventProcessors, notifyEventProcessors } from './eventProcessors';
import { updateSession } from './session';

/**
Expand Down Expand Up @@ -525,7 +517,7 @@ export class Scope implements ScopeInterface {
propagationContext: this._propagationContext,
};

return this._notifyEventProcessors([...getGlobalEventProcessors(), ...this._eventProcessors], event, hint);
return notifyEventProcessors([...getGlobalEventProcessors(), ...this._eventProcessors], event, hint);
}

/**
Expand Down Expand Up @@ -559,40 +551,6 @@ export class Scope implements ScopeInterface {
return this._breadcrumbs;
}

/**
* This will be called after {@link applyToEvent} is finished.
*/
protected _notifyEventProcessors(
processors: EventProcessor[],
event: Event | null,
hint: EventHint,
index: number = 0,
): PromiseLike<Event | null> {
return new SyncPromise<Event | null>((resolve, reject) => {
const processor = processors[index];
if (event === null || typeof processor !== 'function') {
resolve(event);
} else {
const result = processor({ ...event }, hint) as Event | null;

__DEBUG_BUILD__ &&
processor.id &&
result === null &&
logger.log(`Event processor "${processor.id}" dropped event`);

if (isThenable(result)) {
void result
.then(final => this._notifyEventProcessors(processors, final, hint, index + 1).then(resolve))
.then(null, reject);
} else {
void this._notifyEventProcessors(processors, result, hint, index + 1)
.then(resolve)
.then(null, reject);
}
}
});
}

/**
* This will be called on every set call.
*/
Expand Down Expand Up @@ -629,21 +587,6 @@ export class Scope implements ScopeInterface {
}
}

/**
* Returns the global event processors.
*/
function getGlobalEventProcessors(): EventProcessor[] {
return getGlobalSingleton<EventProcessor[]>('globalEventProcessors', () => []);
}

/**
* Add a EventProcessor to be kept globally.
* @param callback EventProcessor to add
*/
export function addGlobalEventProcessor(callback: EventProcessor): void {
getGlobalEventProcessors().push(callback);
}

function generatePropagationContext(): PropagationContext {
return {
traceId: uuid4(),
Expand Down
8 changes: 8 additions & 0 deletions packages/types/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { DataCategory } from './datacategory';
import type { DsnComponents } from './dsn';
import type { DynamicSamplingContext, Envelope } from './envelope';
import type { Event, EventHint } from './event';
import type { EventProcessor } from './eventprocessor';
import type { Integration, IntegrationClass } from './integration';
import type { ClientOptions } from './options';
import type { Scope } from './scope';
Expand Down Expand Up @@ -120,6 +121,13 @@ export interface Client<O extends ClientOptions = ClientOptions> {
*/
flush(timeout?: number): PromiseLike<boolean>;

/**
* Adds an event processor that applies to any event processed by this client.
*
* TODO (v8): Make this a required method.
*/
addEventProcessor?(eventProcessor: EventProcessor): void;

/** Returns the client's instance of the given integration class, it any. */
getIntegration<T extends Integration>(integration: IntegrationClass<T>): T | null;

Expand Down
6 changes: 6 additions & 0 deletions packages/types/src/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,10 @@ export interface Integration {
* An optional hook that allows to preprocess an event _before_ it is passed to all other event processors.
*/
preprocessEvent?(event: Event, hint: EventHint | undefined, client: Client): void;

/**
* An optional hook that allows to process an event.
* Return `null` to drop the event, or mutate the event & return it.
*/
processEvent?(event: Event, hint: EventHint | undefined, client: Client): Event | null | PromiseLike<Event | null>;
}

0 comments on commit c2d603d

Please sign in to comment.