From d3fe4734acb85ca5dbf7d5366e2399bcb92b82d5 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Wed, 6 Sep 2023 16:52:19 +0200 Subject: [PATCH] feat(nextjs): Remove `EdgeClient` and use `ServerRuntimeClient` (#8932) This PR removes the `EdgeClient` and duplicate `eventbuilder` functions. --- packages/nextjs/src/edge/edgeclient.ts | 174 ------------------- packages/nextjs/src/edge/eventbuilder.ts | 130 -------------- packages/nextjs/src/edge/index.ts | 30 +++- packages/nextjs/test/edge/edgeclient.test.ts | 57 ------ 4 files changed, 26 insertions(+), 365 deletions(-) delete mode 100644 packages/nextjs/src/edge/edgeclient.ts delete mode 100644 packages/nextjs/src/edge/eventbuilder.ts delete mode 100644 packages/nextjs/test/edge/edgeclient.test.ts diff --git a/packages/nextjs/src/edge/edgeclient.ts b/packages/nextjs/src/edge/edgeclient.ts deleted file mode 100644 index 39b795c81a53..000000000000 --- a/packages/nextjs/src/edge/edgeclient.ts +++ /dev/null @@ -1,174 +0,0 @@ -import type { Scope } from '@sentry/core'; -import { - addTracingExtensions, - BaseClient, - createCheckInEnvelope, - getDynamicSamplingContextFromClient, - SDK_VERSION, -} from '@sentry/core'; -import type { - CheckIn, - ClientOptions, - DynamicSamplingContext, - Event, - EventHint, - MonitorConfig, - SerializedCheckIn, - Severity, - SeverityLevel, - TraceContext, -} from '@sentry/types'; -import { logger, uuid4 } from '@sentry/utils'; - -import { eventFromMessage, eventFromUnknownInput } from './eventbuilder'; -import type { EdgeTransportOptions } from './transport'; - -export type EdgeClientOptions = ClientOptions; - -/** - * The Sentry Edge SDK Client. - */ -export class EdgeClient extends BaseClient { - /** - * Creates a new Edge SDK instance. - * @param options Configuration options for this SDK. - */ - public constructor(options: EdgeClientOptions) { - options._metadata = options._metadata || {}; - options._metadata.sdk = options._metadata.sdk || { - name: 'sentry.javascript.nextjs', - packages: [ - { - name: 'npm:@sentry/nextjs', - version: SDK_VERSION, - }, - ], - version: SDK_VERSION, - }; - - // The Edge client always supports tracing - addTracingExtensions(); - - super(options); - } - - /** - * @inheritDoc - */ - public eventFromException(exception: unknown, hint?: EventHint): PromiseLike { - return Promise.resolve(eventFromUnknownInput(this._options.stackParser, exception, hint)); - } - - /** - * @inheritDoc - */ - public eventFromMessage( - message: string, - // eslint-disable-next-line deprecation/deprecation - level: Severity | SeverityLevel = 'info', - hint?: EventHint, - ): PromiseLike { - return Promise.resolve( - eventFromMessage(this._options.stackParser, message, level, hint, this._options.attachStacktrace), - ); - } - - /** - * Create a cron monitor check in and send it to Sentry. - * - * @param checkIn An object that describes a check in. - * @param upsertMonitorConfig An optional object that describes a monitor config. Use this if you want - * to create a monitor automatically when sending a check in. - */ - public captureCheckIn(checkIn: CheckIn, monitorConfig?: MonitorConfig, scope?: Scope): string { - const id = checkIn.status !== 'in_progress' && checkIn.checkInId ? checkIn.checkInId : uuid4(); - if (!this._isEnabled()) { - __DEBUG_BUILD__ && logger.warn('SDK not enabled, will not capture checkin.'); - return id; - } - - const options = this.getOptions(); - const { release, environment, tunnel } = options; - - const serializedCheckIn: SerializedCheckIn = { - check_in_id: id, - monitor_slug: checkIn.monitorSlug, - status: checkIn.status, - release, - environment, - }; - - if (checkIn.status !== 'in_progress') { - serializedCheckIn.duration = checkIn.duration; - } - - if (monitorConfig) { - serializedCheckIn.monitor_config = { - schedule: monitorConfig.schedule, - checkin_margin: monitorConfig.checkinMargin, - max_runtime: monitorConfig.maxRuntime, - timezone: monitorConfig.timezone, - }; - } - - const [dynamicSamplingContext, traceContext] = this._getTraceInfoFromScope(scope); - if (traceContext) { - serializedCheckIn.contexts = { - trace: traceContext, - }; - } - - const envelope = createCheckInEnvelope( - serializedCheckIn, - dynamicSamplingContext, - this.getSdkMetadata(), - tunnel, - this.getDsn(), - ); - - __DEBUG_BUILD__ && logger.info('Sending checkin:', checkIn.monitorSlug, checkIn.status); - void this._sendEnvelope(envelope); - return id; - } - - /** - * @inheritDoc - */ - protected _prepareEvent(event: Event, hint: EventHint, scope?: Scope): PromiseLike { - event.platform = event.platform || 'edge'; - event.contexts = { - ...event.contexts, - runtime: event.contexts?.runtime || { - name: 'edge', - }, - }; - event.server_name = event.server_name || process.env.SENTRY_NAME; - return super._prepareEvent(event, hint, scope); - } - - /** Extract trace information from scope */ - private _getTraceInfoFromScope( - scope: Scope | undefined, - ): [dynamicSamplingContext: Partial | undefined, traceContext: TraceContext | undefined] { - if (!scope) { - return [undefined, undefined]; - } - - const span = scope.getSpan(); - if (span) { - return [span?.transaction?.getDynamicSamplingContext(), span?.getTraceContext()]; - } - - const { traceId, spanId, parentSpanId, dsc } = scope.getPropagationContext(); - const traceContext: TraceContext = { - trace_id: traceId, - span_id: spanId, - parent_span_id: parentSpanId, - }; - if (dsc) { - return [dsc, traceContext]; - } - - return [getDynamicSamplingContextFromClient(traceId, this, scope), traceContext]; - } -} diff --git a/packages/nextjs/src/edge/eventbuilder.ts b/packages/nextjs/src/edge/eventbuilder.ts deleted file mode 100644 index 4e483fce3ff7..000000000000 --- a/packages/nextjs/src/edge/eventbuilder.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { getCurrentHub } from '@sentry/core'; -import type { - Event, - EventHint, - Exception, - Mechanism, - Severity, - SeverityLevel, - StackFrame, - StackParser, -} from '@sentry/types'; -import { - addExceptionMechanism, - addExceptionTypeValue, - extractExceptionKeysForMessage, - isError, - isPlainObject, - normalizeToSize, -} from '@sentry/utils'; - -/** - * Extracts stack frames from the error.stack string - */ -export function parseStackFrames(stackParser: StackParser, error: Error): StackFrame[] { - return stackParser(error.stack || '', 1); -} - -/** - * Extracts stack frames from the error and builds a Sentry Exception - */ -export function exceptionFromError(stackParser: StackParser, error: Error): Exception { - const exception: Exception = { - type: error.name || error.constructor.name, - value: error.message, - }; - - const frames = parseStackFrames(stackParser, error); - if (frames.length) { - exception.stacktrace = { frames }; - } - - return exception; -} - -/** - * Builds and Event from a Exception - * @hidden - */ -export function eventFromUnknownInput(stackParser: StackParser, exception: unknown, hint?: EventHint): Event { - let ex: unknown = exception; - const providedMechanism: Mechanism | undefined = - hint && hint.data && (hint.data as { mechanism: Mechanism }).mechanism; - const mechanism: Mechanism = providedMechanism || { - handled: true, - type: 'generic', - }; - - if (!isError(exception)) { - if (isPlainObject(exception)) { - // This will allow us to group events based on top-level keys - // which is much better than creating new group when any key/value change - const message = `Non-Error exception captured with keys: ${extractExceptionKeysForMessage(exception)}`; - - const hub = getCurrentHub(); - const client = hub.getClient(); - const normalizeDepth = client && client.getOptions().normalizeDepth; - hub.configureScope(scope => { - scope.setExtra('__serialized__', normalizeToSize(exception, normalizeDepth)); - }); - - ex = (hint && hint.syntheticException) || new Error(message); - (ex as Error).message = message; - } else { - // This handles when someone does: `throw "something awesome";` - // We use synthesized Error here so we can extract a (rough) stack trace. - ex = (hint && hint.syntheticException) || new Error(exception as string); - (ex as Error).message = exception as string; - } - mechanism.synthetic = true; - } - - const event = { - exception: { - values: [exceptionFromError(stackParser, ex as Error)], - }, - }; - - addExceptionTypeValue(event, undefined, undefined); - addExceptionMechanism(event, mechanism); - - return { - ...event, - event_id: hint && hint.event_id, - }; -} - -/** - * Builds and Event from a Message - * @hidden - */ -export function eventFromMessage( - stackParser: StackParser, - message: string, - // eslint-disable-next-line deprecation/deprecation - level: Severity | SeverityLevel = 'info', - hint?: EventHint, - attachStacktrace?: boolean, -): Event { - const event: Event = { - event_id: hint && hint.event_id, - level, - message, - }; - - if (attachStacktrace && hint && hint.syntheticException) { - const frames = parseStackFrames(stackParser, hint.syntheticException); - if (frames.length) { - event.exception = { - values: [ - { - value: message, - stacktrace: { frames }, - }, - ], - }; - } - } - - return event; -} diff --git a/packages/nextjs/src/edge/index.ts b/packages/nextjs/src/edge/index.ts index 6c2967d30f9b..281dd215001f 100644 --- a/packages/nextjs/src/edge/index.ts +++ b/packages/nextjs/src/edge/index.ts @@ -1,10 +1,16 @@ -import { getIntegrationsToSetup, initAndBind, Integrations as CoreIntegrations } from '@sentry/core'; +import type { ServerRuntimeClientOptions } from '@sentry/core'; +import { + getIntegrationsToSetup, + initAndBind, + Integrations as CoreIntegrations, + SDK_VERSION, + ServerRuntimeClient, +} from '@sentry/core'; import type { Options } from '@sentry/types'; import { createStackParser, GLOBAL_OBJ, nodeStackLineParser, stackParserFromStackParserOptions } from '@sentry/utils'; import { getVercelEnv } from '../common/getVercelEnv'; import { setAsyncLocalStorageAsyncContextStrategy } from './asyncLocalStorageAsyncContextStrategy'; -import { EdgeClient } from './edgeclient'; import { makeEdgeTransport } from './transport'; const nodeStackParser = createStackParser(nodeStackLineParser()); @@ -53,14 +59,30 @@ export function init(options: EdgeOptions = {}): void { options.instrumenter = 'sentry'; } - const clientOptions = { + const clientOptions: ServerRuntimeClientOptions = { ...options, stackParser: stackParserFromStackParserOptions(options.stackParser || nodeStackParser), integrations: getIntegrationsToSetup(options), transport: options.transport || makeEdgeTransport, }; - initAndBind(EdgeClient, clientOptions); + clientOptions._metadata = clientOptions._metadata || {}; + clientOptions._metadata.sdk = clientOptions._metadata.sdk || { + name: 'sentry.javascript.nextjs', + packages: [ + { + name: 'npm:@sentry/nextjs', + version: SDK_VERSION, + }, + ], + version: SDK_VERSION, + }; + + clientOptions.platform = 'edge'; + clientOptions.runtime = { name: 'edge' }; + clientOptions.serverName = process.env.SENTRY_NAME; + + initAndBind(ServerRuntimeClient, clientOptions); // TODO?: Sessiontracking } diff --git a/packages/nextjs/test/edge/edgeclient.test.ts b/packages/nextjs/test/edge/edgeclient.test.ts deleted file mode 100644 index cba4a751c71e..000000000000 --- a/packages/nextjs/test/edge/edgeclient.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { createTransport } from '@sentry/core'; -import type { Event, EventHint } from '@sentry/types'; - -import type { EdgeClientOptions } from '../../src/edge/edgeclient'; -import { EdgeClient } from '../../src/edge/edgeclient'; - -const PUBLIC_DSN = 'https://username@domain/123'; - -function getDefaultEdgeClientOptions(options: Partial = {}): EdgeClientOptions { - return { - integrations: [], - transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => Promise.resolve({})), - stackParser: () => [], - instrumenter: 'sentry', - ...options, - }; -} - -describe('NodeClient', () => { - describe('_prepareEvent', () => { - test('adds platform to event', () => { - const options = getDefaultEdgeClientOptions({ dsn: PUBLIC_DSN }); - const client = new EdgeClient(options); - - const event: Event = {}; - const hint: EventHint = {}; - (client as any)._prepareEvent(event, hint); - - expect(event.platform).toEqual('edge'); - }); - - test('adds runtime context to event', () => { - const options = getDefaultEdgeClientOptions({ dsn: PUBLIC_DSN }); - const client = new EdgeClient(options); - - const event: Event = {}; - const hint: EventHint = {}; - (client as any)._prepareEvent(event, hint); - - expect(event.contexts?.runtime).toEqual({ - name: 'edge', - }); - }); - - test("doesn't clobber existing runtime data", () => { - const options = getDefaultEdgeClientOptions({ dsn: PUBLIC_DSN }); - const client = new EdgeClient(options); - - const event: Event = { contexts: { runtime: { name: 'foo', version: '1.2.3' } } }; - const hint: EventHint = {}; - (client as any)._prepareEvent(event, hint); - - expect(event.contexts?.runtime).toEqual({ name: 'foo', version: '1.2.3' }); - expect(event.contexts?.runtime).not.toEqual({ name: 'edge' }); - }); - }); -});