@platformatic/metrics 3.13.1 → 3.15.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/index.js +53 -1
- package/package.json +2 -1
- package/test/otlp.test.js +141 -0
package/index.js
CHANGED
|
@@ -189,6 +189,58 @@ export async function collectMetrics (applicationId, workerId, metricsConfig = {
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
return {
|
|
192
|
-
registry
|
|
192
|
+
registry,
|
|
193
|
+
otlpBridge: null
|
|
193
194
|
}
|
|
194
195
|
}
|
|
196
|
+
|
|
197
|
+
export async function setupOtlpExporter (registry, otlpExporterConfig, applicationId) {
|
|
198
|
+
if (!otlpExporterConfig || !otlpExporterConfig.endpoint) {
|
|
199
|
+
return null
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Check if explicitly disabled
|
|
203
|
+
if (otlpExporterConfig.enabled === false || otlpExporterConfig.enabled === 'false') {
|
|
204
|
+
return null
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Dynamically import PromClientBridge to defer loading until after telemetry is initialized
|
|
208
|
+
const { PromClientBridge } = await import('@platformatic/promotel')
|
|
209
|
+
|
|
210
|
+
const {
|
|
211
|
+
endpoint,
|
|
212
|
+
headers,
|
|
213
|
+
interval = 60000,
|
|
214
|
+
serviceName = applicationId,
|
|
215
|
+
serviceVersion
|
|
216
|
+
} = otlpExporterConfig
|
|
217
|
+
|
|
218
|
+
const otlpEndpointOptions = {
|
|
219
|
+
url: endpoint
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (headers) {
|
|
223
|
+
otlpEndpointOptions.headers = headers
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const conversionOptions = {
|
|
227
|
+
serviceName
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (serviceVersion) {
|
|
231
|
+
conversionOptions.serviceVersion = serviceVersion
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const bridge = new PromClientBridge({
|
|
235
|
+
registry,
|
|
236
|
+
otlpEndpoint: otlpEndpointOptions,
|
|
237
|
+
interval,
|
|
238
|
+
conversionOptions,
|
|
239
|
+
onError: (error) => {
|
|
240
|
+
// Log error but don't crash the application
|
|
241
|
+
console.error('OTLP metrics export error:', error)
|
|
242
|
+
}
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
return bridge
|
|
246
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@platformatic/metrics",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.15.0",
|
|
4
4
|
"description": "Platformatic Capability Metrics",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"homepage": "https://github.com/platformatic/platformatic#readme",
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@platformatic/http-metrics": "^0.2.1",
|
|
19
|
+
"@platformatic/promotel": "^0.1.0",
|
|
19
20
|
"prom-client": "^15.1.2"
|
|
20
21
|
},
|
|
21
22
|
"devDependencies": {
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import { test } from 'node:test'
|
|
3
|
+
import { client, collectMetrics, setupOtlpExporter } from '../index.js'
|
|
4
|
+
|
|
5
|
+
test('setupOtlpExporter returns null when no config provided', async () => {
|
|
6
|
+
const registry = new client.Registry()
|
|
7
|
+
const bridge = await setupOtlpExporter(registry, null, 'test-app')
|
|
8
|
+
assert.strictEqual(bridge, null)
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test('setupOtlpExporter returns null when no endpoint provided', async () => {
|
|
12
|
+
const registry = new client.Registry()
|
|
13
|
+
const bridge = await setupOtlpExporter(registry, {}, 'test-app')
|
|
14
|
+
assert.strictEqual(bridge, null)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('setupOtlpExporter returns null when explicitly disabled', async () => {
|
|
18
|
+
const registry = new client.Registry()
|
|
19
|
+
const bridge = await setupOtlpExporter(registry, {
|
|
20
|
+
enabled: false,
|
|
21
|
+
endpoint: 'http://localhost:4318/v1/metrics'
|
|
22
|
+
}, 'test-app')
|
|
23
|
+
assert.strictEqual(bridge, null)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('setupOtlpExporter returns null when disabled via string', async () => {
|
|
27
|
+
const registry = new client.Registry()
|
|
28
|
+
const bridge = await setupOtlpExporter(registry, {
|
|
29
|
+
enabled: 'false',
|
|
30
|
+
endpoint: 'http://localhost:4318/v1/metrics'
|
|
31
|
+
}, 'test-app')
|
|
32
|
+
assert.strictEqual(bridge, null)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('setupOtlpExporter creates bridge with minimal config', async () => {
|
|
36
|
+
const registry = new client.Registry()
|
|
37
|
+
const bridge = await setupOtlpExporter(registry, {
|
|
38
|
+
endpoint: 'http://localhost:4318/v1/metrics'
|
|
39
|
+
}, 'test-app')
|
|
40
|
+
|
|
41
|
+
assert.ok(bridge, 'Bridge should be created')
|
|
42
|
+
assert.strictEqual(bridge.running, false, 'Bridge should not be running initially')
|
|
43
|
+
|
|
44
|
+
const config = bridge.config
|
|
45
|
+
assert.strictEqual(config.otlpEndpoint.url, 'http://localhost:4318/v1/metrics')
|
|
46
|
+
assert.strictEqual(config.interval, 60000, 'Default interval should be 60000')
|
|
47
|
+
assert.strictEqual(config.conversionOptions.serviceName, 'test-app')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test('setupOtlpExporter creates bridge with full config', async () => {
|
|
51
|
+
const registry = new client.Registry()
|
|
52
|
+
const bridge = await setupOtlpExporter(registry, {
|
|
53
|
+
endpoint: 'http://collector:4318/v1/metrics',
|
|
54
|
+
headers: {
|
|
55
|
+
'x-api-key': 'secret123',
|
|
56
|
+
'x-custom-header': 'value'
|
|
57
|
+
},
|
|
58
|
+
interval: 30000,
|
|
59
|
+
serviceName: 'my-service',
|
|
60
|
+
serviceVersion: '1.2.3'
|
|
61
|
+
}, 'test-app')
|
|
62
|
+
|
|
63
|
+
assert.ok(bridge, 'Bridge should be created')
|
|
64
|
+
|
|
65
|
+
const config = bridge.config
|
|
66
|
+
assert.strictEqual(config.otlpEndpoint.url, 'http://collector:4318/v1/metrics')
|
|
67
|
+
assert.deepStrictEqual(config.otlpEndpoint.headers, {
|
|
68
|
+
'x-api-key': 'secret123',
|
|
69
|
+
'x-custom-header': 'value'
|
|
70
|
+
})
|
|
71
|
+
assert.strictEqual(config.interval, 30000)
|
|
72
|
+
assert.strictEqual(config.conversionOptions.serviceName, 'my-service')
|
|
73
|
+
assert.strictEqual(config.conversionOptions.serviceVersion, '1.2.3')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('setupOtlpExporter uses applicationId as default serviceName', async () => {
|
|
77
|
+
const registry = new client.Registry()
|
|
78
|
+
const bridge = await setupOtlpExporter(registry, {
|
|
79
|
+
endpoint: 'http://localhost:4318/v1/metrics'
|
|
80
|
+
}, 'my-app-id')
|
|
81
|
+
|
|
82
|
+
assert.ok(bridge, 'Bridge should be created')
|
|
83
|
+
assert.strictEqual(bridge.config.conversionOptions.serviceName, 'my-app-id')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('collectMetrics returns registry and null otlpBridge', async () => {
|
|
87
|
+
const result = await collectMetrics('test-service', 1, {
|
|
88
|
+
defaultMetrics: false,
|
|
89
|
+
httpMetrics: false
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
assert.ok(result.registry)
|
|
93
|
+
assert.strictEqual(result.otlpBridge, null)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test('bridge lifecycle - start and stop', async () => {
|
|
97
|
+
const registry = new client.Registry()
|
|
98
|
+
const bridge = await setupOtlpExporter(registry, {
|
|
99
|
+
endpoint: 'http://localhost:4318/v1/metrics',
|
|
100
|
+
interval: 60000
|
|
101
|
+
}, 'test-app')
|
|
102
|
+
|
|
103
|
+
assert.ok(bridge, 'Bridge should be created')
|
|
104
|
+
assert.strictEqual(bridge.running, false, 'Should not be running initially')
|
|
105
|
+
|
|
106
|
+
// Start bridge
|
|
107
|
+
bridge.start()
|
|
108
|
+
assert.strictEqual(bridge.running, true, 'Should be running after start')
|
|
109
|
+
|
|
110
|
+
// Stop bridge
|
|
111
|
+
bridge.stop()
|
|
112
|
+
assert.strictEqual(bridge.running, false, 'Should not be running after stop')
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test('bridge handles errors gracefully', async () => {
|
|
116
|
+
const registry = new client.Registry()
|
|
117
|
+
|
|
118
|
+
// Add a test metric
|
|
119
|
+
const counter = new client.Counter({
|
|
120
|
+
name: 'test_counter',
|
|
121
|
+
help: 'Test counter',
|
|
122
|
+
registers: [registry]
|
|
123
|
+
})
|
|
124
|
+
counter.inc(5)
|
|
125
|
+
|
|
126
|
+
const bridge = await setupOtlpExporter(registry, {
|
|
127
|
+
endpoint: 'http://invalid-endpoint-that-does-not-exist:9999/v1/metrics',
|
|
128
|
+
interval: 1000 // Short interval for testing
|
|
129
|
+
}, 'test-app')
|
|
130
|
+
|
|
131
|
+
assert.ok(bridge, 'Bridge should be created')
|
|
132
|
+
|
|
133
|
+
// The bridge should not crash the app even with invalid endpoint
|
|
134
|
+
// Errors are logged but don't throw
|
|
135
|
+
bridge.start()
|
|
136
|
+
assert.strictEqual(bridge.running, true)
|
|
137
|
+
|
|
138
|
+
// Clean up
|
|
139
|
+
bridge.stop()
|
|
140
|
+
assert.strictEqual(bridge.running, false)
|
|
141
|
+
})
|