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

XMLHttpRequest #595

Merged
merged 31 commits into from
Dec 19, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8fce91a
feat(xml-http-request): new plugin for auto instrumentation for xml-h…
obecny Dec 5, 2019
0875f3f
chore: new example for xml-http-request and updated examples structur…
obecny Dec 5, 2019
f1c6013
chore: updating readme
obecny Dec 5, 2019
6038bcc
chore: linting
obecny Dec 5, 2019
ac68ab9
chore: fixing origin for tests
obecny Dec 5, 2019
87dcdc6
chore: linting
obecny Dec 5, 2019
6e10df7
chore: updating to use b3 format from core
obecny Dec 5, 2019
e91d8f9
chore: updates after reviews
obecny Dec 6, 2019
8e01e96
chore: wrong function call
obecny Dec 6, 2019
898c190
chore: updating attribute names
obecny Dec 6, 2019
54d0ba0
chore: linting
obecny Dec 6, 2019
9f2c563
chore: adding preflight requests, fixing few other issues
obecny Dec 9, 2019
8a17c2a
chore: adding image to examples, updating readme
obecny Dec 9, 2019
db00221
chore: forcing async to be true, but propagate rest params
obecny Dec 9, 2019
ef66398
chore: fixing type for open and send function
obecny Dec 9, 2019
7d884db
chore: fixing format for headers
obecny Dec 9, 2019
3e39b1a
chore: reviews
obecny Dec 10, 2019
bcc1204
chore: decrement task count when span exists
obecny Dec 10, 2019
c9163e5
chore: changes after review
obecny Dec 10, 2019
f73aacb
chore: adding weakmap for keeping information connected with xhr
obecny Dec 10, 2019
88e122a
chore: renaming config param
obecny Dec 10, 2019
c3dac11
chore: not needed cast
obecny Dec 10, 2019
524da7a
chore: updating title
obecny Dec 11, 2019
50e151e
chore: refactored xhr, removed tracing dependency, few other issues f…
obecny Dec 11, 2019
6c143e7
chore: reviews
obecny Dec 13, 2019
3d7b32e
chore: refactored for collecting timing resources
obecny Dec 16, 2019
91bad5d
chore: merge branch 'master' into xml-http-request
obecny Dec 16, 2019
5aa23ca
chore: fixes after merging
obecny Dec 16, 2019
ab9795a
chore: reviews
obecny Dec 18, 2019
4d4e65f
chore: reviews
obecny Dec 19, 2019
ca96cb1
Merge branch 'master' into xml-http-request
mayurkale22 Dec 19, 2019
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
2 changes: 1 addition & 1 deletion examples/tracer-web/examples/xml-http-request/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const webTracerWithZone = new WebTracer({
plugins: [
new XMLHttpRequestPlugin({
ignoreUrls: ['http://localhost:8090/sockjs-node/info'],
propagateTraceHeaderUrls: [
propagateTraceHeaderCorsUrls: [
'http://localhost:8090'
obecny marked this conversation as resolved.
Show resolved Hide resolved
obecny marked this conversation as resolved.
Show resolved Hide resolved
]
})
Expand Down
25 changes: 18 additions & 7 deletions packages/opentelemetry-plugin-xml-http-request/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
/**
* method "open" from XMLHttpRequest
*/
import * as tracing from '@opentelemetry/tracing';

export type OpenFunction = (
method: string,
url: string,
Expand All @@ -42,14 +44,23 @@ export type SendBody =
| null;

/**
* Wrapped XMLHttpRequest
* interface to store information in weak map about spans, resources and
* callbacks
*/
export interface XMLHttpRequestWrapped extends XMLHttpRequest {
__OT_SPAN_ID?: string;
export interface XhrMem {
// span assigned to xhr
span: tracing.Span;
// resources created between send and end - possible candidates for
// cors preflight requests
resourcesCreatedInTheMiddle?: {
observer: PerformanceObserver;
entries: PerformanceResourceTiming[];
};
callbackToRemoveEvents?: Function;
}

export type PropagateTraceHeaderUrl = string | RegExp;
export type PropagateTraceHeaderCorsUrl = string | RegExp;

export type PropagateTraceHeaderUrls =
| PropagateTraceHeaderUrl
| PropagateTraceHeaderUrl[];
export type PropagateTraceHeaderCorsUrls =
| PropagateTraceHeaderCorsUrl
| PropagateTraceHeaderCorsUrl[];
121 changes: 57 additions & 64 deletions packages/opentelemetry-plugin-xml-http-request/src/xhr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,18 @@ import { EventNames } from './enums/EventNames';
import { Format } from './enums/Format';
import {
OpenFunction,
PropagateTraceHeaderUrls,
PropagateTraceHeaderCorsUrls,
SendBody,
SendFunction,
XMLHttpRequestWrapped,
XhrMem,
} from './types';

/**
* XMLHttpRequest config
*/
export interface XMLHttpRequestPluginConfig extends types.PluginConfig {
// urls which should include trace headers when origin doesn't match
propagateTraceHeaderUrls?: PropagateTraceHeaderUrls;
propagateTraceHeaderCorsUrls?: PropagateTraceHeaderCorsUrls;
}

/**
Expand All @@ -61,16 +61,7 @@ export class XMLHttpRequestPlugin extends BasePlugin<XMLHttpRequest> {
protected _config!: XMLHttpRequestPluginConfig;

private _tasksCount = 0;
private _callbackToRemoveEvents: { [key: string]: Function } = {};
private _spans: { [key: string]: tracing.Span } = {};
// resources created between send and end - possible candidates for
// cors preflight requests
private _resourcesCreatedInTheMiddle: {
[key: string]: {
observer: PerformanceObserver;
entries: PerformanceResourceTiming[];
};
} = {};
private _xhrMem = new WeakMap<XMLHttpRequest, XhrMem>();
private _usedResources: { [key: string]: PerformanceResourceTiming[] } = {};
obecny marked this conversation as resolved.
Show resolved Hide resolved
obecny marked this conversation as resolved.
Show resolved Hide resolved

constructor(config: XMLHttpRequestPluginConfig = {}) {
Expand All @@ -85,7 +76,8 @@ export class XMLHttpRequestPlugin extends BasePlugin<XMLHttpRequest> {
* @private
*/
private _addHeaders(xhr: XMLHttpRequest, span: types.Span) {
let propagateTraceHeaderUrls = this._config.propagateTraceHeaderUrls || [];
let propagateTraceHeaderUrls =
this._config.propagateTraceHeaderCorsUrls || [];
if (
typeof propagateTraceHeaderUrls === 'string' ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we have this logic handled by the utility function for matching strings?

propagateTraceHeaderUrls instanceof RegExp
Expand Down Expand Up @@ -155,29 +147,35 @@ export class XMLHttpRequestPlugin extends BasePlugin<XMLHttpRequest> {
/**
* will collect information about all resources created
* between "send" and "end"
* @param spanId
* @param xhr
* @param spanName
* @private
*/
private _addPossibleCorsPreflightResourceObserver(
spanId: string,
xhr: XMLHttpRequest,
spanName: string
) {
this._resourcesCreatedInTheMiddle[spanId] = {
const xhrMem = this._xhrMem.get(xhr);
if (!xhrMem) {
return;
}
xhrMem.resourcesCreatedInTheMiddle = {
observer: new PerformanceObserver(list => {
const entries = list.getEntries() as PerformanceResourceTiming[];
entries.forEach(entry => {
if (
entry.initiatorType === 'xmlhttprequest' &&
entry.name === spanName
) {
this._resourcesCreatedInTheMiddle[spanId].entries.push(entry);
if (xhrMem.resourcesCreatedInTheMiddle) {
xhrMem.resourcesCreatedInTheMiddle.entries.push(entry);
}
}
});
}),
entries: [],
};
this._resourcesCreatedInTheMiddle[spanId].observer.observe({
xhrMem.resourcesCreatedInTheMiddle.observer.observe({
entryTypes: ['resource'],
});
}
Expand All @@ -189,9 +187,7 @@ export class XMLHttpRequestPlugin extends BasePlugin<XMLHttpRequest> {
private _clearResources() {
if (this._tasksCount === 0) {
((otperformance as unknown) as Performance).clearResourceTimings();
this._callbackToRemoveEvents = {};
this._resourcesCreatedInTheMiddle = {};
this._spans = {};
this._xhrMem = new WeakMap<XMLHttpRequest, XhrMem>();
obecny marked this conversation as resolved.
Show resolved Hide resolved
this._usedResources = {};
}
}
Expand Down Expand Up @@ -235,12 +231,10 @@ export class XMLHttpRequestPlugin extends BasePlugin<XMLHttpRequest> {
* @param xhr
* @private
*/
private _cleanPreviousSpanInformation(xhr: XMLHttpRequestWrapped) {
const existingSpanId = xhr.__OT_SPAN_ID;
if (existingSpanId) {
const callbackToRemoveEvents = this._callbackToRemoveEvents[
existingSpanId
];
private _cleanPreviousSpanInformation(xhr: XMLHttpRequest) {
const xhrMem = this._xhrMem.get(xhr);
if (xhrMem) {
const callbackToRemoveEvents = xhrMem.callbackToRemoveEvents;
if (callbackToRemoveEvents) {
callbackToRemoveEvents();
}
Expand All @@ -255,7 +249,7 @@ export class XMLHttpRequestPlugin extends BasePlugin<XMLHttpRequest> {
* @private
*/
private _createSpan(
xhr: XMLHttpRequestWrapped,
xhr: XMLHttpRequest,
url: string,
method: string
): tracing.Span | undefined {
Expand All @@ -275,11 +269,11 @@ export class XMLHttpRequestPlugin extends BasePlugin<XMLHttpRequest> {

currentSpan.addEvent(EventNames.METHOD_OPEN);

const spanId = currentSpan.context().spanId;
this._spans[spanId] = currentSpan;
this._xhrMem.set(xhr, {
span: currentSpan,
});

this._cleanPreviousSpanInformation(xhr);
xhr.__OT_SPAN_ID = spanId;

return currentSpan;
}
Expand Down Expand Up @@ -314,7 +308,7 @@ export class XMLHttpRequestPlugin extends BasePlugin<XMLHttpRequest> {
user?: string | null,
pass?: string | null
): void {
plugin._createSpan(this as XMLHttpRequestWrapped, url, method);
plugin._createSpan(this as XMLHttpRequest, url, method);

return original.call(this, method, url, true, user, pass);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating a new thread on this line as the old one is deep in the history and on a line that has been outdated for a long time.

@draffensperger @mayurkale22

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think I missed this in my review, but I definitely agree that we should preserve the original behavior of the XHR function including the async parameter.

};
Expand All @@ -328,24 +322,23 @@ export class XMLHttpRequestPlugin extends BasePlugin<XMLHttpRequest> {
protected _patchSend() {
const plugin = this;

function endSpan(xhr: XMLHttpRequestWrapped, eventName: string) {
const spanId = xhr.__OT_SPAN_ID;
const callbackToRemoveEvents = spanId
? plugin._callbackToRemoveEvents[spanId]
: undefined;
function endSpan(xhr: XMLHttpRequest, eventName: string) {
const xhrMem = plugin._xhrMem.get(xhr);
if (!xhrMem) {
return;
}
const callbackToRemoveEvents = xhrMem.callbackToRemoveEvents;

let resourcesCreatedInTheMiddle = spanId
? plugin._resourcesCreatedInTheMiddle[spanId]
: undefined;
let resourcesCreatedInTheMiddle = xhrMem.resourcesCreatedInTheMiddle;
let entries;
if (resourcesCreatedInTheMiddle) {
entries = resourcesCreatedInTheMiddle.entries.slice();
}
if (typeof callbackToRemoveEvents === 'function') {
callbackToRemoveEvents();
}
const currentSpan = spanId ? plugin._spans[spanId] : undefined;
if (currentSpan && spanId) {
const currentSpan = xhrMem.span;
if (currentSpan) {
plugin._findResourceAndAddNetworkEvents(currentSpan, entries);
currentSpan.addEvent(eventName);

Expand Down Expand Up @@ -378,38 +371,41 @@ export class XMLHttpRequestPlugin extends BasePlugin<XMLHttpRequest> {
plugin._clearResources();
}

function onError(this: XMLHttpRequestWrapped) {
function onError(this: XMLHttpRequest) {
endSpan(this, EventNames.EVENT_ERROR);
}

function onTimeout(this: XMLHttpRequestWrapped) {
function onTimeout(this: XMLHttpRequest) {
endSpan(this, EventNames.EVENT_TIMEOUT);
}

function onLoad(this: XMLHttpRequestWrapped) {
function onLoad(this: XMLHttpRequest) {
if (this.status < 299) {
endSpan(this, EventNames.EVENT_LOAD);
} else {
endSpan(this, EventNames.EVENT_ERROR);
}
}

function unregister(xhr: XMLHttpRequestWrapped, spanId: string) {
function unregister(xhr: XMLHttpRequest) {
xhr.removeEventListener('load', onLoad);
xhr.removeEventListener('error', onError);
xhr.removeEventListener('timeout', onTimeout);
delete plugin._callbackToRemoveEvents[spanId];
delete xhr.__OT_SPAN_ID;
const xhrMem = plugin._xhrMem.get(xhr);
if (xhrMem) {
delete xhrMem.callbackToRemoveEvents;
obecny marked this conversation as resolved.
Show resolved Hide resolved
}
}

return (original: SendFunction): SendFunction => {
obecny marked this conversation as resolved.
Show resolved Hide resolved
return function patchSend(this: XMLHttpRequest, body?: SendBody): void {
const spanId = (this as XMLHttpRequestWrapped).__OT_SPAN_ID;
const currentSpan: types.Span | undefined = spanId
? plugin._spans[spanId]
: undefined;
const xhrMem = plugin._xhrMem.get(this);
if (!xhrMem) {
return;
dyladan marked this conversation as resolved.
Show resolved Hide resolved
}
const currentSpan: types.Span = xhrMem.span;

if (currentSpan && spanId) {
if (currentSpan) {
plugin._tasksCount++;
currentSpan.addEvent(EventNames.METHOD_SEND);
const spanName = (currentSpan as tracing.Span).name;
Expand All @@ -418,14 +414,15 @@ export class XMLHttpRequestPlugin extends BasePlugin<XMLHttpRequest> {
this.addEventListener('error', onError);
this.addEventListener('timeout', onTimeout);

plugin._callbackToRemoveEvents[spanId] = () => {
unregister(this as XMLHttpRequestWrapped, spanId);
plugin._resourcesCreatedInTheMiddle[spanId].observer.disconnect();
delete plugin._resourcesCreatedInTheMiddle[spanId];
xhrMem.callbackToRemoveEvents = () => {
unregister(this);
if (xhrMem.resourcesCreatedInTheMiddle) {
xhrMem.resourcesCreatedInTheMiddle.observer.disconnect();
delete xhrMem.resourcesCreatedInTheMiddle;
}
};
plugin._addHeaders(this, currentSpan);

plugin._addPossibleCorsPreflightResourceObserver(spanId, spanName);
plugin._addPossibleCorsPreflightResourceObserver(this, spanName);
}
return original.call(this, body);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return original.call(this, body);
return original.apply(this, arguments);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. call is faster
  2. we already know there is just one para, we know it's type so we can use it and typescript will also validate the type of this is correcty
  3. You don't have arguments word in typescript , so you would still declare array of aguments and define type for it, which would be quite weird anyway

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and the same here, thx
@draffensperger @mayurkale22 ^^

};
Expand Down Expand Up @@ -462,9 +459,5 @@ export class XMLHttpRequestPlugin extends BasePlugin<XMLHttpRequest> {

shimmer.unwrap(XMLHttpRequest.prototype, 'open');
shimmer.unwrap(XMLHttpRequest.prototype, 'send');

Object.keys(this._callbackToRemoveEvents).forEach(key =>
this._callbackToRemoveEvents[key]()
);
}
}
12 changes: 6 additions & 6 deletions packages/opentelemetry-plugin-xml-http-request/test/xhr.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ describe('xhr', () => {
prepareData = (
done: any,
fileUrl: string,
propagateTraceHeaderUrls?: any
propagateTraceHeaderCorsUrls?: any
) => {
sandbox = sinon.createSandbox();
sandbox.stub(performance, 'timeOrigin').value(0);
Expand All @@ -148,7 +148,7 @@ describe('xhr', () => {
scopeManager: new ZoneScopeManager(),
plugins: [
new XMLHttpRequestPlugin({
propagateTraceHeaderUrls,
propagateTraceHeaderCorsUrls: propagateTraceHeaderCorsUrls,
}),
],
});
Expand Down Expand Up @@ -179,8 +179,8 @@ describe('xhr', () => {
};

beforeEach(done => {
const propagateTraceHeaderUrls = [window.location.origin];
prepareData(done, url, propagateTraceHeaderUrls);
const propagateTraceHeaderCorsUrls = [window.location.origin];
prepareData(done, url, propagateTraceHeaderCorsUrls);
});

afterEach(() => {
Expand Down Expand Up @@ -346,7 +346,7 @@ describe('xhr', () => {

describe(
'AND origin does NOT match window.location but match with' +
' propagateTraceHeaderUrls',
' propagateTraceHeaderCorsUrls',
() => {
beforeEach(done => {
clearData();
Expand Down Expand Up @@ -378,7 +378,7 @@ describe('xhr', () => {
);
describe(
'AND origin does NOT match window.location And does NOT match' +
' with propagateTraceHeaderUrls',
' with propagateTraceHeaderCorsUrls',
() => {
beforeEach(done => {
clearData();
Expand Down