@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
|
@@ -9,7 +9,10 @@ import type {
|
|
|
9
9
|
OtlpNumberDataPoint,
|
|
10
10
|
} from '@posthog/types'
|
|
11
11
|
import type { Logger } from '../types'
|
|
12
|
-
import { isArray
|
|
12
|
+
import { isArray } from '../utils'
|
|
13
|
+
import { FlushTimer } from '../utils/flush-timer'
|
|
14
|
+
import { RetryAfterWindow } from '../utils/retry-after'
|
|
15
|
+
import { MAX_FLUSH_BACKOFF_MS, NO_JITTER, backoffDelayMs, drawJitter } from '../utils/backoff'
|
|
13
16
|
import { toOtlpKeyValueList } from '../utils/otlp-any-value'
|
|
14
17
|
import {
|
|
15
18
|
DEFAULT_HISTOGRAM_BOUNDS,
|
|
@@ -62,7 +65,11 @@ interface SeriesState {
|
|
|
62
65
|
*/
|
|
63
66
|
export class PostHogMetrics {
|
|
64
67
|
private _series = new Map<string, SeriesState>()
|
|
65
|
-
private _flushTimer
|
|
68
|
+
private readonly _flushTimer = new FlushTimer(() =>
|
|
69
|
+
this.flush().catch((e) => {
|
|
70
|
+
this._logger.error('Metrics flush failed:', e)
|
|
71
|
+
})
|
|
72
|
+
)
|
|
66
73
|
// Serializes flushes — a manual flush() during an in-flight timer flush
|
|
67
74
|
// queues behind it instead of racing it for the same window.
|
|
68
75
|
private _flushPromise: Promise<void> | null = null
|
|
@@ -73,6 +80,9 @@ export class PostHogMetrics {
|
|
|
73
80
|
// types under one name produces charts that blend both series.
|
|
74
81
|
private _typeByName = new Map<string, MetricType>()
|
|
75
82
|
private _typeCollisionWarned = new Set<string>()
|
|
83
|
+
private _retryAfter = new RetryAfterWindow()
|
|
84
|
+
private _consecutiveFlushFailures = 0
|
|
85
|
+
private _flushJitter = NO_JITTER
|
|
76
86
|
// Bumped by reset(). A flush that was in flight when reset() ran (e.g. it
|
|
77
87
|
// lost a shutdown race) sees a stale generation when its send settles and
|
|
78
88
|
// discards its window instead of merging it back and re-arming the timer.
|
|
@@ -136,7 +146,10 @@ export class PostHogMetrics {
|
|
|
136
146
|
/** Clears the flush timer, drops the current window, and invalidates in-flight flushes. */
|
|
137
147
|
reset(): void {
|
|
138
148
|
this._generation++
|
|
139
|
-
this.
|
|
149
|
+
this._retryAfter.reset()
|
|
150
|
+
this._consecutiveFlushFailures = 0
|
|
151
|
+
this._flushJitter = NO_JITTER
|
|
152
|
+
this._flushTimer.clear()
|
|
140
153
|
this._series = new Map()
|
|
141
154
|
this._flushPromise = null
|
|
142
155
|
this._seriesCapWarned = false
|
|
@@ -284,26 +297,35 @@ export class PostHogMetrics {
|
|
|
284
297
|
return result
|
|
285
298
|
}
|
|
286
299
|
|
|
300
|
+
// Every capture calls this, so a pending timer is left alone — re-arming on
|
|
301
|
+
// each one would push the flush out for as long as metrics keep arriving.
|
|
287
302
|
private _armFlushTimer(): void {
|
|
288
|
-
if (this._flushTimer) {
|
|
303
|
+
if (this._flushTimer.pending) {
|
|
289
304
|
return
|
|
290
305
|
}
|
|
291
|
-
this._flushTimer
|
|
292
|
-
this._flushTimer = undefined
|
|
293
|
-
this.flush().catch((e) => {
|
|
294
|
-
this._logger.error('Metrics flush failed:', e)
|
|
295
|
-
})
|
|
296
|
-
}, this._config.flushIntervalMs)
|
|
306
|
+
this._flushTimer.arm(this._nextFlushDelay())
|
|
297
307
|
}
|
|
298
308
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
309
|
+
// A floor, not a replacement: the header never retries us sooner than our own
|
|
310
|
+
// backoff would have. The backoff doubles per consecutive failure so a
|
|
311
|
+
// sustained outage is not retried every interval, and the jitter is drawn
|
|
312
|
+
// once per failure so a fleet refused together does not return together.
|
|
313
|
+
private _nextFlushDelay(): number {
|
|
314
|
+
return Math.max(
|
|
315
|
+
backoffDelayMs(
|
|
316
|
+
this._config.flushIntervalMs,
|
|
317
|
+
this._consecutiveFlushFailures,
|
|
318
|
+
this._flushJitter,
|
|
319
|
+
MAX_FLUSH_BACKOFF_MS
|
|
320
|
+
),
|
|
321
|
+
this._retryAfter.remainingMs()
|
|
322
|
+
)
|
|
304
323
|
}
|
|
305
324
|
|
|
306
325
|
private async _doFlush(): Promise<void> {
|
|
326
|
+
// A flush retires the pending timer, so a delay armed for a window this
|
|
327
|
+
// flush may close cannot outlive it.
|
|
328
|
+
this._flushTimer.clear()
|
|
307
329
|
if (this._series.size === 0) {
|
|
308
330
|
return
|
|
309
331
|
}
|
|
@@ -323,6 +345,21 @@ export class PostHogMetrics {
|
|
|
323
345
|
// reconfigured, so this window is dropped whatever the outcome was.
|
|
324
346
|
return
|
|
325
347
|
}
|
|
348
|
+
this._retryAfter.record(outcome)
|
|
349
|
+
// Before any delay is taken from it, so the timer armed below is measured
|
|
350
|
+
// against this outcome rather than the one before it.
|
|
351
|
+
if (outcome.kind === 'retry-later') {
|
|
352
|
+
this._consecutiveFlushFailures++
|
|
353
|
+
this._flushJitter = drawJitter()
|
|
354
|
+
} else {
|
|
355
|
+
this._consecutiveFlushFailures = 0
|
|
356
|
+
this._flushJitter = NO_JITTER
|
|
357
|
+
}
|
|
358
|
+
// Outright, not through the ratchet: a timer a mid-flight capture armed is
|
|
359
|
+
// measured against a window this outcome may just have closed.
|
|
360
|
+
if (this._flushTimer.pending) {
|
|
361
|
+
this._flushTimer.arm(this._nextFlushDelay())
|
|
362
|
+
}
|
|
326
363
|
switch (outcome.kind) {
|
|
327
364
|
case 'ok':
|
|
328
365
|
return
|
|
@@ -331,7 +368,7 @@ export class PostHogMetrics {
|
|
|
331
368
|
// the next flush instead of being lost — and re-arm the timer, since
|
|
332
369
|
// with no new captures nothing else would schedule that flush.
|
|
333
370
|
this._mergeWindowBack(window)
|
|
334
|
-
this.
|
|
371
|
+
this._flushTimer.armNoEarlierThan(this._nextFlushDelay())
|
|
335
372
|
return
|
|
336
373
|
case 'too-large':
|
|
337
374
|
this._logger.warn('Metrics batch exceeded the server size limit and was dropped')
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { MetricAttributeValue, OtlpMetric, OtlpMetricsPayload } from '@posthog/types'
|
|
2
|
-
import {
|
|
3
|
-
import { buildOtlpResourceAttributes } from '../utils/otlp-resource'
|
|
2
|
+
import { buildOtlpResourceAttributes, toOtlpResourceKeyValueList } from '../utils/otlp-resource'
|
|
4
3
|
import type { ResolvedPostHogMetricsConfig } from './types'
|
|
5
4
|
|
|
6
5
|
/**
|
|
@@ -81,7 +80,7 @@ export function buildOtlpMetricsPayload(
|
|
|
81
80
|
return {
|
|
82
81
|
resourceMetrics: [
|
|
83
82
|
{
|
|
84
|
-
resource: { attributes:
|
|
83
|
+
resource: { attributes: toOtlpResourceKeyValueList(resourceAttributes) },
|
|
85
84
|
scopeMetrics: [
|
|
86
85
|
{
|
|
87
86
|
scope: { name: scopeName, version: scopeVersion },
|
|
@@ -20,8 +20,16 @@ import type { BeforeSendMetricFn, MetricAttributeValue, OtlpMetricsPayload } fro
|
|
|
20
20
|
/** Same tagged outcome shape as `SendLogsBatchOutcome` — one policy for both signals. */
|
|
21
21
|
export type SendMetricsBatchOutcome =
|
|
22
22
|
| { kind: 'ok' }
|
|
23
|
-
| { kind: 'retry-later'; error: unknown }
|
|
24
|
-
| {
|
|
23
|
+
| { kind: 'retry-later'; error: unknown; retryAfterMs?: number }
|
|
24
|
+
| {
|
|
25
|
+
kind: 'too-large'
|
|
26
|
+
/**
|
|
27
|
+
* True when the SDK measured the body itself rather than the endpoint
|
|
28
|
+
* refusing it, so the caller can split this drain without lowering the
|
|
29
|
+
* batch size it keeps between them.
|
|
30
|
+
*/
|
|
31
|
+
measuredLocally?: boolean
|
|
32
|
+
}
|
|
25
33
|
| { kind: 'fatal'; error: unknown }
|
|
26
34
|
|
|
27
35
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { OtlpLogsPayload, OtlpMetricsPayload } from '@posthog/types'
|
|
1
|
+
import type { OtlpLogsPayload, OtlpMetricsPayload, OtlpTracesPayload } from '@posthog/types'
|
|
2
2
|
import type { SendMetricsBatchOutcome } from './metrics/types'
|
|
3
|
+
import type { SendTracesBatchOutcome } from './traces/types'
|
|
3
4
|
import { SimpleEventEmitter } from './eventemitter'
|
|
4
5
|
import { getFeatureFlagValue, minimizeFlagCalledEventProperties, normalizeFlagsResponse } from './featureFlagUtils'
|
|
5
6
|
import { gzipCompress, isGzipSupported } from './gzip'
|
|
@@ -40,6 +41,7 @@ import {
|
|
|
40
41
|
getEventUuid,
|
|
41
42
|
safeJsonStringify,
|
|
42
43
|
} from './utils'
|
|
44
|
+
import { parseRetryAfterMs } from './utils/retry-after'
|
|
43
45
|
import { uuidv7 } from './vendor/uuidv7'
|
|
44
46
|
import {
|
|
45
47
|
ErrorPropertiesBuilder,
|
|
@@ -69,6 +71,20 @@ class PostHogFetchHttpError extends Error {
|
|
|
69
71
|
return this.response.status
|
|
70
72
|
}
|
|
71
73
|
|
|
74
|
+
/**
|
|
75
|
+
* The response's `Retry-After` as milliseconds from now, when it sent a usable
|
|
76
|
+
* one, clamped to `MAX_RETRY_AFTER_MS`.
|
|
77
|
+
*/
|
|
78
|
+
get retryAfterMs(): number | undefined {
|
|
79
|
+
try {
|
|
80
|
+
return parseRetryAfterMs(this.response.headers?.get('retry-after'))
|
|
81
|
+
} catch {
|
|
82
|
+
// `headers.get` is injected transport code; a throwing one must not turn a
|
|
83
|
+
// retriable failure into an unhandled rejection.
|
|
84
|
+
return undefined
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
72
88
|
get bodyReadTimedOut(): boolean {
|
|
73
89
|
return this._bodyReadTimedOut
|
|
74
90
|
}
|
|
@@ -205,6 +221,46 @@ function isRetryableFlagsFetchError(
|
|
|
205
221
|
return code !== 'ECONNREFUSED'
|
|
206
222
|
}
|
|
207
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Ceiling on what the SDK will put on the wire: a body over it is reported as
|
|
226
|
+
* too large without a request being made, and a batch of one that still exceeds
|
|
227
|
+
* it is dropped. The ingestion service decompresses a `Content-Encoding: gzip`
|
|
228
|
+
* request before it applies its own `MAX_REQUEST_BODY_SIZE_BYTES`, so the size
|
|
229
|
+
* that has to stay under the limit is the uncompressed one measured here.
|
|
230
|
+
*
|
|
231
|
+
* Set to the largest limit any known deployment configures — 10 MiB, what the
|
|
232
|
+
* ingestion service runs with — rather than the 2 MB the service falls back to
|
|
233
|
+
* when nothing configures it. The ceiling only earns its place by refusing a
|
|
234
|
+
* body that no deployment would have accepted: at 2 MB it would instead refuse
|
|
235
|
+
* bodies the service takes today, dropping records with no `413` to show for
|
|
236
|
+
* them. Deployments configured lower, and proxies in front of them, are covered
|
|
237
|
+
* by the `413` path, which stays the primary mechanism.
|
|
238
|
+
*/
|
|
239
|
+
const OTLP_MAX_BODY_BYTES = 10 * 1024 * 1024
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* A request body's size on the wire. `Buffer` where it exists, `TextEncoder`
|
|
243
|
+
* elsewhere.
|
|
244
|
+
*
|
|
245
|
+
* Total by construction: it runs on hosts that define only part of the web
|
|
246
|
+
* platform — `Blob` in particular is absent on some server runtimes — and a
|
|
247
|
+
* size that cannot be measured is reported as `0`, leaving the body to be sent
|
|
248
|
+
* rather than turning a missing global into a failed export.
|
|
249
|
+
*/
|
|
250
|
+
function byteLengthOf(body: string | Blob | Uint8Array): number {
|
|
251
|
+
try {
|
|
252
|
+
if (typeof body !== 'string') {
|
|
253
|
+
return body instanceof Uint8Array ? body.byteLength : body.size
|
|
254
|
+
}
|
|
255
|
+
if (typeof Buffer !== 'undefined') {
|
|
256
|
+
return Buffer.byteLength(body, STRING_FORMAT)
|
|
257
|
+
}
|
|
258
|
+
return new TextEncoder().encode(body).length
|
|
259
|
+
} catch {
|
|
260
|
+
return 0
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
208
264
|
export function isPostHogFetchContentTooLargeError(err: unknown): err is PostHogFetchHttpError & { status: 413 } {
|
|
209
265
|
return typeof err === 'object' && err instanceof PostHogFetchHttpError && err.status === 413
|
|
210
266
|
}
|
|
@@ -232,19 +288,35 @@ function isPostHogEventProperties(value: JsonType | undefined): value is PostHog
|
|
|
232
288
|
*/
|
|
233
289
|
export type SendLogsBatchOutcome =
|
|
234
290
|
| { kind: 'ok' }
|
|
235
|
-
| {
|
|
236
|
-
|
|
291
|
+
| {
|
|
292
|
+
kind: 'too-large'
|
|
293
|
+
/**
|
|
294
|
+
* True when the SDK measured the body itself rather than the endpoint
|
|
295
|
+
* refusing it, so the caller can split this drain without lowering the
|
|
296
|
+
* batch size it keeps between them.
|
|
297
|
+
*/
|
|
298
|
+
measuredLocally?: boolean
|
|
299
|
+
}
|
|
300
|
+
| { kind: 'retry-later'; error: unknown; retryAfterMs?: number }
|
|
237
301
|
| { kind: 'fatal'; error: unknown }
|
|
238
302
|
|
|
239
303
|
/**
|
|
240
304
|
* Each signal keeps its own exported outcome type because each belongs to a
|
|
241
|
-
* separate host contract. The wrappers return this value directly, so
|
|
242
|
-
* drifting out of shape fails to compile.
|
|
305
|
+
* separate host contract. The wrappers return this value directly, so any of
|
|
306
|
+
* the three drifting out of shape fails to compile.
|
|
243
307
|
*/
|
|
244
308
|
type SendOtlpBatchOutcome =
|
|
245
309
|
| { kind: 'ok' }
|
|
246
|
-
| {
|
|
247
|
-
|
|
310
|
+
| {
|
|
311
|
+
kind: 'too-large'
|
|
312
|
+
/**
|
|
313
|
+
* True when the SDK measured the body itself rather than the endpoint
|
|
314
|
+
* refusing it, so the caller can split this drain without lowering the
|
|
315
|
+
* batch size it keeps between them.
|
|
316
|
+
*/
|
|
317
|
+
measuredLocally?: boolean
|
|
318
|
+
}
|
|
319
|
+
| { kind: 'retry-later'; error: unknown; retryAfterMs?: number }
|
|
248
320
|
| { kind: 'fatal'; error: unknown }
|
|
249
321
|
|
|
250
322
|
export enum QuotaLimitedFeature {
|
|
@@ -1326,11 +1398,20 @@ export abstract class PostHogCoreStateless {
|
|
|
1326
1398
|
if (this.pendingFlushPromise) {
|
|
1327
1399
|
return
|
|
1328
1400
|
}
|
|
1329
|
-
void this.
|
|
1401
|
+
void this.flushAutomatic().catch(async (err) => {
|
|
1330
1402
|
await logFlushError(err)
|
|
1331
1403
|
})
|
|
1332
1404
|
}
|
|
1333
1405
|
|
|
1406
|
+
/**
|
|
1407
|
+
* The flush the SDK runs on its own, from the interval timer or the `flushAt`
|
|
1408
|
+
* threshold. Separate from `flush()` so a host can hold back work that an
|
|
1409
|
+
* endpoint has asked it to wait on, which an explicit flush overrides.
|
|
1410
|
+
*/
|
|
1411
|
+
protected flushAutomatic(): Promise<void> {
|
|
1412
|
+
return this.flush()
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1334
1415
|
private async waitForPendingPromises(
|
|
1335
1416
|
maxPromiseId: number,
|
|
1336
1417
|
ignoredPromises: (Promise<any> | null | undefined)[] = []
|
|
@@ -1642,9 +1723,9 @@ export abstract class PostHogCoreStateless {
|
|
|
1642
1723
|
}
|
|
1643
1724
|
|
|
1644
1725
|
/**
|
|
1645
|
-
* Shared implementation behind the OTLP senders, which differ only in
|
|
1646
|
-
* Returns a tagged outcome instead of throwing so the
|
|
1647
|
-
* have to know the core's error class hierarchy.
|
|
1726
|
+
* Shared implementation behind the three OTLP senders, which differ only in
|
|
1727
|
+
* path and auth style. Returns a tagged outcome instead of throwing so the
|
|
1728
|
+
* queue owners don't have to know the core's error class hierarchy.
|
|
1648
1729
|
*
|
|
1649
1730
|
* Exhausted 408/429/5xx stay `retry-later`, unlike the events `_flush()`
|
|
1650
1731
|
* which drops anything that isn't a network error: every OTLP queue is
|
|
@@ -1653,27 +1734,64 @@ export abstract class PostHogCoreStateless {
|
|
|
1653
1734
|
*/
|
|
1654
1735
|
private async _sendOtlpBatch({
|
|
1655
1736
|
path,
|
|
1737
|
+
auth,
|
|
1656
1738
|
payload,
|
|
1657
1739
|
}: {
|
|
1658
|
-
path: 'logs' | 'metrics'
|
|
1659
|
-
|
|
1740
|
+
path: 'logs' | 'metrics' | 'traces'
|
|
1741
|
+
auth: 'query-token' | 'bearer'
|
|
1742
|
+
payload: OtlpLogsPayload | OtlpMetricsPayload | OtlpTracesPayload
|
|
1660
1743
|
}): Promise<SendOtlpBatchOutcome> {
|
|
1661
1744
|
if (this.disabled) {
|
|
1662
1745
|
return { kind: 'fatal', error: new Error('The client is disabled') }
|
|
1663
1746
|
}
|
|
1664
1747
|
|
|
1665
|
-
|
|
1666
|
-
|
|
1748
|
+
// Serialised behind a guard: a payload too big to hold as one string throws
|
|
1749
|
+
// `RangeError` here, which escapes the tagged-outcome contract and leaves the
|
|
1750
|
+
// caller retrying a batch it can never send. Reported as too-large so it takes
|
|
1751
|
+
// the same halve-and-isolate path as a batch that serialises but is oversized.
|
|
1752
|
+
let serialized: string
|
|
1753
|
+
try {
|
|
1754
|
+
serialized = JSON.stringify(payload)
|
|
1755
|
+
} catch (error) {
|
|
1756
|
+
this.logMsgIfDebug(() =>
|
|
1757
|
+
console.warn(`[PostHog] Could not serialize a ${path} batch; reporting it as too large`, error)
|
|
1758
|
+
)
|
|
1759
|
+
return { kind: 'too-large', measuredLocally: true }
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
// Measured on the uncompressed payload: the endpoint decompresses the body
|
|
1763
|
+
// and applies its limit to what comes out, so one that gzips small is still
|
|
1764
|
+
// refused on its decompressed size. A batch the endpoint cannot accept is
|
|
1765
|
+
// reported without being sent — and before it is compressed — so the caller
|
|
1766
|
+
// halves it, and ultimately isolates and drops the one oversized record,
|
|
1767
|
+
// without spending a request or a gzip pass on each attempt.
|
|
1768
|
+
const payloadBytes = byteLengthOf(serialized)
|
|
1769
|
+
if (payloadBytes > OTLP_MAX_BODY_BYTES) {
|
|
1770
|
+
this.logMsgIfDebug(() =>
|
|
1771
|
+
console.warn(
|
|
1772
|
+
`[PostHog] Not sending a ${path} batch of ${payloadBytes} bytes: the endpoint accepts at most ${OTLP_MAX_BODY_BYTES}`
|
|
1773
|
+
)
|
|
1774
|
+
)
|
|
1775
|
+
return { kind: 'too-large', measuredLocally: true }
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
const url =
|
|
1779
|
+
auth === 'bearer'
|
|
1780
|
+
? `${this.host}/i/v1/${path}`
|
|
1781
|
+
: `${this.host}/i/v1/${path}?token=${encodeURIComponent(this.apiKey)}`
|
|
1667
1782
|
|
|
1668
1783
|
const gzippedPayload = !this.disableCompression ? await this.compressPayload(serialized) : null
|
|
1784
|
+
const body = gzippedPayload || serialized
|
|
1785
|
+
|
|
1669
1786
|
const fetchOptions: PostHogFetchOptions = {
|
|
1670
1787
|
method: 'POST',
|
|
1671
1788
|
headers: {
|
|
1672
1789
|
...this.getCustomHeaders(),
|
|
1673
1790
|
'Content-Type': 'application/json',
|
|
1791
|
+
...(auth === 'bearer' && { Authorization: `Bearer ${this.apiKey}` }),
|
|
1674
1792
|
...(gzippedPayload !== null && { 'Content-Encoding': 'gzip' }),
|
|
1675
1793
|
},
|
|
1676
|
-
body
|
|
1794
|
+
body,
|
|
1677
1795
|
}
|
|
1678
1796
|
|
|
1679
1797
|
try {
|
|
@@ -1686,6 +1804,12 @@ export abstract class PostHogCoreStateless {
|
|
|
1686
1804
|
if (isPostHogFetchContentTooLargeError(err)) {
|
|
1687
1805
|
return false
|
|
1688
1806
|
}
|
|
1807
|
+
if (err instanceof PostHogFetchHttpError && err.retryAfterMs !== undefined) {
|
|
1808
|
+
// The endpoint named a wait. This loop retries on a fixed short
|
|
1809
|
+
// delay, so retrying here would spend every attempt inside the
|
|
1810
|
+
// window; hand it to the queue's backoff instead.
|
|
1811
|
+
return false
|
|
1812
|
+
}
|
|
1689
1813
|
return isPostHogFetchRetryableError(err)
|
|
1690
1814
|
},
|
|
1691
1815
|
}
|
|
@@ -1696,18 +1820,31 @@ export abstract class PostHogCoreStateless {
|
|
|
1696
1820
|
return { kind: 'too-large' }
|
|
1697
1821
|
}
|
|
1698
1822
|
if (isPostHogFetchRetryableError(err)) {
|
|
1699
|
-
|
|
1823
|
+
const retryAfterMs = err instanceof PostHogFetchHttpError ? err.retryAfterMs : undefined
|
|
1824
|
+
return { kind: 'retry-later', error: err, ...(retryAfterMs !== undefined && { retryAfterMs }) }
|
|
1700
1825
|
}
|
|
1701
1826
|
return { kind: 'fatal', error: err }
|
|
1702
1827
|
}
|
|
1703
1828
|
}
|
|
1704
1829
|
|
|
1705
1830
|
async _sendLogsBatch(payload: OtlpLogsPayload): Promise<SendLogsBatchOutcome> {
|
|
1706
|
-
return this._sendOtlpBatch({ path: 'logs', payload })
|
|
1831
|
+
return this._sendOtlpBatch({ path: 'logs', auth: 'query-token', payload })
|
|
1707
1832
|
}
|
|
1708
1833
|
|
|
1709
1834
|
async _sendMetricsBatch(payload: OtlpMetricsPayload): Promise<SendMetricsBatchOutcome> {
|
|
1710
|
-
return this._sendOtlpBatch({ path: 'metrics', payload })
|
|
1835
|
+
return this._sendOtlpBatch({ path: 'metrics', auth: 'query-token', payload })
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
/**
|
|
1839
|
+
* The `TracesHost._sendTracesBatch` implementation, so `PostHogTraces` can
|
|
1840
|
+
* use any core-based SDK as its host.
|
|
1841
|
+
*
|
|
1842
|
+
* Authenticates with `Authorization: Bearer` rather than the `?token=` query
|
|
1843
|
+
* parameter the logs and metrics senders use: it's the service's primary auth
|
|
1844
|
+
* path, and server runtimes have no CORS preflight to avoid.
|
|
1845
|
+
*/
|
|
1846
|
+
async _sendTracesBatch(payload: OtlpTracesPayload): Promise<SendTracesBatchOutcome> {
|
|
1847
|
+
return this._sendOtlpBatch({ path: 'traces', auth: 'bearer', payload })
|
|
1711
1848
|
}
|
|
1712
1849
|
|
|
1713
1850
|
private fetchWithRetry<T>(
|
|
@@ -1732,25 +1869,7 @@ export abstract class PostHogCoreStateless {
|
|
|
1732
1869
|
requestTimeout?: number
|
|
1733
1870
|
): Promise<T | void> {
|
|
1734
1871
|
const body = options.body ? options.body : ''
|
|
1735
|
-
|
|
1736
|
-
try {
|
|
1737
|
-
if (body instanceof Blob) {
|
|
1738
|
-
reqByteLength = body.size
|
|
1739
|
-
} else if (body instanceof Uint8Array) {
|
|
1740
|
-
reqByteLength = body.byteLength
|
|
1741
|
-
} else {
|
|
1742
|
-
reqByteLength = Buffer.byteLength(body, STRING_FORMAT)
|
|
1743
|
-
}
|
|
1744
|
-
} catch {
|
|
1745
|
-
if (body instanceof Blob) {
|
|
1746
|
-
reqByteLength = body.size
|
|
1747
|
-
} else if (body instanceof Uint8Array) {
|
|
1748
|
-
reqByteLength = body.byteLength
|
|
1749
|
-
} else {
|
|
1750
|
-
const encoded = new TextEncoder().encode(body)
|
|
1751
|
-
reqByteLength = encoded.length
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1872
|
+
const reqByteLength = byteLengthOf(body)
|
|
1754
1873
|
|
|
1755
1874
|
const retriableOptions = { ...this._retryOptions, ...retryOptions }
|
|
1756
1875
|
let attempt = 0
|