Skip to content

Commit

Permalink
feat(node): Add trace context to checkin (#8503)
Browse files Browse the repository at this point in the history
This PR adds `trace` context to checkin bodies as well as to the checkin
envelope header.
  • Loading branch information
AbhiPrasad authored Jul 12, 2023
1 parent 1167436 commit f0afc37
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 20 deletions.
36 changes: 26 additions & 10 deletions packages/core/src/checkin.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,42 @@
import type { CheckInEvelope, CheckInItem, DsnComponents, SdkMetadata, SerializedCheckIn } from '@sentry/types';
import { createEnvelope, dsnToString } from '@sentry/utils';
import type {
CheckInEvelope,
CheckInItem,
DsnComponents,
DynamicSamplingContext,
SdkMetadata,
SerializedCheckIn,
} from '@sentry/types';
import { createEnvelope, dropUndefinedKeys, dsnToString } from '@sentry/utils';

/**
* Create envelope from check in item.
*/
export function createCheckInEnvelope(
checkIn: SerializedCheckIn,
dynamicSamplingContext?: Partial<DynamicSamplingContext>,
metadata?: SdkMetadata,
tunnel?: string,
dsn?: DsnComponents,
): CheckInEvelope {
const headers: CheckInEvelope[0] = {
sent_at: new Date().toISOString(),
...(metadata &&
metadata.sdk && {
sdk: {
name: metadata.sdk.name,
version: metadata.sdk.version,
},
}),
...(!!tunnel && !!dsn && { dsn: dsnToString(dsn) }),
};

if (metadata && metadata.sdk) {
headers.sdk = {
name: metadata.sdk.name,
version: metadata.sdk.version,
};
}

if (!!tunnel && !!dsn) {
headers.dsn = dsnToString(dsn);
}

if (dynamicSamplingContext) {
headers.trace = dropUndefinedKeys(dynamicSamplingContext) as DynamicSamplingContext;
}

const item = createCheckInEnvelopeItem(checkIn);
return createEnvelope<CheckInEvelope>(headers, [item]);
}
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,15 @@ export function startTransaction(
* to create a monitor automatically when sending a check in.
*/
export function captureCheckIn(checkIn: CheckIn, upsertMonitorConfig?: MonitorConfig): string {
const client = getCurrentHub().getClient();
const hub = getCurrentHub();
const scope = hub.getScope();
const client = hub.getClient();
if (!client) {
__DEBUG_BUILD__ && logger.warn('Cannot capture check-in. No client defined.');
} else if (!client.captureCheckIn) {
__DEBUG_BUILD__ && logger.warn('Cannot capture check-in. Client does not support sending check-ins.');
} else {
return client.captureCheckIn(checkIn, upsertMonitorConfig);
return client.captureCheckIn(checkIn, upsertMonitorConfig, scope);
}

return uuid4();
Expand Down
8 changes: 8 additions & 0 deletions packages/core/test/lib/checkin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ describe('createCheckInEnvelope', () => {
monitor_slug: 'b7645b8e-b47d-4398-be9a-d16b0dac31cb',
status: 'in_progress',
},
{
trace_id: '86f39e84263a4de99c326acab3bfe3bd',
public_key: 'testPublicKey',
},
{
sdk: {
name: 'testSdkName',
Expand All @@ -30,6 +34,10 @@ describe('createCheckInEnvelope', () => {
name: 'testSdkName',
version: 'testSdkVersion',
},
trace: {
trace_id: '86f39e84263a4de99c326acab3bfe3bd',
public_key: 'testPublicKey',
},
sent_at: expect.any(String),
});
});
Expand Down
53 changes: 50 additions & 3 deletions packages/nextjs/src/edge/edgeclient.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import type { Scope } from '@sentry/core';
import { addTracingExtensions, BaseClient, createCheckInEnvelope, SDK_VERSION } 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';

Expand Down Expand Up @@ -72,7 +80,7 @@ export class EdgeClient extends BaseClient<EdgeClientOptions> {
* @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): string {
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.');
Expand Down Expand Up @@ -103,7 +111,20 @@ export class EdgeClient extends BaseClient<EdgeClientOptions> {
};
}

const envelope = createCheckInEnvelope(serializedCheckIn, this.getSdkMetadata(), tunnel, this.getDsn());
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);
Expand All @@ -124,4 +145,30 @@ export class EdgeClient extends BaseClient<EdgeClientOptions> {
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<DynamicSamplingContext> | 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];
}
}
54 changes: 51 additions & 3 deletions packages/node/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import type { Scope } from '@sentry/core';
import { addTracingExtensions, BaseClient, createCheckInEnvelope, SDK_VERSION, SessionFlusher } from '@sentry/core';
import {
addTracingExtensions,
BaseClient,
createCheckInEnvelope,
getDynamicSamplingContextFromClient,
SDK_VERSION,
SessionFlusher,
} from '@sentry/core';
import type {
CheckIn,
DynamicSamplingContext,
Event,
EventHint,
MonitorConfig,
SerializedCheckIn,
Severity,
SeverityLevel,
TraceContext,
} from '@sentry/types';
import { logger, resolvedSyncPromise, uuid4 } from '@sentry/utils';
import * as os from 'os';
Expand Down Expand Up @@ -154,7 +163,7 @@ export class NodeClient extends BaseClient<NodeClientOptions> {
* to create a monitor automatically when sending a check in.
* @returns A string representing the id of the check in.
*/
public captureCheckIn(checkIn: CheckIn, monitorConfig?: MonitorConfig): string {
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.');
Expand Down Expand Up @@ -185,7 +194,20 @@ export class NodeClient extends BaseClient<NodeClientOptions> {
};
}

const envelope = createCheckInEnvelope(serializedCheckIn, this.getSdkMetadata(), tunnel, this.getDsn());
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);
Expand Down Expand Up @@ -220,4 +242,30 @@ export class NodeClient extends BaseClient<NodeClientOptions> {
this._sessionFlusher.incrementSessionStatusCount();
}
}

