chore(plugin-https): sync https tests with http (#597)
* chore(plugin-https): sync https tests with http * chore: use Http instead typeof http * chore: review finding, improve https detection * chore: fix node 8 * chore: fix path to test files
This commit is contained in:
parent
daff102296
commit
54879ab3d6
|
@ -6,7 +6,7 @@
|
|||
"types": "build/src/index.d.ts",
|
||||
"repository": "open-telemetry/opentelemetry-js",
|
||||
"scripts": {
|
||||
"test": "nyc ts-mocha -p tsconfig.json test/**/*/*.test.ts",
|
||||
"test": "nyc ts-mocha -p tsconfig.json test/**/*.test.ts",
|
||||
"tdd": "yarn test -- --watch-extensions ts --watch",
|
||||
"clean": "rimraf build/*",
|
||||
"check": "gts check",
|
||||
|
|
|
@ -29,7 +29,7 @@ import { HttpPlugin, plugin } from '../../src/http';
|
|||
import { assertSpan } from '../utils/assertSpan';
|
||||
import { DummyPropagation } from '../utils/DummyPropagation';
|
||||
import { httpRequest } from '../utils/httpRequest';
|
||||
import * as utils from '../../src/utils';
|
||||
import { OT_REQUEST_HEADER } from '../../src/utils';
|
||||
import { HttpPluginConfig, Http } from '../../src/types';
|
||||
import { AttributeNames } from '../../src/enums/AttributeNames';
|
||||
|
||||
|
@ -77,8 +77,8 @@ describe('HttpPlugin', () => {
|
|||
assert.strictEqual(process.versions.node, plugin.version);
|
||||
});
|
||||
|
||||
it('moduleName should be http', () => {
|
||||
assert.strictEqual('http', plugin.moduleName);
|
||||
it(`moduleName should be ${protocol}`, () => {
|
||||
assert.strictEqual(protocol, plugin.moduleName);
|
||||
});
|
||||
|
||||
describe('enable()', () => {
|
||||
|
@ -123,7 +123,7 @@ describe('HttpPlugin', () => {
|
|||
|
||||
it('should generate valid spans (client side and server side)', async () => {
|
||||
const result = await httpRequest.get(
|
||||
`http://${hostname}:${serverPort}${pathname}`
|
||||
`${protocol}://${hostname}:${serverPort}${pathname}`
|
||||
);
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
const [incomingSpan, outgoingSpan] = spans;
|
||||
|
@ -142,14 +142,14 @@ describe('HttpPlugin', () => {
|
|||
assertSpan(outgoingSpan, SpanKind.CLIENT, validations);
|
||||
});
|
||||
|
||||
it(`should not trace requests with '${utils.OT_REQUEST_HEADER}' header`, async () => {
|
||||
it(`should not trace requests with '${OT_REQUEST_HEADER}' header`, async () => {
|
||||
const testPath = '/outgoing/do-not-trace';
|
||||
doNock(hostname, testPath, 200, 'Ok');
|
||||
|
||||
const options = {
|
||||
host: hostname,
|
||||
path: testPath,
|
||||
headers: { [utils.OT_REQUEST_HEADER]: 1 },
|
||||
headers: { [OT_REQUEST_HEADER]: 1 },
|
||||
};
|
||||
|
||||
const result = await httpRequest.get(options);
|
||||
|
@ -171,7 +171,7 @@ describe('HttpPlugin', () => {
|
|||
(url: string) => url.endsWith(`/ignored/function`),
|
||||
],
|
||||
ignoreOutgoingUrls: [
|
||||
`http://${hostname}:${serverPort}/ignored/string`,
|
||||
`${protocol}://${hostname}:${serverPort}/ignored/string`,
|
||||
/\/ignored\/regexp$/i,
|
||||
(url: string) => url.endsWith(`/ignored/function`),
|
||||
],
|
||||
|
@ -190,11 +190,11 @@ describe('HttpPlugin', () => {
|
|||
plugin.disable();
|
||||
});
|
||||
|
||||
it('http module should be patched', () => {
|
||||
it(`${protocol} module should be patched`, () => {
|
||||
assert.strictEqual(http.Server.prototype.emit.__wrapped, true);
|
||||
});
|
||||
|
||||
it("should not patch if it's not a http module", () => {
|
||||
it(`should not patch if it's not a ${protocol} module`, () => {
|
||||
const httpNotPatched = new HttpPlugin(
|
||||
plugin.component,
|
||||
process.versions.node
|
||||
|
@ -204,7 +204,7 @@ describe('HttpPlugin', () => {
|
|||
|
||||
it('should generate valid spans (client side and server side)', async () => {
|
||||
const result = await httpRequest.get(
|
||||
`http://${hostname}:${serverPort}${pathname}`
|
||||
`${protocol}://${hostname}:${serverPort}${pathname}`
|
||||
);
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
const [incomingSpan, outgoingSpan] = spans;
|
||||
|
@ -223,14 +223,14 @@ describe('HttpPlugin', () => {
|
|||
assertSpan(outgoingSpan, SpanKind.CLIENT, validations);
|
||||
});
|
||||
|
||||
it(`should not trace requests with '${utils.OT_REQUEST_HEADER}' header`, async () => {
|
||||
it(`should not trace requests with '${OT_REQUEST_HEADER}' header`, async () => {
|
||||
const testPath = '/outgoing/do-not-trace';
|
||||
doNock(hostname, testPath, 200, 'Ok');
|
||||
|
||||
const options = {
|
||||
host: hostname,
|
||||
path: testPath,
|
||||
headers: { [utils.OT_REQUEST_HEADER]: 1 },
|
||||
headers: { [OT_REQUEST_HEADER]: 1 },
|
||||
};
|
||||
|
||||
const result = await httpRequest.get(options);
|
||||
|
@ -395,13 +395,13 @@ describe('HttpPlugin', () => {
|
|||
}
|
||||
|
||||
for (const arg of ['string', {}, new Date()]) {
|
||||
it(`should be tracable and not throw exception in http plugin when passing the following argument ${JSON.stringify(
|
||||
it(`should be tracable and not throw exception in ${protocol} plugin when passing the following argument ${JSON.stringify(
|
||||
arg
|
||||
)}`, async () => {
|
||||
try {
|
||||
await httpRequest.get(arg);
|
||||
} catch (error) {
|
||||
// http request has been made
|
||||
// request has been made
|
||||
// nock throw
|
||||
assert.ok(error.message.startsWith('Nock: No match for request'));
|
||||
}
|
||||
|
@ -411,14 +411,14 @@ describe('HttpPlugin', () => {
|
|||
}
|
||||
|
||||
for (const arg of [true, 1, false, 0, '']) {
|
||||
it(`should not throw exception in http plugin when passing the following argument ${JSON.stringify(
|
||||
it(`should not throw exception in ${protocol} plugin when passing the following argument ${JSON.stringify(
|
||||
arg
|
||||
)}`, async () => {
|
||||
try {
|
||||
// @ts-ignore
|
||||
await httpRequest.get(arg);
|
||||
} catch (error) {
|
||||
// http request has been made
|
||||
// request has been made
|
||||
// nock throw
|
||||
assert.ok(
|
||||
error.stack.indexOf(
|
||||
|
@ -447,7 +447,7 @@ describe('HttpPlugin', () => {
|
|||
|
||||
const promiseRequest = new Promise((resolve, reject) => {
|
||||
const req = http.request(
|
||||
`http://${hostname}${testPath}`,
|
||||
`${protocol}://${hostname}${testPath}`,
|
||||
(resp: http.IncomingMessage) => {
|
||||
let data = '';
|
||||
resp.on('data', chunk => {
|
||||
|
@ -488,7 +488,7 @@ describe('HttpPlugin', () => {
|
|||
|
||||
const promiseRequest = new Promise((resolve, reject) => {
|
||||
const req = http.request(
|
||||
`http://${hostname}${testPath}`,
|
||||
`${protocol}://${hostname}${testPath}`,
|
||||
(resp: http.IncomingMessage) => {
|
||||
let data = '';
|
||||
resp.on('data', chunk => {
|
||||
|
@ -512,14 +512,14 @@ describe('HttpPlugin', () => {
|
|||
});
|
||||
|
||||
it('should have 1 ended span when request is aborted', async () => {
|
||||
nock('http://my.server.com')
|
||||
nock(`${protocol}://my.server.com`)
|
||||
.get('/')
|
||||
.socketDelay(50)
|
||||
.reply(200, '<html></html>');
|
||||
|
||||
const promiseRequest = new Promise((resolve, reject) => {
|
||||
const req = http.request(
|
||||
'http://my.server.com',
|
||||
`${protocol}://my.server.com`,
|
||||
(resp: http.IncomingMessage) => {
|
||||
let data = '';
|
||||
resp.on('data', chunk => {
|
||||
|
|
|
@ -38,6 +38,7 @@ import { HttpPluginConfig } from '../../src/types';
|
|||
import { customAttributeFunction } from './http-enable.test';
|
||||
|
||||
const memoryExporter = new InMemorySpanExporter();
|
||||
const protocol = 'http';
|
||||
|
||||
describe('Packages', () => {
|
||||
describe('get', () => {
|
||||
|
@ -93,8 +94,8 @@ describe('Packages', () => {
|
|||
// https://github.com/nock/nock/pull/1551
|
||||
// https://github.com/sindresorhus/got/commit/bf1aa5492ae2bc78cbbec6b7d764906fb156e6c2#diff-707a4781d57c42085155dcb27edb9ccbR258
|
||||
// TODO: check if this is still the case when new version
|
||||
'http://info.cern.ch/'
|
||||
: `http://www.google.com/search?q=axios&oq=axios&aqs=chrome.0.69i59l2j0l3j69i60.811j0j7&sourceid=chrome&ie=UTF-8`
|
||||
`${protocol}://info.cern.ch/`
|
||||
: `${protocol}://www.google.com/search?q=axios&oq=axios&aqs=chrome.0.69i59l2j0l3j69i60.811j0j7&sourceid=chrome&ie=UTF-8`
|
||||
);
|
||||
const result = await httpPackage.get(urlparsed.href!);
|
||||
if (!resHeaders) {
|
||||
|
|
|
@ -30,7 +30,7 @@ import {
|
|||
SimpleSpanProcessor,
|
||||
} from '@opentelemetry/tracing';
|
||||
import { HttpPluginConfig } from '../../src/types';
|
||||
|
||||
const protocol = 'http';
|
||||
const serverPort = 32345;
|
||||
const hostname = 'localhost';
|
||||
const memoryExporter = new InMemorySpanExporter();
|
||||
|
@ -70,7 +70,7 @@ describe('HttpPlugin Integration tests', () => {
|
|||
|
||||
before(() => {
|
||||
const ignoreConfig = [
|
||||
`http://${hostname}:${serverPort}/ignored/string`,
|
||||
`${protocol}://${hostname}:${serverPort}/ignored/string`,
|
||||
/\/ignored\/regexp$/i,
|
||||
(url: string) => url.endsWith(`/ignored/function`),
|
||||
];
|
||||
|
@ -93,7 +93,9 @@ describe('HttpPlugin Integration tests', () => {
|
|||
let spans = memoryExporter.getFinishedSpans();
|
||||
assert.strictEqual(spans.length, 0);
|
||||
|
||||
const result = await httpRequest.get(`http://google.fr/?query=test`);
|
||||
const result = await httpRequest.get(
|
||||
`${protocol}://google.fr/?query=test`
|
||||
);
|
||||
|
||||
spans = memoryExporter.getFinishedSpans();
|
||||
const span = spans[0];
|
||||
|
@ -118,7 +120,7 @@ describe('HttpPlugin Integration tests', () => {
|
|||
assert.strictEqual(spans.length, 0);
|
||||
|
||||
const result = await httpRequest.get(
|
||||
new url.URL('http://google.fr/?query=test')
|
||||
new url.URL(`${protocol}://google.fr/?query=test`)
|
||||
);
|
||||
|
||||
spans = memoryExporter.getFinishedSpans();
|
||||
|
@ -144,7 +146,7 @@ describe('HttpPlugin Integration tests', () => {
|
|||
assert.strictEqual(spans.length, 0);
|
||||
|
||||
const result = await httpRequest.get(
|
||||
new url.URL('http://google.fr/?query=test'),
|
||||
new url.URL(`${protocol}://google.fr/?query=test`),
|
||||
{ headers: { 'x-foo': 'foo' } }
|
||||
);
|
||||
|
||||
|
@ -168,7 +170,7 @@ describe('HttpPlugin Integration tests', () => {
|
|||
});
|
||||
|
||||
it('custom attributes should show up on client spans', async () => {
|
||||
const result = await httpRequest.get(`http://google.fr/`);
|
||||
const result = await httpRequest.get(`${protocol}://google.fr/`);
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
const span = spans[0];
|
||||
const validations = {
|
||||
|
@ -192,7 +194,7 @@ describe('HttpPlugin Integration tests', () => {
|
|||
assert.strictEqual(spans.length, 0);
|
||||
const options = Object.assign(
|
||||
{ headers: { Expect: '100-continue' } },
|
||||
url.parse('http://google.fr/')
|
||||
url.parse(`${protocol}://google.fr/`)
|
||||
);
|
||||
|
||||
const result = await httpRequest.get(options);
|
||||
|
@ -223,7 +225,7 @@ describe('HttpPlugin Integration tests', () => {
|
|||
{ Expect: '100-continue', 'user-agent': 'http-plugin-test' },
|
||||
{ 'user-agent': 'http-plugin-test' },
|
||||
]) {
|
||||
it(`should create a span for GET requests and add propagation when using the following signature: http.get(url, options, callback) and following headers: ${JSON.stringify(
|
||||
it(`should create a span for GET requests and add propagation when using the following signature: get(url, options, callback) and following headers: ${JSON.stringify(
|
||||
headers
|
||||
)}`, done => {
|
||||
let validations: {
|
||||
|
@ -239,7 +241,7 @@ describe('HttpPlugin Integration tests', () => {
|
|||
assert.strictEqual(spans.length, 0);
|
||||
const options = { headers };
|
||||
const req = http.get(
|
||||
'http://google.fr/',
|
||||
`${protocol}://google.fr/`,
|
||||
options,
|
||||
(resp: http.IncomingMessage) => {
|
||||
const res = (resp as unknown) as http.IncomingMessage & {
|
||||
|
@ -266,7 +268,7 @@ describe('HttpPlugin Integration tests', () => {
|
|||
}
|
||||
);
|
||||
|
||||
req.once('close', () => {
|
||||
req.on('close', () => {
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
assert.strictEqual(spans.length, 1);
|
||||
assert.ok(spans[0].name.indexOf('GET /') >= 0);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
[![devDependencies][devDependencies-image]][devDependencies-url]
|
||||
[![Apache License][license-image]][license-image]
|
||||
|
||||
This module provides automatic instrumentation for [`https`](http://nodejs.org/dist/latest/docs/api/https.html).
|
||||
This module provides automatic instrumentation for [`https`](http://nodejs.org/api/https.html).
|
||||
|
||||
For automatic instrumentation see the
|
||||
[@opentelemetry/node](https://github.com/open-telemetry/opentelemetry-js/tree/master/packages/opentelemetry-node) package.
|
||||
|
|
|
@ -6,10 +6,11 @@
|
|||
"types": "build/src/index.d.ts",
|
||||
"repository": "open-telemetry/opentelemetry-js",
|
||||
"scripts": {
|
||||
"test": "nyc ts-mocha -p tsconfig.json 'test/**/*.ts'",
|
||||
"test": "nyc ts-mocha -p tsconfig.json test/**/*.test.ts",
|
||||
"tdd": "yarn test -- --watch-extensions ts --watch",
|
||||
"clean": "rimraf build/*",
|
||||
"check": "gts check",
|
||||
"codecov": "nyc report --reporter=json && codecov -f coverage/*.json -p ../../",
|
||||
"precompile": "tsc --version",
|
||||
"compile": "tsc -p .",
|
||||
"fix": "gts fix",
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
import { HttpPlugin, Func, HttpRequestArgs } from '@opentelemetry/plugin-http';
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import { URL } from 'url';
|
||||
import * as semver from 'semver';
|
||||
import * as shimmer from 'shimmer';
|
||||
import * as utils from './utils';
|
||||
|
@ -81,12 +82,13 @@ export class HttpsPlugin extends HttpPlugin {
|
|||
return (original: Func<http.ClientRequest>): Func<http.ClientRequest> => {
|
||||
const plugin = this;
|
||||
return function httpsOutgoingRequest(
|
||||
options,
|
||||
options: https.RequestOptions | string | URL,
|
||||
...args: HttpRequestArgs
|
||||
): http.ClientRequest {
|
||||
// Makes sure options will have default HTTPS parameters
|
||||
if (typeof options === 'object') {
|
||||
utils.setDefaultOptions(options);
|
||||
if (typeof options === 'object' && !(options instanceof URL)) {
|
||||
options = Object.assign({}, options);
|
||||
utils.setDefaultOptions(options as https.RequestOptions);
|
||||
}
|
||||
return plugin._getPatchOutgoingRequestFunction()(original)(
|
||||
options,
|
||||
|
@ -105,17 +107,9 @@ export class HttpsPlugin extends HttpPlugin {
|
|||
) {
|
||||
return (original: Func<http.ClientRequest>): Func<http.ClientRequest> => {
|
||||
return function httpsOutgoingRequest(
|
||||
options: https.RequestOptions | string,
|
||||
options: https.RequestOptions | string | URL,
|
||||
...args: HttpRequestArgs
|
||||
): http.ClientRequest {
|
||||
const optionsType = typeof options;
|
||||
// Makes sure options will have default HTTPS parameters
|
||||
if (optionsType === 'object') {
|
||||
utils.setDefaultOptions(options as https.RequestOptions);
|
||||
} else if (typeof args[0] === 'object' && optionsType === 'string') {
|
||||
utils.setDefaultOptions(args[0] as https.RequestOptions);
|
||||
}
|
||||
|
||||
return plugin._getPatchOutgoingGetFunction(clientRequest)(original)(
|
||||
options,
|
||||
...args
|
||||
|
|
|
@ -14,16 +14,17 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { NoopLogger } from '@opentelemetry/core';
|
||||
import { NodeTracer } from '@opentelemetry/node';
|
||||
import { Http } from '@opentelemetry/plugin-http';
|
||||
import * as assert from 'assert';
|
||||
import * as fs from 'fs';
|
||||
import * as https from 'https';
|
||||
import { AddressInfo } from 'net';
|
||||
import * as nock from 'nock';
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
import { plugin } from '../../src/https';
|
||||
import { NodeTracer } from '@opentelemetry/node';
|
||||
import { NoopLogger } from '@opentelemetry/core';
|
||||
import { Http } from '@opentelemetry/plugin-http';
|
||||
import { AddressInfo } from 'net';
|
||||
import { DummyPropagation } from '../utils/DummyPropagation';
|
||||
import { httpsRequest } from '../utils/httpsRequest';
|
||||
|
||||
|
|
|
@ -24,24 +24,36 @@ import {
|
|||
Http,
|
||||
HttpPluginConfig,
|
||||
OT_REQUEST_HEADER,
|
||||
AttributeNames,
|
||||
} from '@opentelemetry/plugin-http';
|
||||
import { CanonicalCode, Span as ISpan, SpanKind } from '@opentelemetry/types';
|
||||
import * as assert from 'assert';
|
||||
import * as fs from 'fs';
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import * as path from 'path';
|
||||
import * as nock from 'nock';
|
||||
import { HttpsPlugin, plugin } from '../../src/https';
|
||||
import { assertSpan } from '../utils/assertSpan';
|
||||
import { DummyPropagation } from '../utils/DummyPropagation';
|
||||
import { httpsRequest } from '../utils/httpsRequest';
|
||||
|
||||
const applyCustomAttributesOnSpanErrorMessage =
|
||||
'bad applyCustomAttributesOnSpan function';
|
||||
|
||||
let server: https.Server;
|
||||
const serverPort = 32345;
|
||||
const protocol = 'https';
|
||||
const hostname = 'localhost';
|
||||
const pathname = '/test';
|
||||
const memoryExporter = new InMemorySpanExporter();
|
||||
const httpTextFormat = new DummyPropagation();
|
||||
const logger = new NoopLogger();
|
||||
const tracer = new NodeTracer({
|
||||
logger,
|
||||
httpTextFormat,
|
||||
});
|
||||
tracer.addSpanProcessor(new SimpleSpanProcessor(memoryExporter));
|
||||
|
||||
function doNock(
|
||||
hostname: string,
|
||||
|
@ -70,191 +82,231 @@ describe('HttpsPlugin', () => {
|
|||
assert.strictEqual(process.versions.node, plugin.version);
|
||||
});
|
||||
|
||||
it('moduleName should be https', () => {
|
||||
assert.strictEqual('https', plugin.moduleName);
|
||||
it(`moduleName should be ${protocol}`, () => {
|
||||
assert.strictEqual(protocol, plugin.moduleName);
|
||||
});
|
||||
|
||||
describe('enable()', () => {
|
||||
const httpTextFormat = new DummyPropagation();
|
||||
const logger = new NoopLogger();
|
||||
const tracer = new NodeTracer({
|
||||
logger,
|
||||
httpTextFormat,
|
||||
});
|
||||
tracer.addSpanProcessor(new SimpleSpanProcessor(memoryExporter));
|
||||
beforeEach(() => {
|
||||
memoryExporter.reset();
|
||||
});
|
||||
|
||||
before(() => {
|
||||
const config: HttpPluginConfig = {
|
||||
ignoreIncomingPaths: [
|
||||
`/ignored/string`,
|
||||
/\/ignored\/regexp$/i,
|
||||
(url: string) => url.endsWith(`/ignored/function`),
|
||||
],
|
||||
ignoreOutgoingUrls: [
|
||||
`${protocol}://${hostname}:${serverPort}/ignored/string`,
|
||||
/\/ignored\/regexp$/i,
|
||||
(url: string) => url.endsWith(`/ignored/function`),
|
||||
],
|
||||
applyCustomAttributesOnSpan: customAttributeFunction,
|
||||
};
|
||||
plugin.enable((https as unknown) as Http, tracer, tracer.logger, config);
|
||||
server = https.createServer(
|
||||
{
|
||||
key: fs.readFileSync('test/fixtures/server-key.pem'),
|
||||
cert: fs.readFileSync('test/fixtures/server-cert.pem'),
|
||||
},
|
||||
(request, response) => {
|
||||
response.end('Test Server Response');
|
||||
}
|
||||
);
|
||||
|
||||
server.listen(serverPort);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
server.close();
|
||||
plugin.disable();
|
||||
});
|
||||
|
||||
it('https module should be patched', () => {
|
||||
assert.strictEqual(https.Server.prototype.emit.__wrapped, true);
|
||||
});
|
||||
|
||||
it("should not patch if it's not a http module", () => {
|
||||
const httpNotPatched = new HttpsPlugin(process.versions.node).enable(
|
||||
{} as Http,
|
||||
tracer,
|
||||
tracer.logger,
|
||||
{}
|
||||
);
|
||||
assert.strictEqual(Object.keys(httpNotPatched).length, 0);
|
||||
});
|
||||
|
||||
it('should generate valid spans (client side and server side)', async () => {
|
||||
const result = await httpsRequest.get(
|
||||
`${protocol}://${hostname}:${serverPort}${pathname}`
|
||||
);
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
const [incomingSpan, outgoingSpan] = spans;
|
||||
const validations = {
|
||||
hostname,
|
||||
httpStatusCode: result.statusCode!,
|
||||
httpMethod: result.method!,
|
||||
pathname,
|
||||
resHeaders: result.resHeaders,
|
||||
reqHeaders: result.reqHeaders,
|
||||
component: plugin.component,
|
||||
};
|
||||
|
||||
assert.strictEqual(spans.length, 2);
|
||||
assertSpan(incomingSpan, SpanKind.SERVER, validations);
|
||||
assertSpan(outgoingSpan, SpanKind.CLIENT, validations);
|
||||
});
|
||||
|
||||
it(`should not trace requests with '${OT_REQUEST_HEADER}' header`, async () => {
|
||||
const testPath = '/outgoing/do-not-trace';
|
||||
doNock(hostname, testPath, 200, 'Ok');
|
||||
|
||||
const options = {
|
||||
host: hostname,
|
||||
path: testPath,
|
||||
headers: { [OT_REQUEST_HEADER]: 1 },
|
||||
};
|
||||
|
||||
const result = await httpsRequest.get(options);
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
assert.strictEqual(result.data, 'Ok');
|
||||
assert.strictEqual(spans.length, 0);
|
||||
});
|
||||
|
||||
const httpErrorCodes = [400, 401, 403, 404, 429, 501, 503, 504, 500, 505];
|
||||
|
||||
for (let i = 0; i < httpErrorCodes.length; i++) {
|
||||
it(`should test span for GET requests with https error ${httpErrorCodes[i]}`, async () => {
|
||||
const testPath = '/outgoing/rootSpan/1';
|
||||
|
||||
doNock(
|
||||
hostname,
|
||||
testPath,
|
||||
httpErrorCodes[i],
|
||||
httpErrorCodes[i].toString()
|
||||
);
|
||||
|
||||
const isReset = memoryExporter.getFinishedSpans().length === 0;
|
||||
assert.ok(isReset);
|
||||
|
||||
const result = await httpsRequest.get(
|
||||
`${protocol}://${hostname}${testPath}`
|
||||
);
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
const reqSpan = spans[0];
|
||||
|
||||
assert.strictEqual(result.data, httpErrorCodes[i].toString());
|
||||
assert.strictEqual(spans.length, 1);
|
||||
|
||||
const validations = {
|
||||
hostname,
|
||||
httpStatusCode: result.statusCode!,
|
||||
httpMethod: 'GET',
|
||||
pathname: testPath,
|
||||
resHeaders: result.resHeaders,
|
||||
reqHeaders: result.reqHeaders,
|
||||
component: plugin.component,
|
||||
};
|
||||
|
||||
assertSpan(reqSpan, SpanKind.CLIENT, validations);
|
||||
describe('with bad plugin options', () => {
|
||||
let pluginWithBadOptions: HttpsPlugin;
|
||||
beforeEach(() => {
|
||||
memoryExporter.reset();
|
||||
});
|
||||
}
|
||||
|
||||
it('should create a child span for GET requests', async () => {
|
||||
const testPath = '/outgoing/rootSpan/childs/1';
|
||||
doNock(hostname, testPath, 200, 'Ok');
|
||||
const name = 'TestRootSpan';
|
||||
const span = tracer.startSpan(name);
|
||||
return tracer.withSpan(span, async () => {
|
||||
const result = await httpsRequest.get(
|
||||
`${protocol}://${hostname}${testPath}`
|
||||
before(() => {
|
||||
const config: HttpPluginConfig = {
|
||||
ignoreIncomingPaths: [
|
||||
(url: string) => {
|
||||
throw new Error('bad ignoreIncomingPaths function');
|
||||
},
|
||||
],
|
||||
ignoreOutgoingUrls: [
|
||||
(url: string) => {
|
||||
throw new Error('bad ignoreOutgoingUrls function');
|
||||
},
|
||||
],
|
||||
applyCustomAttributesOnSpan: () => {
|
||||
throw new Error(applyCustomAttributesOnSpanErrorMessage);
|
||||
},
|
||||
};
|
||||
pluginWithBadOptions = new HttpsPlugin(process.versions.node);
|
||||
pluginWithBadOptions.enable(
|
||||
(https as unknown) as Http,
|
||||
tracer,
|
||||
tracer.logger,
|
||||
config
|
||||
);
|
||||
server = https.createServer(
|
||||
{
|
||||
key: fs.readFileSync('test/fixtures/server-key.pem'),
|
||||
cert: fs.readFileSync('test/fixtures/server-cert.pem'),
|
||||
},
|
||||
(request, response) => {
|
||||
response.end('Test Server Response');
|
||||
}
|
||||
);
|
||||
|
||||
server.listen(serverPort);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
server.close();
|
||||
pluginWithBadOptions.disable();
|
||||
});
|
||||
|
||||
it('should generate valid spans (client side and server side)', async () => {
|
||||
const result = await httpsRequest.get(
|
||||
`${protocol}://${hostname}:${serverPort}${pathname}`
|
||||
);
|
||||
span.end();
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
const [reqSpan, localSpan] = spans;
|
||||
const [incomingSpan, outgoingSpan] = spans;
|
||||
const validations = {
|
||||
hostname,
|
||||
httpStatusCode: result.statusCode!,
|
||||
httpMethod: 'GET',
|
||||
pathname: testPath,
|
||||
httpMethod: result.method!,
|
||||
pathname,
|
||||
resHeaders: result.resHeaders,
|
||||
reqHeaders: result.reqHeaders,
|
||||
component: plugin.component,
|
||||
};
|
||||
|
||||
assert.ok(localSpan.name.indexOf('TestRootSpan') >= 0);
|
||||
assert.strictEqual(spans.length, 2);
|
||||
assert.ok(reqSpan.name.indexOf(testPath) >= 0);
|
||||
assert.strictEqual(
|
||||
localSpan.spanContext.traceId,
|
||||
reqSpan.spanContext.traceId
|
||||
);
|
||||
assertSpan(reqSpan, SpanKind.CLIENT, validations);
|
||||
assert.notStrictEqual(
|
||||
localSpan.spanContext.spanId,
|
||||
reqSpan.spanContext.spanId
|
||||
);
|
||||
assertSpan(incomingSpan, SpanKind.SERVER, validations);
|
||||
assertSpan(outgoingSpan, SpanKind.CLIENT, validations);
|
||||
});
|
||||
|
||||
it(`should not trace requests with '${OT_REQUEST_HEADER}' header`, async () => {
|
||||
const testPath = '/outgoing/do-not-trace';
|
||||
doNock(hostname, testPath, 200, 'Ok');
|
||||
|
||||
const options = {
|
||||
host: hostname,
|
||||
path: testPath,
|
||||
headers: { [OT_REQUEST_HEADER]: 1 },
|
||||
};
|
||||
|
||||
const result = await httpsRequest.get(options);
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
assert.strictEqual(result.data, 'Ok');
|
||||
assert.strictEqual(spans.length, 0);
|
||||
});
|
||||
});
|
||||
describe('with good plugin options', () => {
|
||||
beforeEach(() => {
|
||||
memoryExporter.reset();
|
||||
});
|
||||
|
||||
for (let i = 0; i < httpErrorCodes.length; i++) {
|
||||
it(`should test child spans for GET requests with https error ${httpErrorCodes[i]}`, async () => {
|
||||
const testPath = '/outgoing/rootSpan/childs/1';
|
||||
doNock(
|
||||
hostname,
|
||||
testPath,
|
||||
httpErrorCodes[i],
|
||||
httpErrorCodes[i].toString()
|
||||
before(() => {
|
||||
const config: HttpPluginConfig = {
|
||||
ignoreIncomingPaths: [
|
||||
`/ignored/string`,
|
||||
/\/ignored\/regexp$/i,
|
||||
(url: string) => url.endsWith(`/ignored/function`),
|
||||
],
|
||||
ignoreOutgoingUrls: [
|
||||
`${protocol}://${hostname}:${serverPort}/ignored/string`,
|
||||
/\/ignored\/regexp$/i,
|
||||
(url: string) => url.endsWith(`/ignored/function`),
|
||||
],
|
||||
applyCustomAttributesOnSpan: customAttributeFunction,
|
||||
};
|
||||
plugin.enable(
|
||||
(https as unknown) as Http,
|
||||
tracer,
|
||||
tracer.logger,
|
||||
config
|
||||
);
|
||||
server = https.createServer(
|
||||
{
|
||||
key: fs.readFileSync('test/fixtures/server-key.pem'),
|
||||
cert: fs.readFileSync('test/fixtures/server-cert.pem'),
|
||||
},
|
||||
(request, response) => {
|
||||
response.end('Test Server Response');
|
||||
}
|
||||
);
|
||||
|
||||
server.listen(serverPort);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
server.close();
|
||||
plugin.disable();
|
||||
});
|
||||
|
||||
it(`${protocol} module should be patched`, () => {
|
||||
assert.strictEqual(https.Server.prototype.emit.__wrapped, true);
|
||||
});
|
||||
|
||||
it(`should not patch if it's not a ${protocol} module`, () => {
|
||||
const httpsNotPatched = new HttpsPlugin(process.versions.node).enable(
|
||||
{} as Http,
|
||||
tracer,
|
||||
tracer.logger,
|
||||
{}
|
||||
);
|
||||
assert.strictEqual(Object.keys(httpsNotPatched).length, 0);
|
||||
});
|
||||
|
||||
it('should generate valid spans (client side and server side)', async () => {
|
||||
const result = await httpsRequest.get(
|
||||
`${protocol}://${hostname}:${serverPort}${pathname}`
|
||||
);
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
const [incomingSpan, outgoingSpan] = spans;
|
||||
const validations = {
|
||||
hostname,
|
||||
httpStatusCode: result.statusCode!,
|
||||
httpMethod: result.method!,
|
||||
pathname,
|
||||
resHeaders: result.resHeaders,
|
||||
reqHeaders: result.reqHeaders,
|
||||
component: plugin.component,
|
||||
};
|
||||
|
||||
assert.strictEqual(spans.length, 2);
|
||||
assertSpan(incomingSpan, SpanKind.SERVER, validations);
|
||||
assertSpan(outgoingSpan, SpanKind.CLIENT, validations);
|
||||
});
|
||||
|
||||
it(`should not trace requests with '${OT_REQUEST_HEADER}' header`, async () => {
|
||||
const testPath = '/outgoing/do-not-trace';
|
||||
doNock(hostname, testPath, 200, 'Ok');
|
||||
|
||||
const options = {
|
||||
host: hostname,
|
||||
path: testPath,
|
||||
headers: { [OT_REQUEST_HEADER]: 1 },
|
||||
};
|
||||
|
||||
const result = await httpsRequest.get(options);
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
assert.strictEqual(result.data, 'Ok');
|
||||
assert.strictEqual(spans.length, 0);
|
||||
});
|
||||
|
||||
const httpErrorCodes = [400, 401, 403, 404, 429, 501, 503, 504, 500, 505];
|
||||
|
||||
for (let i = 0; i < httpErrorCodes.length; i++) {
|
||||
it(`should test span for GET requests with http error ${httpErrorCodes[i]}`, async () => {
|
||||
const testPath = '/outgoing/rootSpan/1';
|
||||
|
||||
doNock(
|
||||
hostname,
|
||||
testPath,
|
||||
httpErrorCodes[i],
|
||||
httpErrorCodes[i].toString()
|
||||
);
|
||||
|
||||
const isReset = memoryExporter.getFinishedSpans().length === 0;
|
||||
assert.ok(isReset);
|
||||
|
||||
const result = await httpsRequest.get(
|
||||
`${protocol}://${hostname}${testPath}`
|
||||
);
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
const reqSpan = spans[0];
|
||||
|
||||
assert.strictEqual(result.data, httpErrorCodes[i].toString());
|
||||
assert.strictEqual(spans.length, 1);
|
||||
|
||||
const validations = {
|
||||
hostname,
|
||||
httpStatusCode: result.statusCode!,
|
||||
httpMethod: 'GET',
|
||||
pathname: testPath,
|
||||
resHeaders: result.resHeaders,
|
||||
reqHeaders: result.reqHeaders,
|
||||
component: plugin.component,
|
||||
};
|
||||
|
||||
assertSpan(reqSpan, SpanKind.CLIENT, validations);
|
||||
});
|
||||
}
|
||||
|
||||
it('should create a child span for GET requests', async () => {
|
||||
const testPath = '/outgoing/rootSpan/childs/1';
|
||||
doNock(hostname, testPath, 200, 'Ok');
|
||||
const name = 'TestRootSpan';
|
||||
const span = tracer.startSpan(name);
|
||||
return tracer.withSpan(span, async () => {
|
||||
|
@ -288,194 +340,302 @@ describe('HttpsPlugin', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it('should create multiple child spans for GET requests', async () => {
|
||||
const testPath = '/outgoing/rootSpan/childs';
|
||||
const num = 5;
|
||||
doNock(hostname, testPath, 200, 'Ok', num);
|
||||
const name = 'TestRootSpan';
|
||||
const span = tracer.startSpan(name);
|
||||
await tracer.withSpan(span, async () => {
|
||||
for (let i = 0; i < num; i++) {
|
||||
await httpsRequest.get(`${protocol}://${hostname}${testPath}`);
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
assert.ok(spans[i].name.indexOf(testPath) >= 0);
|
||||
assert.strictEqual(
|
||||
span.context().traceId,
|
||||
spans[i].spanContext.traceId
|
||||
for (let i = 0; i < httpErrorCodes.length; i++) {
|
||||
it(`should test child spans for GET requests with http error ${httpErrorCodes[i]}`, async () => {
|
||||
const testPath = '/outgoing/rootSpan/childs/1';
|
||||
doNock(
|
||||
hostname,
|
||||
testPath,
|
||||
httpErrorCodes[i],
|
||||
httpErrorCodes[i].toString()
|
||||
);
|
||||
}
|
||||
span.end();
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
// 5 child spans ended + 1 span (root)
|
||||
assert.strictEqual(spans.length, 6);
|
||||
});
|
||||
});
|
||||
const name = 'TestRootSpan';
|
||||
const span = tracer.startSpan(name);
|
||||
return tracer.withSpan(span, async () => {
|
||||
const result = await httpsRequest.get(
|
||||
`${protocol}://${hostname}${testPath}`
|
||||
);
|
||||
span.end();
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
const [reqSpan, localSpan] = spans;
|
||||
const validations = {
|
||||
hostname,
|
||||
httpStatusCode: result.statusCode!,
|
||||
httpMethod: 'GET',
|
||||
pathname: testPath,
|
||||
resHeaders: result.resHeaders,
|
||||
reqHeaders: result.reqHeaders,
|
||||
component: plugin.component,
|
||||
};
|
||||
|
||||
for (const ignored of ['string', 'function', 'regexp']) {
|
||||
it(`should not trace ignored requests (client and server side) with type ${ignored}`, async () => {
|
||||
const testPath = `/ignored/${ignored}`;
|
||||
|
||||
await httpsRequest.get(
|
||||
`${protocol}://${hostname}:${serverPort}${testPath}`
|
||||
);
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
assert.strictEqual(spans.length, 0);
|
||||
});
|
||||
}
|
||||
|
||||
for (const arg of ['string', '', {}, new Date()]) {
|
||||
it(`should be tracable and not throw exception in https plugin when passing the following argument ${JSON.stringify(
|
||||
arg
|
||||
)}`, async () => {
|
||||
try {
|
||||
await httpsRequest.get(arg);
|
||||
} catch (error) {
|
||||
// https request has been made
|
||||
// nock throw
|
||||
assert.ok(error.message.startsWith('Nock: No match for request'));
|
||||
}
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
assert.strictEqual(spans.length, 1);
|
||||
});
|
||||
}
|
||||
|
||||
for (const arg of [true, 1, false, 0]) {
|
||||
it(`should not throw exception in https plugin when passing the following argument ${JSON.stringify(
|
||||
arg
|
||||
)}`, async () => {
|
||||
try {
|
||||
// @ts-ignore
|
||||
await httpsRequest.get(arg);
|
||||
} catch (error) {
|
||||
// https request has been made
|
||||
// nock throw
|
||||
assert.ok(
|
||||
error.stack.indexOf('/node_modules/nock/lib/intercept.js') > 0
|
||||
);
|
||||
}
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
// for this arg with don't provide trace. We pass arg to original method (https.get)
|
||||
assert.strictEqual(spans.length, 0);
|
||||
});
|
||||
}
|
||||
|
||||
it('should have 1 ended span when request throw on bad "options" object', () => {
|
||||
nock.cleanAll();
|
||||
nock.enableNetConnect();
|
||||
try {
|
||||
https.request({ protocol: 'telnet' });
|
||||
assert.fail();
|
||||
} catch (error) {
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
assert.strictEqual(spans.length, 1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should have 1 ended span when response.end throw an exception', async () => {
|
||||
const testPath = '/outgoing/rootSpan/childs/1';
|
||||
doNock(hostname, testPath, 400, 'Not Ok');
|
||||
|
||||
const promiseRequest = new Promise((resolve, reject) => {
|
||||
const req = https.request(
|
||||
`${protocol}://${hostname}${testPath}`,
|
||||
(resp: http.IncomingMessage) => {
|
||||
let data = '';
|
||||
resp.on('data', chunk => {
|
||||
data += chunk;
|
||||
});
|
||||
resp.on('end', () => {
|
||||
reject(new Error(data));
|
||||
});
|
||||
}
|
||||
);
|
||||
return req.end();
|
||||
});
|
||||
|
||||
try {
|
||||
await promiseRequest;
|
||||
assert.fail();
|
||||
} catch (error) {
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
assert.strictEqual(spans.length, 1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should have 1 ended span when request is aborted', async () => {
|
||||
nock(`${protocol}://my.server.com`)
|
||||
.get('/')
|
||||
.socketDelay(50)
|
||||
.reply(200, '<html></html>');
|
||||
|
||||
const promiseRequest = new Promise((resolve, reject) => {
|
||||
const req = https.request(
|
||||
`${protocol}://my.server.com`,
|
||||
(resp: http.IncomingMessage) => {
|
||||
let data = '';
|
||||
resp.on('data', chunk => {
|
||||
data += chunk;
|
||||
});
|
||||
resp.on('end', () => {
|
||||
resolve(data);
|
||||
});
|
||||
}
|
||||
);
|
||||
req.setTimeout(10, () => {
|
||||
req.abort();
|
||||
reject('timeout');
|
||||
assert.ok(localSpan.name.indexOf('TestRootSpan') >= 0);
|
||||
assert.strictEqual(spans.length, 2);
|
||||
assert.ok(reqSpan.name.indexOf(testPath) >= 0);
|
||||
assert.strictEqual(
|
||||
localSpan.spanContext.traceId,
|
||||
reqSpan.spanContext.traceId
|
||||
);
|
||||
assertSpan(reqSpan, SpanKind.CLIENT, validations);
|
||||
assert.notStrictEqual(
|
||||
localSpan.spanContext.spanId,
|
||||
reqSpan.spanContext.spanId
|
||||
);
|
||||
});
|
||||
});
|
||||
return req.end();
|
||||
});
|
||||
|
||||
try {
|
||||
await promiseRequest;
|
||||
assert.fail();
|
||||
} catch (error) {
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
const [span] = spans;
|
||||
assert.strictEqual(spans.length, 1);
|
||||
assert.strictEqual(span.status.code, CanonicalCode.ABORTED);
|
||||
assert.ok(Object.keys(span.attributes).length > 6);
|
||||
}
|
||||
});
|
||||
|
||||
it('should have 1 ended span when request is aborted after receiving response', async () => {
|
||||
nock(`${protocol}://my.server.com`)
|
||||
.get('/')
|
||||
.delay({
|
||||
body: 50,
|
||||
})
|
||||
.replyWithFile(200, `${process.cwd()}/package.json`);
|
||||
|
||||
const promiseRequest = new Promise((resolve, reject) => {
|
||||
const req = https.request(
|
||||
`${protocol}://my.server.com`,
|
||||
(resp: http.IncomingMessage) => {
|
||||
let data = '';
|
||||
resp.on('data', chunk => {
|
||||
req.abort();
|
||||
data += chunk;
|
||||
});
|
||||
resp.on('end', () => {
|
||||
resolve(data);
|
||||
});
|
||||
it('should create multiple child spans for GET requests', async () => {
|
||||
const testPath = '/outgoing/rootSpan/childs';
|
||||
const num = 5;
|
||||
doNock(hostname, testPath, 200, 'Ok', num);
|
||||
const name = 'TestRootSpan';
|
||||
const span = tracer.startSpan(name);
|
||||
await tracer.withSpan(span, async () => {
|
||||
for (let i = 0; i < num; i++) {
|
||||
await httpsRequest.get(`${protocol}://${hostname}${testPath}`);
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
assert.ok(spans[i].name.indexOf(testPath) >= 0);
|
||||
assert.strictEqual(
|
||||
span.context().traceId,
|
||||
spans[i].spanContext.traceId
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return req.end();
|
||||
span.end();
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
// 5 child spans ended + 1 span (root)
|
||||
assert.strictEqual(spans.length, 6);
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
await promiseRequest;
|
||||
assert.fail();
|
||||
} catch (error) {
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
const [span] = spans;
|
||||
assert.strictEqual(spans.length, 1);
|
||||
assert.strictEqual(span.status.code, CanonicalCode.ABORTED);
|
||||
assert.ok(Object.keys(span.attributes).length > 7);
|
||||
for (const ignored of ['string', 'function', 'regexp']) {
|
||||
it(`should not trace ignored requests (client and server side) with type ${ignored}`, async () => {
|
||||
const testPath = `/ignored/${ignored}`;
|
||||
|
||||
await httpsRequest.get(
|
||||
`${protocol}://${hostname}:${serverPort}${testPath}`
|
||||
);
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
assert.strictEqual(spans.length, 0);
|
||||
});
|
||||
}
|
||||
|
||||
for (const arg of ['string', {}, new Date()]) {
|
||||
it(`should be tracable and not throw exception in ${protocol} plugin when passing the following argument ${JSON.stringify(
|
||||
arg
|
||||
)}`, async () => {
|
||||
try {
|
||||
await httpsRequest.get(arg);
|
||||
} catch (error) {
|
||||
// request has been made
|
||||
// nock throw
|
||||
assert.ok(error.message.startsWith('Nock: No match for request'));
|
||||
}
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
assert.strictEqual(spans.length, 1);
|
||||
});
|
||||
}
|
||||
|
||||
for (const arg of [true, 1, false, 0, '']) {
|
||||
it(`should not throw exception in https plugin when passing the following argument ${JSON.stringify(
|
||||
arg
|
||||
)}`, async () => {
|
||||
try {
|
||||
// @ts-ignore
|
||||
await httpsRequest.get(arg);
|
||||
} catch (error) {
|
||||
// request has been made
|
||||
// nock throw
|
||||
assert.ok(
|
||||
error.stack.indexOf(
|
||||
path.normalize('/node_modules/nock/lib/intercept.js')
|
||||
) > 0
|
||||
);
|
||||
}
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
// for this arg with don't provide trace. We pass arg to original method (https.get)
|
||||
assert.strictEqual(spans.length, 0);
|
||||
});
|
||||
}
|
||||
|
||||
it('should have 1 ended span when request throw on bad "options" object', () => {
|
||||
try {
|
||||
https.request({ protocol: 'telnet' });
|
||||
} catch (error) {
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
assert.strictEqual(spans.length, 1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should have 1 ended span when response.end throw an exception', async () => {
|
||||
const testPath = '/outgoing/rootSpan/childs/1';
|
||||
doNock(hostname, testPath, 400, 'Not Ok');
|
||||
|
||||
const promiseRequest = new Promise((resolve, reject) => {
|
||||
const req = https.request(
|
||||
`${protocol}://${hostname}${testPath}`,
|
||||
(resp: http.IncomingMessage) => {
|
||||
let data = '';
|
||||
resp.on('data', chunk => {
|
||||
data += chunk;
|
||||
});
|
||||
resp.on('end', () => {
|
||||
reject(new Error(data));
|
||||
});
|
||||
}
|
||||
);
|
||||
return req.end();
|
||||
});
|
||||
|
||||
try {
|
||||
await promiseRequest;
|
||||
assert.fail();
|
||||
} catch (error) {
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
assert.strictEqual(spans.length, 1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should have 1 ended span when request throw on bad "options" object', () => {
|
||||
nock.cleanAll();
|
||||
nock.enableNetConnect();
|
||||
try {
|
||||
https.request({ protocol: 'telnet' });
|
||||
assert.fail();
|
||||
} catch (error) {
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
assert.strictEqual(spans.length, 1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should have 1 ended span when response.end throw an exception', async () => {
|
||||
const testPath = '/outgoing/rootSpan/childs/1';
|
||||
doNock(hostname, testPath, 400, 'Not Ok');
|
||||
|
||||
const promiseRequest = new Promise((resolve, reject) => {
|
||||
const req = https.request(
|
||||
`${protocol}://${hostname}${testPath}`,
|
||||
(resp: http.IncomingMessage) => {
|
||||
let data = '';
|
||||
resp.on('data', chunk => {
|
||||
data += chunk;
|
||||
});
|
||||
resp.on('end', () => {
|
||||
reject(new Error(data));
|
||||
});
|
||||
}
|
||||
);
|
||||
return req.end();
|
||||
});
|
||||
|
||||
try {
|
||||
await promiseRequest;
|
||||
assert.fail();
|
||||
} catch (error) {
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
assert.strictEqual(spans.length, 1);
|
||||
}
|
||||
});
|
||||
|
||||
it('should have 1 ended span when request is aborted', async () => {
|
||||
nock(`${protocol}://my.server.com`)
|
||||
.get('/')
|
||||
.socketDelay(50)
|
||||
.reply(200, '<html></html>');
|
||||
|
||||
const promiseRequest = new Promise((resolve, reject) => {
|
||||
const req = https.request(
|
||||
`${protocol}://my.server.com`,
|
||||
(resp: http.IncomingMessage) => {
|
||||
let data = '';
|
||||
resp.on('data', chunk => {
|
||||
data += chunk;
|
||||
});
|
||||
resp.on('end', () => {
|
||||
resolve(data);
|
||||
});
|
||||
}
|
||||
);
|
||||
req.setTimeout(10, () => {
|
||||
req.abort();
|
||||
reject('timeout');
|
||||
});
|
||||
return req.end();
|
||||
});
|
||||
|
||||
try {
|
||||
await promiseRequest;
|
||||
assert.fail();
|
||||
} catch (error) {
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
const [span] = spans;
|
||||
assert.strictEqual(spans.length, 1);
|
||||
assert.strictEqual(span.status.code, CanonicalCode.ABORTED);
|
||||
assert.ok(Object.keys(span.attributes).length > 6);
|
||||
}
|
||||
});
|
||||
|
||||
it('should have 1 ended span when request is aborted after receiving response', async () => {
|
||||
nock(`${protocol}://my.server.com`)
|
||||
.get('/')
|
||||
.delay({
|
||||
body: 50,
|
||||
})
|
||||
.replyWithFile(200, `${process.cwd()}/package.json`);
|
||||
|
||||
const promiseRequest = new Promise((resolve, reject) => {
|
||||
const req = https.request(
|
||||
`${protocol}://my.server.com`,
|
||||
(resp: http.IncomingMessage) => {
|
||||
let data = '';
|
||||
resp.on('data', chunk => {
|
||||
req.abort();
|
||||
data += chunk;
|
||||
});
|
||||
resp.on('end', () => {
|
||||
resolve(data);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return req.end();
|
||||
});
|
||||
|
||||
try {
|
||||
await promiseRequest;
|
||||
assert.fail();
|
||||
} catch (error) {
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
const [span] = spans;
|
||||
assert.strictEqual(spans.length, 1);
|
||||
assert.strictEqual(span.status.code, CanonicalCode.ABORTED);
|
||||
assert.ok(Object.keys(span.attributes).length > 7);
|
||||
}
|
||||
});
|
||||
|
||||
it("should have 1 ended span when response is listened by using req.on('response')", done => {
|
||||
const host = `${protocol}://${hostname}`;
|
||||
nock(host)
|
||||
.get('/')
|
||||
.reply(404);
|
||||
const req = https.request(`${host}/`);
|
||||
req.on('response', response => {
|
||||
response.on('data', () => {});
|
||||
response.on('end', () => {
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
const [span] = spans;
|
||||
assert.strictEqual(spans.length, 1);
|
||||
assert.ok(Object.keys(span.attributes).length > 6);
|
||||
assert.strictEqual(
|
||||
span.attributes[AttributeNames.HTTP_STATUS_CODE],
|
||||
404
|
||||
);
|
||||
assert.strictEqual(span.status.code, CanonicalCode.NOT_FOUND);
|
||||
done();
|
||||
});
|
||||
});
|
||||
req.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
|
||||
import { NoopLogger } from '@opentelemetry/core';
|
||||
import { SpanKind, Span } from '@opentelemetry/types';
|
||||
import { SpanKind } from '@opentelemetry/types';
|
||||
import * as assert from 'assert';
|
||||
import * as https from 'https';
|
||||
import * as http from 'http';
|
||||
|
@ -34,13 +34,12 @@ import {
|
|||
InMemorySpanExporter,
|
||||
SimpleSpanProcessor,
|
||||
} from '@opentelemetry/tracing';
|
||||
import { Http } from '@opentelemetry/plugin-http';
|
||||
|
||||
import { Http, HttpPluginConfig } from '@opentelemetry/plugin-http';
|
||||
import { customAttributeFunction } from './https-enable.test';
|
||||
|
||||
const memoryExporter = new InMemorySpanExporter();
|
||||
|
||||
export const customAttributeFunction = (span: Span): void => {
|
||||
span.setAttribute('span kind', SpanKind.CLIENT);
|
||||
};
|
||||
const protocol = 'https';
|
||||
|
||||
describe('Packages', () => {
|
||||
describe('get', () => {
|
||||
|
@ -57,7 +56,10 @@ describe('Packages', () => {
|
|||
});
|
||||
|
||||
before(() => {
|
||||
plugin.enable((https as unknown) as Http, tracer, tracer.logger);
|
||||
const config: HttpPluginConfig = {
|
||||
applyCustomAttributesOnSpan: customAttributeFunction,
|
||||
};
|
||||
plugin.enable((https as unknown) as Http, tracer, tracer.logger, config);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
|
@ -93,8 +95,8 @@ describe('Packages', () => {
|
|||
// https://github.com/nock/nock/pull/1551
|
||||
// https://github.com/sindresorhus/got/commit/bf1aa5492ae2bc78cbbec6b7d764906fb156e6c2#diff-707a4781d57c42085155dcb27edb9ccbR258
|
||||
// TODO: check if this is still the case when new version
|
||||
'https://www.google.com'
|
||||
: `https://www.google.com/search?q=axios&oq=axios&aqs=chrome.0.69i59l2j0l3j69i60.811j0j7&sourceid=chrome&ie=UTF-8`
|
||||
`${protocol}://www.google.com`
|
||||
: `${protocol}://www.google.com/search?q=axios&oq=axios&aqs=chrome.0.69i59l2j0l3j69i60.811j0j7&sourceid=chrome&ie=UTF-8`
|
||||
);
|
||||
const result = await httpPackage.get(urlparsed.href!);
|
||||
if (!resHeaders) {
|
||||
|
|
|
@ -14,24 +14,25 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
InMemorySpanExporter,
|
||||
SimpleSpanProcessor,
|
||||
} from '@opentelemetry/tracing';
|
||||
import { NoopLogger } from '@opentelemetry/core';
|
||||
import { NodeTracer } from '@opentelemetry/node';
|
||||
import { HttpPluginConfig, Http } from '@opentelemetry/plugin-http';
|
||||
import { Span, SpanKind } from '@opentelemetry/types';
|
||||
import { SpanKind, Span } from '@opentelemetry/types';
|
||||
import * as assert from 'assert';
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import * as url from 'url';
|
||||
import { plugin } from '../../src/https';
|
||||
import { assertSpan } from '../utils/assertSpan';
|
||||
import { DummyPropagation } from '../utils/DummyPropagation';
|
||||
import { httpsRequest } from '../utils/httpsRequest';
|
||||
import * as url from 'url';
|
||||
import * as utils from '../utils/utils';
|
||||
import { NodeTracer } from '@opentelemetry/node';
|
||||
import {
|
||||
InMemorySpanExporter,
|
||||
SimpleSpanProcessor,
|
||||
} from '@opentelemetry/tracing';
|
||||
|
||||
const protocol = 'https';
|
||||
const serverPort = 42345;
|
||||
const hostname = 'localhost';
|
||||
const memoryExporter = new InMemorySpanExporter();
|
||||
|
@ -71,7 +72,7 @@ describe('HttpsPlugin Integration tests', () => {
|
|||
|
||||
before(() => {
|
||||
const ignoreConfig = [
|
||||
`https://${hostname}:${serverPort}/ignored/string`,
|
||||
`${protocol}://${hostname}:${serverPort}/ignored/string`,
|
||||
/\/ignored\/regexp$/i,
|
||||
(url: string) => url.endsWith(`/ignored/function`),
|
||||
];
|
||||
|
@ -94,7 +95,9 @@ describe('HttpsPlugin Integration tests', () => {
|
|||
let spans = memoryExporter.getFinishedSpans();
|
||||
assert.strictEqual(spans.length, 0);
|
||||
|
||||
const result = await httpsRequest.get(`https://google.fr/?query=test`);
|
||||
const result = await httpsRequest.get(
|
||||
`${protocol}://google.fr/?query=test`
|
||||
);
|
||||
|
||||
spans = memoryExporter.getFinishedSpans();
|
||||
const span = spans[0];
|
||||
|
@ -114,8 +117,62 @@ describe('HttpsPlugin Integration tests', () => {
|
|||
assertSpan(span, SpanKind.CLIENT, validations);
|
||||
});
|
||||
|
||||
it('should create a rootSpan for GET requests and add propagation headers if URL is used', async () => {
|
||||
let spans = memoryExporter.getFinishedSpans();
|
||||
assert.strictEqual(spans.length, 0);
|
||||
|
||||
const result = await httpsRequest.get(
|
||||
new url.URL(`${protocol}://google.fr/?query=test`)
|
||||
);
|
||||
|
||||
spans = memoryExporter.getFinishedSpans();
|
||||
const span = spans[0];
|
||||
const validations = {
|
||||
hostname: 'google.fr',
|
||||
httpStatusCode: result.statusCode!,
|
||||
httpMethod: 'GET',
|
||||
pathname: '/',
|
||||
path: '/?query=test',
|
||||
resHeaders: result.resHeaders,
|
||||
reqHeaders: result.reqHeaders,
|
||||
component: plugin.component,
|
||||
};
|
||||
|
||||
assert.strictEqual(spans.length, 1);
|
||||
assert.ok(span.name.indexOf('GET /') >= 0);
|
||||
assertSpan(span, SpanKind.CLIENT, validations);
|
||||
});
|
||||
|
||||
it('should create a rootSpan for GET requests and add propagation headers if URL and options are used', async () => {
|
||||
let spans = memoryExporter.getFinishedSpans();
|
||||
assert.strictEqual(spans.length, 0);
|
||||
|
||||
const result = await httpsRequest.get(
|
||||
new url.URL(`${protocol}://google.fr/?query=test`),
|
||||
{ headers: { 'x-foo': 'foo' } }
|
||||
);
|
||||
|
||||
spans = memoryExporter.getFinishedSpans();
|
||||
const span = spans[0];
|
||||
const validations = {
|
||||
hostname: 'google.fr',
|
||||
httpStatusCode: result.statusCode!,
|
||||
httpMethod: 'GET',
|
||||
pathname: '/',
|
||||
path: '/?query=test',
|
||||
resHeaders: result.resHeaders,
|
||||
reqHeaders: result.reqHeaders,
|
||||
component: plugin.component,
|
||||
};
|
||||
|
||||
assert.strictEqual(spans.length, 1);
|
||||
assert.ok(span.name.indexOf('GET /') >= 0);
|
||||
assert.strictEqual(result.reqHeaders['x-foo'], 'foo');
|
||||
assertSpan(span, SpanKind.CLIENT, validations);
|
||||
});
|
||||
|
||||
it('custom attributes should show up on client spans', async () => {
|
||||
const result = await httpsRequest.get(`https://google.fr/`);
|
||||
const result = await httpsRequest.get(`${protocol}://google.fr/`);
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
const span = spans[0];
|
||||
const validations = {
|
||||
|
@ -139,7 +196,7 @@ describe('HttpsPlugin Integration tests', () => {
|
|||
assert.strictEqual(spans.length, 0);
|
||||
const options = Object.assign(
|
||||
{ headers: { Expect: '100-continue' } },
|
||||
url.parse('https://google.fr/')
|
||||
url.parse(`${protocol}://google.fr/`)
|
||||
);
|
||||
|
||||
const result = await httpsRequest.get(options);
|
||||
|
@ -170,7 +227,7 @@ describe('HttpsPlugin Integration tests', () => {
|
|||
{ Expect: '100-continue', 'user-agent': 'https-plugin-test' },
|
||||
{ 'user-agent': 'https-plugin-test' },
|
||||
]) {
|
||||
it(`should create a span for GET requests and add propagation when using the following signature: https.get(url, options, callback) and following headers: ${JSON.stringify(
|
||||
it(`should create a span for GET requests and add propagation when using the following signature: get(url, options, callback) and following headers: ${JSON.stringify(
|
||||
headers
|
||||
)}`, done => {
|
||||
let validations: {
|
||||
|
@ -186,7 +243,7 @@ describe('HttpsPlugin Integration tests', () => {
|
|||
assert.strictEqual(spans.length, 0);
|
||||
const options = { headers };
|
||||
const req = https.get(
|
||||
'https://google.fr/',
|
||||
`${protocol}://google.fr/`,
|
||||
options,
|
||||
(resp: http.IncomingMessage) => {
|
||||
const res = (resp as unknown) as http.IncomingMessage & {
|
||||
|
@ -212,6 +269,7 @@ describe('HttpsPlugin Integration tests', () => {
|
|||
});
|
||||
}
|
||||
);
|
||||
|
||||
req.on('close', () => {
|
||||
const spans = memoryExporter.getFinishedSpans();
|
||||
assert.strictEqual(spans.length, 1);
|
||||
|
|
|
@ -14,16 +14,16 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SpanKind } from '@opentelemetry/types';
|
||||
import { SpanKind, Status } from '@opentelemetry/types';
|
||||
import { hrTimeToNanoseconds } from '@opentelemetry/core';
|
||||
import * as assert from 'assert';
|
||||
import * as http from 'http';
|
||||
import { DummyPropagation } from './DummyPropagation';
|
||||
import { ReadableSpan } from '@opentelemetry/tracing';
|
||||
import {
|
||||
AttributeNames,
|
||||
parseResponseStatus,
|
||||
} from '@opentelemetry/plugin-http';
|
||||
import { DummyPropagation } from './DummyPropagation';
|
||||
import { ReadableSpan } from '@opentelemetry/tracing';
|
||||
|
||||
export const assertSpan = (
|
||||
span: ReadableSpan,
|
||||
|
@ -36,6 +36,7 @@ export const assertSpan = (
|
|||
pathname: string;
|
||||
reqHeaders?: http.OutgoingHttpHeaders;
|
||||
path?: string | null;
|
||||
forceStatus?: Status;
|
||||
component: string;
|
||||
}
|
||||
) => {
|
||||
|
@ -70,14 +71,22 @@ export const assertSpan = (
|
|||
span.attributes[AttributeNames.HTTP_STATUS_CODE],
|
||||
validations.httpStatusCode
|
||||
);
|
||||
assert.ok(span.endTime);
|
||||
|
||||
assert.strictEqual(span.links.length, 0);
|
||||
assert.strictEqual(span.events.length, 0);
|
||||
|
||||
assert.deepStrictEqual(
|
||||
span.status,
|
||||
parseResponseStatus(validations.httpStatusCode)
|
||||
validations.forceStatus || parseResponseStatus(validations.httpStatusCode)
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
(span.attributes[AttributeNames.HTTP_URL] as string).indexOf(
|
||||
span.attributes[AttributeNames.HTTP_HOSTNAME] as string
|
||||
) > -1,
|
||||
'should be consistent'
|
||||
);
|
||||
assert.ok(span.endTime, 'must be finished');
|
||||
assert.ok(hrTimeToNanoseconds(span.duration), 'must have positive duration');
|
||||
|
||||
if (validations.reqHeaders) {
|
||||
|
|
|
@ -16,59 +16,56 @@
|
|||
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import { RequestOptions } from 'https';
|
||||
import * as url from 'url';
|
||||
import { URL } from 'url';
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
|
||||
export const httpsRequest = {
|
||||
get: (
|
||||
options: string | RequestOptions
|
||||
): Promise<{
|
||||
data: string;
|
||||
statusCode: number | undefined;
|
||||
resHeaders: http.IncomingHttpHeaders;
|
||||
reqHeaders: http.OutgoingHttpHeaders;
|
||||
method: string | undefined;
|
||||
}> => {
|
||||
const _options =
|
||||
typeof options === 'string'
|
||||
? Object.assign(url.parse(options), {
|
||||
headers: {
|
||||
'user-agent': 'https-plugin-test',
|
||||
},
|
||||
})
|
||||
: options;
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = https.get(_options, (resp: http.IncomingMessage) => {
|
||||
const res = (resp as unknown) as http.IncomingMessage & {
|
||||
req: http.IncomingMessage;
|
||||
};
|
||||
let data = '';
|
||||
resp.on('data', chunk => {
|
||||
data += chunk;
|
||||
});
|
||||
resp.on('end', () => {
|
||||
resolve({
|
||||
data,
|
||||
statusCode: res.statusCode,
|
||||
/* tslint:disable:no-any */
|
||||
reqHeaders: (res.req as any).getHeaders
|
||||
? (res.req as any).getHeaders()
|
||||
: (res.req as any)._headers,
|
||||
/* tslint:enable:no-any */
|
||||
resHeaders: res.headers,
|
||||
method: res.req.method,
|
||||
});
|
||||
});
|
||||
resp.on('error', err => {
|
||||
reject(err);
|
||||
type GetResult = Promise<{
|
||||
data: string;
|
||||
statusCode: number | undefined;
|
||||
resHeaders: http.IncomingHttpHeaders;
|
||||
reqHeaders: http.OutgoingHttpHeaders;
|
||||
method: string | undefined;
|
||||
}>;
|
||||
|
||||
function get(input: string | URL, options?: https.RequestOptions): GetResult;
|
||||
function get(input: https.RequestOptions): GetResult;
|
||||
function get(input: any, options?: any): GetResult {
|
||||
return new Promise((resolve, reject) => {
|
||||
let req: http.ClientRequest;
|
||||
|
||||
function onGetResponseCb(resp: http.IncomingMessage): void {
|
||||
const res = (resp as unknown) as http.IncomingMessage & {
|
||||
req: http.IncomingMessage;
|
||||
};
|
||||
let data = '';
|
||||
resp.on('data', chunk => {
|
||||
data += chunk;
|
||||
});
|
||||
resp.on('end', () => {
|
||||
resolve({
|
||||
data,
|
||||
statusCode: res.statusCode,
|
||||
reqHeaders: req.getHeaders ? req.getHeaders() : (req as any)._headers,
|
||||
resHeaders: res.headers,
|
||||
method: res.req.method,
|
||||
});
|
||||
});
|
||||
req.on('error', err => {
|
||||
resp.on('error', err => {
|
||||
reject(err);
|
||||
});
|
||||
return req;
|
||||
}
|
||||
req =
|
||||
options != null
|
||||
? https.get(input, options, onGetResponseCb)
|
||||
: https.get(input, onGetResponseCb);
|
||||
req.on('error', err => {
|
||||
reject(err);
|
||||
});
|
||||
},
|
||||
return req;
|
||||
});
|
||||
}
|
||||
|
||||
export const httpsRequest = {
|
||||
get,
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue