react-native-otel 0.1.0 → 0.1.4

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 (54) hide show
  1. package/lib/module/context/span-context.js +56 -5
  2. package/lib/module/context/span-context.js.map +1 -1
  3. package/lib/module/core/ids.js +21 -7
  4. package/lib/module/core/ids.js.map +1 -1
  5. package/lib/module/core/meter.js +73 -10
  6. package/lib/module/core/meter.js.map +1 -1
  7. package/lib/module/core/tracer.js +71 -2
  8. package/lib/module/core/tracer.js.map +1 -1
  9. package/lib/module/exporters/console-exporter.js +8 -1
  10. package/lib/module/exporters/console-exporter.js.map +1 -1
  11. package/lib/module/exporters/otlp-http-exporter.js +94 -20
  12. package/lib/module/exporters/otlp-http-exporter.js.map +1 -1
  13. package/lib/module/exporters/wal.js +73 -0
  14. package/lib/module/exporters/wal.js.map +1 -0
  15. package/lib/module/index.js.map +1 -1
  16. package/lib/module/instrumentation/errors.js +21 -0
  17. package/lib/module/instrumentation/errors.js.map +1 -1
  18. package/lib/module/instrumentation/network.js +11 -0
  19. package/lib/module/instrumentation/network.js.map +1 -1
  20. package/lib/module/sdk.js +24 -2
  21. package/lib/module/sdk.js.map +1 -1
  22. package/lib/typescript/src/context/span-context.d.ts +14 -4
  23. package/lib/typescript/src/context/span-context.d.ts.map +1 -1
  24. package/lib/typescript/src/core/ids.d.ts.map +1 -1
  25. package/lib/typescript/src/core/meter.d.ts +10 -2
  26. package/lib/typescript/src/core/meter.d.ts.map +1 -1
  27. package/lib/typescript/src/core/tracer.d.ts +10 -5
  28. package/lib/typescript/src/core/tracer.d.ts.map +1 -1
  29. package/lib/typescript/src/exporters/console-exporter.d.ts.map +1 -1
  30. package/lib/typescript/src/exporters/otlp-http-exporter.d.ts +11 -2
  31. package/lib/typescript/src/exporters/otlp-http-exporter.d.ts.map +1 -1
  32. package/lib/typescript/src/exporters/types.d.ts +17 -3
  33. package/lib/typescript/src/exporters/types.d.ts.map +1 -1
  34. package/lib/typescript/src/exporters/wal.d.ts +19 -0
  35. package/lib/typescript/src/exporters/wal.d.ts.map +1 -0
  36. package/lib/typescript/src/index.d.ts +2 -0
  37. package/lib/typescript/src/index.d.ts.map +1 -1
  38. package/lib/typescript/src/instrumentation/errors.d.ts.map +1 -1
  39. package/lib/typescript/src/instrumentation/network.d.ts.map +1 -1
  40. package/lib/typescript/src/sdk.d.ts +1 -0
  41. package/lib/typescript/src/sdk.d.ts.map +1 -1
  42. package/package.json +7 -2
  43. package/src/context/span-context.ts +61 -8
  44. package/src/core/ids.ts +21 -7
  45. package/src/core/meter.ts +103 -12
  46. package/src/core/tracer.ts +99 -13
  47. package/src/exporters/console-exporter.ts +18 -4
  48. package/src/exporters/otlp-http-exporter.ts +116 -23
  49. package/src/exporters/types.ts +24 -3
  50. package/src/exporters/wal.ts +86 -0
  51. package/src/index.ts +2 -0
  52. package/src/instrumentation/errors.ts +27 -0
  53. package/src/instrumentation/network.ts +10 -0
  54. package/src/sdk.ts +31 -2
@@ -1,14 +1,65 @@
1
1
  "use strict";
2
2
 
3
- // Module-level singleton. Tracks only the current active screen span.
4
- // Network spans do NOT touch this context (handled via activeNetworkSpans map).
3
+ // Public interface what external consumers see when they import spanContext.
4
+ // Hides push/pop to prevent misuse; use tracer.startActiveSpan() or
5
+ // tracer.withSpan() for safe nested context management.
6
+
7
+ // Each manual push is tracked by a unique token so that pop() can find the
8
+ // exact entry to remove regardless of concurrent async interleaving.
9
+
5
10
  class SpanContextManager {
11
+ // Screen-level span set by navigation instrumentation.
12
+
13
+ // Manual spans pushed by startActiveSpan / withSpan.
14
+ // Separate from screenSpan_ because they have different lifecycles.
15
+ manualStack_ = [];
16
+
17
+ // ─── Public API (backward-compatible) ──────────────────────────────────────
18
+
19
+ // Set the screen-level span. Called by navigation on route change.
20
+ // Clears the manual stack so stale sub-operation context from the previous
21
+ // screen does not leak into the new one.
6
22
  setCurrent(span) {
7
- this.current_ = span;
23
+ this.screenSpan_ = span;
24
+ this.manualStack_ = [];
8
25
  }
26
+
27
+ // Return the most specific active span. Manual stack takes precedence.
9
28
  current() {
10
- return this.current_;
29
+ const top = this.manualStack_[this.manualStack_.length - 1];
30
+ return top?.span ?? this.screenSpan_;
31
+ }
32
+
33
+ // ─── Internal API (used by Tracer only, not re-exported from index.ts) ─────
34
+
35
+ // Push span as the active context. Returns a token required by pop().
36
+ /** @internal */
37
+ push(span) {
38
+ const token = Symbol();
39
+ this.manualStack_.push({
40
+ span,
41
+ token
42
+ });
43
+ return token;
44
+ }
45
+
46
+ // Remove the entry matching token, regardless of position in the stack.
47
+ // Identity-based (not positional) so concurrent async operations cannot
48
+ // accidentally pop each other's entries.
49
+ /** @internal */
50
+ pop(token) {
51
+ const idx = this.manualStack_.findIndex(e => e.token === token);
52
+ if (idx !== -1) {
53
+ this.manualStack_.splice(idx, 1);
54
+ }
11
55
  }
12
56
  }
