@superblocksteam/shared 0.9130.0 → 0.9132.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/socket/tracedSocket.d.ts +43 -0
- package/dist/socket/tracedSocket.d.ts.map +1 -0
- package/dist/socket/tracedSocket.js +163 -0
- package/dist/socket/tracedSocket.js.map +1 -0
- package/dist-esm/socket/tracedSocket.d.ts +43 -0
- package/dist-esm/socket/tracedSocket.d.ts.map +1 -0
- package/dist-esm/socket/tracedSocket.js +159 -0
- package/dist-esm/socket/tracedSocket.js.map +1 -0
- package/package.json +1 -1
- package/src/socket/tracedSocket.ts +220 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Tracer } from '@opentelemetry/api';
|
|
2
|
+
import WebSocket from 'isomorphic-ws';
|
|
3
|
+
import { ISocket, SocketError, SocketMessage } from './socket';
|
|
4
|
+
import { GenericMiddleware, ISocketClient, MethodHandler, MethodHandlers, RequestContextBase, SocketTimeouts } from './types';
|
|
5
|
+
export declare class TracedSocket<ImplementedMethods, CallableMethods, RequestContext extends RequestContextBase> extends ISocket<ImplementedMethods, CallableMethods, RequestContext> {
|
|
6
|
+
/**
|
|
7
|
+
* The TracedSocket class extends the ISocket class to add tracing capabilities for each connection. It manages
|
|
8
|
+
* spans for handler calls to provide detailed tracing information.
|
|
9
|
+
*
|
|
10
|
+
* Unlike Express, the ISocket handler call chain is fully recursive. This recursion makes it challenging to determine
|
|
11
|
+
* when to create and end spans for specific handlers, as handlers may call other handlers before returning a result.
|
|
12
|
+
*
|
|
13
|
+
* An example span for recursive call
|
|
14
|
+
* Main span ----------------------------------------------------------------------------------
|
|
15
|
+
* Handler1 ----------------------------------------------------------------------------
|
|
16
|
+
* Handler2 ----------------------------------------------------------------------
|
|
17
|
+
*
|
|
18
|
+
* To manage this, we keep a reference to the most recently created span and complete that span when we go one step deeper into the recursion.
|
|
19
|
+
* This approach works for now since we don't support much parallelism, but it will need to be updated when we start handling requests concurrently.
|
|
20
|
+
*
|
|
21
|
+
* An example span for recursive call after the above fixes
|
|
22
|
+
* Main span ----------------------------------------------------------------------------------
|
|
23
|
+
* Handler1 -----------------------------
|
|
24
|
+
* Handler2 ------------------------------------------------
|
|
25
|
+
*
|
|
26
|
+
*/
|
|
27
|
+
private activeSpanByRequestId;
|
|
28
|
+
private middlewareSpanByReqId;
|
|
29
|
+
private lastActiveSpan;
|
|
30
|
+
private tracer;
|
|
31
|
+
constructor(ws: WebSocket, requestHandlers: MethodHandlers<ImplementedMethods, CallableMethods, RequestContext>, globalMiddlewares: GenericMiddleware<CallableMethods, RequestContext>[], tracer: Tracer, timeouts?: SocketTimeouts, logger?: {
|
|
32
|
+
error: (message?: string) => void;
|
|
33
|
+
});
|
|
34
|
+
protected callHandler<Params, Result, CallableMethods, RequestContext extends RequestContextBase>(handler: MethodHandler<Params, Result, CallableMethods, RequestContext>, params: Params, ctx: RequestContext, client: ISocketClient<CallableMethods>, next: () => Promise<Result>): Promise<Result>;
|
|
35
|
+
protected handleMessage(message: SocketMessage): Promise<void>;
|
|
36
|
+
request<Params, Result>(method: string, params: Params, authorization?: string): Promise<Result>;
|
|
37
|
+
protected decorateToSend(message: SocketMessage): SocketMessage;
|
|
38
|
+
protected respond<Result>(requestId: number, result: Result): void;
|
|
39
|
+
protected respondError(requestId: number, error: SocketError, exception?: Error): void;
|
|
40
|
+
private addEvent;
|
|
41
|
+
close(): void;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=tracedSocket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracedSocket.d.ts","sourceRoot":"","sources":["../../src/socket/tracedSocket.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,MAAM,EAEP,MAAM,oBAAoB,CAAC;AAE5B,OAAO,SAAS,MAAM,eAAe,CAAC;AAUtC,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAmC9H,qBAAa,YAAY,CAAC,kBAAkB,EAAE,eAAe,EAAE,cAAc,SAAS,kBAAkB,CAAE,SAAQ,OAAO,CACvH,kBAAkB,EAClB,eAAe,EACf,cAAc,CACf;IACC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,qBAAqB,CAA2B;IACxD,OAAO,CAAC,qBAAqB,CAA2B;IACxD,OAAO,CAAC,cAAc,CAAmB;IACzC,OAAO,CAAC,MAAM,CAAS;gBAGrB,EAAE,EAAE,SAAS,EACb,eAAe,EAAE,cAAc,CAAC,kBAAkB,EAAE,eAAe,EAAE,cAAc,CAAC,EACpF,iBAAiB,EAAE,iBAAiB,CAAC,eAAe,EAAE,cAAc,CAAC,EAAE,EACvE,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,cAAc,EACzB,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE;cAMhC,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,cAAc,SAAS,kBAAkB,EACpG,OAAO,EAAE,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,cAAc,CAAC,EACvE,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,aAAa,CAAC,eAAe,CAAC,EACtC,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GAC1B,OAAO,CAAC,MAAM,CAAC;cAkCF,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAqC7D,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAKvG,SAAS,CAAC,cAAc,CAAC,OAAO,EAAE,aAAa,GAAG,aAAa;IAK/D,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAKlE,SAAS,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,KAAK,GAAG,IAAI;IAStF,OAAO,CAAC,QAAQ;IAQT,KAAK,IAAI,IAAI;CAQrB"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TracedSocket = void 0;
|
|
4
|
+
const api_1 = require("@opentelemetry/api");
|
|
5
|
+
const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
|
|
6
|
+
const observability_1 = require("../observability");
|
|
7
|
+
const socket_1 = require("./socket");
|
|
8
|
+
function isPromise(obj) {
|
|
9
|
+
return obj !== null && typeof obj === 'object' && typeof obj.then === 'function';
|
|
10
|
+
}
|
|
11
|
+
// Taken from https://github.com/gadget-inc/opentelemetry-instrumentations/blob/main/packages/opentelemetry-instrumentation-ws/src/index.ts#L21
|
|
12
|
+
const endSpan = (traced, span) => {
|
|
13
|
+
try {
|
|
14
|
+
const result = traced();
|
|
15
|
+
if (isPromise(result)) {
|
|
16
|
+
return Promise.resolve(result)
|
|
17
|
+
.catch((err) => {
|
|
18
|
+
setHttpStatusFromError(span, typeof err === 'string' ? new Error(err) : err);
|
|
19
|
+
throw err;
|
|
20
|
+
})
|
|
21
|
+
.finally(() => span.end());
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
span.end();
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
setHttpStatusFromError(span, error);
|
|
30
|
+
span.end();
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
function setHttpStatusFromError(span, error) {
|
|
35
|
+
// Simplified error handling without server-side dependencies
|
|
36
|
+
span.setAttribute(observability_1.OBS_TAG_HTTP_STATUS_CODE, 500);
|
|
37
|
+
span.recordException(error);
|
|
38
|
+
span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: error.message });
|
|
39
|
+
}
|
|
40
|
+
class TracedSocket extends socket_1.ISocket {
|
|
41
|
+
constructor(ws, requestHandlers, globalMiddlewares, tracer, timeouts, logger) {
|
|
42
|
+
super(ws, requestHandlers, globalMiddlewares, timeouts, logger);
|
|
43
|
+
/**
|
|
44
|
+
* The TracedSocket class extends the ISocket class to add tracing capabilities for each connection. It manages
|
|
45
|
+
* spans for handler calls to provide detailed tracing information.
|
|
46
|
+
*
|
|
47
|
+
* Unlike Express, the ISocket handler call chain is fully recursive. This recursion makes it challenging to determine
|
|
48
|
+
* when to create and end spans for specific handlers, as handlers may call other handlers before returning a result.
|
|
49
|
+
*
|
|
50
|
+
* An example span for recursive call
|
|
51
|
+
* Main span ----------------------------------------------------------------------------------
|
|
52
|
+
* Handler1 ----------------------------------------------------------------------------
|
|
53
|
+
* Handler2 ----------------------------------------------------------------------
|
|
54
|
+
*
|
|
55
|
+
* To manage this, we keep a reference to the most recently created span and complete that span when we go one step deeper into the recursion.
|
|
56
|
+
* This approach works for now since we don't support much parallelism, but it will need to be updated when we start handling requests concurrently.
|
|
57
|
+
*
|
|
58
|
+
* An example span for recursive call after the above fixes
|
|
59
|
+
* Main span ----------------------------------------------------------------------------------
|
|
60
|
+
* Handler1 -----------------------------
|
|
61
|
+
* Handler2 ------------------------------------------------
|
|
62
|
+
*
|
|
63
|
+
*/
|
|
64
|
+
this.activeSpanByRequestId = new Map();
|
|
65
|
+
this.middlewareSpanByReqId = new Map();
|
|
66
|
+
this.tracer = tracer;
|
|
67
|
+
}
|
|
68
|
+
async callHandler(handler, params, ctx, client, next) {
|
|
69
|
+
let currentContext = api_1.context.active();
|
|
70
|
+
const middleWareSpan = this.middlewareSpanByReqId.get(ctx.requestId);
|
|
71
|
+
const activeSpan = this.activeSpanByRequestId.get(ctx.requestId);
|
|
72
|
+
// We need to end the middleware span since callHandler is called recursively
|
|
73
|
+
// Otherwise spans will be nested which is not representing actual time taken to execute a middleware
|
|
74
|
+
// Since we are ending the span here, it won't capture error state of nested handlers
|
|
75
|
+
if (middleWareSpan) {
|
|
76
|
+
middleWareSpan.end();
|
|
77
|
+
if (activeSpan) {
|
|
78
|
+
currentContext = api_1.trace.setSpan(api_1.context.active(), activeSpan);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
let result;
|
|
82
|
+
await api_1.context.with(currentContext, async () => {
|
|
83
|
+
await this.tracer.startActiveSpan(`WS HANDLER ${handler.name === '' ? ctx.method : handler.name}`, {
|
|
84
|
+
attributes: {
|
|
85
|
+
[semantic_conventions_1.SEMATTRS_MESSAGING_SYSTEM]: 'ws',
|
|
86
|
+
[semantic_conventions_1.SEMATTRS_MESSAGING_DESTINATION_KIND]: 'websocket'
|
|
87
|
+
},
|
|
88
|
+
kind: api_1.SpanKind.SERVER
|
|
89
|
+
}, async (span) => {
|
|
90
|
+
this.middlewareSpanByReqId.set(ctx.requestId, span);
|
|
91
|
+
result = await endSpan(() => super.callHandler(handler, params, ctx, client, next), span);
|
|
92
|
+
this.middlewareSpanByReqId.delete(ctx.requestId);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
async handleMessage(message) {
|
|
98
|
+
if (message.request) {
|
|
99
|
+
const spanName = message.request.method;
|
|
100
|
+
const requestId = message.request.id;
|
|
101
|
+
const payload = message.request.payload;
|
|
102
|
+
await api_1.context.with(api_1.propagation.extract(api_1.ROOT_CONTEXT, message.request), async () => await this.tracer.startActiveSpan(`WS SERVER ${spanName}`, {
|
|
103
|
+
attributes: {
|
|
104
|
+
[semantic_conventions_1.SEMATTRS_MESSAGING_SYSTEM]: 'ws',
|
|
105
|
+
[semantic_conventions_1.SEMATTRS_MESSAGING_DESTINATION_KIND]: 'websocket',
|
|
106
|
+
[observability_1.OBS_TAG_HTTP_ROUTE]: spanName,
|
|
107
|
+
[observability_1.OBS_TAG_APPLICATION_ID]: payload?.['applicationId'],
|
|
108
|
+
[observability_1.OBS_TAG_API_ID]: payload?.['apiId'],
|
|
109
|
+
[observability_1.OBS_TAG_BRANCH]: payload?.['branch'] ?? payload?.['branchName'],
|
|
110
|
+
[observability_1.OBS_TAG_COMMIT_ID]: payload?.['commitId']
|
|
111
|
+
},
|
|
112
|
+
kind: api_1.SpanKind.SERVER
|
|
113
|
+
}, async (span) => {
|
|
114
|
+
this.lastActiveSpan = span;
|
|
115
|
+
this.activeSpanByRequestId.set(requestId, span);
|
|
116
|
+
const result = await endSpan(() => super.handleMessage(message), span);
|
|
117
|
+
this.activeSpanByRequestId.delete(requestId);
|
|
118
|
+
return result;
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
else if (message.response) {
|
|
122
|
+
this.addEvent('ws.received-response', { ['ws.response.id']: message.response.id });
|
|
123
|
+
return await super.handleMessage(message);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
request(method, params, authorization) {
|
|
127
|
+
this.addEvent('ws.send-request', { ['ws.request.method']: method, ['ws.request.id']: this.nxtRequestId });
|
|
128
|
+
return super.request(method, params, authorization);
|
|
129
|
+
}
|
|
130
|
+
decorateToSend(message) {
|
|
131
|
+
api_1.propagation.inject(api_1.context.active(), message.request);
|
|
132
|
+
return message;
|
|
133
|
+
}
|
|
134
|
+
respond(requestId, result) {
|
|
135
|
+
this.addEvent('ws.send-response', { ['ws.response.id']: requestId });
|
|
136
|
+
return super.respond(requestId, result);
|
|
137
|
+
}
|
|
138
|
+
respondError(requestId, error, exception) {
|
|
139
|
+
this.addEvent('ws.send-error', { ['ws.response.id']: requestId });
|
|
140
|
+
if (this.lastActiveSpan) {
|
|
141
|
+
setHttpStatusFromError(this.lastActiveSpan, exception ?? new Error(error.message));
|
|
142
|
+
this.lastActiveSpan.setAttribute(observability_1.OBS_SOCKET_STATUS_CODE, error.code);
|
|
143
|
+
}
|
|
144
|
+
return super.respondError(requestId, error, exception);
|
|
145
|
+
}
|
|
146
|
+
addEvent(eventName, attributes) {
|
|
147
|
+
// Since we do not have parent requestId in all the methods, we are assuming latest created span is the only active span
|
|
148
|
+
// This assumption can be broken if we start sending multiple request through the same connection.
|
|
149
|
+
if (this.lastActiveSpan) {
|
|
150
|
+
this.lastActiveSpan.addEvent(eventName, attributes, new Date());
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
close() {
|
|
154
|
+
for (const span of this.activeSpanByRequestId.values()) {
|
|
155
|
+
span.end();
|
|
156
|
+
}
|
|
157
|
+
this.activeSpanByRequestId = new Map();
|
|
158
|
+
this.lastActiveSpan = undefined;
|
|
159
|
+
super.close();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
exports.TracedSocket = TracedSocket;
|
|
163
|
+
//# sourceMappingURL=tracedSocket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracedSocket.js","sourceRoot":"","sources":["../../src/socket/tracedSocket.ts"],"names":[],"mappings":";;;AAAA,4CAW4B;AAC5B,8EAAqH;AAErH,oDAQ0B;AAC1B,qCAA+D;AAG/D,SAAS,SAAS,CAAC,GAAY;IAC7B,OAAO,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,OAAQ,GAAwB,CAAC,IAAI,KAAK,UAAU,CAAC;AACzG,CAAC;AAED,+IAA+I;AAC/I,MAAM,OAAO,GAAG,CAAC,MAAwC,EAAE,IAAU,EAAE,EAAE;IACvE,IAAI;QACF,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;QACxB,IAAI,SAAS,CAAC,MAAM,CAAC,EAAE;YACrB,OAAO,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC;iBAC3B,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;gBACb,sBAAsB,CAAC,IAAI,EAAE,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;gBAC7E,MAAM,GAAG,CAAC;YACZ,CAAC,CAAC;iBACD,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;SAC9B;aAAM;YACL,IAAI,CAAC,GAAG,EAAE,CAAC;YACX,OAAO,MAAM,CAAC;SACf;KACF;IAAC,OAAO,KAAK,EAAE;QACd,sBAAsB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG,EAAE,CAAC;QACX,MAAM,KAAK,CAAC;KACb;AACH,CAAC,CAAC;AAEF,SAAS,sBAAsB,CAAC,IAAU,EAAE,KAAY;IACtD,6DAA6D;IAC7D,IAAI,CAAC,YAAY,CAAC,wCAAwB,EAAE,GAAG,CAAC,CAAC;IACjD,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC5B,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,oBAAc,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;AACzE,CAAC;AAED,MAAa,YAA6F,SAAQ,gBAIjH;IA2BC,YACE,EAAa,EACb,eAAoF,EACpF,iBAAuE,EACvE,MAAc,EACd,QAAyB,EACzB,MAA8C;QAE9C,KAAK,CAAC,EAAE,EAAE,eAAe,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;QAlClE;;;;;;;;;;;;;;;;;;;;WAoBG;QACK,0BAAqB,GAAG,IAAI,GAAG,EAAgB,CAAC;QAChD,0BAAqB,GAAG,IAAI,GAAG,EAAgB,CAAC;QAatD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAES,KAAK,CAAC,WAAW,CACzB,OAAuE,EACvE,MAAc,EACd,GAAmB,EACnB,MAAsC,EACtC,IAA2B;QAE3B,IAAI,cAAc,GAAG,aAAO,CAAC,MAAM,EAAE,CAAC;QACtC,MAAM,cAAc,GAAG,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACrE,MAAM,UAAU,GAAG,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACjE,6EAA6E;QAC7E,qGAAqG;QACrG,qFAAqF;QACrF,IAAI,cAAc,EAAE;YAClB,cAAc,CAAC,GAAG,EAAE,CAAC;YACrB,IAAI,UAAU,EAAE;gBACd,cAAc,GAAG,WAAK,CAAC,OAAO,CAAC,aAAO,CAAC,MAAM,EAAE,EAAE,UAAU,CAAC,CAAC;aAC9D;SACF;QACD,IAAI,MAAM,CAAC;QACX,MAAM,aAAO,CAAC,IAAI,CAAC,cAAc,EAAE,KAAK,IAAI,EAAE;YAC5C,MAAM,IAAI,CAAC,MAAM,CAAC,eAAe,CAC/B,cAAc,OAAO,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAC/D;gBACE,UAAU,EAAE;oBACV,CAAC,gDAAyB,CAAC,EAAE,IAAI;oBACjC,CAAC,0DAAmC,CAAC,EAAE,WAAW;iBACnD;gBACD,IAAI,EAAE,cAAQ,CAAC,MAAM;aACtB,EACD,KAAK,EAAE,IAAU,EAAE,EAAE;gBACnB,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;gBACpD,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC;gBAC1F,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACnD,CAAC,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;QACH,OAAO,MAAgB,CAAC;IAC1B,CAAC;IAES,KAAK,CAAC,aAAa,CAAC,OAAsB;QAClD,IAAI,OAAO,CAAC,OAAO,EAAE;YACnB,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC;YACxC,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YACrC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC;YACxC,MAAM,aAAO,CAAC,IAAI,CAChB,iBAAW,CAAC,OAAO,CAAC,kBAAY,EAAE,OAAO,CAAC,OAAO,CAAC,EAClD,KAAK,IAAI,EAAE,CACT,MAAM,IAAI,CAAC,MAAM,CAAC,eAAe,CAC/B,aAAa,QAAQ,EAAE,EACvB;gBACE,UAAU,EAAE;oBACV,CAAC,gDAAyB,CAAC,EAAE,IAAI;oBACjC,CAAC,0DAAmC,CAAC,EAAE,WAAW;oBAClD,CAAC,kCAAkB,CAAC,EAAE,QAAQ;oBAC9B,CAAC,sCAAsB,CAAC,EAAE,OAAO,EAAE,CAAC,eAAe,CAAC;oBACpD,CAAC,8BAAc,CAAC,EAAE,OAAO,EAAE,CAAC,OAAO,CAAC;oBACpC,CAAC,8BAAc,CAAC,EAAE,OAAO,EAAE,CAAC,QAAQ,CAAC,IAAI,OAAO,EAAE,CAAC,YAAY,CAAC;oBAChE,CAAC,iCAAiB,CAAC,EAAE,OAAO,EAAE,CAAC,UAAU,CAAC;iBAC3C;gBACD,IAAI,EAAE,cAAQ,CAAC,MAAM;aACtB,EACD,KAAK,EAAE,IAAU,EAAE,EAAE;gBACnB,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;gBAC3B,IAAI,CAAC,qBAAqB,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;gBAChD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC;gBACvE,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;gBAC7C,OAAO,MAAM,CAAC;YAChB,CAAC,CACF,CACJ,CAAC;SACH;aAAM,IAAI,OAAO,CAAC,QAAQ,EAAE;YAC3B,IAAI,CAAC,QAAQ,CAAC,sBAAsB,EAAE,EAAE,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;YACnF,OAAO,MAAM,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;SAC3C;IACH,CAAC;IAEM,OAAO,CAAiB,MAAc,EAAE,MAAc,EAAE,aAAsB;QACnF,IAAI,CAAC,QAAQ,CAAC,iBAAiB,EAAE,EAAE,CAAC,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC,eAAe,CAAC,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC;QAC1G,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,CAAC;IACtD,CAAC;IAES,cAAc,CAAC,OAAsB;QAC7C,iBAAW,CAAC,MAAM,CAAC,aAAO,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;QACtD,OAAO,OAAO,CAAC;IACjB,CAAC;IAES,OAAO,CAAS,SAAiB,EAAE,MAAc;QACzD,IAAI,CAAC,QAAQ,CAAC,kBAAkB,EAAE,EAAE,CAAC,gBAAgB,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC;QACrE,OAAO,KAAK,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;IAES,YAAY,CAAC,SAAiB,EAAE,KAAkB,EAAE,SAAiB;QAC7E,IAAI,CAAC,QAAQ,CAAC,eAAe,EAAE,EAAE,CAAC,gBAAgB,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC;QAClE,IAAI,IAAI,CAAC,cAAc,EAAE;YACvB,sBAAsB,CAAC,IAAI,CAAC,cAAc,EAAE,SAAS,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;YACnF,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,sCAAsB,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;SACtE;QACD,OAAO,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;IACzD,CAAC;IAEO,QAAQ,CAAC,SAAiB,EAAE,UAAkC;QACpE,wHAAwH;QACxH,kGAAkG;QAClG,IAAI,IAAI,CAAC,cAAc,EAAE;YACvB,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,SAAS,EAAE,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;SACjE;IACH,CAAC;IAEM,KAAK;QACV,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,qBAAqB,CAAC,MAAM,EAAE,EAAE;YACtD,IAAI,CAAC,GAAG,EAAE,CAAC;SACZ;QACD,IAAI,CAAC,qBAAqB,GAAG,IAAI,GAAG,EAAE,CAAC;QACvC,IAAI,CAAC,cAAc,GAAG,SAAS,CAAC;QAChC,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;CACF;AAhKD,oCAgKC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Tracer } from '@opentelemetry/api';
|
|
2
|
+
import WebSocket from 'isomorphic-ws';
|
|
3
|
+
import { ISocket, SocketError, SocketMessage } from './socket';
|
|
4
|
+
import { GenericMiddleware, ISocketClient, MethodHandler, MethodHandlers, RequestContextBase, SocketTimeouts } from './types';
|
|
5
|
+
export declare class TracedSocket<ImplementedMethods, CallableMethods, RequestContext extends RequestContextBase> extends ISocket<ImplementedMethods, CallableMethods, RequestContext> {
|
|
6
|
+
/**
|
|
7
|
+
* The TracedSocket class extends the ISocket class to add tracing capabilities for each connection. It manages
|
|
8
|
+
* spans for handler calls to provide detailed tracing information.
|
|
9
|
+
*
|
|
10
|
+
* Unlike Express, the ISocket handler call chain is fully recursive. This recursion makes it challenging to determine
|
|
11
|
+
* when to create and end spans for specific handlers, as handlers may call other handlers before returning a result.
|
|
12
|
+
*
|
|
13
|
+
* An example span for recursive call
|
|
14
|
+
* Main span ----------------------------------------------------------------------------------
|
|
15
|
+
* Handler1 ----------------------------------------------------------------------------
|
|
16
|
+
* Handler2 ----------------------------------------------------------------------
|
|
17
|
+
*
|
|
18
|
+
* To manage this, we keep a reference to the most recently created span and complete that span when we go one step deeper into the recursion.
|
|
19
|
+
* This approach works for now since we don't support much parallelism, but it will need to be updated when we start handling requests concurrently.
|
|
20
|
+
*
|
|
21
|
+
* An example span for recursive call after the above fixes
|
|
22
|
+
* Main span ----------------------------------------------------------------------------------
|
|
23
|
+
* Handler1 -----------------------------
|
|
24
|
+
* Handler2 ------------------------------------------------
|
|
25
|
+
*
|
|
26
|
+
*/
|
|
27
|
+
private activeSpanByRequestId;
|
|
28
|
+
private middlewareSpanByReqId;
|
|
29
|
+
private lastActiveSpan;
|
|
30
|
+
private tracer;
|
|
31
|
+
constructor(ws: WebSocket, requestHandlers: MethodHandlers<ImplementedMethods, CallableMethods, RequestContext>, globalMiddlewares: GenericMiddleware<CallableMethods, RequestContext>[], tracer: Tracer, timeouts?: SocketTimeouts, logger?: {
|
|
32
|
+
error: (message?: string) => void;
|
|
33
|
+
});
|
|
34
|
+
protected callHandler<Params, Result, CallableMethods, RequestContext extends RequestContextBase>(handler: MethodHandler<Params, Result, CallableMethods, RequestContext>, params: Params, ctx: RequestContext, client: ISocketClient<CallableMethods>, next: () => Promise<Result>): Promise<Result>;
|
|
35
|
+
protected handleMessage(message: SocketMessage): Promise<void>;
|
|
36
|
+
request<Params, Result>(method: string, params: Params, authorization?: string): Promise<Result>;
|
|
37
|
+
protected decorateToSend(message: SocketMessage): SocketMessage;
|
|
38
|
+
protected respond<Result>(requestId: number, result: Result): void;
|
|
39
|
+
protected respondError(requestId: number, error: SocketError, exception?: Error): void;
|
|
40
|
+
private addEvent;
|
|
41
|
+
close(): void;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=tracedSocket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracedSocket.d.ts","sourceRoot":"","sources":["../../src/socket/tracedSocket.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,MAAM,EAEP,MAAM,oBAAoB,CAAC;AAE5B,OAAO,SAAS,MAAM,eAAe,CAAC;AAUtC,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAmC9H,qBAAa,YAAY,CAAC,kBAAkB,EAAE,eAAe,EAAE,cAAc,SAAS,kBAAkB,CAAE,SAAQ,OAAO,CACvH,kBAAkB,EAClB,eAAe,EACf,cAAc,CACf;IACC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,OAAO,CAAC,qBAAqB,CAA2B;IACxD,OAAO,CAAC,qBAAqB,CAA2B;IACxD,OAAO,CAAC,cAAc,CAAmB;IACzC,OAAO,CAAC,MAAM,CAAS;gBAGrB,EAAE,EAAE,SAAS,EACb,eAAe,EAAE,cAAc,CAAC,kBAAkB,EAAE,eAAe,EAAE,cAAc,CAAC,EACpF,iBAAiB,EAAE,iBAAiB,CAAC,eAAe,EAAE,cAAc,CAAC,EAAE,EACvE,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,cAAc,EACzB,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE;cAMhC,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,cAAc,SAAS,kBAAkB,EACpG,OAAO,EAAE,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,cAAc,CAAC,EACvE,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,aAAa,CAAC,eAAe,CAAC,EACtC,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GAC1B,OAAO,CAAC,MAAM,CAAC;cAkCF,aAAa,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAqC7D,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAKvG,SAAS,CAAC,cAAc,CAAC,OAAO,EAAE,aAAa,GAAG,aAAa;IAK/D,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAKlE,SAAS,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,KAAK,GAAG,IAAI;IAStF,OAAO,CAAC,QAAQ;IAQT,KAAK,IAAI,IAAI;CAQrB"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { ROOT_CONTEXT, SpanKind, SpanStatusCode, context as otelCtx, propagation, trace } from '@opentelemetry/api';
|
|
2
|
+
import { SEMATTRS_MESSAGING_DESTINATION_KIND, SEMATTRS_MESSAGING_SYSTEM } from '@opentelemetry/semantic-conventions';
|
|
3
|
+
import { OBS_SOCKET_STATUS_CODE, OBS_TAG_API_ID, OBS_TAG_APPLICATION_ID, OBS_TAG_BRANCH, OBS_TAG_COMMIT_ID, OBS_TAG_HTTP_ROUTE, OBS_TAG_HTTP_STATUS_CODE } from '../observability';
|
|
4
|
+
import { ISocket } from './socket';
|
|
5
|
+
function isPromise(obj) {
|
|
6
|
+
return obj !== null && typeof obj === 'object' && typeof obj.then === 'function';
|
|
7
|
+
}
|
|
8
|
+
// Taken from https://github.com/gadget-inc/opentelemetry-instrumentations/blob/main/packages/opentelemetry-instrumentation-ws/src/index.ts#L21
|
|
9
|
+
const endSpan = (traced, span) => {
|
|
10
|
+
try {
|
|
11
|
+
const result = traced();
|
|
12
|
+
if (isPromise(result)) {
|
|
13
|
+
return Promise.resolve(result)
|
|
14
|
+
.catch((err) => {
|
|
15
|
+
setHttpStatusFromError(span, typeof err === 'string' ? new Error(err) : err);
|
|
16
|
+
throw err;
|
|
17
|
+
})
|
|
18
|
+
.finally(() => span.end());
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
span.end();
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
setHttpStatusFromError(span, error);
|
|
27
|
+
span.end();
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
function setHttpStatusFromError(span, error) {
|
|
32
|
+
// Simplified error handling without server-side dependencies
|
|
33
|
+
span.setAttribute(OBS_TAG_HTTP_STATUS_CODE, 500);
|
|
34
|
+
span.recordException(error);
|
|
35
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
|
|
36
|
+
}
|
|
37
|
+
export class TracedSocket extends ISocket {
|
|
38
|
+
constructor(ws, requestHandlers, globalMiddlewares, tracer, timeouts, logger) {
|
|
39
|
+
super(ws, requestHandlers, globalMiddlewares, timeouts, logger);
|
|
40
|
+
/**
|
|
41
|
+
* The TracedSocket class extends the ISocket class to add tracing capabilities for each connection. It manages
|
|
42
|
+
* spans for handler calls to provide detailed tracing information.
|
|
43
|
+
*
|
|
44
|
+
* Unlike Express, the ISocket handler call chain is fully recursive. This recursion makes it challenging to determine
|
|
45
|
+
* when to create and end spans for specific handlers, as handlers may call other handlers before returning a result.
|
|
46
|
+
*
|
|
47
|
+
* An example span for recursive call
|
|
48
|
+
* Main span ----------------------------------------------------------------------------------
|
|
49
|
+
* Handler1 ----------------------------------------------------------------------------
|
|
50
|
+
* Handler2 ----------------------------------------------------------------------
|
|
51
|
+
*
|
|
52
|
+
* To manage this, we keep a reference to the most recently created span and complete that span when we go one step deeper into the recursion.
|
|
53
|
+
* This approach works for now since we don't support much parallelism, but it will need to be updated when we start handling requests concurrently.
|
|
54
|
+
*
|
|
55
|
+
* An example span for recursive call after the above fixes
|
|
56
|
+
* Main span ----------------------------------------------------------------------------------
|
|
57
|
+
* Handler1 -----------------------------
|
|
58
|
+
* Handler2 ------------------------------------------------
|
|
59
|
+
*
|
|
60
|
+
*/
|
|
61
|
+
this.activeSpanByRequestId = new Map();
|
|
62
|
+
this.middlewareSpanByReqId = new Map();
|
|
63
|
+
this.tracer = tracer;
|
|
64
|
+
}
|
|
65
|
+
async callHandler(handler, params, ctx, client, next) {
|
|
66
|
+
let currentContext = otelCtx.active();
|
|
67
|
+
const middleWareSpan = this.middlewareSpanByReqId.get(ctx.requestId);
|
|
68
|
+
const activeSpan = this.activeSpanByRequestId.get(ctx.requestId);
|
|
69
|
+
// We need to end the middleware span since callHandler is called recursively
|
|
70
|
+
// Otherwise spans will be nested which is not representing actual time taken to execute a middleware
|
|
71
|
+
// Since we are ending the span here, it won't capture error state of nested handlers
|
|
72
|
+
if (middleWareSpan) {
|
|
73
|
+
middleWareSpan.end();
|
|
74
|
+
if (activeSpan) {
|
|
75
|
+
currentContext = trace.setSpan(otelCtx.active(), activeSpan);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
let result;
|
|
79
|
+
await otelCtx.with(currentContext, async () => {
|
|
80
|
+
await this.tracer.startActiveSpan(`WS HANDLER ${handler.name === '' ? ctx.method : handler.name}`, {
|
|
81
|
+
attributes: {
|
|
82
|
+
[SEMATTRS_MESSAGING_SYSTEM]: 'ws',
|
|
83
|
+
[SEMATTRS_MESSAGING_DESTINATION_KIND]: 'websocket'
|
|
84
|
+
},
|
|
85
|
+
kind: SpanKind.SERVER
|
|
86
|
+
}, async (span) => {
|
|
87
|
+
this.middlewareSpanByReqId.set(ctx.requestId, span);
|
|
88
|
+
result = await endSpan(() => super.callHandler(handler, params, ctx, client, next), span);
|
|
89
|
+
this.middlewareSpanByReqId.delete(ctx.requestId);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
async handleMessage(message) {
|
|
95
|
+
if (message.request) {
|
|
96
|
+
const spanName = message.request.method;
|
|
97
|
+
const requestId = message.request.id;
|
|
98
|
+
const payload = message.request.payload;
|
|
99
|
+
await otelCtx.with(propagation.extract(ROOT_CONTEXT, message.request), async () => await this.tracer.startActiveSpan(`WS SERVER ${spanName}`, {
|
|
100
|
+
attributes: {
|
|
101
|
+
[SEMATTRS_MESSAGING_SYSTEM]: 'ws',
|
|
102
|
+
[SEMATTRS_MESSAGING_DESTINATION_KIND]: 'websocket',
|
|
103
|
+
[OBS_TAG_HTTP_ROUTE]: spanName,
|
|
104
|
+
[OBS_TAG_APPLICATION_ID]: payload?.['applicationId'],
|
|
105
|
+
[OBS_TAG_API_ID]: payload?.['apiId'],
|
|
106
|
+
[OBS_TAG_BRANCH]: payload?.['branch'] ?? payload?.['branchName'],
|
|
107
|
+
[OBS_TAG_COMMIT_ID]: payload?.['commitId']
|
|
108
|
+
},
|
|
109
|
+
kind: SpanKind.SERVER
|
|
110
|
+
}, async (span) => {
|
|
111
|
+
this.lastActiveSpan = span;
|
|
112
|
+
this.activeSpanByRequestId.set(requestId, span);
|
|
113
|
+
const result = await endSpan(() => super.handleMessage(message), span);
|
|
114
|
+
this.activeSpanByRequestId.delete(requestId);
|
|
115
|
+
return result;
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
else if (message.response) {
|
|
119
|
+
this.addEvent('ws.received-response', { ['ws.response.id']: message.response.id });
|
|
120
|
+
return await super.handleMessage(message);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
request(method, params, authorization) {
|
|
124
|
+
this.addEvent('ws.send-request', { ['ws.request.method']: method, ['ws.request.id']: this.nxtRequestId });
|
|
125
|
+
return super.request(method, params, authorization);
|
|
126
|
+
}
|
|
127
|
+
decorateToSend(message) {
|
|
128
|
+
propagation.inject(otelCtx.active(), message.request);
|
|
129
|
+
return message;
|
|
130
|
+
}
|
|
131
|
+
respond(requestId, result) {
|
|
132
|
+
this.addEvent('ws.send-response', { ['ws.response.id']: requestId });
|
|
133
|
+
return super.respond(requestId, result);
|
|
134
|
+
}
|
|
135
|
+
respondError(requestId, error, exception) {
|
|
136
|
+
this.addEvent('ws.send-error', { ['ws.response.id']: requestId });
|
|
137
|
+
if (this.lastActiveSpan) {
|
|
138
|
+
setHttpStatusFromError(this.lastActiveSpan, exception ?? new Error(error.message));
|
|
139
|
+
this.lastActiveSpan.setAttribute(OBS_SOCKET_STATUS_CODE, error.code);
|
|
140
|
+
}
|
|
141
|
+
return super.respondError(requestId, error, exception);
|
|
142
|
+
}
|
|
143
|
+
addEvent(eventName, attributes) {
|
|
144
|
+
// Since we do not have parent requestId in all the methods, we are assuming latest created span is the only active span
|
|
145
|
+
// This assumption can be broken if we start sending multiple request through the same connection.
|
|
146
|
+
if (this.lastActiveSpan) {
|
|
147
|
+
this.lastActiveSpan.addEvent(eventName, attributes, new Date());
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
close() {
|
|
151
|
+
for (const span of this.activeSpanByRequestId.values()) {
|
|
152
|
+
span.end();
|
|
153
|
+
}
|
|
154
|
+
this.activeSpanByRequestId = new Map();
|
|
155
|
+
this.lastActiveSpan = undefined;
|
|
156
|
+
super.close();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=tracedSocket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracedSocket.js","sourceRoot":"","sources":["../../src/socket/tracedSocket.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Attributes,
|
|
3
|
+
ROOT_CONTEXT,
|
|
4
|
+
Span,
|
|
5
|
+
SpanKind,
|
|
6
|
+
SpanStatusCode,
|
|
7
|
+
TimeInput,
|
|
8
|
+
context as otelCtx,
|
|
9
|
+
propagation,
|
|
10
|
+
Tracer,
|
|
11
|
+
trace
|
|
12
|
+
} from '@opentelemetry/api';
|
|
13
|
+
import { SEMATTRS_MESSAGING_DESTINATION_KIND, SEMATTRS_MESSAGING_SYSTEM } from '@opentelemetry/semantic-conventions';
|
|
14
|
+
import WebSocket from 'isomorphic-ws';
|
|
15
|
+
import {
|
|
16
|
+
OBS_SOCKET_STATUS_CODE,
|
|
17
|
+
OBS_TAG_API_ID,
|
|
18
|
+
OBS_TAG_APPLICATION_ID,
|
|
19
|
+
OBS_TAG_BRANCH,
|
|
20
|
+
OBS_TAG_COMMIT_ID,
|
|
21
|
+
OBS_TAG_HTTP_ROUTE,
|
|
22
|
+
OBS_TAG_HTTP_STATUS_CODE
|
|
23
|
+
} from '../observability';
|
|
24
|
+
import { ISocket, SocketError, SocketMessage } from './socket';
|
|
25
|
+
import { GenericMiddleware, ISocketClient, MethodHandler, MethodHandlers, RequestContextBase, SocketTimeouts } from './types';
|
|
26
|
+
|
|
27
|
+
function isPromise(obj: unknown): obj is Promise<unknown> {
|
|
28
|
+
return obj !== null && typeof obj === 'object' && typeof (obj as Promise<unknown>).then === 'function';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Taken from https://github.com/gadget-inc/opentelemetry-instrumentations/blob/main/packages/opentelemetry-instrumentation-ws/src/index.ts#L21
|
|
32
|
+
const endSpan = (traced: () => unknown | Promise<unknown>, span: Span) => {
|
|
33
|
+
try {
|
|
34
|
+
const result = traced();
|
|
35
|
+
if (isPromise(result)) {
|
|
36
|
+
return Promise.resolve(result)
|
|
37
|
+
.catch((err) => {
|
|
38
|
+
setHttpStatusFromError(span, typeof err === 'string' ? new Error(err) : err);
|
|
39
|
+
throw err;
|
|
40
|
+
})
|
|
41
|
+
.finally(() => span.end());
|
|
42
|
+
} else {
|
|
43
|
+
span.end();
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
} catch (error) {
|
|
47
|
+
setHttpStatusFromError(span, error);
|
|
48
|
+
span.end();
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function setHttpStatusFromError(span: Span, error: Error): void {
|
|
54
|
+
// Simplified error handling without server-side dependencies
|
|
55
|
+
span.setAttribute(OBS_TAG_HTTP_STATUS_CODE, 500);
|
|
56
|
+
span.recordException(error);
|
|
57
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export class TracedSocket<ImplementedMethods, CallableMethods, RequestContext extends RequestContextBase> extends ISocket<
|
|
61
|
+
ImplementedMethods,
|
|
62
|
+
CallableMethods,
|
|
63
|
+
RequestContext
|
|
64
|
+
> {
|
|
65
|
+
/**
|
|
66
|
+
* The TracedSocket class extends the ISocket class to add tracing capabilities for each connection. It manages
|
|
67
|
+
* spans for handler calls to provide detailed tracing information.
|
|
68
|
+
*
|
|
69
|
+
* Unlike Express, the ISocket handler call chain is fully recursive. This recursion makes it challenging to determine
|
|
70
|
+
* when to create and end spans for specific handlers, as handlers may call other handlers before returning a result.
|
|
71
|
+
*
|
|
72
|
+
* An example span for recursive call
|
|
73
|
+
* Main span ----------------------------------------------------------------------------------
|
|
74
|
+
* Handler1 ----------------------------------------------------------------------------
|
|
75
|
+
* Handler2 ----------------------------------------------------------------------
|
|
76
|
+
*
|
|
77
|
+
* To manage this, we keep a reference to the most recently created span and complete that span when we go one step deeper into the recursion.
|
|
78
|
+
* This approach works for now since we don't support much parallelism, but it will need to be updated when we start handling requests concurrently.
|
|
79
|
+
*
|
|
80
|
+
* An example span for recursive call after the above fixes
|
|
81
|
+
* Main span ----------------------------------------------------------------------------------
|
|
82
|
+
* Handler1 -----------------------------
|
|
83
|
+
* Handler2 ------------------------------------------------
|
|
84
|
+
*
|
|
85
|
+
*/
|
|
86
|
+
private activeSpanByRequestId = new Map<number, Span>();
|
|
87
|
+
private middlewareSpanByReqId = new Map<number, Span>();
|
|
88
|
+
private lastActiveSpan: Span | undefined;
|
|
89
|
+
private tracer: Tracer;
|
|
90
|
+
|
|
91
|
+
constructor(
|
|
92
|
+
ws: WebSocket,
|
|
93
|
+
requestHandlers: MethodHandlers<ImplementedMethods, CallableMethods, RequestContext>,
|
|
94
|
+
globalMiddlewares: GenericMiddleware<CallableMethods, RequestContext>[],
|
|
95
|
+
tracer: Tracer,
|
|
96
|
+
timeouts?: SocketTimeouts,
|
|
97
|
+
logger?: { error: (message?: string) => void }
|
|
98
|
+
) {
|
|
99
|
+
super(ws, requestHandlers, globalMiddlewares, timeouts, logger);
|
|
100
|
+
this.tracer = tracer;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
protected async callHandler<Params, Result, CallableMethods, RequestContext extends RequestContextBase>(
|
|
104
|
+
handler: MethodHandler<Params, Result, CallableMethods, RequestContext>,
|
|
105
|
+
params: Params,
|
|
106
|
+
ctx: RequestContext,
|
|
107
|
+
client: ISocketClient<CallableMethods>,
|
|
108
|
+
next: () => Promise<Result>
|
|
109
|
+
): Promise<Result> {
|
|
110
|
+
let currentContext = otelCtx.active();
|
|
111
|
+
const middleWareSpan = this.middlewareSpanByReqId.get(ctx.requestId);
|
|
112
|
+
const activeSpan = this.activeSpanByRequestId.get(ctx.requestId);
|
|
113
|
+
// We need to end the middleware span since callHandler is called recursively
|
|
114
|
+
// Otherwise spans will be nested which is not representing actual time taken to execute a middleware
|
|
115
|
+
// Since we are ending the span here, it won't capture error state of nested handlers
|
|
116
|
+
if (middleWareSpan) {
|
|
117
|
+
middleWareSpan.end();
|
|
118
|
+
if (activeSpan) {
|
|
119
|
+
currentContext = trace.setSpan(otelCtx.active(), activeSpan);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
let result;
|
|
123
|
+
await otelCtx.with(currentContext, async () => {
|
|
124
|
+
await this.tracer.startActiveSpan(
|
|
125
|
+
`WS HANDLER ${handler.name === '' ? ctx.method : handler.name}`,
|
|
126
|
+
{
|
|
127
|
+
attributes: {
|
|
128
|
+
[SEMATTRS_MESSAGING_SYSTEM]: 'ws',
|
|
129
|
+
[SEMATTRS_MESSAGING_DESTINATION_KIND]: 'websocket'
|
|
130
|
+
},
|
|
131
|
+
kind: SpanKind.SERVER
|
|
132
|
+
},
|
|
133
|
+
async (span: Span) => {
|
|
134
|
+
this.middlewareSpanByReqId.set(ctx.requestId, span);
|
|
135
|
+
result = await endSpan(() => super.callHandler(handler, params, ctx, client, next), span);
|
|
136
|
+
this.middlewareSpanByReqId.delete(ctx.requestId);
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
return result as Result;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
protected async handleMessage(message: SocketMessage): Promise<void> {
|
|
144
|
+
if (message.request) {
|
|
145
|
+
const spanName = message.request.method;
|
|
146
|
+
const requestId = message.request.id;
|
|
147
|
+
const payload = message.request.payload;
|
|
148
|
+
await otelCtx.with(
|
|
149
|
+
propagation.extract(ROOT_CONTEXT, message.request),
|
|
150
|
+
async () =>
|
|
151
|
+
await this.tracer.startActiveSpan(
|
|
152
|
+
`WS SERVER ${spanName}`,
|
|
153
|
+
{
|
|
154
|
+
attributes: {
|
|
155
|
+
[SEMATTRS_MESSAGING_SYSTEM]: 'ws',
|
|
156
|
+
[SEMATTRS_MESSAGING_DESTINATION_KIND]: 'websocket',
|
|
157
|
+
[OBS_TAG_HTTP_ROUTE]: spanName,
|
|
158
|
+
[OBS_TAG_APPLICATION_ID]: payload?.['applicationId'],
|
|
159
|
+
[OBS_TAG_API_ID]: payload?.['apiId'],
|
|
160
|
+
[OBS_TAG_BRANCH]: payload?.['branch'] ?? payload?.['branchName'],
|
|
161
|
+
[OBS_TAG_COMMIT_ID]: payload?.['commitId']
|
|
162
|
+
},
|
|
163
|
+
kind: SpanKind.SERVER
|
|
164
|
+
},
|
|
165
|
+
async (span: Span) => {
|
|
166
|
+
this.lastActiveSpan = span;
|
|
167
|
+
this.activeSpanByRequestId.set(requestId, span);
|
|
168
|
+
const result = await endSpan(() => super.handleMessage(message), span);
|
|
169
|
+
this.activeSpanByRequestId.delete(requestId);
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
);
|
|
174
|
+
} else if (message.response) {
|
|
175
|
+
this.addEvent('ws.received-response', { ['ws.response.id']: message.response.id });
|
|
176
|
+
return await super.handleMessage(message);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public request<Params, Result>(method: string, params: Params, authorization?: string): Promise<Result> {
|
|
181
|
+
this.addEvent('ws.send-request', { ['ws.request.method']: method, ['ws.request.id']: this.nxtRequestId });
|
|
182
|
+
return super.request(method, params, authorization);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
protected decorateToSend(message: SocketMessage): SocketMessage {
|
|
186
|
+
propagation.inject(otelCtx.active(), message.request);
|
|
187
|
+
return message;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
protected respond<Result>(requestId: number, result: Result): void {
|
|
191
|
+
this.addEvent('ws.send-response', { ['ws.response.id']: requestId });
|
|
192
|
+
return super.respond(requestId, result);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
protected respondError(requestId: number, error: SocketError, exception?: Error): void {
|
|
196
|
+
this.addEvent('ws.send-error', { ['ws.response.id']: requestId });
|
|
197
|
+
if (this.lastActiveSpan) {
|
|
198
|
+
setHttpStatusFromError(this.lastActiveSpan, exception ?? new Error(error.message));
|
|
199
|
+
this.lastActiveSpan.setAttribute(OBS_SOCKET_STATUS_CODE, error.code);
|
|
200
|
+
}
|
|
201
|
+
return super.respondError(requestId, error, exception);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private addEvent(eventName: string, attributes: Attributes | TimeInput): void {
|
|
205
|
+
// Since we do not have parent requestId in all the methods, we are assuming latest created span is the only active span
|
|
206
|
+
// This assumption can be broken if we start sending multiple request through the same connection.
|
|
207
|
+
if (this.lastActiveSpan) {
|
|
208
|
+
this.lastActiveSpan.addEvent(eventName, attributes, new Date());
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
public close(): void {
|
|
213
|
+
for (const span of this.activeSpanByRequestId.values()) {
|
|
214
|
+
span.end();
|
|
215
|
+
}
|
|
216
|
+
this.activeSpanByRequestId = new Map();
|
|
217
|
+
this.lastActiveSpan = undefined;
|
|
218
|
+
super.close();
|
|
219
|
+
}
|
|
220
|
+
}
|