/** Extract trace information from scope */
private _getTraceInfoFromScope(
scope: Scope | undefined,
): [dynamicSamplingContext: Partial<DynamicSamplingContext> | 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];
}
}
5 changes: 5 additions & 0 deletions packages/types/src/checkin.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { TraceContext } from './context';

interface CrontabSchedule {
type: 'crontab';
// The crontab schedule string, e.g. 0 * * * *.
Expand Down Expand Up @@ -36,6 +38,9 @@ export interface SerializedCheckIn {
// See: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
timezone?: string;
};
contexts?: {
trace?: TraceContext;
};
}

interface InProgressCheckIn {
Expand Down
3 changes: 2 additions & 1 deletion packages/types/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,10 @@ export interface Client<O extends ClientOptions = ClientOptions> {
* @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.
* @param scope An optional scope containing event metadata.
* @returns A string representing the id of the check in.
*/
captureCheckIn?(checkIn: CheckIn, monitorConfig?: MonitorConfig): string;
captureCheckIn?(checkIn: CheckIn, monitorConfig?: MonitorConfig, scope?: Scope): string;

/** Returns the current Dsn. */
getDsn(): DsnComponents | undefined;
Expand Down
2 changes: 1 addition & 1 deletion packages/types/src/envelope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ type ReplayRecordingItem = BaseEnvelopeItem<ReplayRecordingItemHeaders, ReplayRe

export type EventEnvelopeHeaders = { event_id: string; sent_at: string; trace?: DynamicSamplingContext };
type SessionEnvelopeHeaders = { sent_at: string };
type CheckInEnvelopeHeaders = BaseEnvelopeHeaders;
type CheckInEnvelopeHeaders = { trace?: DynamicSamplingContext };
type ClientReportEnvelopeHeaders = BaseEnvelopeHeaders;
type ReplayEnvelopeHeaders = BaseEnvelopeHeaders;

Expand Down

0 comments on commit f0afc37

Please sign in to comment.