@oas-tools/oas-telemetry 0.7.1 → 0.8.0-alpha.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.
Files changed (167) hide show
  1. package/.env.example +21 -2
  2. package/README.md +1 -2
  3. package/dist/cjs/config/bootConfig.cjs +19 -14
  4. package/dist/cjs/config/config.cjs +112 -125
  5. package/dist/cjs/config/config.types.cjs +1 -4
  6. package/dist/cjs/docs/openapi.yaml +158 -4
  7. package/dist/cjs/index.cjs +27 -30
  8. package/dist/cjs/routesManager.cjs +62 -70
  9. package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbLogExporter.cjs +202 -190
  10. package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.cjs +204 -99
  11. package/dist/cjs/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.cjs +152 -116
  12. package/dist/cjs/telemetry/custom-implementations/instrumentations/logsInstrumentation.cjs +92 -0
  13. package/dist/cjs/telemetry/custom-implementations/metrics/tsdb/Chunk.cjs +159 -0
  14. package/dist/cjs/telemetry/custom-implementations/metrics/tsdb/Series.cjs +168 -0
  15. package/dist/cjs/telemetry/custom-implementations/metrics/tsdb/SeriesRegistry.cjs +389 -0
  16. package/dist/cjs/telemetry/custom-implementations/metrics/tsdb/types.cjs +2 -0
  17. package/dist/cjs/telemetry/custom-implementations/metrics/tsdb/utils.cjs +77 -0
  18. package/dist/cjs/telemetry/custom-implementations/processors/dynamicMultiLogProcessor.cjs +65 -63
  19. package/dist/cjs/telemetry/custom-implementations/processors/dynamicMultiSpanProcessor.cjs +63 -62
  20. package/dist/cjs/telemetry/custom-implementations/utils/circular.cjs +47 -47
  21. package/dist/cjs/telemetry/custom-implementations/utils/storagePath.cjs +39 -0
  22. package/dist/cjs/telemetry/custom-implementations/wrappers.cjs +141 -138
  23. package/dist/cjs/telemetry/initializeTelemetry.cjs +35 -91
  24. package/dist/cjs/telemetry/telemetryConfigurator.cjs +70 -72
  25. package/dist/cjs/telemetry/telemetryRegistry.cjs +45 -31
  26. package/dist/cjs/tlm-ai/agent.cjs +49 -64
  27. package/dist/cjs/tlm-ai/aiController.cjs +54 -76
  28. package/dist/cjs/tlm-ai/aiRoutes.cjs +17 -20
  29. package/dist/cjs/tlm-ai/aiService.cjs +91 -95
  30. package/dist/cjs/tlm-ai/tools.cjs +177 -174
  31. package/dist/cjs/tlm-auth/authController.cjs +80 -123
  32. package/dist/cjs/tlm-auth/authMiddleware.cjs +25 -30
  33. package/dist/cjs/tlm-auth/authRoutes.cjs +11 -14
  34. package/dist/cjs/tlm-log/logController.cjs +171 -116
  35. package/dist/cjs/tlm-log/logRoutes.cjs +20 -20
  36. package/dist/cjs/tlm-metric/metricsController.cjs +211 -121
  37. package/dist/cjs/tlm-metric/metricsRoutes.cjs +23 -20
  38. package/dist/cjs/tlm-plugin/pluginController.cjs +128 -140
  39. package/dist/cjs/tlm-plugin/pluginProcess.cjs +89 -94
  40. package/dist/cjs/tlm-plugin/pluginRoutes.cjs +11 -14
  41. package/dist/cjs/tlm-plugin/pluginService.cjs +73 -74
  42. package/dist/cjs/tlm-trace/traceController.cjs +169 -117
  43. package/dist/cjs/tlm-trace/traceRoutes.cjs +20 -20
  44. package/dist/cjs/tlm-ui/uiRoutes.cjs +63 -32
  45. package/dist/cjs/tlm-util/utilController.cjs +68 -70
  46. package/dist/cjs/tlm-util/utilRoutes.cjs +51 -63
  47. package/dist/cjs/types/index.cjs +2 -5
  48. package/dist/cjs/utils/logger.cjs +38 -43
  49. package/dist/cjs/utils/regexUtils.cjs +22 -22
  50. package/dist/esm/config/bootConfig.js +6 -0
  51. package/dist/esm/config/config.js +1 -2
  52. package/dist/esm/docs/openapi.yaml +158 -4
  53. package/dist/esm/index.js +9 -8
  54. package/dist/esm/routesManager.js +6 -10
  55. package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbLogExporter.js +47 -8
  56. package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.js +164 -48
  57. package/dist/esm/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.js +69 -29
  58. package/dist/esm/telemetry/custom-implementations/instrumentations/logsInstrumentation.js +85 -0
  59. package/dist/esm/telemetry/custom-implementations/metrics/tsdb/Chunk.js +155 -0
  60. package/dist/esm/telemetry/custom-implementations/metrics/tsdb/Series.js +164 -0
  61. package/dist/esm/telemetry/custom-implementations/metrics/tsdb/SeriesRegistry.js +382 -0
  62. package/dist/esm/telemetry/custom-implementations/metrics/tsdb/types.js +1 -0
  63. package/dist/esm/telemetry/custom-implementations/metrics/tsdb/utils.js +74 -0
  64. package/dist/esm/telemetry/custom-implementations/processors/dynamicMultiLogProcessor.js +2 -1
  65. package/dist/esm/telemetry/custom-implementations/processors/dynamicMultiSpanProcessor.js +1 -1
  66. package/dist/esm/telemetry/custom-implementations/utils/storagePath.js +33 -0
  67. package/dist/esm/telemetry/custom-implementations/wrappers.js +5 -2
  68. package/dist/esm/telemetry/initializeTelemetry.js +27 -69
  69. package/dist/esm/telemetry/telemetryConfigurator.js +42 -40
  70. package/dist/esm/telemetry/telemetryRegistry.js +12 -1
  71. package/dist/esm/tlm-ai/agent.js +5 -3
  72. package/dist/esm/tlm-ai/aiController.js +3 -3
  73. package/dist/esm/tlm-ai/aiService.js +6 -2
  74. package/dist/esm/tlm-ai/tools.js +5 -9
  75. package/dist/esm/tlm-auth/authController.js +3 -2
  76. package/dist/esm/tlm-log/logController.js +84 -4
  77. package/dist/esm/tlm-log/logRoutes.js +5 -2
  78. package/dist/esm/tlm-metric/metricsController.js +172 -49
  79. package/dist/esm/tlm-metric/metricsRoutes.js +10 -4
  80. package/dist/esm/tlm-plugin/pluginController.js +6 -11
  81. package/dist/esm/tlm-plugin/pluginService.js +2 -4
  82. package/dist/esm/tlm-trace/traceController.js +102 -16
  83. package/dist/esm/tlm-trace/traceRoutes.js +5 -2
  84. package/dist/esm/tlm-ui/uiRoutes.js +5 -5
  85. package/dist/esm/tlm-util/utilController.js +3 -9
  86. package/dist/esm/tlm-util/utilRoutes.js +2 -2
  87. package/dist/types/config/bootConfig.d.ts +4 -0
  88. package/dist/types/config/config.d.ts +36 -7
  89. package/dist/types/config/config.types.d.ts +6 -0
  90. package/dist/types/index.d.ts +2 -3
  91. package/dist/types/telemetry/custom-implementations/exporters/InMemoryDbLogExporter.d.ts +4 -1
  92. package/dist/types/telemetry/custom-implementations/exporters/InMemoryDbMetricExporter.d.ts +60 -15
  93. package/dist/types/telemetry/custom-implementations/exporters/InMemoryDbSpanExporter.d.ts +9 -4
  94. package/dist/types/telemetry/custom-implementations/instrumentations/logsInstrumentation.d.ts +23 -0
  95. package/dist/types/telemetry/custom-implementations/metrics/tsdb/Chunk.d.ts +49 -0
  96. package/dist/types/telemetry/custom-implementations/metrics/tsdb/Series.d.ts +67 -0
  97. package/dist/types/telemetry/custom-implementations/metrics/tsdb/SeriesRegistry.d.ts +69 -0
  98. package/dist/types/telemetry/custom-implementations/metrics/tsdb/types.d.ts +68 -0
  99. package/dist/types/telemetry/custom-implementations/metrics/tsdb/utils.d.ts +21 -0
  100. package/dist/types/telemetry/custom-implementations/processors/dynamicMultiLogProcessor.d.ts +2 -2
  101. package/dist/types/telemetry/custom-implementations/utils/storagePath.d.ts +12 -0
  102. package/dist/types/telemetry/custom-implementations/wrappers.d.ts +1 -1
  103. package/dist/types/telemetry/telemetryConfigurator.d.ts +1 -1
  104. package/dist/types/telemetry/telemetryRegistry.d.ts +8 -0
  105. package/dist/types/tlm-ai/agent.d.ts +1 -1
  106. package/dist/types/tlm-ai/aiService.d.ts +1 -1
  107. package/dist/types/tlm-log/logController.d.ts +2 -0
  108. package/dist/types/tlm-metric/metricsController.d.ts +16 -2
  109. package/dist/types/tlm-trace/traceController.d.ts +3 -1
  110. package/dist/types/types/index.d.ts +2 -2
  111. package/dist/ui/assets/{ApiDocsPage-C_VVPPHa.js → ApiDocsPage-BFUrXE5F.js} +2 -2
  112. package/dist/ui/assets/CollapsibleCard-STA1GVQO.js +1 -0
  113. package/dist/ui/assets/DevToolsPage-BRSfZqO_.js +1 -0
  114. package/dist/ui/assets/LandingPage-DzeDy7q7.js +6 -0
  115. package/dist/ui/assets/LogsPage-BeiFrV2X.js +1 -0
  116. package/dist/ui/assets/{NotFoundPage-B3quk3P1.js → NotFoundPage-fRNOatbM.js} +1 -1
  117. package/dist/ui/assets/PluginCreatePage-Ch_RXsdf.js +50 -0
  118. package/dist/ui/assets/PluginPage-Cl65ZZ_n.js +27 -0
  119. package/dist/ui/assets/TraceSpansPage-BoK4M5Hh.js +6 -0
  120. package/dist/ui/assets/VirtualizedListPanel-zcj0v7DL.js +16 -0
  121. package/dist/ui/assets/alert-BkNVKxJN.js +1133 -0
  122. package/dist/ui/assets/badge-CN7FeufU.js +1 -0
  123. package/dist/ui/assets/{chevron-down-CPsvsmqj.js → chevron-down-CG--ounh.js} +1 -1
  124. package/dist/ui/assets/{chevron-up-Df9jMo1X.js → chevron-up-B6tzMAOm.js} +1 -1
  125. package/dist/ui/assets/{circle-alert-DOPQPvU8.js → circle-alert-BDF8Tq9y.js} +1 -1
  126. package/dist/ui/assets/dialog-BrpWNk36.js +15 -0
  127. package/dist/ui/assets/index-6xOVKwKn.js +305 -0
  128. package/dist/ui/assets/index-D6f1KjWV.css +1 -0
  129. package/dist/ui/assets/index-D96rVSkR.js +1 -0
  130. package/dist/ui/assets/info-99kuqpbx.js +6 -0
  131. package/dist/ui/assets/{input-Dzvg_ZEZ.js → input-B-01QDg_.js} +1 -1
  132. package/dist/ui/assets/label-CQLeZjM1.js +1 -0
  133. package/dist/ui/assets/{loader-circle-CrvlRy5o.js → loader-circle-BoDGk-BO.js} +1 -1
  134. package/dist/ui/assets/{loginPage-qa4V-B70.js → loginPage-8F4EEd1B.js} +1 -1
  135. package/dist/ui/assets/metrics-page-D1GxaB_c.css +1 -0
  136. package/dist/ui/assets/metrics-page-DPtteXqY.js +31 -0
  137. package/dist/ui/assets/popover-DS_8DYYt.js +11 -0
  138. package/dist/ui/assets/select-DYjegiXi.js +6 -0
  139. package/dist/ui/assets/separator-DGsRxIrl.js +6 -0
  140. package/dist/ui/assets/severityOptions-DEOvJqC9.js +11 -0
  141. package/dist/ui/assets/square-pen-DPhgYz6O.js +6 -0
  142. package/dist/ui/assets/switch-Di9NJH2A.js +1 -0
  143. package/dist/ui/assets/trace-DJq1miYa.js +1 -0
  144. package/dist/ui/assets/upload-BiLTpCnX.js +11 -0
  145. package/dist/ui/assets/{utilService-DNyqzwj0.js → utilService-CNZOmadC.js} +1 -1
  146. package/dist/ui/assets/wand-sparkles-CPoBNFFg.js +6 -0
  147. package/dist/ui/index.html +2 -2
  148. package/package.json +44 -48
  149. package/dist/ui/assets/CollapsibleCard-B3KR_8mL.js +0 -1
  150. package/dist/ui/assets/DevToolsPage-OyZcDcmw.js +0 -1
  151. package/dist/ui/assets/LandingPage-CppFBA6K.js +0 -6
  152. package/dist/ui/assets/LogsPage-9Fq8GArS.js +0 -26
  153. package/dist/ui/assets/PluginCreatePage-X_aCH4t4.js +0 -50
  154. package/dist/ui/assets/PluginPage-DMDSihrZ.js +0 -27
  155. package/dist/ui/assets/alert-jQ9HCPIf.js +0 -1133
  156. package/dist/ui/assets/badge-CNq0-mH5.js +0 -1
  157. package/dist/ui/assets/card-DFAwwhN3.js +0 -1
  158. package/dist/ui/assets/index-BkD6DijD.js +0 -15
  159. package/dist/ui/assets/index-CERGVYZK.js +0 -292
  160. package/dist/ui/assets/index-CSIPf9qw.css +0 -1
  161. package/dist/ui/assets/label-DuVnkZ4q.js +0 -1
  162. package/dist/ui/assets/select-DhS8YUtJ.js +0 -1
  163. package/dist/ui/assets/separator-isK4chBP.js +0 -6
  164. package/dist/ui/assets/severityOptions-O38dSOfk.js +0 -11
  165. package/dist/ui/assets/switch-Z3mImG9n.js +0 -1
  166. package/dist/ui/assets/tabs-_77MUUQe.js +0 -16
  167. package/dist/ui/assets/upload-C1LT4Gkb.js +0 -16
