Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(instrumentation-document-load): add custom root span to document page load instrumentation #1092

Closed
wants to merge 6 commits into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ import {
getPerformanceNavigationEntries,
} from './utils';

/**
* DocumentLoadInstrumentationConfig Config
*/
export interface DocumentLoadInstrumentationConfig
extends InstrumentationConfig {
// Custom root span
rootSpan?: Span;
}

/**
* This class represents a document load plugin
*/
Expand All @@ -53,12 +62,16 @@ export class DocumentLoadInstrumentation extends InstrumentationBase<unknown> {
*
* @param config
*/
constructor(config: InstrumentationConfig = {}) {
constructor(config: DocumentLoadInstrumentationConfig = {}) {
super('@opentelemetry/instrumentation-document-load', VERSION, config);
}

init() {}

private _getConfig(): DocumentLoadInstrumentationConfig {
return this._config;
}

/**
* callback to be executed when page is loaded
*/
Expand Down Expand Up @@ -89,61 +102,71 @@ export class DocumentLoadInstrumentation extends InstrumentationBase<unknown> {
* Collects information about performance and creates appropriate spans
*/
private _collectPerformance() {
const metaElement = Array.from(document.getElementsByTagName('meta')).find(
e => e.getAttribute('name') === TRACE_PARENT_HEADER
);
const entries = getPerformanceNavigationEntries();
const traceparent = (metaElement && metaElement.content) || '';
context.with(propagation.extract(ROOT_CONTEXT, { traceparent }), () => {
const rootSpan = this._startSpan(
AttributeNames.DOCUMENT_LOAD,
PTN.FETCH_START,
entries
);
if (!rootSpan) {
return;
}
context.with(trace.setSpan(context.active(), rootSpan), () => {
const fetchSpan = this._startSpan(
AttributeNames.DOCUMENT_FETCH,
let rootSpan = this._getConfig().rootSpan;

if (rootSpan) {
this._instrumentRootSpan(rootSpan, entries);
} else {
const metaElement = Array.from(
document.getElementsByTagName('meta')
).find(e => e.getAttribute('name') === TRACE_PARENT_HEADER);
const traceparent = (metaElement && metaElement.content) || '';
context.with(propagation.extract(ROOT_CONTEXT, { traceparent }), () => {
rootSpan = this._startSpan(
AttributeNames.DOCUMENT_LOAD,
PTN.FETCH_START,
entries
);
if (fetchSpan) {
fetchSpan.setAttribute(SemanticAttributes.HTTP_URL, location.href);
context.with(trace.setSpan(context.active(), fetchSpan), () => {
addSpanNetworkEvents(fetchSpan, entries);
this._endSpan(fetchSpan, PTN.RESPONSE_END, entries);
});
}
});

rootSpan.setAttribute(SemanticAttributes.HTTP_URL, location.href);
rootSpan.setAttribute(
SemanticAttributes.HTTP_USER_AGENT,
navigator.userAgent
);
if (!rootSpan) {
return;
}

this._addResourcesSpans(rootSpan);
this._instrumentRootSpan(rootSpan, entries);
this._endSpan(rootSpan, PTN.LOAD_EVENT_END, entries);
}
}

addSpanNetworkEvent(rootSpan, PTN.FETCH_START, entries);
addSpanNetworkEvent(rootSpan, PTN.UNLOAD_EVENT_START, entries);
addSpanNetworkEvent(rootSpan, PTN.UNLOAD_EVENT_END, entries);
addSpanNetworkEvent(rootSpan, PTN.DOM_INTERACTIVE, entries);
addSpanNetworkEvent(
rootSpan,
PTN.DOM_CONTENT_LOADED_EVENT_START,
/**
* Adds child spans and tags to the root span
*/
private _instrumentRootSpan(rootSpan: Span, entries: PerformanceEntries) {
context.with(trace.setSpan(context.active(), rootSpan), () => {
const fetchSpan = this._startSpan(
AttributeNames.DOCUMENT_FETCH,
PTN.FETCH_START,
entries
);
addSpanNetworkEvent(rootSpan, PTN.DOM_CONTENT_LOADED_EVENT_END, entries);
addSpanNetworkEvent(rootSpan, PTN.DOM_COMPLETE, entries);
addSpanNetworkEvent(rootSpan, PTN.LOAD_EVENT_START, entries);
addSpanNetworkEvent(rootSpan, PTN.LOAD_EVENT_END, entries);
if (fetchSpan) {
fetchSpan.setAttribute(SemanticAttributes.HTTP_URL, location.href);
context.with(trace.setSpan(context.active(), fetchSpan), () => {
addSpanNetworkEvents(fetchSpan, entries);
this._endSpan(fetchSpan, PTN.RESPONSE_END, entries);
});
}
});

rootSpan.setAttribute(SemanticAttributes.HTTP_URL, location.href);
rootSpan.setAttribute(
SemanticAttributes.HTTP_USER_AGENT,
navigator.userAgent
);

addSpanPerformancePaintEvents(rootSpan);
this._addResourcesSpans(rootSpan);

this._endSpan(rootSpan, PTN.LOAD_EVENT_END, entries);
});
addSpanNetworkEvent(rootSpan, PTN.FETCH_START, entries);
addSpanNetworkEvent(rootSpan, PTN.UNLOAD_EVENT_START, entries);
addSpanNetworkEvent(rootSpan, PTN.UNLOAD_EVENT_END, entries);
addSpanNetworkEvent(rootSpan, PTN.DOM_INTERACTIVE, entries);
addSpanNetworkEvent(rootSpan, PTN.DOM_CONTENT_LOADED_EVENT_START, entries);
addSpanNetworkEvent(rootSpan, PTN.DOM_CONTENT_LOADED_EVENT_END, entries);
addSpanNetworkEvent(rootSpan, PTN.DOM_COMPLETE, entries);
addSpanNetworkEvent(rootSpan, PTN.LOAD_EVENT_START, entries);
addSpanNetworkEvent(rootSpan, PTN.LOAD_EVENT_END, entries);

addSpanPerformancePaintEvents(rootSpan);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { EventNames } from '../src/enums/EventNames';
const exporter = new InMemorySpanExporter();
const provider = new BasicTracerProvider();
const spanProcessor = new SimpleSpanProcessor(exporter);
const tracer = provider.getTracer('default');

provider.addSpanProcessor(spanProcessor);
provider.register();
Expand Down Expand Up @@ -653,6 +654,60 @@ describe('DocumentLoad Instrumentation', () => {
});
shouldExportCorrectSpan();
});

describe('when custom root span is passed', () => {
let spyEntries: any;
beforeEach(() => {
spyEntries = sandbox.stub(window.performance, 'getEntriesByType');
spyEntries.withArgs('navigation').returns([entries]);
spyEntries.withArgs('resource').returns(resources);
spyEntries.withArgs('paint').returns([]);
});
afterEach(() => {
spyEntries.restore();
});

it('should add child spans to the custom root span', done => {
const customRootSpan = tracer.startSpan('customRootSpan');
plugin = new DocumentLoadInstrumentation({
enabled: false,
rootSpan: customRootSpan,
});
plugin.setTracerProvider(provider);
plugin.enable();
setTimeout(() => {
const spanResource1 = exporter.getFinishedSpans()[1] as ReadableSpan;
const spanResource2 = exporter.getFinishedSpans()[2] as ReadableSpan;

const srEvents1 = spanResource1.events;
const srEvents2 = spanResource2.events;

assert.strictEqual(
spanResource1.parentSpanId,
customRootSpan.spanContext().spanId
);
assert.strictEqual(
spanResource2.parentSpanId,
customRootSpan.spanContext().spanId
);

assert.strictEqual(
spanResource1.attributes[SemanticAttributes.HTTP_URL],
'http://localhost:8090/bundle.js'
);
assert.strictEqual(
spanResource2.attributes[SemanticAttributes.HTTP_URL],
'http://localhost:8090/sockjs-node/info?t=1572620894466'
);

ensureNetworkEventsExists(srEvents1);
ensureNetworkEventsExists(srEvents2);

assert.strictEqual(exporter.getFinishedSpans().length, 3);
done();
});
});
});
});

/**
Expand Down