@zuplo/cli 7.7.7 → 7.7.9
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/node_modules/@posthog/core/dist/error-tracking/index.d.ts +1 -1
- package/node_modules/@posthog/core/dist/error-tracking/index.d.ts.map +1 -1
- package/node_modules/@posthog/core/dist/error-tracking/parsers/base.d.ts.map +1 -1
- package/node_modules/@posthog/core/dist/error-tracking/parsers/base.js +19 -2
- package/node_modules/@posthog/core/dist/error-tracking/parsers/base.mjs +19 -2
- package/node_modules/@posthog/core/dist/index.d.ts +7 -0
- package/node_modules/@posthog/core/dist/index.d.ts.map +1 -1
- package/node_modules/@posthog/core/dist/index.js +82 -21
- package/node_modules/@posthog/core/dist/index.mjs +4 -0
- package/node_modules/@posthog/core/dist/logs/index.d.ts +3 -2
- package/node_modules/@posthog/core/dist/logs/index.d.ts.map +1 -1
- package/node_modules/@posthog/core/dist/logs/index.js +28 -22
- package/node_modules/@posthog/core/dist/logs/index.mjs +29 -23
- package/node_modules/@posthog/core/dist/logs/logs-utils.js +1 -1
- package/node_modules/@posthog/core/dist/logs/logs-utils.mjs +2 -2
- package/node_modules/@posthog/core/dist/logs/types.d.ts +2 -2
- package/node_modules/@posthog/core/dist/metrics/index.d.ts +5 -2
- package/node_modules/@posthog/core/dist/metrics/index.d.ts.map +1 -1
- package/node_modules/@posthog/core/dist/metrics/index.js +28 -14
- package/node_modules/@posthog/core/dist/metrics/index.mjs +29 -15
- package/node_modules/@posthog/core/dist/metrics/metrics-utils.d.ts.map +1 -1
- package/node_modules/@posthog/core/dist/metrics/metrics-utils.js +1 -2
- package/node_modules/@posthog/core/dist/metrics/metrics-utils.mjs +2 -3
- package/node_modules/@posthog/core/dist/metrics/types.d.ts +7 -0
- package/node_modules/@posthog/core/dist/metrics/types.d.ts.map +1 -1
- package/node_modules/@posthog/core/dist/posthog-core-stateless.d.ts +32 -4
- package/node_modules/@posthog/core/dist/posthog-core-stateless.d.ts.map +1 -1
- package/node_modules/@posthog/core/dist/posthog-core-stateless.js +68 -20
- package/node_modules/@posthog/core/dist/posthog-core-stateless.mjs +68 -20
- package/node_modules/@posthog/core/dist/traces/config.d.ts +13 -0
- package/node_modules/@posthog/core/dist/traces/config.d.ts.map +1 -0
- package/node_modules/@posthog/core/dist/traces/config.js +101 -0
- package/node_modules/@posthog/core/dist/traces/config.mjs +63 -0
- package/node_modules/@posthog/core/dist/traces/context.d.ts +18 -0
- package/node_modules/@posthog/core/dist/traces/context.d.ts.map +1 -0
- package/node_modules/@posthog/core/dist/traces/context.js +53 -0
- package/node_modules/@posthog/core/dist/traces/context.mjs +15 -0
- package/node_modules/@posthog/core/dist/traces/ids.d.ts +12 -0
- package/node_modules/@posthog/core/dist/traces/ids.d.ts.map +1 -0
- package/node_modules/@posthog/core/dist/traces/ids.js +92 -0
- package/node_modules/@posthog/core/dist/traces/ids.mjs +42 -0
- package/node_modules/@posthog/core/dist/traces/index.d.ts +150 -0
- package/node_modules/@posthog/core/dist/traces/index.d.ts.map +1 -0
- package/node_modules/@posthog/core/dist/traces/index.js +551 -0
- package/node_modules/@posthog/core/dist/traces/index.mjs +513 -0
- package/node_modules/@posthog/core/dist/traces/otlp.d.ts +24 -0
- package/node_modules/@posthog/core/dist/traces/otlp.d.ts.map +1 -0
- package/node_modules/@posthog/core/dist/traces/otlp.js +158 -0
- package/node_modules/@posthog/core/dist/traces/otlp.mjs +108 -0
- package/node_modules/@posthog/core/dist/traces/sanitize.d.ts +32 -0
- package/node_modules/@posthog/core/dist/traces/sanitize.d.ts.map +1 -0
- package/node_modules/@posthog/core/dist/traces/sanitize.js +93 -0
- package/node_modules/@posthog/core/dist/traces/sanitize.mjs +43 -0
- package/node_modules/@posthog/core/dist/traces/span.d.ts +189 -0
- package/node_modules/@posthog/core/dist/traces/span.d.ts.map +1 -0
- package/node_modules/@posthog/core/dist/traces/span.js +533 -0
- package/node_modules/@posthog/core/dist/traces/span.mjs +462 -0
- package/node_modules/@posthog/core/dist/traces/traceparent.d.ts +51 -0
- package/node_modules/@posthog/core/dist/traces/traceparent.d.ts.map +1 -0
- package/node_modules/@posthog/core/dist/traces/traceparent.js +116 -0
- package/node_modules/@posthog/core/dist/traces/traceparent.mjs +63 -0
- package/node_modules/@posthog/core/dist/traces/types.d.ts +114 -0
- package/node_modules/@posthog/core/dist/traces/types.d.ts.map +1 -0
- package/node_modules/@posthog/core/dist/traces/types.js +18 -0
- package/node_modules/@posthog/core/dist/traces/types.mjs +1 -0
- package/node_modules/@posthog/core/dist/utils/backoff.d.ts +23 -0
- package/node_modules/@posthog/core/dist/utils/backoff.d.ts.map +1 -0
- package/node_modules/@posthog/core/dist/utils/backoff.js +65 -0
- package/node_modules/@posthog/core/dist/utils/backoff.mjs +14 -0
- package/node_modules/@posthog/core/dist/utils/flush-timer.d.ts +26 -0
- package/node_modules/@posthog/core/dist/utils/flush-timer.d.ts.map +1 -0
- package/node_modules/@posthog/core/dist/utils/flush-timer.js +67 -0
- package/node_modules/@posthog/core/dist/utils/flush-timer.mjs +29 -0
- package/node_modules/@posthog/core/dist/utils/json-utils.d.ts +7 -0
- package/node_modules/@posthog/core/dist/utils/json-utils.d.ts.map +1 -1
- package/node_modules/@posthog/core/dist/utils/json-utils.js +27 -0
- package/node_modules/@posthog/core/dist/utils/json-utils.mjs +25 -1
- package/node_modules/@posthog/core/dist/utils/otlp-resource.d.ts +13 -2
- package/node_modules/@posthog/core/dist/utils/otlp-resource.d.ts.map +1 -1
- package/node_modules/@posthog/core/dist/utils/otlp-resource.js +34 -7
- package/node_modules/@posthog/core/dist/utils/otlp-resource.mjs +26 -2
- package/node_modules/@posthog/core/dist/utils/retry-after.d.ts +55 -0
- package/node_modules/@posthog/core/dist/utils/retry-after.d.ts.map +1 -0
- package/node_modules/@posthog/core/dist/utils/retry-after.js +95 -0
- package/node_modules/@posthog/core/dist/utils/retry-after.mjs +50 -0
- package/node_modules/@posthog/core/package.json +3 -3
- package/node_modules/@posthog/core/src/error-tracking/index.ts +24 -1
- package/node_modules/@posthog/core/src/error-tracking/parsers/base.spec.ts +77 -7
- package/node_modules/@posthog/core/src/error-tracking/parsers/base.ts +42 -19
- package/node_modules/@posthog/core/src/index.ts +18 -0
- package/node_modules/@posthog/core/src/logs/index.spec.ts +490 -2
- package/node_modules/@posthog/core/src/logs/index.ts +63 -31
- package/node_modules/@posthog/core/src/logs/logs-utils.ts +2 -2
- package/node_modules/@posthog/core/src/logs/types.ts +2 -2
- package/node_modules/@posthog/core/src/metrics/index.spec.ts +304 -0
- package/node_modules/@posthog/core/src/metrics/index.ts +53 -16
- package/node_modules/@posthog/core/src/metrics/metrics-utils.ts +2 -3
- package/node_modules/@posthog/core/src/metrics/types.ts +10 -2
- package/node_modules/@posthog/core/src/posthog-core-stateless.ts +157 -38
- package/node_modules/@posthog/core/src/traces/config.spec.ts +271 -0
- package/node_modules/@posthog/core/src/traces/config.ts +142 -0
- package/node_modules/@posthog/core/src/traces/context.ts +30 -0
- package/node_modules/@posthog/core/src/traces/ids.spec.ts +115 -0
- package/node_modules/@posthog/core/src/traces/ids.ts +75 -0
- package/node_modules/@posthog/core/src/traces/index.spec.ts +3187 -0
- package/node_modules/@posthog/core/src/traces/index.ts +1060 -0
- package/node_modules/@posthog/core/src/traces/live-spans.spec.ts +69 -0
- package/node_modules/@posthog/core/src/traces/otlp.spec.ts +368 -0
- package/node_modules/@posthog/core/src/traces/otlp.ts +191 -0
- package/node_modules/@posthog/core/src/traces/sanitize.ts +116 -0
- package/node_modules/@posthog/core/src/traces/span.spec.ts +1122 -0
- package/node_modules/@posthog/core/src/traces/span.ts +822 -0
- package/node_modules/@posthog/core/src/traces/traceparent.spec.ts +184 -0
- package/node_modules/@posthog/core/src/traces/traceparent.ts +172 -0
- package/node_modules/@posthog/core/src/traces/types.ts +140 -0
- package/node_modules/@posthog/core/src/utils/backoff.ts +44 -0
- package/node_modules/@posthog/core/src/utils/flush-timer.ts +50 -0
- package/node_modules/@posthog/core/src/utils/json-utils.ts +33 -0
- package/node_modules/@posthog/core/src/utils/otlp-resource.spec.ts +55 -9
- package/node_modules/@posthog/core/src/utils/otlp-resource.ts +45 -4
- package/node_modules/@posthog/core/src/utils/retry-after.ts +135 -0
- package/node_modules/@posthog/types/dist/index.d.ts +1 -0
- package/node_modules/@posthog/types/dist/index.d.ts.map +1 -1
- package/node_modules/@posthog/types/dist/posthog-config.d.ts +10 -5
- package/node_modules/@posthog/types/dist/posthog-config.d.ts.map +1 -1
- package/node_modules/@posthog/types/dist/traces.d.ts +387 -0
- package/node_modules/@posthog/types/dist/traces.d.ts.map +1 -0
- package/node_modules/@posthog/types/dist/traces.js +18 -0
- package/node_modules/@posthog/types/dist/traces.mjs +1 -0
- package/node_modules/@posthog/types/package.json +1 -1
- package/node_modules/@posthog/types/src/index.ts +19 -0
- package/node_modules/@posthog/types/src/posthog-config.ts +10 -5
- package/node_modules/@posthog/types/src/traces.ts +429 -0
- package/node_modules/@zuplo/core/package.json +1 -1
- package/node_modules/@zuplo/graphql/package.json +1 -1
- package/node_modules/@zuplo/openapi-tools/package.json +1 -1
- package/node_modules/@zuplo/otel/package.json +1 -1
- package/node_modules/@zuplo/runtime/out/esm/{chunk-FEB2VFS7.js → chunk-KNAYKBEE.js} +96 -96
- package/node_modules/@zuplo/runtime/out/esm/chunk-KNAYKBEE.js.map +1 -0
- package/node_modules/@zuplo/runtime/out/esm/index.js +1 -1
- package/node_modules/@zuplo/runtime/out/esm/mcp-gateway/index.js +1 -1
- package/node_modules/@zuplo/runtime/out/esm/mocks/index.js +1 -1
- package/node_modules/@zuplo/runtime/package.json +1 -1
- package/package.json +11 -11
- package/node_modules/@zuplo/runtime/out/esm/chunk-FEB2VFS7.js.map +0 -1
- /package/node_modules/@zuplo/runtime/out/esm/{chunk-FEB2VFS7.js.LEGAL.txt → chunk-KNAYKBEE.js.LEGAL.txt} +0 -0
|
@@ -164,6 +164,9 @@ describe('PostHogLogs', () => {
|
|
|
164
164
|
beforeEach(() => {
|
|
165
165
|
mockInstance = createMockInstance()
|
|
166
166
|
logger = createMockLogger()
|
|
167
|
+
// Retry delays carry jitter; pinned to its midpoint so every timing
|
|
168
|
+
// assertion here measures the backoff itself and cannot flake.
|
|
169
|
+
vi.spyOn(Math, 'random').mockReturnValue(0.5)
|
|
167
170
|
})
|
|
168
171
|
|
|
169
172
|
it('constructs without throwing', () => {
|
|
@@ -927,7 +930,7 @@ describe('PostHogLogs', () => {
|
|
|
927
930
|
await logs.flush()
|
|
928
931
|
|
|
929
932
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
930
|
-
expect.stringContaining('Dropping a single log record
|
|
933
|
+
expect.stringContaining('Dropping a single log record with batch size 1')
|
|
931
934
|
)
|
|
932
935
|
})
|
|
933
936
|
|
|
@@ -985,7 +988,7 @@ describe('PostHogLogs', () => {
|
|
|
985
988
|
expect(sendSizes).toEqual([1])
|
|
986
989
|
expect(readQueue(mockInstance)).toHaveLength(0)
|
|
987
990
|
expect(logger.warn).toHaveBeenCalledWith(
|
|
988
|
-
expect.stringContaining('Dropping a single log record
|
|
991
|
+
expect.stringContaining('Dropping a single log record with batch size 1')
|
|
989
992
|
)
|
|
990
993
|
})
|
|
991
994
|
|
|
@@ -1166,6 +1169,468 @@ describe('PostHogLogs', () => {
|
|
|
1166
1169
|
expect(readQueue(mockInstance)).toHaveLength(0)
|
|
1167
1170
|
})
|
|
1168
1171
|
|
|
1172
|
+
it('waits out Retry-After even when a capture re-arms the timer mid-flush', async () => {
|
|
1173
|
+
// The capture that lands while the send is in flight arms a timer at the
|
|
1174
|
+
// plain interval; the 429 then asks for far longer. The earlier timer must
|
|
1175
|
+
// not fire first, or the SDK sends inside the window it was told to skip.
|
|
1176
|
+
mockInstance._sendLogsBatch = vi.fn(async () => {
|
|
1177
|
+
logs.captureLog({ body: 'arrived mid-flush' })
|
|
1178
|
+
return { kind: 'retry-later', error: new Error('429'), retryAfterMs: 300_000 }
|
|
1179
|
+
})
|
|
1180
|
+
|
|
1181
|
+
const logs = new PostHogLogs(
|
|
1182
|
+
mockInstance,
|
|
1183
|
+
resolveForTest({ flushIntervalMs: 5000 }),
|
|
1184
|
+
logger,
|
|
1185
|
+
getContextFor(mockInstance),
|
|
1186
|
+
immediateOnReady
|
|
1187
|
+
)
|
|
1188
|
+
logs.captureLog({ body: 'first' })
|
|
1189
|
+
|
|
1190
|
+
await vi.advanceTimersByTimeAsync(5000)
|
|
1191
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1192
|
+
|
|
1193
|
+
await vi.advanceTimersByTimeAsync(6000)
|
|
1194
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1195
|
+
|
|
1196
|
+
await vi.advanceTimersByTimeAsync(300_000)
|
|
1197
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(2)
|
|
1198
|
+
})
|
|
1199
|
+
|
|
1200
|
+
it('does not let the size trigger send inside a Retry-After window', async () => {
|
|
1201
|
+
mockInstance._sendLogsBatch = vi.fn(() =>
|
|
1202
|
+
Promise.resolve({ kind: 'retry-later', error: new Error('429'), retryAfterMs: 300_000 })
|
|
1203
|
+
)
|
|
1204
|
+
const logs = new PostHogLogs(
|
|
1205
|
+
mockInstance,
|
|
1206
|
+
resolveForTest({ flushIntervalMs: 1000, maxBufferSize: 2 }),
|
|
1207
|
+
logger,
|
|
1208
|
+
getContextFor(mockInstance),
|
|
1209
|
+
immediateOnReady
|
|
1210
|
+
)
|
|
1211
|
+
logs.captureLog({ body: 'first' })
|
|
1212
|
+
await vi.advanceTimersByTimeAsync(1000)
|
|
1213
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1214
|
+
|
|
1215
|
+
// Enough records to trip the size trigger, well inside the window.
|
|
1216
|
+
logs.captureLog({ body: 'second' })
|
|
1217
|
+
logs.captureLog({ body: 'third' })
|
|
1218
|
+
await vi.advanceTimersByTimeAsync(1000)
|
|
1219
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1220
|
+
|
|
1221
|
+
await vi.advanceTimersByTimeAsync(300_000)
|
|
1222
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(2)
|
|
1223
|
+
})
|
|
1224
|
+
|
|
1225
|
+
it('does not let onReconnect send inside a Retry-After window', async () => {
|
|
1226
|
+
// `online` fires on every network handover; it says nothing about the
|
|
1227
|
+
// rate-limit window the endpoint set.
|
|
1228
|
+
mockInstance._sendLogsBatch = vi.fn(() =>
|
|
1229
|
+
Promise.resolve({ kind: 'retry-later', error: new Error('429'), retryAfterMs: 300_000 })
|
|
1230
|
+
)
|
|
1231
|
+
const logs = new PostHogLogs(
|
|
1232
|
+
mockInstance,
|
|
1233
|
+
resolveForTest({ flushIntervalMs: 1000 }),
|
|
1234
|
+
logger,
|
|
1235
|
+
getContextFor(mockInstance),
|
|
1236
|
+
immediateOnReady
|
|
1237
|
+
)
|
|
1238
|
+
logs.captureLog({ body: 'first' })
|
|
1239
|
+
await vi.advanceTimersByTimeAsync(1000)
|
|
1240
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1241
|
+
|
|
1242
|
+
logs.onReconnect()
|
|
1243
|
+
await vi.advanceTimersByTimeAsync(1000)
|
|
1244
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1245
|
+
})
|
|
1246
|
+
|
|
1247
|
+
it('flushes on reconnect once the Retry-After window has passed', async () => {
|
|
1248
|
+
const outcomes: any[] = [{ kind: 'retry-later', error: new Error('429'), retryAfterMs: 5000 }]
|
|
1249
|
+
mockInstance._sendLogsBatch = vi.fn(() => Promise.resolve(outcomes.shift() ?? { kind: 'ok' }))
|
|
1250
|
+
const logs = new PostHogLogs(
|
|
1251
|
+
mockInstance,
|
|
1252
|
+
resolveForTest({ flushIntervalMs: 1000 }),
|
|
1253
|
+
logger,
|
|
1254
|
+
getContextFor(mockInstance),
|
|
1255
|
+
immediateOnReady
|
|
1256
|
+
)
|
|
1257
|
+
logs.captureLog({ body: 'first' })
|
|
1258
|
+
await vi.advanceTimersByTimeAsync(1000)
|
|
1259
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1260
|
+
|
|
1261
|
+
// Stop just short of the deadline, then cross it without letting the
|
|
1262
|
+
// re-armed timer fire — otherwise the timer satisfies the assertion and
|
|
1263
|
+
// the test says nothing about onReconnect.
|
|
1264
|
+
await vi.advanceTimersByTimeAsync(4999)
|
|
1265
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1266
|
+
vi.setSystemTime(Date.now() + 2)
|
|
1267
|
+
|
|
1268
|
+
logs.onReconnect()
|
|
1269
|
+
await vi.advanceTimersByTimeAsync(1)
|
|
1270
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(2)
|
|
1271
|
+
})
|
|
1272
|
+
|
|
1273
|
+
it('does not let a capture after an explicit flush send inside the window', async () => {
|
|
1274
|
+
// `flush()` is the lifecycle path (RN foreground/background, shutdown).
|
|
1275
|
+
// It leaves no timer behind, so the next capture is the one that arms
|
|
1276
|
+
// one — at the plain interval unless the window floors it.
|
|
1277
|
+
mockInstance._sendLogsBatch = vi.fn(() =>
|
|
1278
|
+
Promise.resolve({ kind: 'retry-later', error: new Error('429'), retryAfterMs: 300_000 })
|
|
1279
|
+
)
|
|
1280
|
+
const logs = new PostHogLogs(
|
|
1281
|
+
mockInstance,
|
|
1282
|
+
resolveForTest({ flushIntervalMs: 5000 }),
|
|
1283
|
+
logger,
|
|
1284
|
+
getContextFor(mockInstance),
|
|
1285
|
+
immediateOnReady
|
|
1286
|
+
)
|
|
1287
|
+
logs.captureLog({ body: 'first' })
|
|
1288
|
+
await logs.flush().catch(() => {})
|
|
1289
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1290
|
+
|
|
1291
|
+
logs.captureLog({ body: 'second' })
|
|
1292
|
+
await vi.advanceTimersByTimeAsync(5000)
|
|
1293
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1294
|
+
|
|
1295
|
+
await vi.advanceTimersByTimeAsync(295_000)
|
|
1296
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(2)
|
|
1297
|
+
})
|
|
1298
|
+
|
|
1299
|
+
it('does not let a capture during an explicit flush send inside the window', async () => {
|
|
1300
|
+
// The sibling case covers a capture *after* the flush settles. This one
|
|
1301
|
+
// lands while the send is in flight, so it arms the timer at the plain
|
|
1302
|
+
// interval before the window exists.
|
|
1303
|
+
let release: (v: any) => void = () => {}
|
|
1304
|
+
mockInstance._sendLogsBatch = vi.fn(
|
|
1305
|
+
() =>
|
|
1306
|
+
new Promise((resolve) => {
|
|
1307
|
+
release = resolve
|
|
1308
|
+
})
|
|
1309
|
+
)
|
|
1310
|
+
const logs = new PostHogLogs(
|
|
1311
|
+
mockInstance,
|
|
1312
|
+
resolveForTest({ flushIntervalMs: 5000 }),
|
|
1313
|
+
logger,
|
|
1314
|
+
getContextFor(mockInstance),
|
|
1315
|
+
immediateOnReady
|
|
1316
|
+
)
|
|
1317
|
+
logs.captureLog({ body: 'first' })
|
|
1318
|
+
const flushed = logs.flush().catch(() => {})
|
|
1319
|
+
await vi.advanceTimersByTimeAsync(0)
|
|
1320
|
+
|
|
1321
|
+
logs.captureLog({ body: 'second' })
|
|
1322
|
+
release({ kind: 'retry-later', error: new Error('429'), retryAfterMs: 300_000 })
|
|
1323
|
+
await flushed
|
|
1324
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1325
|
+
|
|
1326
|
+
await vi.advanceTimersByTimeAsync(5000)
|
|
1327
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1328
|
+
})
|
|
1329
|
+
|
|
1330
|
+
it('arms a timer when onReconnect lands inside the window after an explicit flush', async () => {
|
|
1331
|
+
// `flush()` leaves no timer behind, so returning early here without
|
|
1332
|
+
// arming one leaves the records with nothing scheduled at all.
|
|
1333
|
+
mockInstance._sendLogsBatch = vi.fn(() =>
|
|
1334
|
+
Promise.resolve({ kind: 'retry-later', error: new Error('429'), retryAfterMs: 300_000 })
|
|
1335
|
+
)
|
|
1336
|
+
const logs = new PostHogLogs(
|
|
1337
|
+
mockInstance,
|
|
1338
|
+
resolveForTest({ flushIntervalMs: 10_000 }),
|
|
1339
|
+
logger,
|
|
1340
|
+
getContextFor(mockInstance),
|
|
1341
|
+
immediateOnReady
|
|
1342
|
+
)
|
|
1343
|
+
logs.captureLog({ body: 'first' })
|
|
1344
|
+
await logs.flush().catch(() => {})
|
|
1345
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1346
|
+
|
|
1347
|
+
await vi.advanceTimersByTimeAsync(10_000)
|
|
1348
|
+
logs.onReconnect()
|
|
1349
|
+
|
|
1350
|
+
await vi.advanceTimersByTimeAsync(289_000)
|
|
1351
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1352
|
+
await vi.advanceTimersByTimeAsync(2000)
|
|
1353
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(2)
|
|
1354
|
+
})
|
|
1355
|
+
|
|
1356
|
+
it('releases a record captured mid-flush once that flush closes the window', async () => {
|
|
1357
|
+
// The capture arms against the window that was open when it landed; the
|
|
1358
|
+
// outcome then closes that window, so the timer has to come back down.
|
|
1359
|
+
const logs = new PostHogLogs(
|
|
1360
|
+
mockInstance,
|
|
1361
|
+
resolveForTest({ flushIntervalMs: 10_000 }),
|
|
1362
|
+
logger,
|
|
1363
|
+
getContextFor(mockInstance),
|
|
1364
|
+
immediateOnReady
|
|
1365
|
+
)
|
|
1366
|
+
let sends = 0
|
|
1367
|
+
mockInstance._sendLogsBatch = vi.fn(async () => {
|
|
1368
|
+
sends += 1
|
|
1369
|
+
if (sends === 1) {
|
|
1370
|
+
return { kind: 'retry-later', error: new Error('429'), retryAfterMs: 300_000 }
|
|
1371
|
+
}
|
|
1372
|
+
await Promise.resolve()
|
|
1373
|
+
if (sends === 2) {
|
|
1374
|
+
logs.captureLog({ body: 'mid' })
|
|
1375
|
+
}
|
|
1376
|
+
return { kind: 'ok' }
|
|
1377
|
+
})
|
|
1378
|
+
|
|
1379
|
+
logs.captureLog({ body: 'first' })
|
|
1380
|
+
await vi.advanceTimersByTimeAsync(10_000)
|
|
1381
|
+
await logs.flush().catch(() => {})
|
|
1382
|
+
|
|
1383
|
+
await vi.advanceTimersByTimeAsync(10_000)
|
|
1384
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(3)
|
|
1385
|
+
})
|
|
1386
|
+
|
|
1387
|
+
it('keeps the Retry-After window when a batch is refused for size', async () => {
|
|
1388
|
+
// `too-large` is a verdict on the body's size — the SDK's own or a 413 —
|
|
1389
|
+
// so it says nothing about the endpoint's rate limit and must not end the
|
|
1390
|
+
// wait.
|
|
1391
|
+
const outcomes: any[] = [{ kind: 'retry-later', error: new Error('429'), retryAfterMs: 300_000 }]
|
|
1392
|
+
mockInstance._sendLogsBatch = vi.fn(() => Promise.resolve(outcomes.shift() ?? { kind: 'too-large' }))
|
|
1393
|
+
const logs = new PostHogLogs(
|
|
1394
|
+
mockInstance,
|
|
1395
|
+
resolveForTest({ flushIntervalMs: 1000, maxBatchRecordsPerPost: 1 }),
|
|
1396
|
+
logger,
|
|
1397
|
+
getContextFor(mockInstance),
|
|
1398
|
+
immediateOnReady
|
|
1399
|
+
)
|
|
1400
|
+
logs.captureLog({ body: 'first' })
|
|
1401
|
+
await vi.advanceTimersByTimeAsync(1000)
|
|
1402
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1403
|
+
|
|
1404
|
+
// A batch of one the endpoint cannot accept: the record is dropped.
|
|
1405
|
+
await logs.flush()
|
|
1406
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(2)
|
|
1407
|
+
|
|
1408
|
+
logs.captureLog({ body: 'second' })
|
|
1409
|
+
logs.onReconnect()
|
|
1410
|
+
await vi.advanceTimersByTimeAsync(1000)
|
|
1411
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(2)
|
|
1412
|
+
})
|
|
1413
|
+
|
|
1414
|
+
it('ends the window when an explicit flush succeeds', async () => {
|
|
1415
|
+
// The endpoint just accepted a batch, so the wait it asked for earlier is
|
|
1416
|
+
// over — the gated paths must not stay blocked for the rest of it.
|
|
1417
|
+
const outcomes: any[] = [{ kind: 'retry-later', error: new Error('429'), retryAfterMs: 300_000 }]
|
|
1418
|
+
mockInstance._sendLogsBatch = vi.fn(() => Promise.resolve(outcomes.shift() ?? { kind: 'ok' }))
|
|
1419
|
+
const logs = new PostHogLogs(
|
|
1420
|
+
mockInstance,
|
|
1421
|
+
resolveForTest({ flushIntervalMs: 5000, maxBufferSize: 2 }),
|
|
1422
|
+
logger,
|
|
1423
|
+
getContextFor(mockInstance),
|
|
1424
|
+
immediateOnReady
|
|
1425
|
+
)
|
|
1426
|
+
logs.captureLog({ body: 'first' })
|
|
1427
|
+
await vi.advanceTimersByTimeAsync(5000)
|
|
1428
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1429
|
+
|
|
1430
|
+
await logs.flush()
|
|
1431
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(2)
|
|
1432
|
+
|
|
1433
|
+
logs.captureLog({ body: 'second' })
|
|
1434
|
+
logs.onReconnect()
|
|
1435
|
+
await vi.advanceTimersByTimeAsync(1)
|
|
1436
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(3)
|
|
1437
|
+
})
|
|
1438
|
+
|
|
1439
|
+
it('drops a Retry-After wait on reset', async () => {
|
|
1440
|
+
const outcomes: any[] = [{ kind: 'retry-later', error: new Error('429'), retryAfterMs: 300_000 }]
|
|
1441
|
+
mockInstance._sendLogsBatch = vi.fn(() => Promise.resolve(outcomes.shift() ?? { kind: 'ok' }))
|
|
1442
|
+
const logs = new PostHogLogs(
|
|
1443
|
+
mockInstance,
|
|
1444
|
+
resolveForTest({ flushIntervalMs: 1000 }),
|
|
1445
|
+
logger,
|
|
1446
|
+
getContextFor(mockInstance),
|
|
1447
|
+
immediateOnReady
|
|
1448
|
+
)
|
|
1449
|
+
logs.captureLog({ body: 'first' })
|
|
1450
|
+
await vi.advanceTimersByTimeAsync(1000)
|
|
1451
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1452
|
+
|
|
1453
|
+
// Asserted through a gated path: a plain capture would flush either way.
|
|
1454
|
+
logs.reset()
|
|
1455
|
+
logs.captureLog({ body: 'second' })
|
|
1456
|
+
logs.onReconnect()
|
|
1457
|
+
await vi.advanceTimersByTimeAsync(1)
|
|
1458
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(2)
|
|
1459
|
+
})
|
|
1460
|
+
|
|
1461
|
+
it('keeps its own backoff when the endpoint asks for less', async () => {
|
|
1462
|
+
// A proxy answering `Retry-After: 1` must not turn the retry into a
|
|
1463
|
+
// one-second hot loop against an endpoint already refusing traffic.
|
|
1464
|
+
mockInstance._sendLogsBatch = vi.fn(() =>
|
|
1465
|
+
Promise.resolve({ kind: 'retry-later', error: new Error('503'), retryAfterMs: 10 })
|
|
1466
|
+
)
|
|
1467
|
+
const logs = new PostHogLogs(
|
|
1468
|
+
mockInstance,
|
|
1469
|
+
resolveForTest({ flushIntervalMs: 5000 }),
|
|
1470
|
+
logger,
|
|
1471
|
+
getContextFor(mockInstance),
|
|
1472
|
+
immediateOnReady
|
|
1473
|
+
)
|
|
1474
|
+
logs.captureLog({ body: 'first' })
|
|
1475
|
+
await vi.advanceTimersByTimeAsync(5000)
|
|
1476
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1477
|
+
|
|
1478
|
+
await vi.advanceTimersByTimeAsync(1000)
|
|
1479
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1480
|
+
|
|
1481
|
+
await vi.advanceTimersByTimeAsync(5000)
|
|
1482
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(2)
|
|
1483
|
+
})
|
|
1484
|
+
|
|
1485
|
+
it('does not restart a served-out wait for a capture during the retry', async () => {
|
|
1486
|
+
// A deadline, not a duration. The retry's timer has already fired, so the
|
|
1487
|
+
// capture below arms the next one — and it sees the wait still set,
|
|
1488
|
+
// because the send it belongs to has not settled. Holding a duration here
|
|
1489
|
+
// re-arms for the whole window again and leaves the record 300s behind an
|
|
1490
|
+
// endpoint that has already recovered.
|
|
1491
|
+
let settle: ((outcome: any) => void) | undefined
|
|
1492
|
+
let call = 0
|
|
1493
|
+
mockInstance._sendLogsBatch = vi.fn(() => {
|
|
1494
|
+
call++
|
|
1495
|
+
if (call === 1) {
|
|
1496
|
+
return Promise.resolve({ kind: 'retry-later', error: new Error('429'), retryAfterMs: 300_000 })
|
|
1497
|
+
}
|
|
1498
|
+
return new Promise((resolve) => {
|
|
1499
|
+
settle = resolve
|
|
1500
|
+
})
|
|
1501
|
+
})
|
|
1502
|
+
const logs = new PostHogLogs(
|
|
1503
|
+
mockInstance,
|
|
1504
|
+
resolveForTest({ flushIntervalMs: 5000 }),
|
|
1505
|
+
logger,
|
|
1506
|
+
getContextFor(mockInstance),
|
|
1507
|
+
immediateOnReady
|
|
1508
|
+
)
|
|
1509
|
+
logs.captureLog({ body: 'first' })
|
|
1510
|
+
await vi.advanceTimersByTimeAsync(5000)
|
|
1511
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1512
|
+
|
|
1513
|
+
// The wait elapses and the retry goes out, but hangs.
|
|
1514
|
+
await vi.advanceTimersByTimeAsync(300_000)
|
|
1515
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(2)
|
|
1516
|
+
|
|
1517
|
+
logs.captureLog({ body: 'second' })
|
|
1518
|
+
settle?.({ kind: 'ok' })
|
|
1519
|
+
await vi.advanceTimersByTimeAsync(0)
|
|
1520
|
+
|
|
1521
|
+
// One interval, not another window.
|
|
1522
|
+
await vi.advanceTimersByTimeAsync(5000)
|
|
1523
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(3)
|
|
1524
|
+
})
|
|
1525
|
+
|
|
1526
|
+
it('closes the window at the ceiling for a host out-pacing it', async () => {
|
|
1527
|
+
// RN takes flush() on every app-state transition, and each refusal pushes
|
|
1528
|
+
// the deadline out. The ceiling is what stops the gated paths — the size
|
|
1529
|
+
// trigger and onReconnect — from being suppressed for good.
|
|
1530
|
+
mockInstance._sendLogsBatch = vi.fn(() =>
|
|
1531
|
+
Promise.resolve({ kind: 'retry-later', error: new Error('429'), retryAfterMs: 30_000 })
|
|
1532
|
+
)
|
|
1533
|
+
const logs = new PostHogLogs(
|
|
1534
|
+
mockInstance,
|
|
1535
|
+
resolveForTest({ flushIntervalMs: 60_000 }),
|
|
1536
|
+
logger,
|
|
1537
|
+
getContextFor(mockInstance),
|
|
1538
|
+
immediateOnReady
|
|
1539
|
+
)
|
|
1540
|
+
logs.captureLog({ body: 'first' })
|
|
1541
|
+
await logs.flush().catch(() => {})
|
|
1542
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1543
|
+
|
|
1544
|
+
// Lifecycle flushes every 5s against a 30s window, for longer than the
|
|
1545
|
+
// 5-minute ceiling. Sampled rather than asserted through onReconnect:
|
|
1546
|
+
// whether a given moment falls inside a window is timing-dependent, but
|
|
1547
|
+
// past the ceiling it must fall outside one.
|
|
1548
|
+
let sawWindowClosed = false
|
|
1549
|
+
for (let i = 0; i < 70; i++) {
|
|
1550
|
+
await vi.advanceTimersByTimeAsync(5000)
|
|
1551
|
+
// Sampled before the flush: a flush that finds the window closed opens
|
|
1552
|
+
// a fresh one, so sampling after it would always look open.
|
|
1553
|
+
if ((logs as any)._retryAfter.remainingMs() === 0) {
|
|
1554
|
+
sawWindowClosed = true
|
|
1555
|
+
}
|
|
1556
|
+
logs.captureLog({ body: `line ${i}` })
|
|
1557
|
+
await logs.flush().catch(() => {})
|
|
1558
|
+
}
|
|
1559
|
+
expect(sawWindowClosed).toBe(true)
|
|
1560
|
+
})
|
|
1561
|
+
|
|
1562
|
+
it('keeps flushing after a backward clock step', async () => {
|
|
1563
|
+
const outcomes: any[] = [{ kind: 'retry-later', error: new Error('429'), retryAfterMs: 60_000 }]
|
|
1564
|
+
mockInstance._sendLogsBatch = vi.fn(() => Promise.resolve(outcomes.shift() ?? { kind: 'ok' }))
|
|
1565
|
+
const logs = new PostHogLogs(
|
|
1566
|
+
mockInstance,
|
|
1567
|
+
resolveForTest({ flushIntervalMs: 1000, maxBufferSize: 2 }),
|
|
1568
|
+
logger,
|
|
1569
|
+
getContextFor(mockInstance),
|
|
1570
|
+
immediateOnReady
|
|
1571
|
+
)
|
|
1572
|
+
logs.captureLog({ body: 'first' })
|
|
1573
|
+
await vi.advanceTimersByTimeAsync(1000)
|
|
1574
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1575
|
+
|
|
1576
|
+
const real = Date.now()
|
|
1577
|
+
vi.spyOn(Date, 'now').mockImplementation(() => real - 3_600_000)
|
|
1578
|
+
|
|
1579
|
+
// A gated path: suppressed for the size of the step without the guard.
|
|
1580
|
+
logs.captureLog({ body: 'second' })
|
|
1581
|
+
logs.onReconnect()
|
|
1582
|
+
await vi.advanceTimersByTimeAsync(1)
|
|
1583
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(2)
|
|
1584
|
+
})
|
|
1585
|
+
|
|
1586
|
+
it('ends the wait when a later failure names none', async () => {
|
|
1587
|
+
// 429 with a window, then a plain 503: the queue drops back to its own
|
|
1588
|
+
// backoff rather than waiting the old window out on every attempt.
|
|
1589
|
+
const outcomes: any[] = [
|
|
1590
|
+
{ kind: 'retry-later', error: new Error('429'), retryAfterMs: 300_000 },
|
|
1591
|
+
{ kind: 'retry-later', error: new Error('503') },
|
|
1592
|
+
]
|
|
1593
|
+
mockInstance._sendLogsBatch = vi.fn(() => Promise.resolve(outcomes.shift() ?? { kind: 'ok' }))
|
|
1594
|
+
const logs = new PostHogLogs(
|
|
1595
|
+
mockInstance,
|
|
1596
|
+
resolveForTest({ flushIntervalMs: 1000 }),
|
|
1597
|
+
logger,
|
|
1598
|
+
getContextFor(mockInstance),
|
|
1599
|
+
immediateOnReady
|
|
1600
|
+
)
|
|
1601
|
+
logs.captureLog({ body: 'first' })
|
|
1602
|
+
await vi.advanceTimersByTimeAsync(1000)
|
|
1603
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1604
|
+
|
|
1605
|
+
await vi.advanceTimersByTimeAsync(300_000)
|
|
1606
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(2)
|
|
1607
|
+
|
|
1608
|
+
// Back on the plain backoff, not another 300s.
|
|
1609
|
+
await vi.advanceTimersByTimeAsync(4000)
|
|
1610
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(3)
|
|
1611
|
+
})
|
|
1612
|
+
|
|
1613
|
+
it('keeps flushing on the interval while captures keep arriving', async () => {
|
|
1614
|
+
// Every capture arms the timer. Re-arming a pending one would push the
|
|
1615
|
+
// flush out for as long as logs keep coming, stranding a steady stream
|
|
1616
|
+
// that never reaches the size trigger.
|
|
1617
|
+
const logs = new PostHogLogs(
|
|
1618
|
+
mockInstance,
|
|
1619
|
+
resolveForTest({ flushIntervalMs: 10_000, maxBufferSize: 100 }),
|
|
1620
|
+
logger,
|
|
1621
|
+
getContextFor(mockInstance),
|
|
1622
|
+
immediateOnReady
|
|
1623
|
+
)
|
|
1624
|
+
|
|
1625
|
+
for (let i = 0; i < 30; i++) {
|
|
1626
|
+
logs.captureLog({ body: `line ${i}` })
|
|
1627
|
+
await vi.advanceTimersByTimeAsync(2000)
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
expect(mockInstance._sendLogsBatch).toHaveBeenCalled()
|
|
1631
|
+
expect(readQueue(mockInstance)).toHaveLength(0)
|
|
1632
|
+
})
|
|
1633
|
+
|
|
1169
1634
|
it('stops re-arming once the queue is empty', async () => {
|
|
1170
1635
|
const logs = new PostHogLogs(
|
|
1171
1636
|
mockInstance,
|
|
@@ -1185,6 +1650,29 @@ describe('PostHogLogs', () => {
|
|
|
1185
1650
|
expect(mockInstance._sendLogsBatch).toHaveBeenCalledTimes(1)
|
|
1186
1651
|
})
|
|
1187
1652
|
|
|
1653
|
+
it('caps the retry delay at 30s however long the outage runs', async () => {
|
|
1654
|
+
// The logs contract states the backoff as capped at ~30s. Without the cap
|
|
1655
|
+
// a 5s interval reaches 320s after six doublings.
|
|
1656
|
+
mockInstance._sendLogsBatch = vi.fn(() => Promise.resolve({ kind: 'retry-later', error: new Error('down') }))
|
|
1657
|
+
const logs = new PostHogLogs(
|
|
1658
|
+
mockInstance,
|
|
1659
|
+
resolveForTest({ flushIntervalMs: 5000 }),
|
|
1660
|
+
logger,
|
|
1661
|
+
getContextFor(mockInstance),
|
|
1662
|
+
immediateOnReady
|
|
1663
|
+
)
|
|
1664
|
+
logs.captureLog({ body: 'retry-me' })
|
|
1665
|
+
|
|
1666
|
+
// Ten failures, well past the six doublings the exponent allows.
|
|
1667
|
+
for (let i = 0; i < 10; i++) {
|
|
1668
|
+
await vi.advanceTimersByTimeAsync(30_000)
|
|
1669
|
+
}
|
|
1670
|
+
// Capped, the delay settles at 30s and 300s of outage buys ten retries on
|
|
1671
|
+
// top of the two the first doublings allow. Uncapped it reaches 320s and
|
|
1672
|
+
// buys six attempts in total, so the difference is not a rounding one.
|
|
1673
|
+
expect(mockInstance._sendLogsBatch.mock.calls.length).toBeGreaterThanOrEqual(11)
|
|
1674
|
+
})
|
|
1675
|
+
|
|
1188
1676
|
it('backs off exponentially across consecutive failed flushes', async () => {
|
|
1189
1677
|
// Every flush fails, so the record stays queued and the retry interval grows:
|
|
1190
1678
|
// base (initial), base (1st retry), 2x, 4x, ...
|