@@ -0,0 +1,382 @@
1
+ import { Series } from './Series.js';
2
+ import { Chunk } from './Chunk.js';
3
+ /*
4
+ OTEL export = Resource (unchanged) + SCOPE METRICS
5
+ scopeMetrics: {
6
+ scope: {name: library, version: 1.0.0},; //repeated for each metric
7
+ metrics: [
8
+ {
9
+ //Metadata (Repeated for each dataPoint)
10
+ descriptor: {name: metricName, unit, description, ...},
11
+ aggregationTemporality,
12
+ dataPointType,
13
+ // Actual dataPoints are stored in Series
14
+ dataPoints: []
15
+ }
16
+ ]
17
+ }
18
+ */
19
+ // Import fs at top level for disk operations
20
+ import fs from 'fs';
21
+ export class SeriesRegistry {
22
+ // Main storage
23
+ series = new Map(); // seriesId (scopeId:metricName:attributesId) -> Series
24
+ // Cached repeated data
25
+ scopes = new Map(); // scopeId -> scope
26
+ metricMetadataMap = new Map(); // "scopeId:metricName" -> MetricMetadata
27
+ // Faster lookup index
28
+ metricIdIndex = new Map(); // "scopeId:metricName" -> Set<seriesId>
29
+ chunkSize;
30
+ maxChunks;
31
+ constructor(chunkSize = 120, maxChunks = 60) {
32
+ this.chunkSize = chunkSize;
33
+ this.maxChunks = maxChunks;
34
+ }
35
+ /**
36
+ * Store entire ScopeMetrics array efficiently
37
+ * Replaces addSample - processes all metrics in one batch
38
+ */
39
+ storeScopeMetrics(scopeMetrics) {
40
+ for (const scopeMetric of scopeMetrics) {
41
+ const scope = scopeMetric.scope;
42
+ const scopeId = scopeToId(scope);
43
+ this.scopes.set(scopeId, scope);
44
+ for (const metricData of scopeMetric.metrics) {
45
+ const metricName = metricData.descriptor.name;
46
+ const metricId = makeMetricId(scopeId, metricName);
47
+ if (!this.metricMetadataMap.has(metricId)) {
48
+ const { dataPoints: _dataPoints, ...metadata } = metricData;
49
+ this.metricMetadataMap.set(metricId, metadata);
50
+ }
51
+ for (const dp of metricData.dataPoints) {
52
+ const startTime = dp.startTime[0] * 1_000_000_000 + dp.startTime[1];
53
+ const endTime = dp.endTime[0] * 1_000_000_000 + dp.endTime[1];
54
+ const attributes = dp.attributes;
55
+ const attributesId = attributesToId(attributes);
56
+ const seriesId = makeSeriesId(metricId, attributesId);
57
+ if (!this.series.has(seriesId)) {
58
+ this.series.set(seriesId, new Series({ hash: 0, labels: attributes, originalAttributes: attributes }, this.metricMetadataMap.get(metricId), this.chunkSize, this.maxChunks));
59
+ (this.metricIdIndex.get(metricId) ?? this.metricIdIndex.set(metricId, new Set()).get(metricId)).add(seriesId);
60
+ }
61
+ this.series.get(seriesId).append(startTime, endTime, dp.value);
62
+ }
63
+ }
64
+ }
65
+ }
66
+ query(scopeMetrics, startTime, endTime) {
67
+ // If no queries provided, get all scopeMetrics
68
+ if (!scopeMetrics || scopeMetrics.length === 0) {
69
+ const queries = Array.from(this.metricIdIndex.keys()).map(indexKey => {
70
+ const [scopeId, metricName] = indexKey.split(':').slice(0, 2);
71
+ const scope = this.scopes.get(scopeId);
72
+ return {
73
+ scope,
74
+ descriptor: { name: metricName },
75
+ filters: undefined
76
+ };
77
+ });
78
+ return queries
79
+ .map(query => this._querySingle(query, startTime, endTime))
80
+ .filter((result) => result !== null);
81
+ }
82
+ return scopeMetrics
83
+ .map(query => this._querySingle(query, startTime, endTime))
84
+ .filter((result) => result !== null);
85
+ }
86
+ /**
87
+ * Query single metric with attribute filters
88
+ */
89
+ _querySingle(query, startTime, endTime) {
90
+ const scopeId = scopeToId(query.scope);
91
+ const indexKey = makeMetricId(scopeId, query.descriptor.name);
92
+ const seriesKeysInMetric = this.metricIdIndex.get(indexKey);
93
+ if (!seriesKeysInMetric || seriesKeysInMetric.size === 0) {
94
+ return null;
95
+ }
96
+ const scope = this.scopes.get(scopeId);
97
+ const metricData = this.metricMetadataMap.get(indexKey);
98
+ if (!scope || !metricData) {
99
+ return null;
100
+ }
101
+ // Filter by attributes if provided
102
+ const filteredSeriesKeys = query.filters
103
+ ? Array.from(seriesKeysInMetric).filter(key => {
104
+ const series = this.series.get(key);
105
+ return this._matchesAttributeFilters(series, query.filters);
106
+ })
107
+ : Array.from(seriesKeysInMetric);
108
+ if (filteredSeriesKeys.length === 0) {
109
+ return null;
110
+ }
111
+ const matchingSeries = filteredSeriesKeys
112
+ .map(key => {
113
+ const series = this.series.get(key);
114
+ const { startTimes, endTimes, values } = series.querySlices({ startTime, endTime, includeStartTimes: true });
115
+ return {
116
+ id: key,
117
+ attributes: series.getOriginalAttributes(),
118
+ startTimes: startTimes ? Array.from(startTimes) : undefined,
119
+ endTimes: Array.from(endTimes),
120
+ values: Array.isArray(values) ? values : Array.from(values)
121
+ };
122
+ })
123
+ .filter(s => s.endTimes.length > 0);
124
+ return {
125
+ scope,
126
+ descriptor: metricData.descriptor,
127
+ series: matchingSeries
128
+ };
129
+ }
130
+ /**
131
+ * Match series attributes against filters
132
+ * Supports exact match, negation (!), and regex (~)
133
+ */
134
+ _matchesAttributeFilters(series, filters) {
135
+ if (!filters || Object.keys(filters).length === 0) {
136
+ return true;
137
+ }
138
+ const attrs = series.getOriginalAttributes();
139
+ for (const [key, filterValue] of Object.entries(filters)) {
140
+ const attrValue = String(attrs[key] ?? '');
141
+ // Regex match: status=~4.*
142
+ if (filterValue.startsWith('~')) {
143
+ const pattern = filterValue.slice(1);
144
+ try {
145
+ const regex = new RegExp(pattern);
146
+ if (!regex.test(attrValue)) {
147
+ return false;
148
+ }
149
+ }
150
+ catch {
151
+ if (attrValue !== filterValue) {
152
+ return false;
153
+ }
154
+ }
155
+ }
156
+ // Negation: status=!200
157
+ else if (filterValue.startsWith('!')) {
158
+ const negatedValue = filterValue.slice(1);
159
+ if (attrValue === negatedValue) {
160
+ return false;
161
+ }
162
+ }
163
+ // Exact match: method=GET
164
+ else {
165
+ if (attrValue !== filterValue) {
166
+ return false;
167
+ }
168
+ }
169
+ }
170
+ return true;
171
+ }
172
+ evictOldData(retentionTimeNs) {
173
+ const thresholdTime = Date.now() * 1_000_000 - retentionTimeNs;
174
+ let evictedChunks = 0;
175
+ let evictedSeries = 0;
176
+ for (const [key, series] of this.series.entries()) {
177
+ evictedChunks += series.evictOldChunks(thresholdTime);
178
+ if (series.isEmpty()) {
179
+ this.series.delete(key);
180
+ evictedSeries++;
181
+ }
182
+ }
183
+ return { evictedChunks, evictedSeries };
184
+ }
185
+ getStats() {
186
+ const seriesStats = Array.from(this.series.values()).map(s => s.getStats());
187
+ const totalSamples = seriesStats.reduce((sum, s) => sum + s.samples, 0);
188
+ const totalMemory = seriesStats.reduce((sum, s) => sum + s.memoryBytes, 0);
189
+ return {
190
+ totalMetrics: this.metricMetadataMap.size,
191
+ totalScopes: this.scopes.size,
192
+ totalSeries: this.series.size,
193
+ totalSamples,
194
+ memoryUsageBytes: totalMemory
195
+ };
196
+ }
197
+ reset() {
198
+ this.series.clear();
199
+ this.scopes.clear();
200
+ this.metricMetadataMap.clear();
201
+ this.metricIdIndex.clear();
202
+ }
203
+ size() {
204
+ return this.series.size;
205
+ }
206
+ /**
207
+ * Serialize registry to NDJSON format (newline-delimited JSON)
208
+ * Each line is a complete record: metadata, scope, metric, or data point
209
+ * Format:
210
+ * {"type":"header","version":1,"timestamp":"...","stats":{...}}
211
+ * {"type":"scope","id":"...","data":{...}}
212
+ * {"type":"metric","id":"...","data":{...}}
213
+ * {"type":"series","id":"...","labelSet":{...},"chunks":[...]}
214
+ */
215
+ serializeToNDJSON() {
216
+ const lines = [];
217
+ // Header
218
+ lines.push(JSON.stringify({
219
+ type: 'header',
220
+ version: 1,
221
+ timestamp: new Date().toISOString(),
222
+ stats: this.getStats()
223
+ }));
224
+ // Scopes
225
+ for (const [scopeId, scope] of this.scopes.entries()) {
226
+ lines.push(JSON.stringify({
227
+ type: 'scope',
228
+ id: scopeId,
229
+ data: scope
230
+ }));
231
+ }
232
+ // Metric metadata
233
+ for (const [metricId, metadata] of this.metricMetadataMap.entries()) {
234
+ lines.push(JSON.stringify({
235
+ type: 'metric',
236
+ id: metricId,
237
+ data: metadata
238
+ }));
239
+ }
240
+ // Series headers + chunks (one chunk per line for streaming efficiency)
241
+ const serializedSeriesHeaders = new Set();
242
+ for (const [seriesId, series] of this.series.entries()) {
243
+ const seriesPrivate = series;
244
+ // Emit series header once per series
245
+ if (!serializedSeriesHeaders.has(seriesId)) {
246
+ lines.push(JSON.stringify({
247
+ type: 'series',
248
+ seriesId: seriesId,
249
+ labelSet: seriesPrivate.labelSet,
250
+ metadata: seriesPrivate.metadata
251
+ }));
252
+ serializedSeriesHeaders.add(seriesId);
253
+ }
254
+ (seriesPrivate.chunks || []).forEach((chunk, chunkIndex) => {
255
+ const slicedStartTimes = chunk.startTimes.slice(0, chunk.cursor);
256
+ const slicedEndTimes = chunk.endTimes.slice(0, chunk.cursor);
257
+ const slicedValues = chunk.values.slice(0, chunk.cursor);
258
+ const slicedHistograms = chunk.histograms.slice(0, chunk.cursor);
259
+ lines.push(JSON.stringify({
260
+ type: 'chunk',
261
+ seriesId: seriesId,
262
+ chunkIndex: chunkIndex,
263
+ startTimes: Array.from(slicedStartTimes),
264
+ endTimes: Array.from(slicedEndTimes),
265
+ values: Array.from(slicedValues),
266
+ histograms: Array.from(slicedHistograms),
267
+ cursor: chunk.cursor,
268
+ minEndTime: chunk.minEndTime,
269
+ maxEndTime: chunk.maxEndTime,
270
+ isHistogram: chunk.isHistogram
271
+ }));
272
+ });
273
+ }
274
+ // Metric ID index for fast lookup
275
+ for (const [metricId, seriesIdSet] of this.metricIdIndex.entries()) {
276
+ lines.push(JSON.stringify({
277
+ type: 'index',
278
+ id: metricId,
279
+ seriesIds: Array.from(seriesIdSet)
280
+ }));
281
+ }
282
+ return lines.join('\n');
283
+ }
284
+ /**
285
+ * Deserialize from NDJSON format - restore from chunk lines
286
+ */
287
+ deserializeFromNDJSON(ndjsonData) {
288
+ try {
289
+ const lines = ndjsonData.trim().split('\n');
290
+ for (const line of lines) {
291
+ if (!line.trim())
292
+ continue;
293
+ const record = JSON.parse(line);
294
+ switch (record.type) {
295
+ case 'header':
296
+ // Just metadata
297
+ break;
298
+ case 'scope':
299
+ this.scopes.set(record.id, record.data);
300
+ break;
301
+ case 'metric':
302
+ this.metricMetadataMap.set(record.id, record.data);
303
+ break;
304
+ case 'index':
305
+ this.metricIdIndex.set(record.id, new Set(record.seriesIds));
306
+ break;
307
+ case 'series': {
308
+ // Create series stub - will be populated by subsequent chunk lines
309
+ const metadata = this.metricMetadataMap.get(record.seriesId.substring(0, record.seriesId.lastIndexOf('$'))) || record.metadata;
310
+ if (metadata) {
311
+ const series = new Series(record.labelSet, metadata, this.chunkSize, this.maxChunks);
312
+ this.series.set(record.seriesId, series);
313
+ }
314
+ break;
315
+ }
316
+ case 'chunk': {
317
+ // Restore chunk to series
318
+ const series = this.series.get(record.seriesId);
319
+ if (series) {
320
+ const chunk = new Chunk(record.cursor || record.startTimes.length, record.isHistogram);
321
+ const chunkPrivate = chunk;
322
+ chunkPrivate.startTimes = new Float64Array(record.startTimes);
323
+ chunkPrivate.endTimes = new Float64Array(record.endTimes);
324
+ chunkPrivate.values = new Float64Array(record.values);
325
+ chunkPrivate.histograms = record.histograms;
326
+ chunkPrivate.cursor = record.cursor;
327
+ chunkPrivate.minEndTime = record.minEndTime;
328
+ chunkPrivate.maxEndTime = record.maxEndTime;
329
+ chunkPrivate.isHistogram = record.isHistogram;
330
+ const seriesPrivate = series;
331
+ seriesPrivate.chunks.push(chunk);
332
+ }
333
+ break;
334
+ }
335
+ }
336
+ }
337
+ }
338
+ catch {
339
+ // Silently fail if any parsing fails
340
+ }
341
+ }
342
+ /**
343
+ * Save registry to disk as NDJSON (one chunk per line)
344
+ */
345
+ saveToDisk(filePath) {
346
+ try {
347
+ const ndjsonData = this.serializeToNDJSON();
348
+ fs.writeFileSync(filePath, ndjsonData);
349
+ }
350
+ catch {
351
+ // Silently fail - don't interrupt operations
352
+ }
353
+ }
354
+ /**
355
+ * Load registry from disk (NDJSON format)
356
+ */
357
+ loadFromDisk(filePath) {
358
+ try {
359
+ if (!fs.existsSync(filePath)) {
360
+ return;
361
+ }
362
+ const ndjsonData = fs.readFileSync(filePath, 'utf-8');
363
+ this.deserializeFromNDJSON(ndjsonData);
364
+ }
365
+ catch {
366
+ // Silently fail during boot
367
+ }
368
+ }
369
+ }
370
+ // Utility: Create deterministic ID from attributes
371
+ function attributesToId(attributes) {
372
+ return Object.keys(attributes).sort().map(key => `${key}=${attributes[key]}`).join(',') || 'no_attrs';
373
+ }
374
+ function scopeToId(scope) {
375
+ return `${scope.name}@${scope.version ?? 'no_version'}`;
376
+ }
377
+ function makeMetricId(scopeId, metricName) {
378
+ return `${scopeId}:${metricName}`;
379
+ }
380
+ function makeSeriesId(metricId, attributesId) {
381
+ return `${metricId}$${attributesId}`;
382
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Convert raw format to OpenTelemetry format
3
+ *
4
+ * Raw format structure (same for GET response and INSERT input):
5
+ * {
6
+ * scope: { name, version },
7
+ * descriptor: { unit, description, ... },
8
+ * series: [{ attributes, startTimes[], endTimes[], values[] }]
9
+ * }
10
+ *
11
+ * OTEL format structure:
12
+ * {
13
+ * scope: { name, version },
14
+ * metrics: [{ descriptor, dataPoints: [{ attributes, startTime, endTime, value }] }]
15
+ * }
16
+ *
17
+ * Groups dataPoints by endTime to reconstruct original exports
18
+ */
19
+ export function rawToOtel(rawScopeMetrics) {
20
+ const groupedByScope = new Map();
21
+ for (const result of rawScopeMetrics) {
22
+ // Validate that result matches expected raw format
23
+ if (!result.scope) {
24
+ throw new Error(`Invalid metric format: Missing 'scope' property. ` +
25
+ `Expected format: {scope: {name, version}, descriptor: {name, ...}, series: [{attributes, startTimes, endTimes, values}]}. ` +
26
+ `Got: ${JSON.stringify(result).substring(0, 200)}`);
27
+ }
28
+ if (!result.descriptor) {
29
+ throw new Error(`Invalid metric format: Missing 'descriptor' property in metric. ` +
30
+ `Expected format: {scope: {name, version}, descriptor: {name, ...}, series: [{attributes, startTimes, endTimes, values}]}`);
31
+ }
32
+ if (!Array.isArray(result.series)) {
33
+ throw new Error(`Invalid metric format: 'series' must be an array. ` +
34
+ `Expected format: {scope: {name, version}, descriptor: {name, ...}, series: [{attributes, startTimes, endTimes, values}]}`);
35
+ }
36
+ const scopeId = result.scope.version
37
+ ? `${result.scope.name}@${result.scope.version}`
38
+ : `${result.scope.name}@none`;
39
+ if (!groupedByScope.has(scopeId)) {
40
+ groupedByScope.set(scopeId, {
41
+ scope: {
42
+ name: result.scope.name,
43
+ version: result.scope.version || ''
44
+ },
45
+ metrics: []
46
+ });
47
+ }
48
+ const scopeData = groupedByScope.get(scopeId);
49
+ const dataPoints = [];
50
+ for (const series of result.series) {
51
+ for (let i = 0; i < series.endTimes.length; i++) {
52
+ const startTimeNs = series.startTimes?.[i];
53
+ const endTimeNs = series.endTimes[i];
54
+ if (!startTimeNs)
55
+ continue;
56
+ const startSec = Math.floor(startTimeNs / 1_000_000_000);
57
+ const startNano = startTimeNs % 1_000_000_000;
58
+ const endSec = Math.floor(endTimeNs / 1_000_000_000);
59
+ const endNano = endTimeNs % 1_000_000_000;
60
+ dataPoints.push({
61
+ attributes: series.attributes,
62
+ startTime: [startSec, startNano],
63
+ endTime: [endSec, endNano],
64
+ value: series.values[i]
65
+ });
66
+ }
67
+ }
68
+ scopeData.metrics.push({
69
+ descriptor: result.descriptor,
70
+ dataPoints
71
+ });
72
+ }
73
+ return Array.from(groupedByScope.values());
74
+ }
@@ -1,8 +1,9 @@
1
1
  import { callWithTimeout } from '@opentelemetry/core';
2
2
  import logger from '../../../utils/logger.js'; // optional if you want logging
3
3
  export class DynamicMultiLogRecordProcessor {
4
+ _processors = [];
5
+ _forceFlushTimeoutMillis;
4
6
  constructor(initialProcessors = [], forceFlushTimeoutMillis = 30000) {
5
- this._processors = [];
6
7
  this._processors = [...initialProcessors];
7
8
  this._forceFlushTimeoutMillis = forceFlushTimeoutMillis;
8
9
  }
@@ -1,7 +1,7 @@
1
1
  import logger from '../../../utils/logger.js';
2
2
  export class DynamicMultiSpanProcessor {
3
+ _spanProcessors = [];
3
4
  constructor(initialProcessors = []) {
4
- this._spanProcessors = [];
5
5
  this._spanProcessors = [...initialProcessors];
6
6
  }
7
7
  /**
@@ -0,0 +1,33 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { bootEnvVariables } from '../../../config/bootConfig.js';
4
+ /**
5
+ * Get storage path for disk-based persistence
6
+ * Returns null if storage is in-memory (when OASTLM_BOOT_STORAGE_PATH is empty or not set)
7
+ *
8
+ * Environment variables (BOOT - set at startup):
9
+ * - OASTLM_BOOT_STORAGE_PATH: empty or not set = in-memory, else = disk path
10
+ * Examples:
11
+ * - OASTLM_BOOT_STORAGE_PATH="" (or not set) -> in-memory
12
+ * - OASTLM_BOOT_STORAGE_PATH="data/telemetry" -> disk at ./data/telemetry
13
+ * - OASTLM_BOOT_STORAGE_PATH="/var/lib/telemetry" -> disk at /var/lib/telemetry
14
+ */
15
+ export function getStoragePath(name) {
16
+ // If storage path is empty, use in-memory
17
+ if (!bootEnvVariables.OASTLM_BOOT_STORAGE_PATH) {
18
+ console.warn(`BOOT STORAGE: No storage path configured for ${name}. Using in-memory storage. To enable disk storage, set OASTLM_BOOT_STORAGE_PATH environment variable.`);
19
+ return null;
20
+ }
21
+ const filePath = path.join(bootEnvVariables.OASTLM_BOOT_STORAGE_PATH, `${name}.db`);
22
+ try {
23
+ const dir = path.dirname(filePath);
24
+ if (!fs.existsSync(dir)) {
25
+ fs.mkdirSync(dir, { recursive: true });
26
+ }
27
+ }
28
+ catch (error) {
29
+ console.warn(`Failed to create storage directory for ${name}`, error);
30
+ return null;
31
+ }
32
+ return filePath;
33
+ }
@@ -1,8 +1,8 @@
1
1
  import { resourceFromAttributes } from "@opentelemetry/resources";
2
2
  import logger from "../../utils/logger.js";
3
3
  export class Enabler {
4
+ _enabled = true;
4
5
  constructor(enabled) {
5
- this._enabled = true;
6
6
  if (typeof enabled === 'boolean') {
7
7
  this._enabled = enabled;
8
8
  }
@@ -40,6 +40,7 @@ export class Enabler {
40
40
  *
41
41
  */
42
42
  export class EnablerSpanExporter extends Enabler {
43
+ exporter;
43
44
  constructor(exporter) {
44
45
  super();
45
46
  this.exporter = exporter;
@@ -60,6 +61,7 @@ export class EnablerSpanExporter extends Enabler {
60
61
  }
61
62
  }
62
63
  export class EnablerLogExporter extends Enabler {
64
+ exporter;
63
65
  constructor(exporter) {
64
66
  super();
65
67
  this.exporter = exporter;
@@ -74,9 +76,9 @@ export class EnablerLogExporter extends Enabler {
74
76
  }
75
77
  }
76
78
  class EnablerMultiExporter extends Enabler {
79
+ _exporters = [];
77
80
  constructor(exporters) {
78
81
  super();
79
- this._exporters = [];
80
82
  if (exporters && Array.isArray(exporters)) {
81
83
  this._exporters = exporters;
82
84
  }
@@ -121,6 +123,7 @@ export class EnablerMultiLogExporter extends EnablerMultiExporter {
121
123
  }
122
124
  }
123
125
  export class EnablerMetricReader extends Enabler {
126
+ reader;
124
127
  constructor(reader) {
125
128
  super();
126
129
  this.reader = reader;
@@ -1,79 +1,37 @@
1
- import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
2
1
  import logger from '../utils/logger.js';
3
- import { registerInstrumentations } from '@opentelemetry/instrumentation';
4
- import { dynamicMultiLogProcessor, dynamicMultiSpanProcessor, oasTelemetryResource, originalConsoleMethods } from './telemetryRegistry.js';
5
- import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node';
6
- import { SeverityNumber } from '@opentelemetry/api-logs';
7
- import { LoggerProvider } from '@opentelemetry/sdk-logs';
8
2
  import { bootEnvVariables } from '../config/bootConfig.js';
9
- import util from 'util';
3
+ import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
4
+ import { instrumentations, isBootInitialized, setBootInitialized } from './telemetryRegistry.js';
5
+ import { LogsInstrumentation } from './custom-implementations/instrumentations/logsInstrumentation.js';
6
+ import { RuntimeNodeInstrumentation } from '@opentelemetry/instrumentation-runtime-node';
10
7
  // THIS INSTRUMENTATIONS NEED TO BE LOADED BEFORE ANYTHING ELSE
11
8
  // They use monkey-patching to instrument the HTTP server and client.
12
9
  // THIS FILE MUST BE CALLED BEFORE ANYTHING ELSE
13
10
  if (bootEnvVariables.OASTLM_BOOT_MODULE_DISABLED) {
14
- logger.info('🚫 OASTLM module is disabled, Providers not initialized.');
11
+ logger.info('🚫 OASTLM module is disabled, Auto Instrumentations not initialized.');
15
12
  }
16
13
  else {
17
- logger.info('🚀 Initializing Open Telemetry');
18
- initializeTraces();
19
- initializeMetrics();
20
- initializeLogs();
21
- registerInstrumentations({
22
- instrumentations: [
23
- new HttpInstrumentation(),
24
- // new ExpressInstrumentation(),
25
- ],
26
- });
27
- }
28
- function initializeTraces() {
29
- logger.info('📊 Initializing TracerProvider');
30
- const tracerProvider = new NodeTracerProvider({
31
- resource: oasTelemetryResource,
32
- spanProcessors: [dynamicMultiSpanProcessor]
33
- });
34
- tracerProvider.register();
35
- }
36
- function initializeLogs() {
37
- logger.info('📜 Initializing LoggerProvider');
38
- // Create and configure LoggerProvider
39
- const loggerProvider = new LoggerProvider({
40
- resource: oasTelemetryResource,
41
- processors: [dynamicMultiLogProcessor]
42
- });
43
- // Get a logger instance
44
- const loggerInstance = loggerProvider.getLogger('oas-telemetry'); // Use loggerProvider to get the logger
45
- Object.keys(originalConsoleMethods).forEach((method) => {
46
- // @ts-expect-error yes
47
- console[method] = (...args) => {
48
- const severity = getSeverityForMethod(method);
49
- loggerInstance.emit({
50
- severityNumber: severity.number,
51
- severityText: severity.text,
52
- body: util.format(...args),
53
- attributes: { 'source': `console.${method}`, "library": "oas-telemetry" },
54
- });
55
- // @ts-expect-error yes
56
- originalConsoleMethods[method](...args);
57
- };
58
- });
59
- }
60
- function initializeMetrics() {
61
- logger.info('📈 Initializing MeterProvider');
62
- // WARN: Default PeriodicExportingMetricReader is added post initialization (see telemetryConfigurator.ts)
63
- // The in memory exporter is added by default to that reader. More readers are allowed to be added dynamically
64
- }
65
- function getSeverityForMethod(method) {
66
- switch (method) {
67
- case "log":
68
- case "info":
69
- return { number: SeverityNumber.INFO, text: "INFO" };
70
- case "debug":
71
- return { number: SeverityNumber.DEBUG, text: "DEBUG" };
72
- case "warn":
73
- return { number: SeverityNumber.WARN, text: "WARN" };
74
- case "error":
75
- return { number: SeverityNumber.ERROR, text: "ERROR" };
76
- default:
77
- return { number: SeverityNumber.INFO, text: "INFO" };
14
+ if (!isBootInitialized()) {
15
+ // Only HTTP instrumentation por ahora
16
+ const httpInstrumentation = new HttpInstrumentation({
17
+ // Ignore internal telemetry routes
18
+ ignoreIncomingRequestHook: (req) => {
19
+ const url = req.url || '';
20
+ const path = url.split('?')[0]; // Remove query params
21
+ // Ignore all baseUrl routes except those with 'generate'
22
+ if (path.includes(bootEnvVariables.OASTLM_BOOT_BASE_URL)) {
23
+ return !path.includes('generate');
24
+ }
25
+ return false;
26
+ }
27
+ });
28
+ if (!bootEnvVariables.OASTLM_BOOT_AUTOINSTRUMENTATIONS_NODE_DISABLED) {
29
+ instrumentations.push(httpInstrumentation, new RuntimeNodeInstrumentation());
30
+ }
31
+ if (!bootEnvVariables.OASTLM_BOOT_AUTOINSTRUMENTATIONS_LOGS_DISABLED) {
32
+ instrumentations.push(new LogsInstrumentation());
33
+ }
34
+ setBootInitialized(true);
35
+ logger.info('✅ Auto Instrumentations created successfully');
78
36
  }
79
37
  }