@platformatic/itc 3.52.4 → 3.53.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/lib/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { Context } from '@opentelemetry/api'
1
2
  import { EventEmitter } from 'node:events'
2
3
  import { MessagePort } from 'node:worker_threads'
3
4
 
@@ -10,6 +11,22 @@ export interface ITCConstructorOptions {
10
11
  throwOnMissingHandler?: boolean
11
12
  }
12
13
 
14
+ export interface OutgoingMessagingSpanOptions {
15
+ telemetryContext?: Context
16
+ telemetryMetadata?: Record<string, string>
17
+ }
18
+
19
+ export interface OutgoingMessagingSpan {
20
+ meta: {
21
+ mode: string
22
+ sourceApplication?: string
23
+ targetApplication?: string
24
+ telemetry: Record<string, string>
25
+ } | null
26
+ run<T>(callback: () => T): T
27
+ end(error?: Error | null): void
28
+ }
29
+
13
30
  export class ITC extends EventEmitter {
14
31
  constructor (options: ITCConstructorOptions)
15
32
 
@@ -20,3 +37,29 @@ export class ITC extends EventEmitter {
20
37
  listen (): void
21
38
  close (): void
22
39
  }
40
+
41
+ export function initializeITCTelemetry (): Promise<any>
42
+
43
+ export function startOutgoingMessagingSpan (
44
+ mode: string,
45
+ sourceApplication: string,
46
+ targetApplication: string,
47
+ messageName: string,
48
+ options?: OutgoingMessagingSpanOptions
49
+ ): OutgoingMessagingSpan | null
50
+
51
+ export function startOutgoingMessagingSpanSync (
52
+ mode: string,
53
+ sourceApplication: string,
54
+ targetApplication: string,
55
+ messageName: string,
56
+ options?: OutgoingMessagingSpanOptions
57
+ ): OutgoingMessagingSpan | null
58
+
59
+ export function traceIncomingMessagingHandler (
60
+ applicationId: string,
61
+ messageName: string,
62
+ handler: Handler,
63
+ data: any,
64
+ handlerContext?: Record<string, any>
65
+ ): any
package/lib/index.js CHANGED
@@ -49,18 +49,24 @@ export function parseResponse (response) {
49
49
  return response
50
50
  }
51
51
 
52
- export function generateRequest (name, data) {
52
+ export function generateRequest (name, data, meta) {
53
53
  if (typeof name !== 'string') {
54
54
  throw new RequestNameIsNotString(name.toString())
55
55
  }
56
56
 
57
- return {
57
+ const request = {
58
58
  type: PLT_ITC_REQUEST_TYPE,
59
59
  version: PLT_ITC_VERSION,
60
60
  reqId: randomUUID(),
61
61
  name,
62
62
  data
63
63
  }
64
+
65
+ if (typeof meta !== 'undefined') {
66
+ request.meta = meta
67
+ }
68
+
69
+ return request
64
70
  }
65
71
 
66
72
  export function generateResponse (request, error, data) {
@@ -74,13 +80,19 @@ export function generateResponse (request, error, data) {
74
80
  }
75
81
  }
76
82
 
77
- export function generateNotification (name, data) {
78
- return {
83
+ export function generateNotification (name, data, meta) {
84
+ const notification = {
79
85
  type: PLT_ITC_NOTIFICATION_TYPE,
80
86
  version: PLT_ITC_VERSION,
81
87
  name,
82
88
  data
83
89
  }
90
+
91
+ if (typeof meta !== 'undefined') {
92
+ notification.meta = meta
93
+ }
94
+
95
+ return notification
84
96
  }
85
97
 
86
98
  export function generateUnhandledErrorResponse (error) {
@@ -298,7 +310,7 @@ export class ITC extends EventEmitter {
298
310
  try {
299
311
  this._enableKeepAlive()
300
312
 
301
- const request = generateRequest(name, message)
313
+ const request = generateRequest(name, message, options?.meta)
302
314
  this._send(request, options)
303
315
 
304
316
  const promiseWithResolvers = Promise.withResolvers()
@@ -317,7 +329,7 @@ export class ITC extends EventEmitter {
317
329
  }
318
330
 
319
331
  notify (name, message, options) {
320
- this._send(generateNotification(name, message), options)
332
+ this._send(generateNotification(name, message, options?.meta), options)
321
333
  }
322
334
 
323
335
  handle (message, handler) {
@@ -471,3 +483,4 @@ export class ITC extends EventEmitter {
471
483
  }
472
484
 
473
485
  export * as errors from './errors.js'
486
+ export * from './telemetry.js'
@@ -0,0 +1,220 @@
1
+ import { ROOT_CONTEXT, SpanKind, SpanStatusCode, context, propagation, trace } from '@opentelemetry/api'
2
+
3
+ let tracer = null
4
+ let telemetryInitialization = null
5
+
6
+ function createMessagingSpanName (type, application, messageName) {
7
+ return `ITC ${type} ${application}.${messageName}`
8
+ }
9
+
10
+ function createMessagingAttributes ({ mode, sourceApplication, targetApplication, messageName }) {
11
+ const attributes = {
12
+ 'messaging.system': 'platformatic-itc',
13
+ 'messaging.operation': mode,
14
+ 'messaging.message.name': messageName
15
+ }
16
+
17
+ if (targetApplication) {
18
+ attributes['messaging.destination.name'] = targetApplication
19
+ attributes['platformatic.messaging.target'] = targetApplication
20
+ }
21
+
22
+ if (sourceApplication) {
23
+ attributes['platformatic.messaging.source'] = sourceApplication
24
+ }
25
+
26
+ return attributes
27
+ }
28
+
29
+ function createOutgoingMessagingMeta (mode, sourceApplication, targetApplication, telemetryMetadata) {
30
+ if (!telemetryMetadata || Object.keys(telemetryMetadata).length === 0) {
31
+ return null
32
+ }
33
+
34
+ return {
35
+ mode,
36
+ sourceApplication,
37
+ targetApplication,
38
+ telemetry: telemetryMetadata
39
+ }
40
+ }
41
+
42
+ function setSpanError (span, error) {
43
+ span.setAttributes({
44
+ 'error.name': error.name,
45
+ 'error.message': error.message,
46
+ 'error.stack': error.stack
47
+ })
48
+
49
+ span.setStatus({
50
+ code: SpanStatusCode.ERROR,
51
+ message: error.message
52
+ })
53
+ }
54
+
55
+ function endSpan (span, error) {
56
+ if (error) {
57
+ setSpanError(span, error)
58
+ } else {
59
+ span.setStatus({ code: SpanStatusCode.OK })
60
+ }
61
+
62
+ span.end()
63
+ }
64
+
65
+ function getTracer () {
66
+ if (tracer) {
67
+ return tracer
68
+ }
69
+
70
+ const tracerProvider = globalThis.platformatic?.tracerProvider
71
+ if (!tracerProvider) {
72
+ return null
73
+ }
74
+
75
+ tracer = tracerProvider.getTracer('@platformatic/itc')
76
+ return tracer
77
+ }
78
+
79
+ export function initializeITCTelemetry () {
80
+ if (telemetryInitialization) {
81
+ return telemetryInitialization
82
+ }
83
+
84
+ telemetryInitialization = Promise.resolve(globalThis.platformatic?.telemetryReady)
85
+ .catch(() => {
86
+ // Ignore telemetry initialization failures and fall back to untraced messaging.
87
+ })
88
+ .then(() => getTracer())
89
+
90
+ return telemetryInitialization
91
+ }
92
+
93
+ function createNoopOutgoingTelemetry (mode, sourceApplication, targetApplication, telemetryMetadata) {
94
+ const meta = createOutgoingMessagingMeta(mode, sourceApplication, targetApplication, telemetryMetadata)
95
+
96
+ if (!meta) {
97
+ return null
98
+ }
99
+
100
+ return {
101
+ meta,
102
+ run (callback) {
103
+ return callback()
104
+ },
105
+ end () {}
106
+ }
107
+ }
108
+
109
+ function getParentContext (telemetryContext, telemetryMetadata) {
110
+ if (telemetryContext) {
111
+ return telemetryContext
112
+ }
113
+
114
+ if (telemetryMetadata && Object.keys(telemetryMetadata).length > 0) {
115
+ return propagation.extract(ROOT_CONTEXT, telemetryMetadata)
116
+ }
117
+
118
+ return context.active()
119
+ }
120
+
121
+ function createOutgoingMessagingSpan (tracer, mode, sourceApplication, targetApplication, messageName, options = {}) {
122
+ const parentContext = getParentContext(options.telemetryContext, options.telemetryMetadata)
123
+ const spanType = mode === 'notify' ? 'notify' : 'send'
124
+ const spanKind = mode === 'notify' ? SpanKind.PRODUCER : SpanKind.CLIENT
125
+ const span = tracer.startSpan(
126
+ createMessagingSpanName(spanType, targetApplication, messageName),
127
+ {
128
+ kind: spanKind,
129
+ attributes: createMessagingAttributes({
130
+ mode,
131
+ sourceApplication,
132
+ targetApplication,
133
+ messageName
134
+ })
135
+ },
136
+ parentContext
137
+ )
138
+
139
+ const spanContext = trace.setSpan(parentContext, span)
140
+ const telemetryMetadata = {}
141
+ propagation.inject(spanContext, telemetryMetadata)
142
+
143
+ return {
144
+ meta: createOutgoingMessagingMeta(mode, sourceApplication, targetApplication, telemetryMetadata),
145
+ run (callback) {
146
+ return context.with(spanContext, callback)
147
+ },
148
+ end (error) {
149
+ endSpan(span, error)
150
+ }
151
+ }
152
+ }
153
+
154
+ export function startOutgoingMessagingSpan (mode, sourceApplication, targetApplication, messageName, options = {}) {
155
+ const activeTracer = getTracer()
156
+ if (!activeTracer) {
157
+ return createNoopOutgoingTelemetry(mode, sourceApplication, targetApplication, options.telemetryMetadata)
158
+ }
159
+
160
+ return createOutgoingMessagingSpan(activeTracer, mode, sourceApplication, targetApplication, messageName, options)
161
+ }
162
+
163
+ export function startOutgoingMessagingSpanSync (mode, sourceApplication, targetApplication, messageName, options = {}) {
164
+ return startOutgoingMessagingSpan(mode, sourceApplication, targetApplication, messageName, options)
165
+ }
166
+
167
+ export function traceIncomingMessagingHandler (applicationId, messageName, handler, data, handlerContext = {}) {
168
+ const activeTracer = getTracer()
169
+ if (!activeTracer) {
170
+ return handler(data, handlerContext)
171
+ }
172
+
173
+ const request = handlerContext.message ?? {}
174
+ const metadata = request.meta ?? {}
175
+ const mode = handlerContext.notification ? 'notify' : metadata.mode ?? 'send'
176
+ const sourceApplication = metadata.sourceApplication
177
+ const telemetryCarrier = metadata.telemetry ?? {}
178
+ const parentContext = propagation.extract(ROOT_CONTEXT, telemetryCarrier)
179
+ const spanType = handlerContext.notification ? 'consume' : 'handle'
180
+ const spanKind = handlerContext.notification ? SpanKind.CONSUMER : SpanKind.SERVER
181
+ const span = activeTracer.startSpan(
182
+ createMessagingSpanName(spanType, sourceApplication ?? 'unknown', messageName),
183
+ {
184
+ kind: spanKind,
185
+ attributes: createMessagingAttributes({
186
+ mode,
187
+ sourceApplication,
188
+ targetApplication: applicationId,
189
+ messageName
190
+ })
191
+ },
192
+ parentContext
193
+ )
194
+
195
+ const spanContext = trace.setSpan(parentContext, span)
196
+ let result
197
+
198
+ try {
199
+ result = context.with(spanContext, () => handler(data, handlerContext))
200
+ } catch (error) {
201
+ endSpan(span, error)
202
+ throw error
203
+ }
204
+
205
+ if (result?.then) {
206
+ return result.then(
207
+ value => {
208
+ endSpan(span)
209
+ return value
210
+ },
211
+ error => {
212
+ endSpan(span, error)
213
+ throw error
214
+ }
215
+ )
216
+ }
217
+
218
+ endSpan(span)
219
+ return result
220
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/itc",
3
- "version": "3.52.4",
3
+ "version": "3.53.0",
4
4
  "description": "",
5
5
  "main": "lib/index.js",
6
6
  "type": "module",
@@ -25,6 +25,7 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@fastify/error": "^4.0.0",
28
+ "@opentelemetry/api": "^1.9.0",
28
29
  "@watchable/unpromise": "^1.0.2"
29
30
  },
30
31
  "engines": {