@opentelemetry/instrumentation-socket.io
Version:
OpenTelemetry instrumentation for `socket.io` messaging server implementation for Socket.IO communication
308 lines • 14.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SocketIoInstrumentation = void 0;
/*
* 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.
*/
const api_1 = require("@opentelemetry/api");
const instrumentation_1 = require("@opentelemetry/instrumentation");
const semconv_1 = require("./semconv");
const semconv_obsolete_1 = require("./semconv-obsolete");
const AttributeNames_1 = require("./AttributeNames");
/** @knipignore */
const version_1 = require("./version");
const utils_1 = require("./utils");
const reservedEvents = [
'connect',
'connect_error',
'disconnect',
'disconnecting',
'newListener',
'removeListener',
];
class SocketIoInstrumentation extends instrumentation_1.InstrumentationBase {
constructor(config = {}) {
super(version_1.PACKAGE_NAME, version_1.PACKAGE_VERSION, (0, utils_1.normalizeConfig)(config));
}
init() {
const socketInstrumentation = new instrumentation_1.InstrumentationNodeModuleFile('socket.io/dist/socket.js', ['>=3 <5'], (moduleExports, moduleVersion) => {
if (moduleExports === undefined || moduleExports === null) {
return moduleExports;
}
if (moduleVersion === undefined) {
return moduleExports;
}
if ((0, instrumentation_1.isWrapped)(moduleExports?.Socket?.prototype?.on)) {
this._unwrap(moduleExports.Socket.prototype, 'on');
}
this._wrap(moduleExports.Socket.prototype, 'on', this._patchOn(moduleVersion));
if ((0, instrumentation_1.isWrapped)(moduleExports?.Socket?.prototype?.emit)) {
this._unwrap(moduleExports.Socket.prototype, 'emit');
}
this._wrap(moduleExports.Socket.prototype, 'emit', this._patchEmit(moduleVersion));
return moduleExports;
}, moduleExports => {
if ((0, instrumentation_1.isWrapped)(moduleExports?.Socket?.prototype?.on)) {
this._unwrap(moduleExports.Socket.prototype, 'on');
}
if ((0, instrumentation_1.isWrapped)(moduleExports?.Socket?.prototype?.emit)) {
this._unwrap(moduleExports.Socket.prototype, 'emit');
}
return moduleExports;
});
const broadcastOperatorInstrumentation = new instrumentation_1.InstrumentationNodeModuleFile('socket.io/dist/broadcast-operator.js', ['>=4 <5'], (moduleExports, moduleVersion) => {
if (moduleExports === undefined || moduleExports === null) {
return moduleExports;
}
if (moduleVersion === undefined) {
return moduleExports;
}
if ((0, instrumentation_1.isWrapped)(moduleExports?.BroadcastOperator?.prototype?.emit)) {
this._unwrap(moduleExports.BroadcastOperator.prototype, 'emit');
}
this._wrap(moduleExports.BroadcastOperator.prototype, 'emit', this._patchEmit(moduleVersion));
return moduleExports;
}, moduleExports => {
if ((0, instrumentation_1.isWrapped)(moduleExports?.BroadcastOperator?.prototype?.emit)) {
this._unwrap(moduleExports.BroadcastOperator.prototype, 'emit');
}
return moduleExports;
});
const namespaceInstrumentation = new instrumentation_1.InstrumentationNodeModuleFile('socket.io/dist/namespace.js', ['<4'], (moduleExports, moduleVersion) => {
if (moduleExports === undefined || moduleExports === null) {
return moduleExports;
}
if (moduleVersion === undefined) {
return moduleExports;
}
if ((0, instrumentation_1.isWrapped)(moduleExports?.Namespace?.prototype?.emit)) {
this._unwrap(moduleExports.Namespace.prototype, 'emit');
}
this._wrap(moduleExports.Namespace.prototype, 'emit', this._patchEmit(moduleVersion));
return moduleExports;
}, moduleExports => {
if ((0, instrumentation_1.isWrapped)(moduleExports?.Namespace?.prototype?.emit)) {
this._unwrap(moduleExports.Namespace.prototype, 'emit');
}
});
const socketInstrumentationLegacy = new instrumentation_1.InstrumentationNodeModuleFile('socket.io/lib/socket.js', ['2'], (moduleExports, moduleVersion) => {
if (moduleExports === undefined || moduleExports === null) {
return moduleExports;
}
if (moduleVersion === undefined) {
return moduleExports;
}
if ((0, instrumentation_1.isWrapped)(moduleExports.prototype?.on)) {
this._unwrap(moduleExports.prototype, 'on');
}
this._wrap(moduleExports.prototype, 'on', this._patchOn(moduleVersion));
if ((0, instrumentation_1.isWrapped)(moduleExports.prototype?.emit)) {
this._unwrap(moduleExports.prototype, 'emit');
}
this._wrap(moduleExports.prototype, 'emit', this._patchEmit(moduleVersion));
return moduleExports;
}, moduleExports => {
if ((0, instrumentation_1.isWrapped)(moduleExports.prototype?.on)) {
this._unwrap(moduleExports.prototype, 'on');
}
if ((0, instrumentation_1.isWrapped)(moduleExports.prototype?.emit)) {
this._unwrap(moduleExports.prototype, 'emit');
}
return moduleExports;
});
const namespaceInstrumentationLegacy = new instrumentation_1.InstrumentationNodeModuleFile('socket.io/lib/namespace.js', ['2'], (moduleExports, moduleVersion) => {
if (moduleExports === undefined || moduleExports === null) {
return moduleExports;
}
if (moduleVersion === undefined) {
return moduleExports;
}
if ((0, instrumentation_1.isWrapped)(moduleExports?.prototype?.emit)) {
this._unwrap(moduleExports.prototype, 'emit');
}
this._wrap(moduleExports.prototype, 'emit', this._patchEmit(moduleVersion));
return moduleExports;
}, moduleExports => {
if ((0, instrumentation_1.isWrapped)(moduleExports?.prototype?.emit)) {
this._unwrap(moduleExports.prototype, 'emit');
}
});
return [
new instrumentation_1.InstrumentationNodeModuleDefinition('socket.io', ['>=3 <5'], (moduleExports, moduleVersion) => {
if (moduleExports === undefined || moduleExports === null) {
return moduleExports;
}
if (moduleVersion === undefined) {
return moduleExports;
}
if ((0, instrumentation_1.isWrapped)(moduleExports?.Server?.prototype?.on)) {
this._unwrap(moduleExports.Server.prototype, 'on');
}
this._wrap(moduleExports.Server.prototype, 'on', this._patchOn(moduleVersion));
return moduleExports;
}, moduleExports => {
if ((0, instrumentation_1.isWrapped)(moduleExports?.Server?.prototype?.on)) {
this._unwrap(moduleExports.Server.prototype, 'on');
}
return moduleExports;
}, [
broadcastOperatorInstrumentation,
namespaceInstrumentation,
socketInstrumentation,
]),
new instrumentation_1.InstrumentationNodeModuleDefinition('socket.io', ['2'], (moduleExports, moduleVersion) => {
if (moduleExports === undefined || moduleExports === null) {
return moduleExports;
}
if (moduleVersion === undefined) {
return moduleExports;
}
if ((0, instrumentation_1.isWrapped)(moduleExports?.prototype?.on)) {
this._unwrap(moduleExports.prototype, 'on');
}
this._wrap(moduleExports.prototype, 'on', this._patchOn(moduleVersion));
return moduleExports;
}, (moduleExports, moduleVersion) => {
if ((0, instrumentation_1.isWrapped)(moduleExports?.prototype?.on)) {
this._unwrap(moduleExports.prototype, 'on');
}
return moduleExports;
}, [namespaceInstrumentationLegacy, socketInstrumentationLegacy]),
];
}
setConfig(config = {}) {
return super.setConfig((0, utils_1.normalizeConfig)(config));
}
_patchOn(moduleVersion) {
const self = this;
return (original) => {
return function (ev, originalListener) {
if (!self.getConfig().traceReserved && reservedEvents.includes(ev)) {
return original.apply(this, arguments);
}
if (self.getConfig().onIgnoreEventList?.includes(ev)) {
return original.apply(this, arguments);
}
const wrappedListener = function (...args) {
const eventName = ev;
const namespace = this.name || this.adapter?.nsp?.name;
const span = self.tracer.startSpan(`${semconv_obsolete_1.MESSAGING_OPERATION_VALUE_RECEIVE} ${namespace}`, {
kind: api_1.SpanKind.CONSUMER,
attributes: {
[semconv_1.ATTR_MESSAGING_SYSTEM]: 'socket.io',
[semconv_obsolete_1.ATTR_MESSAGING_DESTINATION]: namespace,
[semconv_1.ATTR_MESSAGING_OPERATION]: semconv_obsolete_1.MESSAGING_OPERATION_VALUE_RECEIVE,
[AttributeNames_1.SocketIoInstrumentationAttributes.SOCKET_IO_EVENT_NAME]: eventName,
},
});
const { onHook } = self.getConfig();
if (onHook) {
(0, instrumentation_1.safeExecuteInTheMiddle)(() => onHook(span, { moduleVersion, payload: args }), e => {
if (e)
self._diag.error('onHook error', e);
}, true);
}
return api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), () => self.endSpan(() => originalListener.apply(this, arguments), span));
};
return original.apply(this, [ev, wrappedListener]);
};
};
}
endSpan(traced, span) {
try {
const result = traced();
if ((0, utils_1.isPromise)(result)) {
return result.then(value => {
span.end();
return value;
}, err => {
span.recordException(err);
span.setStatus({
code: api_1.SpanStatusCode.ERROR,
message: err?.message,
});
span.end();
throw err;
});
}
else {
span.end();
return result;
}
}
catch (error) {
span.recordException(error);
span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: error?.message });
span.end();
throw error;
}
}
_patchEmit(moduleVersion) {
const self = this;
return (original) => {
return function (ev, ...args) {
if (!self.getConfig().traceReserved && reservedEvents.includes(ev)) {
return original.apply(this, arguments);
}
if (self.getConfig().emitIgnoreEventList?.includes(ev)) {
return original.apply(this, arguments);
}
const messagingSystem = 'socket.io';
const eventName = ev;
const attributes = {
[semconv_1.ATTR_MESSAGING_SYSTEM]: messagingSystem,
[semconv_obsolete_1.ATTR_MESSAGING_DESTINATION_KIND]: semconv_obsolete_1.MESSAGING_DESTINATION_KIND_VALUE_TOPIC,
[AttributeNames_1.SocketIoInstrumentationAttributes.SOCKET_IO_EVENT_NAME]: eventName,
};
const rooms = (0, utils_1.extractRoomsAttributeValue)(this);
if (rooms.length) {
attributes[AttributeNames_1.SocketIoInstrumentationAttributes.SOCKET_IO_ROOMS] = rooms;
}
const namespace = this.name || this.adapter?.nsp?.name || this.sockets?.name;
if (namespace) {
attributes[AttributeNames_1.SocketIoInstrumentationAttributes.SOCKET_IO_NAMESPACE] =
namespace;
attributes[semconv_obsolete_1.ATTR_MESSAGING_DESTINATION] = namespace;
}
const span = self.tracer.startSpan(`send ${namespace}`, {
kind: api_1.SpanKind.PRODUCER,
attributes,
});
const { emitHook } = self.getConfig();
if (emitHook) {
(0, instrumentation_1.safeExecuteInTheMiddle)(() => emitHook(span, { moduleVersion, payload: args }), e => {
if (e)
self._diag.error('emitHook error', e);
}, true);
}
try {
return api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), () => original.apply(this, arguments));
}
catch (error) {
span.setStatus({
code: api_1.SpanStatusCode.ERROR,
message: error.message,
});
throw error;
}
finally {
span.end();
}
};
};
}
}
exports.SocketIoInstrumentation = SocketIoInstrumentation;
//# sourceMappingURL=socket.io.js.map