Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 2 additions & 79 deletions packages/opentelemetry/src/custom/scope.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,8 @@
import type { Span } from '@opentelemetry/api';
import type { TimedEvent } from '@opentelemetry/sdk-trace-base';
import { Scope } from '@sentry/core';
import type { Breadcrumb, ScopeData, SeverityLevel, Span as SentrySpan } from '@sentry/types';
import { dropUndefinedKeys, logger } from '@sentry/utils';
import type { Span as SentrySpan } from '@sentry/types';
import { logger } from '@sentry/utils';

import { DEBUG_BUILD } from '../debug-build';
import { InternalSentrySemanticAttributes } from '../semanticAttributes';
import { convertOtelTimeToSeconds } from '../utils/convertOtelTimeToSeconds';
import { getActiveSpan } from '../utils/getActiveSpan';
import { getSpanParent } from '../utils/spanData';
import { spanHasEvents } from '../utils/spanTypes';

/** A fork of the classic scope with some otel specific stuff. */
export class OpenTelemetryScope extends Scope {
Expand Down Expand Up @@ -64,74 +57,4 @@ export class OpenTelemetryScope extends Scope {

return this;
}

/** @inheritDoc */
public getScopeData(): ScopeData {
const data = super.getScopeData();

data.breadcrumbs = this._getBreadcrumbs();

return data;
}

/**
* @inheritDoc
*/
protected _getBreadcrumbs(): Breadcrumb[] {
const span = getActiveSpan();
const spanBreadcrumbs = span ? getBreadcrumbsForSpan(span) : [];

return spanBreadcrumbs.length > 0 ? this._breadcrumbs.concat(spanBreadcrumbs) : this._breadcrumbs;
}
}

/**
* Get all breadcrumbs for the given span as well as it's parents.
*/
function getBreadcrumbsForSpan(span: Span): Breadcrumb[] {
const events = span ? getOtelEvents(span) : [];

return events.map(otelEventToBreadcrumb);
}

function otelEventToBreadcrumb(event: TimedEvent): Breadcrumb {
const attributes = event.attributes || {};

const type = attributes[InternalSentrySemanticAttributes.BREADCRUMB_TYPE] as string | undefined;
const level = attributes[InternalSentrySemanticAttributes.BREADCRUMB_LEVEL] as SeverityLevel | undefined;
const eventId = attributes[InternalSentrySemanticAttributes.BREADCRUMB_EVENT_ID] as string | undefined;
const category = attributes[InternalSentrySemanticAttributes.BREADCRUMB_CATEGORY] as string | undefined;
const dataStr = attributes[InternalSentrySemanticAttributes.BREADCRUMB_DATA] as string | undefined;

const breadcrumb: Breadcrumb = dropUndefinedKeys({
timestamp: convertOtelTimeToSeconds(event.time),
message: event.name,
type,
level,
event_id: eventId,
category,
});

if (typeof dataStr === 'string') {
try {
const data = JSON.parse(dataStr);
breadcrumb.data = data;
} catch (e) {} // eslint-disable-line no-empty
}

return breadcrumb;
}

function getOtelEvents(span: Span, events: TimedEvent[] = []): TimedEvent[] {
if (spanHasEvents(span)) {
events.push(...span.events);
}

// Go up parent chain and collect events
const parent = getSpanParent(span);
if (parent) {
return getOtelEvents(parent, events);
}

return events;
}
131 changes: 0 additions & 131 deletions packages/opentelemetry/test/custom/scope.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import { makeSession } from '@sentry/core';
import type { Breadcrumb } from '@sentry/types';

import { OpenTelemetryScope } from '../../src/custom/scope';
import { InternalSentrySemanticAttributes } from '../../src/semanticAttributes';
import { setSpanParent } from '../../src/utils/spanData';
import { createSpan } from '../helpers/createSpan';
import * as GetActiveSpan from './../../src/utils/getActiveSpan';