13
- export const spanContext = new SpanContextManager();
57
+ const manager = new SpanContextManager();
58
+
59
+ // External consumers get the narrowed type — push/pop are hidden.
60
+ export const spanContext = manager;
61
+
62
+ // Internal alias with the concrete type so tracer.ts can call push/pop.
63
+ // Not re-exported from index.ts.
64
+ export const spanContextInternal = manager;
14
65
  //# sourceMappingURL=span-context.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["SpanContextManager","setCurrent","span","current_","current","spanContext"],"sourceRoot":"../../../src","sources":["context/span-context.ts"],"mappings":";;AAEA;AACA;AACA,MAAMA,kBAAkB,CAAC;EAGvBC,UAAUA,CAACC,IAAiC,EAAQ;IAClD,IAAI,CAACC,QAAQ,GAAGD,IAAI;EACtB;EAEAE,OAAOA,CAAA,EAAgC;IACrC,OAAO,IAAI,CAACD,QAAQ;EACtB;AACF;AAEA,OAAO,MAAME,WAAW,GAAG,IAAIL,kBAAkB,CAAC,CAAC","ignoreList":[]}
1
+ {"version":3,"names":["SpanContextManager","manualStack_","setCurrent","span","screenSpan_","current","top","length","push","token","Symbol","pop","idx","findIndex","e","splice","manager","spanContext","spanContextInternal"],"sourceRoot":"../../../src","sources":["context/span-context.ts"],"mappings":";;AAEA;AACA;AACA;;AAMA;AACA;;AAMA,MAAMA,kBAAkB,CAAqC;EAC3D;;EAEA;EACA;EACQC,YAAY,GAAiB,EAAE;;EAEvC;;EAEA;EACA;EACA;EACAC,UAAUA,CAACC,IAAiC,EAAQ;IAClD,IAAI,CAACC,WAAW,GAAGD,IAAI;IACvB,IAAI,CAACF,YAAY,GAAG,EAAE;EACxB;;EAEA;EACAI,OAAOA,CAAA,EAAgC;IACrC,MAAMC,GAAG,GAAG,IAAI,CAACL,YAAY,CAAC,IAAI,CAACA,YAAY,CAACM,MAAM,GAAG,CAAC,CAAC;IAC3D,OAAOD,GAAG,EAAEH,IAAI,IAAI,IAAI,CAACC,WAAW;EACtC;;EAEA;;EAEA;EACA;EACAI,IAAIA,CAACL,IAAqB,EAAU;IAClC,MAAMM,KAAK,GAAGC,MAAM,CAAC,CAAC;IACtB,IAAI,CAACT,YAAY,CAACO,IAAI,CAAC;MAAEL,IAAI;MAAEM;IAAM,CAAC,CAAC;IACvC,OAAOA,KAAK;EACd;;EAEA;EACA;EACA;EACA;EACAE,GAAGA,CAACF,KAAa,EAAQ;IACvB,MAAMG,GAAG,GAAG,IAAI,CAACX,YAAY,CAACY,SAAS,CAAEC,CAAC,IAAKA,CAAC,CAACL,KAAK,KAAKA,KAAK,CAAC;IACjE,IAAIG,GAAG,KAAK,CAAC,CAAC,EAAE;MACd,IAAI,CAACX,YAAY,CAACc,MAAM,CAACH,GAAG,EAAE,CAAC,CAAC;IAClC;EACF;AACF;AAEA,MAAMI,OAAO,GAAG,IAAIhB,kBAAkB,CAAC,CAAC;;AAExC;AACA,OAAO,MAAMiB,WAAqC,GAAGD,OAAO;;AAE5D;AACA;AACA,OAAO,MAAME,mBAAuC,GAAGF,OAAO","ignoreList":[]}
@@ -1,16 +1,30 @@
1
1
  "use strict";
2
2
 
