UNPKG

@sentry/core

Version:
266 lines (214 loc) 7.44 kB
import { logger, dropUndefinedKeys } from '@sentry/utils'; import { DEFAULT_ENVIRONMENT } from '../constants.js'; import { getCurrentHub } from '../hub.js'; import { Span, SpanRecorder } from './span.js'; /** JSDoc */ class Transaction extends Span { /** * The reference to the current hub. */ __init() {this._measurements = {};} __init2() {this._contexts = {};} __init3() {this._frozenDynamicSamplingContext = undefined;} /** * 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) { super(transactionContext);Transaction.prototype.__init.call(this);Transaction.prototype.__init2.call(this);Transaction.prototype.__init3.call(this); this._hub = 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 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) { // 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__) && 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__) && 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__) && logger.log( '[Measurements] Adding measurements to transaction', JSON.stringify(this._measurements, undefined, 2), ); transaction.measurements = this._measurements; } (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log(`[Tracing] Finishing ${this.op} transaction: ${this.name}.`); return this._hub.captureEvent(transaction); } /** * @inheritDoc */ toContext() { const spanContext = super.toContext(); return 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 = this._hub || getCurrentHub(); const client = hub && hub.getClient(); if (!client) return {}; const { environment, release } = client.getOptions() || {}; const { publicKey: public_key } = client.getDsn() || {}; const maybeSampleRate = this.metadata.sampleRate; const sample_rate = maybeSampleRate !== undefined ? maybeSampleRate.toString() : undefined; const scope = hub.getScope(); const { segment: user_segment } = (scope && scope.getUser()) || {}; const source = this.metadata.source; // We don't want to have a transaction name in the DSC if the source is "url" because URLs might contain PII const transaction = source && source !== 'url' ? this.name : undefined; const dsc = dropUndefinedKeys({ environment: environment || DEFAULT_ENVIRONMENT, release, transaction, user_segment, public_key, trace_id: this.traceId, sample_rate, }); // Uncomment if we want to make DSC immutable // this._frozenDynamicSamplingContext = dsc; return dsc; } } export { Transaction }; //# sourceMappingURL=transaction.js.map