Hey,
It's a bit difficult to give code as it's part of a relatively complex app, but I can break down what's happening and hopefully that helps, as well as trying to include the applicable snippets..
Upon starting the app, the service management code forks off a new instance, the 'clusterMaster' and detaches the console. Using the cluster module, clusterMaster launches web worker threads. These threads create the https server and listen for requests (cluster module handling distribution/load balancing of requests to each worker thread, which includes websocket connections of course as they start as https). Regular requests get routed through express, which is showing up fine. WebSocket requests connect to the same https server and are authenticated on upgrade and then the ws connection stays open for the entire remainder of the client's (browser) session. Messages are passed back and forth, mostly using a subscription model but ultimately sent simply with ws.send() of course.
Not using any other common frameworks or anything; node, express, ws. Nothing like react/angular or transpilation or webpack () crap. Not using any other libs listed anywhere on the compatibility page.
My initial instinct was maybe my usage of the cluster module is perhaps interfering.
Some applicable snippets:
clusterMaster.js
launchWorkers() {
return new Promise((resolve,reject) => {
for (let i=0;i<$configuration.service.threads;i++) {
let worker = cluster.fork({ 'SERVICE_INSTANCE_UUID':this.instanceId });
// ... //
}
// ... //
});
}
clusterWorker.js
async start() {
// ... //
this.webHandler.startListening();
}
webHandler.js:
webHandler.startListening() {
return new Promise(async(resolve,reject) => {
// ... //
this.wsServer = new ws.Server({
noServer:true, // we don't pass the http server here to allow async handleUpgrade!
clientTracking: true,
maxPayload: $configuration.service.wsMaxPayload
});
// ... //
this.server = this.httpServer.createServer({ key, cert, ca });
this.wsHandler = new wsHandler(this.wsServer);
this.server.on('upgrade', this.handleUpgrade.bind(this));
// ... //
this.wsServer.on('connection', this.wsHandler.handleConnection.bind(this.wsHandler));
this.server.on('request', routeHandler.app); // routeHandler.app is an express 'app'
this.server.listen({
host: $configuration.service.host,
port: $configuration.service.port,
signal: this.controller.signal
}, () => {
// ... //
resolve();
});
});
}
async handleUpgrade(req, socket, head) {
try {
// websocket Authentication goes here; throw if failed or do below if success:
this.wsServer.handleUpgrade(req, socket, head, (ws) => {
this.wsServer.emit('connection', ws, req);
});
} catch(e) {
socket.write('HTTP/1.1 401 WebSocket Protocol Handshake Failure\r\n\r\n');
socket.destroy();
}
}
webSocketHandler.js
handleConnection(ws, req) {
// ... //
ws.on('message', this.handleMessage.bind(this, ws));
// ... //
}
sendMessage(message, ws) {
// ... //
ws.send(JSON.stringify(message));
// ... //
}
Hopefully this helps clear it up.
As for instrumenting other libs/etc, looking at the API doc, it looks like apm.addPatch etc could be used to sort of make modules for other libs..? obviously this would be more at the application level rather than included as 'plugins' at the library level; I suppose either direction could be useful depending on the need, but I'll have to dig deeper I guess..? Perhaps using a Proxy on some classes with code to create the transpans would do, at least for some things especially, it sounds not much different from what you explained, curious if you already use Proxy or something else..
apm.registerMetric looks potentially useful, although I wish there was a bit more information about it, as in how it gets logged/saves data compared to transpans..
Also; I noticed that the APM Server config seemed to be forcing the default config to be http? caught me off guard for a second during setup that it was still set to http even after stuffing in a cert.. (Edit APM Integration->General->Server Configuration->URL)