Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implement diagnostics channel (#1000)
- Loading branch information
Showing
8 changed files
with
480 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
# Diagnostics Channel Support | ||
|
||
Stability: Experimental. | ||
|
||
Undici support the [`diagnostics_channel`](https://nodejs.org/api/diagnostics_channel.html) (currently available only on Node.js v16+). | ||
It is the preferred way to instrument Undici and retrieve internal informations. | ||
|
||
The channels available are the following. | ||
|
||
## `undici:request:create` | ||
|
||
This message is published when a new outgoing request is created. | ||
|
||
```js | ||
import diagnosticsChannel from 'diagnostics_channel' | ||
|
||
diagnosticsChannel.channel('undici:request:create').subscribe(({ request }) => { | ||
console.log('completed', request.completed) | ||
console.log('method', request.method) | ||
console.log('path', request.path) | ||
console.log('headers') // raw text, e.g: 'bar: bar\r\n' | ||
request.addHeader('hello', 'world') | ||
console.log('headers', request.headers) // e.g. 'bar: bar\r\nhello: world\r\n' | ||
}) | ||
``` | ||
|
||
Note: a request is only loosely completed to a given socket. | ||
|
||
|
||
## `undici:request:bodySent` | ||
|
||
```js | ||
import diagnosticsChannel from 'diagnostics_channel' | ||
|
||
diagnosticsChannel.channel('undici:request:bodySent').subscribe(({ request }) => { | ||
// request is the same object undici:request:create | ||
}) | ||
``` | ||
|
||
## `undici:request:headers` | ||
|
||
This message is published after the response headers have been received, i.e. the reponse has been completed. | ||
|
||
```js | ||
import diagnosticsChannel from 'diagnostics_channel' | ||
|
||
diagnosticsChannel.channel('undici:request:headers').subscribe(({ request, response }) => { | ||
// request is the same object undici:request:create | ||
console.log('statusCode', response.statusCode) | ||
console.log(response.statusText) | ||
// response.headers are buffers. | ||
console.log(response.headers.map((x) => x.toString())) | ||
}) | ||
``` | ||
|
||
## `undici:request:trailers` | ||
|
||
This message is published after the response body and trailers have been received, i.e. the reponse has been completed. | ||
|
||
```js | ||
import diagnosticsChannel from 'diagnostics_channel' | ||
|
||
diagnosticsChannel.channel('undici:request:trailers').subscribe(({ request, trailers }) => { | ||
// request is the same object undici:request:create | ||
console.log('completed', request.completed) | ||
// trailers are buffers. | ||
console.log(trailers.map((x) => x.toString())) | ||
}) | ||
``` | ||
|
||
## `undici:request:error` | ||
|
||
This message is published if the request is going to error, but it has not errored yet. | ||
|
||
```js | ||
import diagnosticsChannel from 'diagnostics_channel' | ||
|
||
diagnosticsChannel.channel('undici:request:error').subscribe(({ request, error }) => { | ||
// request is the same object undici:request:create | ||
}) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
'use strict' | ||
|
||
const t = require('tap') | ||
|
||
let diagnosticsChannel | ||
|
||
try { | ||
diagnosticsChannel = require('diagnostics_channel') | ||
} catch { | ||
t.skip('missing diagnostics_channel') | ||
process.exit(0) | ||
} | ||
|
||
const { Client } = require('../..') | ||
const { createServer, request } = require('http') | ||
|
||
t.plan(3) | ||
|
||
const server = createServer((req, res) => { | ||
res.destroy() | ||
}) | ||
t.teardown(server.close.bind(server)) | ||
|
||
const reqHeaders = { | ||
foo: undefined, | ||
bar: 'bar' | ||
} | ||
|
||
let _req | ||
diagnosticsChannel.channel('undici:request:create').subscribe(({ request }) => { | ||
_req = request | ||
}) | ||
|
||
diagnosticsChannel.channel('undici:request:error').subscribe(({ request, error }) => { | ||
t.equal(_req, request) | ||
t.equal(error.code, 'UND_ERR_SOCKET') | ||
}) | ||
|
||
server.listen(0, () => { | ||
const client = new Client(`http://localhost:${server.address().port}`, { | ||
keepAliveTimeout: 300e3 | ||
}) | ||
t.teardown(client.close.bind(client)) | ||
|
||
client.request({ | ||
path: '/', | ||
method: 'GET', | ||
headers: reqHeaders | ||
}, (err, data) => { | ||
t.equal(err.code, 'UND_ERR_SOCKET') | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
'use strict' | ||
|
||
const t = require('tap') | ||
|
||
let diagnosticsChannel | ||
|
||
try { | ||
diagnosticsChannel = require('diagnostics_channel') | ||
} catch { | ||
t.skip('missing diagnostics_channel') | ||
process.exit(0) | ||
} | ||
|
||
const { Client } = require('../..') | ||
const { createServer, request } = require('http') | ||
|
||
t.plan(14) | ||
|
||
const server = createServer((req, res) => { | ||
res.setHeader('Content-Type', 'text/plain') | ||
res.setHeader('trailer', 'foo') | ||
res.write('hello') | ||
res.addTrailers({ | ||
foo: 'oof' | ||
}) | ||
res.end() | ||
}) | ||
t.teardown(server.close.bind(server)) | ||
|
||
const reqHeaders = { | ||
foo: undefined, | ||
bar: 'bar' | ||
} | ||
|
||
let _req | ||
diagnosticsChannel.channel('undici:request:create').subscribe(({ request }) => { | ||
_req = request | ||
t.equal(request.completed, false) | ||
t.equal(request.method, 'GET') | ||
t.equal(request.path, '/') | ||
t.equal(request.headers, 'bar: bar\r\n') | ||
request.addHeader('hello', 'world') | ||
t.equal(request.headers, 'bar: bar\r\nhello: world\r\n') | ||
}) | ||
|
||
diagnosticsChannel.channel('undici:request:headers').subscribe(({ request, response }) => { | ||
t.equal(_req, request) | ||
t.equal(response.statusCode, 200) | ||
const expectedHeaders = [ | ||
Buffer.from('Content-Type'), | ||
Buffer.from('text/plain'), | ||
Buffer.from('trailer'), | ||
Buffer.from('foo'), | ||
Buffer.from('Date'), | ||
response.headers[5], // This is a date | ||
Buffer.from('Connection'), | ||
Buffer.from('keep-alive'), | ||
Buffer.from('Keep-Alive'), | ||
Buffer.from('timeout=5'), | ||
Buffer.from('Transfer-Encoding'), | ||
Buffer.from('chunked') | ||
] | ||
t.same(response.headers, expectedHeaders) | ||
t.equal(response.statusText, 'OK') | ||
}) | ||
|
||
let endEmitted = false | ||
diagnosticsChannel.channel('undici:request:trailers').subscribe(({ request, trailers }) => { | ||
t.equal(request.completed, true) | ||
t.equal(_req, request) | ||
// This event is emitted after the last chunk has been added to the body stream, | ||
// not when it was consumed by the application | ||
t.equal(endEmitted, false) | ||
t.same(trailers, [Buffer.from('foo'), Buffer.from('oof')]) | ||
}) | ||
|
||
server.listen(0, () => { | ||
const client = new Client(`http://localhost:${server.address().port}`, { | ||
keepAliveTimeout: 300e3 | ||
}) | ||
t.teardown(client.close.bind(client)) | ||
|
||
client.request({ | ||
path: '/', | ||
method: 'GET', | ||
headers: reqHeaders | ||
}, (err, data) => { | ||
t.error(err) | ||
data.body.on('end', function () { | ||
endEmitted = true | ||
}) | ||
}) | ||
}) |
Oops, something went wrong.