-
Notifications
You must be signed in to change notification settings - Fork 810
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
fix(instrumentation-http): Ensure instrumentation of http.get
and https.get
work when used in ESM code
#4866
Changes from 3 commits
1563f93
4eb8607
94933d6
942aa19
28a81e7
ed68ff7
5a40e94
4ff3771
71811c4
8eb6c7d
011af25
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
// Test that instrumentation-http works when used from ESM code. | ||
|
||
import * as assert from 'assert'; | ||
import * as fs from 'fs'; | ||
import * as http from 'http'; | ||
import * as https from 'https'; | ||
|
||
import { SpanKind } from '@opentelemetry/api'; | ||
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; | ||
import { | ||
InMemorySpanExporter, | ||
SimpleSpanProcessor, | ||
} from '@opentelemetry/sdk-trace-base'; | ||
|
||
import { assertSpan } from '../../build/test/utils/assertSpan.js'; | ||
import { HttpInstrumentation } from '../../build/src/index.js'; | ||
|
||
const provider = new NodeTracerProvider(); | ||
const memoryExporter = new InMemorySpanExporter(); | ||
provider.addSpanProcessor(new SimpleSpanProcessor(memoryExporter)); | ||
const instrumentation = new HttpInstrumentation(); | ||
instrumentation.setTracerProvider(provider); | ||
|
||
describe('HttpInstrumentation ESM Integration tests', () => { | ||
let port; | ||
let server; | ||
|
||
before(done => { | ||
server = http.createServer((req, res) => { | ||
req.resume(); | ||
req.on('end', () => { | ||
res.writeHead(200); | ||
res.end('pong'); | ||
}); | ||
}); | ||
|
||
server.listen(0, '127.0.0.1', () => { | ||
port = server.address().port; | ||
assert.ok(Number.isInteger(port)); | ||
done(); | ||
}); | ||
}); | ||
|
||
after(done => { | ||
server.close(done); | ||
}); | ||
|
||
beforeEach(() => { | ||
memoryExporter.reset(); | ||
}); | ||
|
||
it('http.request', async () => { | ||
trentm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const spanValidations = { | ||
httpStatusCode: 200, | ||
httpMethod: 'GET', | ||
hostname: '127.0.0.1', | ||
pathname: '/http.request', | ||
component: 'http', | ||
}; | ||
|
||
await new Promise(resolve => { | ||
const creq = http.request( | ||
`http://127.0.0.1:${port}/http.request`, | ||
cres => { | ||
trentm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
spanValidations.resHeaders = cres.headers; | ||
cres.resume(); | ||
cres.on('end', resolve); | ||
} | ||
); | ||
creq.end(); | ||
}); | ||
|
||
let spans = memoryExporter.getFinishedSpans(); | ||
assert.strictEqual(spans.length, 2); | ||
const span = spans.find(s => s.kind === SpanKind.CLIENT); | ||
assert.strictEqual(span.name, 'GET'); | ||
assertSpan(span, SpanKind.CLIENT, spanValidations); | ||
}); | ||
|
||
it('http.get', async () => { | ||
trentm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const spanValidations = { | ||
httpStatusCode: 200, | ||
httpMethod: 'GET', | ||
hostname: '127.0.0.1', | ||
pathname: '/http.get', | ||
component: 'http', | ||
}; | ||
|
||
await new Promise(resolve => { | ||
http.get(`http://127.0.0.1:${port}/http.get`, cres => { | ||
spanValidations.resHeaders = cres.headers; | ||
cres.resume(); | ||
cres.on('end', resolve); | ||
}); | ||
}); | ||
|
||
let spans = memoryExporter.getFinishedSpans(); | ||
assert.strictEqual(spans.length, 2); | ||
const span = spans.find(s => s.kind === SpanKind.CLIENT); | ||
assert.strictEqual(span.name, 'GET'); | ||
assertSpan(span, SpanKind.CLIENT, spanValidations); | ||
}); | ||
}); | ||
|
||
describe('HttpsInstrumentation ESM Integration tests', () => { | ||
let port; | ||
let server; | ||
|
||
before(done => { | ||
server = https.createServer( | ||
{ | ||
key: fs.readFileSync( | ||
new URL('../fixtures/server-key.pem', import.meta.url) | ||
), | ||
cert: fs.readFileSync( | ||
new URL('../fixtures/server-cert.pem', import.meta.url) | ||
), | ||
}, | ||
(req, res) => { | ||
req.resume(); | ||
req.on('end', () => { | ||
res.writeHead(200); | ||
res.end('pong'); | ||
}); | ||
} | ||
); | ||
|
||
server.listen(0, '127.0.0.1', () => { | ||
port = server.address().port; | ||
assert.ok(Number.isInteger(port)); | ||
done(); | ||
}); | ||
}); | ||
|
||
after(done => { | ||
server.close(done); | ||
}); | ||
|
||
beforeEach(() => { | ||
memoryExporter.reset(); | ||
}); | ||
|
||
it('https.request', async () => { | ||
trentm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const spanValidations = { | ||
httpStatusCode: 200, | ||
httpMethod: 'GET', | ||
hostname: '127.0.0.1', | ||
pathname: '/https.request', | ||
component: 'https', | ||
}; | ||
|
||
await new Promise(resolve => { | ||
const creq = https.request( | ||
`https://127.0.0.1:${port}/https.request`, | ||
{ | ||
rejectUnauthorized: false, | ||
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Show resolved
Hide resolved
|
||
}, | ||
cres => { | ||
spanValidations.resHeaders = cres.headers; | ||
cres.resume(); | ||
cres.on('end', resolve); | ||
} | ||
); | ||
creq.end(); | ||
}); | ||
|
||
let spans = memoryExporter.getFinishedSpans(); | ||
assert.strictEqual(spans.length, 2); | ||
const span = spans.find(s => s.kind === SpanKind.CLIENT); | ||
assert.strictEqual(span.name, 'GET'); | ||
assertSpan(span, SpanKind.CLIENT, spanValidations); | ||
}); | ||
|
||
it('https.get', async () => { | ||
trentm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const spanValidations = { | ||
httpStatusCode: 200, | ||
httpMethod: 'GET', | ||
hostname: '127.0.0.1', | ||
pathname: '/https.get', | ||
component: 'https', | ||
}; | ||
|
||
await new Promise(resolve => { | ||
https.get( | ||
`https://127.0.0.1:${port}/https.get`, | ||
{ | ||
rejectUnauthorized: false, | ||
|
||
}, | ||
cres => { | ||
spanValidations.resHeaders = cres.headers; | ||
cres.resume(); | ||
cres.on('end', resolve); | ||
} | ||
); | ||
}); | ||
|
||
let spans = memoryExporter.getFinishedSpans(); | ||
assert.strictEqual(spans.length, 2); | ||
const span = spans.find(s => s.kind === SpanKind.CLIENT); | ||
assert.strictEqual(span.name, 'GET'); | ||
assertSpan(span, SpanKind.CLIENT, spanValidations); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,7 +49,7 @@ | |
"tdd:node": "npm run test -- --watch-extensions ts --watch", | ||
"tdd:browser": "karma start", | ||
"test:cjs": "nyc mocha 'test/**/*.test.ts' --exclude 'test/browser/**/*.ts'", | ||
"test:esm": "nyc node --experimental-loader=./hook.mjs ../../../node_modules/mocha/bin/mocha 'test/node/*.test.mjs' test/node/*.test.mjs", | ||
"test:esm": "nyc node --experimental-loader=./hook.mjs ../../../node_modules/mocha/bin/mocha 'test/node/*.test.mjs'", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reviewer note: I think this was a typo from when this was originally added. The additional |
||
"test": "npm run test:cjs && npm run test:esm", | ||
"test:browser": "karma start --single-run", | ||
"version": "node ../../../scripts/version-update.js", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -87,10 +87,10 @@ export abstract class InstrumentationBase< | |
return wrap(moduleExports, name, wrapper); | ||
} else { | ||
const wrapped = wrap(Object.assign({}, moduleExports), name, wrapper); | ||
|
||
return Object.defineProperty(moduleExports, name, { | ||
Object.defineProperty(moduleExports, name, { | ||
value: wrapped, | ||
}); | ||
return wrapped; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reviewer note: From a quick check, I didn't see any core or contrib-repo instrumentations using the return value of |
||
} | ||
}; | ||
|
||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -26,14 +26,21 @@ class TestInstrumentationWrapFn extends InstrumentationBase { | |||||
super('test-esm-instrumentation', '0.0.1', config); | ||||||
} | ||||||
init() { | ||||||
console.log('test-esm-instrumentation initialized!'); | ||||||
JamieDanielson marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
return new InstrumentationNodeModuleDefinition( | ||||||
'test-esm-module', | ||||||
['*'], | ||||||
moduleExports => { | ||||||
this._wrap(moduleExports, 'testFunction', () => { | ||||||
return () => 'patched'; | ||||||
const wrapRetval = this._wrap(moduleExports, 'testFunction', () => { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
This is a total nit, but I wonder if we could use a full name for this - primarily because this tends to be a confusing functionality and I'm a sucker for detailed variable names. Maybe something like |
||||||
return function wrappedTestFunction() { | ||||||
return 'patched'; | ||||||
}; | ||||||
}); | ||||||
assert.strictEqual(typeof wrapRetval, 'function'); | ||||||
assert.strictEqual( | ||||||
wrapRetval.name, | ||||||
'wrappedTestFunction', | ||||||
'_wrap(..., "testFunction", ...) return value is the wrapped function' | ||||||
); | ||||||
return moduleExports; | ||||||
}, | ||||||
moduleExports => { | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewer note: This
test:esm
script follows the same pattern used for ESM tests in opentelemetry-instrumentation.