describe('NodeExperimentalScope', () => {
afterEach(() => {
Expand Down Expand Up @@ -93,130 +88,4 @@ describe('NodeExperimentalScope', () => {

expect(scope['_span']).toBeUndefined();
});

describe('_getBreadcrumbs', () => {
it('gets from scope if no span is found', () => {
jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(undefined);

const scope = new OpenTelemetryScope();
const breadcrumbs: Breadcrumb[] = [
{ message: 'test1', timestamp: 1234 },
{ message: 'test2', timestamp: 12345 },
{ message: 'test3', timestamp: 12346 },
];
scope['_breadcrumbs'] = breadcrumbs;

expect(scope['_getBreadcrumbs']()).toEqual(breadcrumbs);
});

it('gets events from active span if found', () => {
const span = createSpan();
jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span);

const scope = new OpenTelemetryScope();

const now = Date.now();

span.addEvent('basic event', now);
span.addEvent('breadcrumb event', {}, now + 1000);
span.addEvent(
'breadcrumb event 2',
{
[InternalSentrySemanticAttributes.BREADCRUMB_DATA]: JSON.stringify({ nested: { indeed: true } }),
[InternalSentrySemanticAttributes.BREADCRUMB_TYPE]: 'test-type',
[InternalSentrySemanticAttributes.BREADCRUMB_LEVEL]: 'info',
[InternalSentrySemanticAttributes.BREADCRUMB_EVENT_ID]: 'test-event-id',
[InternalSentrySemanticAttributes.BREADCRUMB_CATEGORY]: 'test-category',
},
now + 3000,
);
span.addEvent(
'breadcrumb event invalid JSON data',
{
[InternalSentrySemanticAttributes.BREADCRUMB_DATA]: 'this is not JSON...',
},
now + 2000,
);

expect(scope['_getBreadcrumbs']()).toEqual([
{ message: 'basic event', timestamp: now / 1000 },
{ message: 'breadcrumb event', timestamp: now / 1000 + 1 },
{
message: 'breadcrumb event 2',
timestamp: now / 1000 + 3,
data: { nested: { indeed: true } },
level: 'info',
event_id: 'test-event-id',
category: 'test-category',
type: 'test-type',
},
{ message: 'breadcrumb event invalid JSON data', timestamp: now / 1000 + 2 },
]);
});

it('gets events from spans up the parent chain if found', () => {
const span = createSpan();
const parentSpan = createSpan();
const rootSpan = createSpan();
jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span);

setSpanParent(span, parentSpan);
setSpanParent(parentSpan, rootSpan);

const scope = new OpenTelemetryScope();

const now = Date.now();

span.addEvent('basic event', now);
parentSpan.addEvent('parent breadcrumb event', {}, now + 1000);
span.addEvent(
'breadcrumb event 2',
{
[InternalSentrySemanticAttributes.BREADCRUMB_DATA]: JSON.stringify({ nested: true }),
},
now + 3000,
);
rootSpan.addEvent(
'breadcrumb event invalid JSON data',
{
[InternalSentrySemanticAttributes.BREADCRUMB_DATA]: 'this is not JSON...',
},
now + 2000,
);

expect(scope['_getBreadcrumbs']()).toEqual([
{ message: 'basic event', timestamp: now / 1000 },
{ message: 'breadcrumb event 2', timestamp: now / 1000 + 3, data: { nested: true } },
{ message: 'parent breadcrumb event', timestamp: now / 1000 + 1 },
{ message: 'breadcrumb event invalid JSON data', timestamp: now / 1000 + 2 },
]);
});

it('combines scope & span breadcrumbs if both exist', () => {
const span = createSpan();
jest.spyOn(GetActiveSpan, 'getActiveSpan').mockReturnValue(span);

const scope = new OpenTelemetryScope();

const breadcrumbs: Breadcrumb[] = [
{ message: 'test1', timestamp: 1234 },
{ message: 'test2', timestamp: 12345 },
{ message: 'test3', timestamp: 12346 },
];
scope['_breadcrumbs'] = breadcrumbs;

const now = Date.now();

span.addEvent('basic event', now);
span.addEvent('breadcrumb event', {}, now + 1000);

expect(scope['_getBreadcrumbs']()).toEqual([
{ message: 'test1', timestamp: 1234 },
{ message: 'test2', timestamp: 12345 },
{ message: 'test3', timestamp: 12346 },
{ message: 'basic event', timestamp: now / 1000 },
{ message: 'breadcrumb event', timestamp: now / 1000 + 1 },
]);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ describe('Integration | Transactions', () => {
]);
});

it('correctly creates concurrent transaction & spans xxx', async () => {
it('correctly creates concurrent transaction & spans', async () => {
const beforeSendTransaction = jest.fn(() => null);

mockSdkInit({ enableTracing: true, beforeSendTransaction });
Expand Down