@ciphyrshq/sdk 3.0.0 → 3.0.1
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/package.json +21 -5
- package/src/client.test.js +0 -99
- package/src/fail-posture.test.js +0 -385
- package/src/no-network.test-helper.js +0 -108
- package/src/propagation.test.js +0 -429
- package/src/protect-tool.test.js +0 -446
- package/src/secret-detector.test.js +0 -155
- package/src/tracer.test.js +0 -194
package/src/tracer.test.js
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CiphyrsTracer — buffered span and metric ingestion.
|
|
3
|
-
* Run with: npm test
|
|
4
|
-
*
|
|
5
|
-
* This is the only route spans, traces and metrics take to the platform, and
|
|
6
|
-
* its failure mode is silence: when ingestion breaks nothing errors, data just
|
|
7
|
-
* never arrives, and an empty dashboard is indistinguishable from a quiet day.
|
|
8
|
-
*
|
|
9
|
-
* The buffers are spliced empty BEFORE the send, so a `.catch` that only
|
|
10
|
-
* logged meant any failure the client's retries could not absorb destroyed
|
|
11
|
-
* that batch permanently. sdk-node's client.js already re-queued; this package
|
|
12
|
-
* did not, so the same customer got different durability from two Ciphyrs
|
|
13
|
-
* SDKs doing the same job.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
// No test in this package may open a socket — see no-network.test-helper.js.
|
|
17
|
-
// Imported per file because node --test runs each file in its own process.
|
|
18
|
-
import './no-network.test-helper.js'
|
|
19
|
-
import { describe, it, beforeEach, afterEach } from 'node:test'
|
|
20
|
-
import assert from 'node:assert/strict'
|
|
21
|
-
import { CiphyrsTracer } from './tracer.js'
|
|
22
|
-
|
|
23
|
-
// console.error is the delivery mechanism for "your telemetry is not arriving",
|
|
24
|
-
// so it is asserted on rather than silenced.
|
|
25
|
-
let logged
|
|
26
|
-
let realError
|
|
27
|
-
beforeEach(() => { logged = []; realError = console.error; console.error = (m) => logged.push(String(m)) })
|
|
28
|
-
afterEach(() => { console.error = realError })
|
|
29
|
-
|
|
30
|
-
function fakeClient({ failIngest = false, failMetrics = false } = {}) {
|
|
31
|
-
const calls = { ingest: [], metrics: [] }
|
|
32
|
-
return {
|
|
33
|
-
calls,
|
|
34
|
-
_baseUrl: 'https://api',
|
|
35
|
-
trace: {
|
|
36
|
-
ingest: async (project, trace, spans) => {
|
|
37
|
-
calls.ingest.push({ trace, spans })
|
|
38
|
-
if (failIngest) throw new Error('collector unreachable')
|
|
39
|
-
return { ok: true }
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
_request: async (url, opts) => {
|
|
43
|
-
calls.metrics.push(opts.body)
|
|
44
|
-
if (failMetrics) throw new Error('collector unreachable')
|
|
45
|
-
return { ok: true }
|
|
46
|
-
},
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const tracerFor = (client, opts = {}) =>
|
|
51
|
-
// flushIntervalMs is large so the timer never races an assertion; every
|
|
52
|
-
// flush below is explicit.
|
|
53
|
-
new CiphyrsTracer(client, { projectName: 'billing', flushIntervalMs: 3_600_000, ...opts })
|
|
54
|
-
|
|
55
|
-
// Reaches the private #spanBuffer the only way a test legitimately can:
|
|
56
|
-
// enqueue, flush, and observe what a subsequent flush still tries to send.
|
|
57
|
-
const pendingSpanCount = async (tracer, client) => {
|
|
58
|
-
const before = client.calls.ingest.length
|
|
59
|
-
await tracer.flush()
|
|
60
|
-
return client.calls.ingest.slice(before).reduce((n, c) => n + c.spans.length, 0)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
describe('CiphyrsTracer — a successful flush', () => {
|
|
64
|
-
it('sends buffered spans grouped into one call per trace', async () => {
|
|
65
|
-
const client = fakeClient()
|
|
66
|
-
const t = tracerFor(client)
|
|
67
|
-
t._enqueueSpan({ trace_id: 'A', name: 'a1' })
|
|
68
|
-
t._enqueueSpan({ trace_id: 'A', name: 'a2' })
|
|
69
|
-
t._enqueueSpan({ trace_id: 'B', name: 'b1' })
|
|
70
|
-
await t.flush()
|
|
71
|
-
assert.equal(client.calls.ingest.length, 2, 'traces must not be merged')
|
|
72
|
-
assert.deepEqual(client.calls.ingest.map(c => c.spans.length), [2, 1])
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('sends buffered metrics', async () => {
|
|
76
|
-
const client = fakeClient()
|
|
77
|
-
const t = tracerFor(client)
|
|
78
|
-
t.emitMetric('token_cost', 0.42, { unit: 'usd' })
|
|
79
|
-
await t.flush()
|
|
80
|
-
assert.equal(client.calls.metrics[0].metrics[0].name, 'token_cost')
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
it('empties the buffers, so nothing is sent twice', async () => {
|
|
84
|
-
const client = fakeClient()
|
|
85
|
-
const t = tracerFor(client)
|
|
86
|
-
t._enqueueSpan({ trace_id: 'A', name: 'a1' })
|
|
87
|
-
t.emitMetric('m', 1)
|
|
88
|
-
await t.flush()
|
|
89
|
-
await t.flush()
|
|
90
|
-
assert.equal(client.calls.ingest.length, 1)
|
|
91
|
-
assert.equal(client.calls.metrics.length, 1)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('does nothing when there is nothing buffered', async () => {
|
|
95
|
-
const client = fakeClient()
|
|
96
|
-
await tracerFor(client).flush()
|
|
97
|
-
assert.equal(client.calls.ingest.length + client.calls.metrics.length, 0)
|
|
98
|
-
})
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
describe('CiphyrsTracer — a failed flush must not destroy the batch', () => {
|
|
102
|
-
it('re-queues spans and says so', async () => {
|
|
103
|
-
const client = fakeClient({ failIngest: true })
|
|
104
|
-
const t = tracerFor(client)
|
|
105
|
-
t._enqueueSpan({ trace_id: 'A', name: 'a1' })
|
|
106
|
-
await t.flush()
|
|
107
|
-
assert.match(logged.join('\n'), /re-queued/)
|
|
108
|
-
assert.equal(await pendingSpanCount(t, client), 1, 'the span was destroyed')
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
it('re-queues metrics and says so', async () => {
|
|
112
|
-
const client = fakeClient({ failMetrics: true })
|
|
113
|
-
const t = tracerFor(client)
|
|
114
|
-
t.emitMetric('token_cost', 0.42)
|
|
115
|
-
await t.flush()
|
|
116
|
-
const before = client.calls.metrics.length
|
|
117
|
-
await t.flush()
|
|
118
|
-
assert.ok(client.calls.metrics.length > before, 'the metric was destroyed')
|
|
119
|
-
assert.equal(client.calls.metrics.at(-1).metrics[0].name, 'token_cost')
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
it('delivers the batch once the collector comes back', async () => {
|
|
123
|
-
// Re-queueing is only worth anything if a later attempt picks it up.
|
|
124
|
-
const client = fakeClient({ failIngest: true })
|
|
125
|
-
const t = tracerFor(client)
|
|
126
|
-
t._enqueueSpan({ trace_id: 'A', name: 'a1' })
|
|
127
|
-
await t.flush()
|
|
128
|
-
client.trace.ingest = async (p, trace, spans) => {
|
|
129
|
-
client.calls.ingest.push({ trace, spans }); return { ok: true }
|
|
130
|
-
}
|
|
131
|
-
await t.flush()
|
|
132
|
-
assert.equal(client.calls.ingest.at(-1).spans[0].name, 'a1')
|
|
133
|
-
// ...and is then gone, not resent forever.
|
|
134
|
-
const n = client.calls.ingest.length
|
|
135
|
-
await t.flush()
|
|
136
|
-
assert.equal(client.calls.ingest.length, n)
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
it('keeps two failed traces in the order they were produced', async () => {
|
|
140
|
-
// Spans post one trace at a time, so the second failure lands on top of
|
|
141
|
-
// the first — the one case where re-queueing at the front would silently
|
|
142
|
-
// reverse the timeline.
|
|
143
|
-
const client = fakeClient({ failIngest: true })
|
|
144
|
-
const t = tracerFor(client)
|
|
145
|
-
t._enqueueSpan({ trace_id: 'A', name: 'a1' })
|
|
146
|
-
t._enqueueSpan({ trace_id: 'B', name: 'b1' })
|
|
147
|
-
await t.flush()
|
|
148
|
-
const before = client.calls.ingest.length
|
|
149
|
-
await t.flush()
|
|
150
|
-
const names = client.calls.ingest.slice(before).flatMap(c => c.spans.map(s => s.name))
|
|
151
|
-
assert.deepEqual(names, ['a1', 'b1'])
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
it('bounds the re-queue so an outage cannot exhaust memory', async () => {
|
|
155
|
-
// A tracing SDK that OOMs the process it observes is worse than one that
|
|
156
|
-
// drops telemetry — but it has to say what it dropped.
|
|
157
|
-
const client = fakeClient({ failIngest: true })
|
|
158
|
-
const t = tracerFor(client, { batchSize: 5 }) // cap = max(5*20, 1000) = 1000
|
|
159
|
-
for (let i = 0; i < 1200; i++) t._enqueueSpan({ trace_id: 'A', name: `s${i}` })
|
|
160
|
-
await t.flush()
|
|
161
|
-
assert.match(logged.join('\n'), /dropped \d+ oldest/)
|
|
162
|
-
assert.ok(await pendingSpanCount(t, client) <= 1000)
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
it('drops the oldest, keeping the most recent spans', async () => {
|
|
166
|
-
const client = fakeClient({ failIngest: true })
|
|
167
|
-
const t = tracerFor(client, { batchSize: 5 })
|
|
168
|
-
for (let i = 0; i < 1200; i++) t._enqueueSpan({ trace_id: 'A', name: `s${i}` })
|
|
169
|
-
await t.flush()
|
|
170
|
-
const before = client.calls.ingest.length
|
|
171
|
-
await t.flush()
|
|
172
|
-
const names = client.calls.ingest.slice(before).flatMap(c => c.spans.map(s => s.name))
|
|
173
|
-
assert.equal(names.at(-1), 's1199', 'threw away the newest instead of the oldest')
|
|
174
|
-
assert.ok(!names.includes('s0'))
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
it('a metrics failure does not take the spans down with it', async () => {
|
|
178
|
-
const client = fakeClient({ failMetrics: true })
|
|
179
|
-
const t = tracerFor(client)
|
|
180
|
-
t._enqueueSpan({ trace_id: 'A', name: 'a1' })
|
|
181
|
-
t.emitMetric('m', 1)
|
|
182
|
-
await t.flush()
|
|
183
|
-
assert.equal(client.calls.ingest.length, 1, 'spans were sent')
|
|
184
|
-
assert.equal(await pendingSpanCount(t, client), 0, 'spans were needlessly re-queued')
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
it('never rejects — observability must not crash the observed process', async () => {
|
|
188
|
-
const client = fakeClient({ failIngest: true, failMetrics: true })
|
|
189
|
-
const t = tracerFor(client)
|
|
190
|
-
t._enqueueSpan({ trace_id: 'A', name: 'a1' })
|
|
191
|
-
t.emitMetric('m', 1)
|
|
192
|
-
await t.flush() // must resolve
|
|
193
|
-
})
|
|
194
|
-
})
|