Skip to content

Commit

Permalink
feat(longtasks): allow callback to add span attributes on collection
Browse files Browse the repository at this point in the history
This adds a callback parameter to the LongTaskInstrumentation to be
executed when a longtask is observed. The return value of the callback
is an object of span attributes to be attached to the longtask span.
This feature allows sites using client-side navigation to add window
location details to spans and enables hydrating spans with relevant
information on application state.
  • Loading branch information
crellison committed Jan 31, 2022
1 parent aedc7ff commit 0244cc2
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

import { SpanAttributes } from '@opentelemetry/api';
import { hrTime } from '@opentelemetry/core';
import {
InstrumentationBase,
Expand All @@ -33,6 +34,8 @@ interface TaskAttributionTiming extends PerformanceEntry {
containerName: string;
}

type ObserverCallback = (entry: PerformanceLongTaskTiming) => SpanAttributes;

const LONGTASK_PERFORMANCE_TYPE = 'longtask';

export class LongTaskInstrumentation extends InstrumentationBase {
Expand All @@ -41,13 +44,18 @@ export class LongTaskInstrumentation extends InstrumentationBase {
moduleName = this.component;

private _observer?: PerformanceObserver;
private _observerCallback?: ObserverCallback;

/**
*
* @param config
*/
constructor(config: InstrumentationConfig = {}) {
constructor(
config: InstrumentationConfig = {},
observerCallback?: ObserverCallback
) {
super('@opentelemetry/instrumentation-long-task', VERSION, config);
this._observerCallback = observerCallback;
}

init() {}
Expand All @@ -69,6 +77,9 @@ export class LongTaskInstrumentation extends InstrumentationBase {
const span = this.tracer.startSpan(LONGTASK_PERFORMANCE_TYPE, {
startTime: hrTime(entry.startTime),
});
if (this._observerCallback) {
span.setAttributes(this._observerCallback(entry));
}
span.setAttribute('component', this.component);
span.setAttribute('longtask.name', entry.name);
span.setAttribute('longtask.entry_type', entry.entryType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,26 @@ function generateLongTask() {
while (performance.now() - startingTimestamp < LONGTASK_DURATION) {}
}

async function waitForLongTask(exportSpy: sinon.SinonStub) {
let taskStart: number;
return await new Promise<number>(resolve => {
// Resolve promise when export gets called
exportSpy.callsFake(() => {
if (!taskStart) {
// Hasn't generated expected longtask yet
return;
}
resolve(taskStart);
});
setTimeout(() => {
// Cleanup any past longtasks
exportSpy.resetHistory();
taskStart = performance.timeOrigin + performance.now();
generateLongTask();
}, 1);
});
}

describe('LongTaskInstrumentation', () => {
let longTaskInstrumentation: LongTaskInstrumentation;
let sandbox: sinon.SinonSandbox;
Expand Down Expand Up @@ -65,23 +85,7 @@ describe('LongTaskInstrumentation', () => {
});

it('should report long taking tasks', async () => {
let taskStart: number;
await new Promise<void>(resolve => {
// Resolve promise when export gets called
exportSpy.callsFake(() => {
if (!taskStart) {
// Hasn't generated expected longtask yet
return;
}
resolve();
});
setTimeout(() => {
// Cleanup any past longtasks
exportSpy.resetHistory();
taskStart = performance.timeOrigin + performance.now();
generateLongTask();
}, 1);
});
const taskStart = await waitForLongTask(exportSpy);

assert.strictEqual(exportSpy.args.length, 1, 'should export once');
assert.strictEqual(
Expand All @@ -101,4 +105,27 @@ describe('LongTaskInstrumentation', () => {
"span duration should be longtask's"
);
});

it('should attach additional attributes from callback', async () => {
deregister();
const additionalAttributes = { foo: 'bar' };
longTaskInstrumentation = new LongTaskInstrumentation(
{
enabled: false,
},
() => additionalAttributes
);
deregister = registerInstrumentations({
instrumentations: [longTaskInstrumentation],
});

await waitForLongTask(exportSpy);
const span: ReadableSpan = exportSpy.args[0][0][0];
assert.ok(
Object.entries(additionalAttributes).every(([key, value]) => {
return span.attributes[key] === value;
}),
'span should have key/value pairs from additional attributes'
);
});
});

0 comments on commit 0244cc2

Please sign in to comment.