Skip to content

Commit

Permalink
ref(core): Split transaction finish into underlying `_finishTransac…
Browse files Browse the repository at this point in the history
…tion` (#9137)

This makes it easier to extend the transaction (e.g. for POTEL) and make
use of the _finishing_ code without actually sending the transaction to
sentry.

This is needed for
#9085.
  • Loading branch information
mydea authored Oct 2, 2023
1 parent f22bb15 commit f112204
Showing 1 changed file with 88 additions and 77 deletions.
165 changes: 88 additions & 77 deletions packages/core/src/tracing/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import type {
Context,
Contexts,
DynamicSamplingContext,
Event,
Measurements,
MeasurementUnit,
Transaction as TransactionInterface,
TransactionContext,
TransactionEvent,
TransactionMetadata,
} from '@sentry/types';
import { dropUndefinedKeys, logger } from '@sentry/utils';
Expand Down Expand Up @@ -134,84 +134,10 @@ export class Transaction extends SpanClass implements TransactionInterface {
* @inheritDoc
*/
public finish(endTimestamp?: number): string | undefined {
// This transaction is already finished, so we should not flush it again.
if (this.endTimestamp !== undefined) {
const transaction = this._finishTransaction(endTimestamp);
if (!transaction) {
return undefined;
}

if (!this.name) {
__DEBUG_BUILD__ && logger.warn('Transaction has no name, falling back to `<unlabeled transaction>`.');
this.name = '<unlabeled transaction>';
}

// just sets the end timestamp
super.finish(endTimestamp);

const client = this._hub.getClient();
if (client && client.emit) {
client.emit('finishTransaction', this);
}

if (this.sampled !== true) {
// At this point if `sampled !== true` we want to discard the transaction.
__DEBUG_BUILD__ && logger.log('[Tracing] Discarding transaction because its trace was not chosen to be sampled.');

if (client) {
client.recordDroppedEvent('sample_rate', 'transaction');
}

return undefined;
}

const finishedSpans = this.spanRecorder ? this.spanRecorder.spans.filter(s => s !== this && s.endTimestamp) : [];

if (this._trimEnd && finishedSpans.length > 0) {
this.endTimestamp = finishedSpans.reduce((prev: SpanClass, current: SpanClass) => {
if (prev.endTimestamp && current.endTimestamp) {
return prev.endTimestamp > current.endTimestamp ? prev : current;
}
return prev;
}).endTimestamp;
}

const metadata = this.metadata;

const transaction: Event = {
contexts: {
...this._contexts,
// We don't want to override trace context
trace: this.getTraceContext(),
},
spans: finishedSpans,
start_timestamp: this.startTimestamp,
tags: this.tags,
timestamp: this.endTimestamp,
transaction: this.name,
type: 'transaction',
sdkProcessingMetadata: {
...metadata,
dynamicSamplingContext: this.getDynamicSamplingContext(),
},
...(metadata.source && {
transaction_info: {
source: metadata.source,
},
}),
};

const hasMeasurements = Object.keys(this._measurements).length > 0;

if (hasMeasurements) {
__DEBUG_BUILD__ &&
logger.log(
'[Measurements] Adding measurements to transaction',
JSON.stringify(this._measurements, undefined, 2),
);
transaction.measurements = this._measurements;
}

__DEBUG_BUILD__ && logger.log(`[Tracing] Finishing ${this.op} transaction: ${this.name}.`);

return this._hub.captureEvent(transaction);
}

Expand Down Expand Up @@ -289,4 +215,89 @@ export class Transaction extends SpanClass implements TransactionInterface {
public setHub(hub: Hub): void {
this._hub = hub;
}

/**
* Finish the transaction & prepare the event to send to Sentry.
*/
protected _finishTransaction(endTimestamp?: number): TransactionEvent | undefined {
// This transaction is already finished, so we should not flush it again.
if (this.endTimestamp !== undefined) {
return undefined;
}

if (!this.name) {
__DEBUG_BUILD__ && logger.warn('Transaction has no name, falling back to `<unlabeled transaction>`.');
this.name = '<unlabeled transaction>';
}

// just sets the end timestamp
super.finish(endTimestamp);

const client = this._hub.getClient();
if (client && client.emit) {
client.emit('finishTransaction', this);
}

if (this.sampled !== true) {
// At this point if `sampled !== true` we want to discard the transaction.
__DEBUG_BUILD__ && logger.log('[Tracing] Discarding transaction because its trace was not chosen to be sampled.');

if (client) {
client.recordDroppedEvent('sample_rate', 'transaction');
}

return undefined;
}

const finishedSpans = this.spanRecorder ? this.spanRecorder.spans.filter(s => s !== this && s.endTimestamp) : [];

if (this._trimEnd && finishedSpans.length > 0) {
this.endTimestamp = finishedSpans.reduce((prev: SpanClass, current: SpanClass) => {
if (prev.endTimestamp && current.endTimestamp) {
return prev.endTimestamp > current.endTimestamp ? prev : current;
}
return prev;
}).endTimestamp;
}

const metadata = this.metadata;

const transaction: TransactionEvent = {
contexts: {
...this._contexts,
// We don't want to override trace context
trace: this.getTraceContext(),
},
spans: finishedSpans,
start_timestamp: this.startTimestamp,
tags: this.tags,
timestamp: this.endTimestamp,
transaction: this.name,
type: 'transaction',
sdkProcessingMetadata: {
...metadata,
dynamicSamplingContext: this.getDynamicSamplingContext(),
},
...(metadata.source && {
transaction_info: {
source: metadata.source,
},
}),
};

const hasMeasurements = Object.keys(this._measurements).length > 0;

if (hasMeasurements) {
__DEBUG_BUILD__ &&
logger.log(
'[Measurements] Adding measurements to transaction',
JSON.stringify(this._measurements, undefined, 2),
);
transaction.measurements = this._measurements;
}

__DEBUG_BUILD__ && logger.log(`[Tracing] Finishing ${this.op} transaction: ${this.name}.`);

return transaction;
}
}

0 comments on commit f112204

Please sign in to comment.