@sentry/core
Version:
Base implementation for all Sentry JavaScript SDKs
284 lines (232 loc) • 7.71 kB
JavaScript
Object.defineProperty(exports, '__esModule', { value: true });
const utils = require('@sentry/utils');
const hub = require('../hub.js');
const dynamicSamplingContext = require('./dynamicSamplingContext.js');
const span = require('./span.js');
/** JSDoc */
class Transaction extends span.Span {
/**
* The reference to the current hub.
*/
/**
* This constructor should never be called manually. Those instrumenting tracing should use
* `Sentry.startTransaction()`, and internal methods should use `hub.startTransaction()`.
* @internal
* @hideconstructor
* @hidden
*/
constructor(transactionContext, hub$1) {
super(transactionContext);
// We need to delete description since it's set by the Span class constructor
// but not needed for transactions.
delete this.description;
this._measurements = {};
this._contexts = {};
this._hub = hub$1 || hub.getCurrentHub();
this._name = transactionContext.name || '';
this.metadata = {
source: 'custom',
...transactionContext.metadata,
spanMetadata: {},
};
this._trimEnd = transactionContext.trimEnd;
// this is because transactions are also spans, and spans have a transaction pointer
this.transaction = this;
// If Dynamic Sampling Context is provided during the creation of the transaction, we freeze it as it usually means
// there is incoming Dynamic Sampling Context. (Either through an incoming request, a baggage meta-tag, or other means)
const incomingDynamicSamplingContext = this.metadata.dynamicSamplingContext;
if (incomingDynamicSamplingContext) {
// We shallow copy this in case anything writes to the original reference of the passed in `dynamicSamplingContext`
this._frozenDynamicSamplingContext = { ...incomingDynamicSamplingContext };
}
}
/** Getter for `name` property */
get name() {
return this._name;
}
/** Setter for `name` property, which also sets `source` as custom */
set name(newName) {
this.setName(newName);
}
/**
* JSDoc
*/
setName(name, source = 'custom') {
this._name = name;
this.metadata.source = source;
}
/**
* Attaches SpanRecorder to the span itself
* @param maxlen maximum number of spans that can be recorded
*/
initSpanRecorder(maxlen = 1000) {
if (!this.spanRecorder) {
this.spanRecorder = new span.SpanRecorder(maxlen);
}
this.spanRecorder.add(this);
}
/**
* @inheritDoc
*/
setContext(key, context) {
if (context === null) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete this._contexts[key];
} else {
this._contexts[key] = context;
}
}
/**
* @inheritDoc
*/
setMeasurement(name, value, unit = '') {
this._measurements[name] = { value, unit };
}
/**
* @inheritDoc
*/
setMetadata(newMetadata) {
this.metadata = { ...this.metadata, ...newMetadata };
}
/**
* @inheritDoc
*/
finish(endTimestamp) {
const transaction = this._finishTransaction(endTimestamp);
if (!transaction) {
return undefined;
}
return this._hub.captureEvent(transaction);
}
/**
* @inheritDoc
*/
toContext() {
const spanContext = super.toContext();
return utils.dropUndefinedKeys({
...spanContext,
name: this.name,
trimEnd: this._trimEnd,
});
}
/**
* @inheritDoc
*/
updateWithContext(transactionContext) {
super.updateWithContext(transactionContext);
this.name = transactionContext.name || '';
this._trimEnd = transactionContext.trimEnd;
return this;
}
/**
* @inheritdoc
*
* @experimental
*/
getDynamicSamplingContext() {
if (this._frozenDynamicSamplingContext) {
return this._frozenDynamicSamplingContext;
}
const hub$1 = this._hub || hub.getCurrentHub();
const client = hub$1.getClient();
if (!client) return {};
const scope = hub$1.getScope();
const dsc = dynamicSamplingContext.getDynamicSamplingContextFromClient(this.traceId, client, scope);
const maybeSampleRate = this.metadata.sampleRate;
if (maybeSampleRate !== undefined) {
dsc.sample_rate = `${maybeSampleRate}`;
}
// We don't want to have a transaction name in the DSC if the source is "url" because URLs might contain PII
const source = this.metadata.source;
if (source && source !== 'url') {
dsc.transaction = this.name;
}
if (this.sampled !== undefined) {
dsc.sampled = String(this.sampled);
}
// Uncomment if we want to make DSC immutable
// this._frozenDynamicSamplingContext = dsc;
return dsc;
}
/**
* Override the current hub with a new one.
* Used if you want another hub to finish the transaction.
*
* @internal
*/
setHub(hub) {
this._hub = hub;
}
/**
* Finish the transaction & prepare the event to send to Sentry.
*/
_finishTransaction(endTimestamp) {
// This transaction is already finished, so we should not flush it again.
if (this.endTimestamp !== undefined) {
return undefined;
}
if (!this.name) {
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.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.
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.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, current) => {
if (prev.endTimestamp && current.endTimestamp) {
return prev.endTimestamp > current.endTimestamp ? prev : current;
}
return prev;
}).endTimestamp;
}
const metadata = this.metadata;
const transaction = {
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) {
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
utils.logger.log(
'[Measurements] Adding measurements to transaction',
JSON.stringify(this._measurements, undefined, 2),
);
transaction.measurements = this._measurements;
}
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log(`[Tracing] Finishing ${this.op} transaction: ${this.name}.`);
return transaction;
}
}
exports.Transaction = Transaction;
//# sourceMappingURL=transaction.js.map