3
- function randomHex(length) {
4
- let result = '';
5
- for (let i = 0; i < length; i++) {
6
- result += Math.floor(Math.random() * 16).toString(16);
3
+ function randomBytesHex(byteCount) {
4
+ try {
5
+ const bytes = new Uint8Array(byteCount);
6
+ crypto.getRandomValues(bytes);
7
+ return Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
8
+ } catch {
9
+ // Fallback if crypto.getRandomValues is unavailable
10
+ if (__DEV__) {
11
+ console.warn('[react-native-otel] crypto.getRandomValues unavailable; falling back to Math.random for ID generation. IDs may collide at scale.');
12
+ }
13
+ let result = '';
14
+ for (let i = 0; i < byteCount * 2; i++) {
15
+ result += Math.floor(Math.random() * 16).toString(16);
16
+ }
17
+ return result;
7
18
  }
8
- return result;
9
19
  }
20
+
21
+ // 128-bit trace ID per OTel spec (32 hex chars)
10
22
  export function generateTraceId() {
11
- return randomHex(32);
23
+ return randomBytesHex(16);
12
24
  }
25
+
26
+ // 64-bit span ID per OTel spec (16 hex chars)
13
27
  export function generateSpanId() {
14
- return randomHex(16);
28
+ return randomBytesHex(8);
15
29
  }
16
30
  //# sourceMappingURL=ids.js.map
@@ -1 +1 @@
1
- {"version":3,"names":["randomHex","length","result","i","Math","floor","random","toString","generateTraceId","generateSpanId"],"sourceRoot":"../../../src","sources":["core/ids.ts"],"mappings":";;AAAA,SAASA,SAASA,CAACC,MAAc,EAAU;EACzC,IAAIC,MAAM,GAAG,EAAE;EACf,KAAK,IAAIC,CAAC,GAAG,CAAC,EAAEA,CAAC,GAAGF,MAAM,EAAEE,CAAC,EAAE,EAAE;IAC/BD,MAAM,IAAIE,IAAI,CAACC,KAAK,CAACD,IAAI,CAACE,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,CAACC,QAAQ,CAAC,EAAE,CAAC;EACvD;EACA,OAAOL,MAAM;AACf;AAEA,OAAO,SAASM,eAAeA,CAAA,EAAW;EACxC,OAAOR,SAAS,CAAC,EAAE,CAAC;AACtB;AAEA,OAAO,SAASS,cAAcA,CAAA,EAAW;EACvC,OAAOT,SAAS,CAAC,EAAE,CAAC;AACtB","ignoreList":[]}
1
+ {"version":3,"names":["randomBytesHex","byteCount","bytes","Uint8Array","crypto","getRandomValues","Array","from","b","toString","padStart","join","__DEV__","console","warn","result","i","Math","floor","random","generateTraceId","generateSpanId"],"sourceRoot":"../../../src","sources":["core/ids.ts"],"mappings":";;AAAA,SAASA,cAAcA,CAACC,SAAiB,EAAU;EACjD,IAAI;IACF,MAAMC,KAAK,GAAG,IAAIC,UAAU,CAACF,SAAS,CAAC;IACvCG,MAAM,CAACC,eAAe,CAACH,KAAK,CAAC;IAC7B,OAAOI,KAAK,CAACC,IAAI,CAACL,KAAK,EAAGM,CAAC,IAAKA,CAAC,CAACC,QAAQ,CAAC,EAAE,CAAC,CAACC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAACC,IAAI,CAAC,EAAE,CAAC;EAC3E,CAAC,CAAC,MAAM;IACN;IACA,IAAIC,OAAO,EAAE;MACXC,OAAO,CAACC,IAAI,CACV,kIACF,CAAC;IACH;IACA,IAAIC,MAAM,GAAG,EAAE;IACf,KAAK,IAAIC,CAAC,GAAG,CAAC,EAAEA,CAAC,GAAGf,SAAS,GAAG,CAAC,EAAEe,CAAC,EAAE,EAAE;MACtCD,MAAM,IAAIE,IAAI,CAACC,KAAK,CAACD,IAAI,CAACE,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,CAACV,QAAQ,CAAC,EAAE,CAAC;IACvD;IACA,OAAOM,MAAM;EACf;AACF;;AAEA;AACA,OAAO,SAASK,eAAeA,CAAA,EAAW;EACxC,OAAOpB,cAAc,CAAC,EAAE,CAAC;AAC3B;;AAEA;AACA,OAAO,SAASqB,cAAcA,CAAA,EAAW;EACvC,OAAOrB,cAAc,CAAC,CAAC,CAAC;AAC1B","ignoreList":[]}
@@ -2,6 +2,9 @@
2
2
 
3
3
  import { sanitizeAttributes } from "./attributes.js";
4
4
  import { now } from "./clock.js";
5
+
6
+ // Default bucket boundaries in milliseconds — covers typical mobile latencies.
7
+ const DEFAULT_HISTOGRAM_BOUNDARIES = [0, 5, 10, 25, 50, 75, 100, 250, 500, 1000];
5
8
  export class Counter {
6
9
  constructor(name, pushToBuffer) {
7
10
  this.name = name;
@@ -18,18 +21,69 @@ export class Counter {
18
21
  }
19
22
  }
20
23
  export class Histogram {
21
- constructor(name, pushToBuffer) {
24
+ // Keyed by serialized attributes so concurrent recordings with different
25
+ // attribute sets are tracked independently.
26
+ buckets = new Map();
27
+ constructor(name, pushToBuffer, options) {
22
28
  this.name = name;
23
29
  this.pushToBuffer = pushToBuffer;
30
+ this.boundaries = options?.boundaries ?? DEFAULT_HISTOGRAM_BOUNDARIES;
24
31
  }
25
32
  record(value, attrs) {
26
- this.pushToBuffer({
27
- type: 'histogram',
28
- name: this.name,
29
- value,
30
- timestampMs: now(),
31
- attributes: attrs ? sanitizeAttributes(attrs) : {}
32
- });
33
+ const sanitized = attrs ? sanitizeAttributes(attrs) : {};
34
+ const key = JSON.stringify(sanitized);
35
+ let bucket = this.buckets.get(key);
36
+ if (!bucket) {
37
+ bucket = {
38
+ count: 0,
39
+ sum: 0,
40
+ bucketCounts: new Array(this.boundaries.length + 1).fill(0),
41
+ startTimeMs: now(),
42
+ lastTimeMs: now(),
43
+ attributes: sanitized
44
+ };
45
+ this.buckets.set(key, bucket);
46
+ }
47
+ bucket.count += 1;
48
+ bucket.sum += value;
49
+ bucket.lastTimeMs = now();
50
+
51
+ // Place value into its bucket (first boundary that the value is <= to).
52
+ let placed = false;
53
+ for (let i = 0; i < this.boundaries.length; i++) {
54
+ if (value <= this.boundaries[i]) {
55
+ bucket.bucketCounts[i] += 1;
56
+ placed = true;
57
+ break;
58
+ }
59
+ }
60
+ // +Inf bucket
61
+ if (!placed) {
62
+ bucket.bucketCounts[this.boundaries.length] += 1;
63
+ }
64
+ }
65
+
66
+ // Called by Meter.flush() — drains accumulated buckets into the export buffer.
67
+ flush() {
68
+ for (const bucket of this.buckets.values()) {
69
+ const record = {
70
+ type: 'histogram',
71
+ name: this.name,
72
+ count: bucket.count,
73
+ sum: bucket.sum,
74
+ bucketBoundaries: this.boundaries,
75
+ bucketCounts: bucket.bucketCounts,
76
+ timestampMs: bucket.lastTimeMs,
77
+ attributes: bucket.attributes
78
+ };
79
+ this.pushToBuffer(record);
80
+ }
81
+ this.buckets.clear();
82
+ }
83
+
84
+ // Returns whether there is any accumulated data.
85
+ hasData() {
86
+ return this.buckets.size > 0;
33
87
  }
34
88
  }
35
89
  export class Gauge {
@@ -49,19 +103,28 @@ export class Gauge {
49
103
  }
50
104
  export class Meter {
51
105
  buffer = [];
106
+ histograms = [];
52
107
  constructor(exporter) {
53
108
  this.exporter = exporter;
54
109
  }
55
110
  createCounter(name) {
56
111
  return new Counter(name, r => this.buffer.push(r));
57
112
  }
58
- createHistogram(name) {
59
- return new Histogram(name, r => this.buffer.push(r));
113
+ createHistogram(name, options) {
114
+ const histogram = new Histogram(name, r => this.buffer.push(r), options);
115
+ this.histograms.push(histogram);
116
+ return histogram;
60
117
  }
61
118
  createGauge(name) {
62
119
  return new Gauge(name, r => this.buffer.push(r));
63
120
  }
64
121
  flush() {
122
+ // Drain all histogram buckets into the buffer first.
123
+ for (const histogram of this.histograms) {
124
+ if (histogram.hasData()) {
125
+ histogram.flush();
126
+ }
127
+ }
65
128
  if (this.buffer.length === 0) return;
66
129
  const toExport = this.buffer.splice(0, this.buffer.length);
67
130
  this.exporter?.export(toExport);
@@ -1 +1 @@
1
- {"version":3,"names":["sanitizeAttributes","now","Counter","constructor","name","pushToBuffer","add","value","attrs","type","timestampMs","attributes","Histogram","record","Gauge","set","Meter","buffer","exporter","createCounter","r","push","createHistogram","createGauge","flush","length","toExport","splice","export"],"sourceRoot":"../../../src","sources":["core/meter.ts"],"mappings":";;AAEA,SAASA,kBAAkB,QAAQ,iBAAc;AACjD,SAASC,GAAG,QAAQ,YAAS;AAE7B,OAAO,MAAMC,OAAO,CAAC;EACnBC,WAAWA,CACDC,IAAY,EACZC,YAA4C,EACpD;IAAA,KAFQD,IAAY,GAAZA,IAAY;IAAA,KACZC,YAA4C,GAA5CA,YAA4C;EACnD;EAEHC,GAAGA,CAACC,KAAa,EAAEC,KAAkB,EAAQ;IAC3C,IAAI,CAACH,YAAY,CAAC;MAChBI,IAAI,EAAE,SAAS;MACfL,IAAI,EAAE,IAAI,CAACA,IAAI;MACfG,KAAK;MACLG,WAAW,EAAET,GAAG,CAAC,CAAC;MAClBU,UAAU,EAAEH,KAAK,GAAGR,kBAAkB,CAACQ,KAAK,CAAC,GAAG,CAAC;IACnD,CAAC,CAAC;EACJ;AACF;AAEA,OAAO,MAAMI,SAAS,CAAC;EACrBT,WAAWA,CACDC,IAAY,EACZC,YAA4C,EACpD;IAAA,KAFQD,IAAY,GAAZA,IAAY;IAAA,KACZC,YAA4C,GAA5CA,YAA4C;EACnD;EAEHQ,MAAMA,CAACN,KAAa,EAAEC,KAAkB,EAAQ;IAC9C,IAAI,CAACH,YAAY,CAAC;MAChBI,IAAI,EAAE,WAAW;MACjBL,IAAI,EAAE,IAAI,CAACA,IAAI;MACfG,KAAK;MACLG,WAAW,EAAET,GAAG,CAAC,CAAC;MAClBU,UAAU,EAAEH,KAAK,GAAGR,kBAAkB,CAACQ,KAAK,CAAC,GAAG,CAAC;IACnD,CAAC,CAAC;EACJ;AACF;AAEA,OAAO,MAAMM,KAAK,CAAC;EACjBX,WAAWA,CACDC,IAAY,EACZC,YAA4C,EACpD;IAAA,KAFQD,IAAY,GAAZA,IAAY;IAAA,KACZC,YAA4C,GAA5CA,YAA4C;EACnD;EAEHU,GAAGA,CAACR,KAAa,EAAEC,KAAkB,EAAQ;IAC3C,IAAI,CAACH,YAAY,CAAC;MAChBI,IAAI,EAAE,OAAO;MACbL,IAAI,EAAE,IAAI,CAACA,IAAI;MACfG,KAAK;MACLG,WAAW,EAAET,GAAG,CAAC,CAAC;MAClBU,UAAU,EAAEH,KAAK,GAAGR,kBAAkB,CAACQ,KAAK,CAAC,GAAG,CAAC;IACnD,CAAC,CAAC;EACJ;AACF;AAEA,OAAO,MAAMQ,KAAK,CAAC;EACTC,MAAM,GAAmB,EAAE;EAGnCd,WAAWA,CAACe,QAAyB,EAAE;IACrC,IAAI,CAACA,QAAQ,GAAGA,QAAQ;EAC1B;EAEAC,aAAaA,CAACf,IAAY,EAAW;IACnC,OAAO,IAAIF,OAAO,CAACE,IAAI,EAAGgB,CAAC,IAAK,IAAI,CAACH,MAAM,CAACI,IAAI,CAACD,CAAC,CAAC,CAAC;EACtD;EAEAE,eAAeA,CAAClB,IAAY,EAAa;IACvC,OAAO,IAAIQ,SAAS,CAACR,IAAI,EAAGgB,CAAC,IAAK,IAAI,CAACH,MAAM,CAACI,IAAI,CAACD,CAAC,CAAC,CAAC;EACxD;EAEAG,WAAWA,CAACnB,IAAY,EAAS;IAC/B,OAAO,IAAIU,KAAK,CAACV,IAAI,EAAGgB,CAAC,IAAK,IAAI,CAACH,MAAM,CAACI,IAAI,CAACD,CAAC,CAAC,CAAC;EACpD;EAEAI,KAAKA,CAAA,EAAS;IACZ,IAAI,IAAI,CAACP,MAAM,CAACQ,MAAM,KAAK,CAAC,EAAE;IAC9B,MAAMC,QAAQ,GAAG,IAAI,CAACT,MAAM,CAACU,MAAM,CAAC,CAAC,EAAE,IAAI,CAACV,MAAM,CAACQ,MAAM,CAAC;IAC1D,IAAI,CAACP,QAAQ,EAAEU,MAAM,CAACF,QAAQ,CAAC;EACjC;AACF","ignoreList":[]}
1
+ {"version":3,"names":["sanitizeAttributes","now","DEFAULT_HISTOGRAM_BOUNDARIES","Counter","constructor","name","pushToBuffer","add","value","attrs","type","timestampMs","attributes","Histogram","buckets","Map","options","boundaries","record","sanitized","key","JSON","stringify","bucket","get","count","sum","bucketCounts","Array","length","fill","startTimeMs","lastTimeMs","set","placed","i","flush","values","bucketBoundaries","clear","hasData","size","Gauge","Meter","buffer","histograms","exporter","createCounter","r","push","createHistogram","histogram","createGauge","toExport","splice","export"],"sourceRoot":"../../../src","sources":["core/meter.ts"],"mappings":"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","ignoreList":[]}
@@ -2,19 +2,22 @@
2
2
 
3
3
  import { ATTR_EXCEPTION_MESSAGE, ATTR_EXCEPTION_STACKTRACE, ATTR_EXCEPTION_TYPE } from '@opentelemetry/semantic-conventions';
4
4
  import { Span, NoopSpan } from "./span.js";
5
- import { spanContext } from "../context/span-context.js";
5
+ import { spanContext, spanContextInternal } from "../context/span-context.js";
6
6
  export class Tracer {
7
7
  constructor(params) {
8
8
  this.exporter = params.exporter;
9
9
  this.sampleRate = params.sampleRate ?? 1.0;
10
10
  this.getUserAttributes = params.getUserAttributes;
11
11
  }
12
+
13
+ // Create a span without making it the active context.
14
+ // Use startActiveSpan() when you want sub-operations to auto-parent.
12
15
  startSpan(name, options) {
13
16
  if (this.sampleRate < 1.0 && Math.random() > this.sampleRate) {
14
17
  return new NoopSpan();
15
18
  }
16
19
 
17
- // Resolve parent: explicit > current screen span > none (new trace)
20
+ // Resolve parent: explicit > current active span > none (new root trace)
18
21
  const parent = options?.parent !== undefined ? options.parent ?? undefined : spanContext.current() ?? undefined;
19
22
  const userAttrs = this.getUserAttributes();
20
23
  return new Span({
@@ -28,6 +31,72 @@ export class Tracer {
28
31
  exporter: this.exporter
29
32
  });
30
33
  }
34
+
35
+ // Create a span, make it the active context for the duration of fn, then
36
+ // automatically end it. Sub-operations started inside fn via startSpan() will
37
+ // automatically parent to this span.
38
+ //
39
+ // For concurrent async work (multiple in-flight awaits), pass parent
40
+ // explicitly to startSpan() instead — the shared context stack is not safe
41
+ // for interleaved async operations.
42
+
43
+ startActiveSpan(name, optionsOrFn, fn) {
44
+ const options = typeof optionsOrFn === 'function' ? undefined : optionsOrFn;
45
+ const callback = typeof optionsOrFn === 'function' ? optionsOrFn : fn;
46
+ const span = this.startSpan(name, options);
47
+ const token = spanContextInternal.push(span);
48
+ const cleanup = (isError, err) => {
49
+ if (isError && err instanceof Error) {
50
+ span.setStatus('ERROR', err.message);
51
+ }
52
+ span.end();
53
+ spanContextInternal.pop(token);
54
+ };
55
+ try {
56
+ const result = callback(span);
57
+ if (result instanceof Promise) {
58
+ // Token-based pop fires after the promise settles, preserving identity
59
+ // even if other startActiveSpan calls interleave on the event loop.
60
+ return result.then(v => {
61
+ cleanup(false);
62
+ return v;
63
+ }, e => {
64
+ cleanup(true, e);
65
+ throw e;
66
+ });
67
+ }
68
+ cleanup(false);
69
+ return result;
70
+ } catch (e) {
71
+ cleanup(true, e);
72
+ throw e;
73
+ }
74
+ }
75
+
76
+ // Make an existing span the active context for the duration of fn.
77
+ // Does NOT end the span — the caller owns its lifetime.
78
+ // Safe for synchronous work. For concurrent async work, see startActiveSpan.
79
+ withSpan(span, fn) {
80
+ const token = spanContextInternal.push(span);
81
+ const cleanup = () => spanContextInternal.pop(token);
82
+ try {
83
+ const result = fn(span);
84
+ if (result instanceof Promise) {
85
+ return result.then(v => {
86
+ cleanup();
87
+ return v;
88
+ }, e => {
89
+ cleanup();
90
+ throw e;
91
+ });
92
+ }
93
+ cleanup();
94
+ return result;
95
+ } catch (e) {
96
+ cleanup();
97
+ throw e;
98
+ }
99
+ }
31
100
  recordEvent(name, attributes) {
32
101
  spanContext.current()?.addEvent(name, attributes);
33
102
  }
@@ -1 +1 @@
1
- {"version":3,"names":["ATTR_EXCEPTION_MESSAGE","ATTR_EXCEPTION_STACKTRACE","ATTR_EXCEPTION_TYPE","Span","NoopSpan","spanContext","Tracer","constructor","params","exporter","sampleRate","getUserAttributes","startSpan","name","options","Math","random","parent","undefined","current","userAttrs","kind","attributes","recordEvent","addEvent","recordException","error","span","message","stack","setStatus","end"],"sourceRoot":"../../../src","sources":["core/tracer.ts"],"mappings":";;AAAA,SACEA,sBAAsB,EACtBC,yBAAyB,EACzBC,mBAAmB,QACd,qCAAqC;AAI5C,SAASC,IAAI,EAAEC,QAAQ,QAAQ,WAAQ;AACvC,SAASC,WAAW,QAAQ,4BAAyB;AAErD,OAAO,MAAMC,MAAM,CAAC;EAKlBC,WAAWA,CAACC,MAIX,EAAE;IACD,IAAI,CAACC,QAAQ,GAAGD,MAAM,CAACC,QAAQ;IAC/B,IAAI,CAACC,UAAU,GAAGF,MAAM,CAACE,UAAU,IAAI,GAAG;IAC1C,IAAI,CAACC,iBAAiB,GAAGH,MAAM,CAACG,iBAAiB;EACnD;EAEAC,SAASA,CACPC,IAAY,EACZC,OAOC,EACgB;IACjB,IAAI,IAAI,CAACJ,UAAU,GAAG,GAAG,IAAIK,IAAI,CAACC,MAAM,CAAC,CAAC,GAAG,IAAI,CAACN,UAAU,EAAE;MAC5D,OAAO,IAAIN,QAAQ,CAAC,CAAC;IACvB;;IAEA;IACA,MAAMa,MAA+B,GACnCH,OAAO,EAAEG,MAAM,KAAKC,SAAS,GACzBJ,OAAO,CAACG,MAAM,IAAIC,SAAS,GAC3Bb,WAAW,CAACc,OAAO,CAAC,CAAC,IAAID,SAAS;IAExC,MAAME,SAAS,GAAG,IAAI,CAACT,iBAAiB,CAAC,CAAC;IAC1C,OAAO,IAAIR,IAAI,CAAC;MACdU,IAAI;MACJQ,IAAI,EAAEP,OAAO,EAAEO,IAAI;MACnBC,UAAU,EAAE;QAAE,GAAGF,SAAS;QAAE,GAAGN,OAAO,EAAEQ;MAAW,CAAC;MACpDL,MAAM;MACNR,QAAQ,EAAE,IAAI,CAACA;IACjB,CAAC,CAAC;EACJ;EAEAc,WAAWA,CAACV,IAAY,EAAES,UAAuB,EAAQ;IACvDjB,WAAW,CAACc,OAAO,CAAC,CAAC,EAAEK,QAAQ,CAACX,IAAI,EAAES,UAAU,CAAC;EACnD;EAEAG,eAAeA,CAACC,KAAY,EAAEJ,UAAuB,EAAQ;IAC3D,MAAMK,IAAI,GAAG,IAAI,CAACf,SAAS,CAAC,aAAac,KAAK,CAACb,IAAI,EAAE,EAAE;MACrDQ,IAAI,EAAE,UAAU;MAChBC,UAAU,EAAE;QACV,CAACpB,mBAAmB,GAAGwB,KAAK,CAACb,IAAI;QACjC,CAACb,sBAAsB,GAAG0B,KAAK,CAACE,OAAO;QACvC,CAAC3B,yBAAyB,GAAGyB,KAAK,CAACG,KAAK,IAAI,EAAE;QAC9C,GAAGP;MACL;IACF,CAAC,CAAC;IACFK,IAAI,CAACG,SAAS,CAAC,OAAO,EAAEJ,KAAK,CAACE,OAAO,CAAC;IACtCD,IAAI,CAACI,GAAG,CAAC,CAAC;EACZ;AACF","ignoreList":[]}
1
+ {"version":3,"names":["ATTR_EXCEPTION_MESSAGE","ATTR_EXCEPTION_STACKTRACE","ATTR_EXCEPTION_TYPE","Span","NoopSpan","spanContext","spanContextInternal","Tracer","constructor","params","exporter","sampleRate","getUserAttributes","startSpan","name","options","Math","random","parent","undefined","current","userAttrs","kind","attributes","startActiveSpan","optionsOrFn","fn","callback","span","token","push","cleanup","isError","err","Error","setStatus","message","end","pop","result","Promise","then","v","e","withSpan","recordEvent","addEvent","recordException","error","stack"],"sourceRoot":"../../../src","sources":["core/tracer.ts"],"mappings":";;AAAA,SACEA,sBAAsB,EACtBC,yBAAyB,EACzBC,mBAAmB,QACd,qCAAqC;AAI5C,SAASC,IAAI,EAAEC,QAAQ,QAAQ,WAAQ;AACvC,SAASC,WAAW,EAAEC,mBAAmB,QAAQ,4BAAyB;AAU1E,OAAO,MAAMC,MAAM,CAAC;EAKlBC,WAAWA,CAACC,MAIX,EAAE;IACD,IAAI,CAACC,QAAQ,GAAGD,MAAM,CAACC,QAAQ;IAC/B,IAAI,CAACC,UAAU,GAAGF,MAAM,CAACE,UAAU,IAAI,GAAG;IAC1C,IAAI,CAACC,iBAAiB,GAAGH,MAAM,CAACG,iBAAiB;EACnD;;EAEA;EACA;EACAC,SAASA,CAACC,IAAY,EAAEC,OAAqB,EAAmB;IAC9D,IAAI,IAAI,CAACJ,UAAU,GAAG,GAAG,IAAIK,IAAI,CAACC,MAAM,CAAC,CAAC,GAAG,IAAI,CAACN,UAAU,EAAE;MAC5D,OAAO,IAAIP,QAAQ,CAAC,CAAC;IACvB;;IAEA;IACA,MAAMc,MAA+B,GACnCH,OAAO,EAAEG,MAAM,KAAKC,SAAS,GACzBJ,OAAO,CAACG,MAAM,IAAIC,SAAS,GAC3Bd,WAAW,CAACe,OAAO,CAAC,CAAC,IAAID,SAAS;IAExC,MAAME,SAAS,GAAG,IAAI,CAACT,iBAAiB,CAAC,CAAC;IAC1C,OAAO,IAAIT,IAAI,CAAC;MACdW,IAAI;MACJQ,IAAI,EAAEP,OAAO,EAAEO,IAAI;MACnBC,UAAU,EAAE;QAAE,GAAGF,SAAS;QAAE,GAAGN,OAAO,EAAEQ;MAAW,CAAC;MACpDL,MAAM;MACNR,QAAQ,EAAE,IAAI,CAACA;IACjB,CAAC,CAAC;EACJ;;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;;EAOAc,eAAeA,CACbV,IAAY,EACZW,WAAyD,EACzDC,EAAiC,EAC9B;IACH,MAAMX,OAAO,GAAG,OAAOU,WAAW,KAAK,UAAU,GAAGN,SAAS,GAAGM,WAAW;IAC3E,MAAME,QAAQ,GAAG,OAAOF,WAAW,KAAK,UAAU,GAAGA,WAAW,GAAGC,EAAG;IAEtE,MAAME,IAAI,GAAG,IAAI,CAACf,SAAS,CAACC,IAAI,EAAEC,OAAO,CAAC;IAC1C,MAAMc,KAAK,GAAGvB,mBAAmB,CAACwB,IAAI,CAACF,IAAI,CAAC;IAE5C,MAAMG,OAAO,GAAGA,CAACC,OAAgB,EAAEC,GAAa,KAAK;MACnD,IAAID,OAAO,IAAIC,GAAG,YAAYC,KAAK,EAAE;QACnCN,IAAI,CAACO,SAAS,CAAC,OAAO,EAAEF,GAAG,CAACG,OAAO,CAAC;MACtC;MACAR,IAAI,CAACS,GAAG,CAAC,CAAC;MACV/B,mBAAmB,CAACgC,GAAG,CAACT,KAAK,CAAC;IAChC,CAAC;IAED,IAAI;MACF,MAAMU,MAAM,GAAGZ,QAAQ,CAACC,IAAI,CAAC;MAC7B,IAAIW,MAAM,YAAYC,OAAO,EAAE;QAC7B;QACA;QACA,OAAOD,MAAM,CAACE,IAAI,CACfC,CAAC,IAAK;UACLX,OAAO,CAAC,KAAK,CAAC;UACd,OAAOW,CAAC;QACV,CAAC,EACAC,CAAU,IAAK;UACdZ,OAAO,CAAC,IAAI,EAAEY,CAAC,CAAC;UAChB,MAAMA,CAAC;QACT,CACF,CAAC;MACH;MACAZ,OAAO,CAAC,KAAK,CAAC;MACd,OAAOQ,MAAM;IACf,CAAC,CAAC,OAAOI,CAAC,EAAE;MACVZ,OAAO,CAAC,IAAI,EAAEY,CAAC,CAAC;MAChB,MAAMA,CAAC;IACT;EACF;;EAEA;EACA;EACA;EACAC,QAAQA,CAAIhB,IAAqB,EAAEF,EAAgC,EAAK;IACtE,MAAMG,KAAK,GAAGvB,mBAAmB,CAACwB,IAAI,CAACF,IAAI,CAAC;IAE5C,MAAMG,OAAO,GAAGA,CAAA,KAAMzB,mBAAmB,CAACgC,GAAG,CAACT,KAAK,CAAC;IAEpD,IAAI;MACF,MAAMU,MAAM,GAAGb,EAAE,CAACE,IAAI,CAAC;MACvB,IAAIW,MAAM,YAAYC,OAAO,EAAE;QAC7B,OAAOD,MAAM,CAACE,IAAI,CACfC,CAAC,IAAK;UACLX,OAAO,CAAC,CAAC;UACT,OAAOW,CAAC;QACV,CAAC,EACAC,CAAU,IAAK;UACdZ,OAAO,CAAC,CAAC;UACT,MAAMY,CAAC;QACT,CACF,CAAC;MACH;MACAZ,OAAO,CAAC,CAAC;MACT,OAAOQ,MAAM;IACf,CAAC,CAAC,OAAOI,CAAC,EAAE;MACVZ,OAAO,CAAC,CAAC;MACT,MAAMY,CAAC;IACT;EACF;EAEAE,WAAWA,CAAC/B,IAAY,EAAES,UAAuB,EAAQ;IACvDlB,WAAW,CAACe,OAAO,CAAC,CAAC,EAAE0B,QAAQ,CAAChC,IAAI,EAAES,UAAU,CAAC;EACnD;EAEAwB,eAAeA,CAACC,KAAY,EAAEzB,UAAuB,EAAQ;IAC3D,MAAMK,IAAI,GAAG,IAAI,CAACf,SAAS,CAAC,aAAamC,KAAK,CAAClC,IAAI,EAAE,EAAE;MACrDQ,IAAI,EAAE,UAAU;MAChBC,UAAU,EAAE;QACV,CAACrB,mBAAmB,GAAG8C,KAAK,CAAClC,IAAI;QACjC,CAACd,sBAAsB,GAAGgD,KAAK,CAACZ,OAAO;QACvC,CAACnC,yBAAyB,GAAG+C,KAAK,CAACC,KAAK,IAAI,EAAE;QAC9C,GAAG1B;MACL;IACF,CAAC,CAAC;IACFK,IAAI,CAACO,SAAS,CAAC,OAAO,EAAEa,KAAK,CAACZ,OAAO,CAAC;IACtCR,IAAI,CAACS,GAAG,CAAC,CAAC;EACZ;AACF","ignoreList":[]}
@@ -34,7 +34,14 @@ export class ConsoleMetricExporter {
34
34
  export(metrics) {
35
35
  if (!shouldLog(this.debug)) return;
36
36
  for (const metric of metrics) {
37
- console.log(`[OTEL METRIC] ${metric.name} ${metric.type} value=${metric.value}`, Object.keys(metric.attributes).length > 0 ? metric.attributes : '');
37
+ if (metric.type === 'histogram') {
38
+ const h = metric;
39
+ const avg = h.count > 0 ? (h.sum / h.count).toFixed(2) : '0';
40
+ const bucketStr = h.bucketBoundaries.map((b, i) => `≤${b}:${h.bucketCounts[i]}`).concat([`+Inf:${h.bucketCounts[h.bucketBoundaries.length]}`]).join(' ');
41
+ console.log(`[OTEL METRIC] ${h.name} histogram count=${h.count} sum=${h.sum} avg=${avg} [${bucketStr}]`, Object.keys(h.attributes).length > 0 ? h.attributes : '');
42
+ } else {
43
+ console.log(`[OTEL METRIC] ${metric.name} ${metric.type} value=${metric.value}`, Object.keys(metric.attributes).length > 0 ? metric.attributes : '');
44
+ }
38
45
  }
39
46
  }
40
47
  }
@@ -1 +1 @@
1
- {"version":3,"names":["shouldLog","debug","process","env","NODE_ENV","ConsoleSpanExporter","constructor","export","spans","span","duration","endTimeMs","undefined","startTimeMs","lines","name","traceId","spanId","parentSpanId","status","statusMessage","JSON","stringify","attributes","events","length","push","event","offset","timestampMs","attrsStr","Object","keys","droppedEventsCount","console","log","join","ConsoleMetricExporter","metrics","metric","type","value","ConsoleLogExporter","logs","traceInfo","severity","padEnd","body"],"sourceRoot":"../../../src","sources":["exporters/console-exporter.ts"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["shouldLog","debug","process","env","NODE_ENV","ConsoleSpanExporter","constructor","export","spans","span","duration","endTimeMs","undefined","startTimeMs","lines","name","traceId","spanId","parentSpanId","status","statusMessage","JSON","stringify","attributes","events","length","push","event","offset","timestampMs","attrsStr","Object","keys","droppedEventsCount","console","log","join","ConsoleMetricExporter","metrics","metric","type","h","avg","count","sum","toFixed","bucketStr","bucketBoundaries","map","b","i","bucketCounts","concat","value","ConsoleLogExporter","logs","traceInfo","severity","padEnd","body"],"sourceRoot":"../../../src","sources":["exporters/console-exporter.ts"],"mappings":"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","ignoreList":[]}
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
 
3
+ import { Wal, fetchWithRetry } from "./wal.js";
4
+
3
5
  // ─── OTLP attribute value serialization ──────────────────────────────────────
4
6
 
5
7
  function toOtlpValue(value) {
@@ -75,6 +77,19 @@ export class OtlpHttpExporter {
75
77
  setResource(resource) {
76
78
  this.resource_ = resource;
77
79
  }
80
+
81
+ // Called by OtelSDK.init() when a StorageAdapter is configured.
82
+ // Initialises the WAL and replays any undelivered batches from the previous session.
83
+ setStorage(storage) {
84
+ this.wal_ = new Wal(storage, '@react-native-otel/wal/spans');
85
+ this.replayWal();
86
+ }
87
+ replayWal() {
88
+ if (!this.wal_) return;
89
+ for (const batch of this.wal_.readAll()) {
90
+ this.deliverBatch(batch.data, batch.id);
91
+ }
92
+ }
78
93
  export(spans) {
79
94
  this.buffer.push(...spans);
80
95
  if (this.buffer.length >= this.batchSize) {
@@ -84,7 +99,12 @@ export class OtlpHttpExporter {
84
99
  flush() {
85
100
  if (this.buffer.length === 0) return;
86
101
  const batch = this.buffer.splice(0);
87
- this.send(batch);
102
+ if (this.wal_) {
103
+ const id = this.wal_.write(batch);
104
+ this.deliverBatch(batch, id);
105
+ } else {
106
+ this.deliverBatch(batch, undefined);
107
+ }
88
108
  }
89
109
 
90
110
  // Clear the flush timer and send any remaining buffered spans.
@@ -95,9 +115,23 @@ export class OtlpHttpExporter {
95
115
  }
96
116
  this.flush();
97
117
  }
98
- send(spans) {
118
+ deliverBatch(spans, walId) {
119
+ const body = this.buildBody(spans);
120
+ fetchWithRetry(this.tracesEndpoint, {
121
+ method: 'POST',
122
+ headers: this.headers,
123
+ body
124
+ }).then(success => {
125
+ if (success && walId !== undefined) {
126
+ this.wal_?.delete(walId);
127
+ }
128
+ }).catch(() => {
129
+ // Leave in WAL for next session
130
+ });
131
+ }
132
+ buildBody(spans) {
99
133
  const resourceAttrs = this.resource_ ? toOtlpAttributes(this.resource_) : [];
100
- const body = JSON.stringify({
134
+ return JSON.stringify({
101
135
  resourceSpans: [{
102
136
  resource: {
103
137
  attributes: resourceAttrs
@@ -111,11 +145,6 @@ export class OtlpHttpExporter {
111
145
  }]
112
146
  }]
113
147
  });
114
- fetch(this.tracesEndpoint, {
115
- method: 'POST',
116
- headers: this.headers,
117
- body
118
- }).catch(() => {});
119
148
  }
120
149
  toOtlpSpan(span) {
121
150
  return {
@@ -149,7 +178,8 @@ export class OtlpHttpExporter {
149
178
 
150
179
  // ─── Metric exporter ──────────────────────────────────────────────────────────
151
180
 
152
- // OTLP aggregation temporality: 2 = CUMULATIVE
181
+ // OTLP aggregation temporality constants
182
+ const AGGREGATION_TEMPORALITY_DELTA = 1;
153
183
  const AGGREGATION_TEMPORALITY_CUMULATIVE = 2;
154
184
  export class OtlpHttpMetricExporter {
155
185
  constructor(options) {
@@ -162,11 +192,40 @@ export class OtlpHttpMetricExporter {
162
192
  setResource(resource) {
163
193
  this.resource_ = resource;
164
194
  }
195
+ setStorage(storage) {
196
+ this.wal_ = new Wal(storage, '@react-native-otel/wal/metrics');
197
+ this.replayWal();
198
+ }
199
+ replayWal() {
200
+ if (!this.wal_) return;
201
+ for (const batch of this.wal_.readAll()) {
202
+ this.deliverBatch(batch.data, batch.id);
203
+ }
204
+ }
165
205
  export(metrics) {
166
206
  if (metrics.length === 0) return;
167
- this.send(metrics);
207
+ if (this.wal_) {
208
+ const id = this.wal_.write(metrics);
209
+ this.deliverBatch(metrics, id);
210
+ } else {
211
+ this.deliverBatch(metrics, undefined);
212
+ }
168
213
  }
169
- send(metrics) {
214
+ deliverBatch(metrics, walId) {
215
+ const body = this.buildBody(metrics);
216
+ fetchWithRetry(this.metricsEndpoint, {
217
+ method: 'POST',
218
+ headers: this.headers,
219
+ body
220
+ }).then(success => {
221
+ if (success && walId !== undefined) {
222
+ this.wal_?.delete(walId);
223
+ }
224
+ }).catch(() => {
225
+ // Leave in WAL for next session
226
+ });
227
+ }
228
+ buildBody(metrics) {
170
229
  const resourceAttrs = this.resource_ ? toOtlpAttributes(this.resource_) : [];
171
230
 
172
231
  // Group records by name so each unique metric name becomes one OTLP metric.
@@ -181,8 +240,6 @@ export class OtlpHttpMetricExporter {
181
240
  }
182
241
  const otlpMetrics = Array.from(byName.entries()).map(([name, records]) => {
183
242
  const type = records[0]?.type;
184
-
185
- // Counters → sum; histograms + gauges → gauge (no bucket data available).
186
243
  if (type === 'counter') {
187
244
  return {
188
245
  name,
@@ -198,6 +255,28 @@ export class OtlpHttpMetricExporter {
198
255
  }
199
256
  };
200
257
  }
258
+ if (type === 'histogram') {
259
+ return {
260
+ name,
261
+ histogram: {
262
+ dataPoints: records.map(r => ({
263
+ count: String(r.count),
264
+ sum: r.sum,
265
+ // bucketCounts in OTLP are string-encoded uint64
266
+ bucketCounts: r.bucketCounts.map(String),
267
+ // explicitBounds does not include the implicit +Inf upper bound
268
+ explicitBounds: r.bucketBoundaries,
269
+ startTimeUnixNano: msToNano(r.timestampMs),
270
+ timeUnixNano: msToNano(r.timestampMs),
271
+ attributes: toOtlpAttributes(r.attributes)
272
+ })),
273
+ // Each flush window is independent — use DELTA semantics.
274
+ aggregationTemporality: AGGREGATION_TEMPORALITY_DELTA
275
+ }
276
+ };
277
+ }
278
+
279
+ // gauge
201
280
  return {
202
281
  name,
203
282
  gauge: {
@@ -209,7 +288,7 @@ export class OtlpHttpMetricExporter {
209
288
  }
210
289
  };
211
290
  });
212
- const body = JSON.stringify({
291
+ return JSON.stringify({
213
292
  resourceMetrics: [{
214
293
  resource: {
215
294
  attributes: resourceAttrs
@@ -223,11 +302,6 @@ export class OtlpHttpMetricExporter {
223
302
  }]
224
303
  }]
225
304
  });
226
- fetch(this.metricsEndpoint, {
227
- method: 'POST',
228
- headers: this.headers,
229
- body
230
- }).catch(() => {});
231
305
  }
232
306
  }
233
307
 
@@ -307,7 +381,7 @@ export class OtlpHttpLogExporter {
307
381
  }]
308
382
  }]
309
383
  });
310
- fetch(this.logsEndpoint, {
384
+ fetchWithRetry(this.logsEndpoint, {
311
385
  method: 'POST',
312
386
  headers: this.headers,
313
387
  body