@posthog/core 1.27.9 → 1.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/logs/index.d.ts +94 -4
- package/dist/logs/index.d.ts.map +1 -1
- package/dist/logs/index.js +147 -4
- package/dist/logs/index.mjs +148 -5
- package/dist/logs/logs-utils.d.ts +2 -1
- package/dist/logs/logs-utils.d.ts.map +1 -1
- package/dist/logs/types.d.ts +140 -8
- package/dist/logs/types.d.ts.map +1 -1
- package/dist/posthog-core-stateless.d.ts +34 -0
- package/dist/posthog-core-stateless.d.ts.map +1 -1
- package/dist/posthog-core-stateless.js +39 -0
- package/dist/posthog-core-stateless.mjs +39 -0
- package/dist/surveys/events.d.ts +22 -0
- package/dist/surveys/events.d.ts.map +1 -0
- package/dist/surveys/events.js +95 -0
- package/dist/surveys/events.mjs +43 -0
- package/dist/surveys/index.d.ts +4 -0
- package/dist/surveys/index.d.ts.map +1 -0
- package/dist/surveys/index.js +83 -0
- package/dist/surveys/index.mjs +4 -0
- package/dist/surveys/translations.d.ts +38 -0
- package/dist/surveys/translations.d.ts.map +1 -0
- package/dist/surveys/translations.js +207 -0
- package/dist/surveys/translations.mjs +158 -0
- package/dist/testing/test-utils.d.ts.map +1 -1
- package/dist/testing/test-utils.js +1 -0
- package/dist/testing/test-utils.mjs +1 -0
- package/dist/types.d.ts +31 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +3 -0
- package/dist/utils/logger.mjs +3 -0
- package/package.json +26 -2
- package/src/index.ts +12 -1
- package/src/logs/index.spec.ts +891 -17
- package/src/logs/index.ts +337 -13
- package/src/logs/logs-utils.spec.ts +2 -1
- package/src/logs/logs-utils.ts +1 -1
- package/src/logs/types.ts +150 -25
- package/src/posthog-core-stateless.ts +64 -0
- package/src/surveys/events.spec.ts +52 -0
- package/src/surveys/events.ts +80 -0
- package/src/surveys/index.ts +18 -0
- package/src/surveys/translations.spec.ts +205 -0
- package/src/surveys/translations.ts +244 -0
- package/src/testing/test-utils.ts +1 -0
- package/src/types.ts +38 -2
- package/src/utils/logger.ts +6 -2
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,8 @@ export * from './utils';
|
|
|
4
4
|
export * as ErrorTracking from './error-tracking';
|
|
5
5
|
export { buildOtlpLogRecord, buildOtlpLogsPayload, getOtlpSeverityNumber, getOtlpSeverityText, toOtlpAnyValue, toOtlpKeyValueList, } from './logs/logs-utils';
|
|
6
6
|
export { PostHogLogs } from './logs';
|
|
7
|
-
export type { BufferedLogEntry, PostHogLogsConfig, ResolvedPostHogLogsConfig } from './logs/types';
|
|
7
|
+
export type { BeforeSendLogFn, BufferedLogEntry, CaptureLogger, LogSdkContext, PostHogLogsConfig, ResolvedPostHogLogsConfig, } from './logs/types';
|
|
8
|
+
export type { CaptureLogOptions, LogAttributeValue, LogAttributes, LogSeverityLevel } from './logs/types';
|
|
8
9
|
export { uuidv7 } from './vendor/uuidv7';
|
|
9
10
|
export * from './posthog-core';
|
|
10
11
|
export * from './posthog-core-stateless';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,EAAE,YAAY,EAAE,0BAA0B,EAAE,MAAM,QAAQ,CAAA;AACjE,cAAc,SAAS,CAAA;AACvB,OAAO,KAAK,aAAa,MAAM,kBAAkB,CAAA;AACjD,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,EACd,kBAAkB,GACnB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAA;AACpC,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,EAAE,YAAY,EAAE,0BAA0B,EAAE,MAAM,QAAQ,CAAA;AACjE,cAAc,SAAS,CAAA;AACvB,OAAO,KAAK,aAAa,MAAM,kBAAkB,CAAA;AACjD,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,EACd,kBAAkB,GACnB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAA;AACpC,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,cAAc,CAAA;AAIrB,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AACzG,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,cAAc,gBAAgB,CAAA;AAC9B,cAAc,0BAA0B,CAAA;AACxC,cAAc,mBAAmB,CAAA;AACjC,cAAc,SAAS,CAAA;AACvB,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA"}
|
package/dist/logs/index.d.ts
CHANGED
|
@@ -1,17 +1,107 @@
|
|
|
1
|
-
import type { LogSdkContext } from '@posthog/types';
|
|
2
1
|
import { Logger } from '../types';
|
|
3
2
|
import type { PostHogCoreStateless } from '../posthog-core-stateless';
|
|
4
|
-
import type { CaptureLogOptions, ResolvedPostHogLogsConfig } from './types';
|
|
3
|
+
import type { CaptureLogOptions, LogSdkContext, ResolvedPostHogLogsConfig } from './types';
|
|
5
4
|
export declare class PostHogLogs {
|
|
6
5
|
private readonly _instance;
|
|
7
6
|
private readonly _config;
|
|
8
7
|
private readonly _logger;
|
|
9
8
|
private readonly _getContext;
|
|
10
9
|
private readonly _onReady;
|
|
11
|
-
private
|
|
10
|
+
private readonly _waitForStoragePersist;
|
|
12
11
|
private _maxBufferSize;
|
|
13
|
-
|
|
12
|
+
private _flushIntervalMs;
|
|
13
|
+
private _maxBatchRecordsPerPost;
|
|
14
|
+
private _flushTimer?;
|
|
15
|
+
private _flushPromise;
|
|
16
|
+
private _rateCapWindowMs;
|
|
17
|
+
private _maxLogsPerInterval?;
|
|
18
|
+
private _intervalWindowStart;
|
|
19
|
+
private _intervalLogCount;
|
|
20
|
+
private _droppedWarned;
|
|
21
|
+
constructor(_instance: PostHogCoreStateless, _config: ResolvedPostHogLogsConfig, _logger: Logger, _getContext: () => LogSdkContext, _onReady: (fn: () => void) => void, _waitForStoragePersist?: () => Promise<void>);
|
|
14
22
|
captureLog(options: CaptureLogOptions): void;
|
|
23
|
+
/**
|
|
24
|
+
* Runs the configured `beforeSend` hook(s) on a capture record:
|
|
25
|
+
* - single fn OR array of fns (chain, left-to-right)
|
|
26
|
+
* - returning `null` drops the record (logged at info)
|
|
27
|
+
* - a thrown error is logged and the chain *continues* with the previous
|
|
28
|
+
* result — a buggy user filter must never crash the caller's
|
|
29
|
+
* `captureLog()` call
|
|
30
|
+
*/
|
|
31
|
+
private _runBeforeSend;
|
|
32
|
+
/**
|
|
33
|
+
* Returns `true` if this capture fits within the current rate-cap window,
|
|
34
|
+
* `false` if it should be dropped.
|
|
35
|
+
*
|
|
36
|
+
* Fixed (tumbling) window: the counter resets the first time `captureLog`
|
|
37
|
+
* fires after `rateCapWindowMs` has elapsed — no timer needed.
|
|
38
|
+
* `maxLogsPerInterval === undefined` means unbounded.
|
|
39
|
+
*
|
|
40
|
+
* Wall-clock safety: if `Date.now()` jumps backward (manual device-clock
|
|
41
|
+
* change, big NTP correction), `elapsed` goes negative. We treat that the
|
|
42
|
+
* same as "window expired" and reset — otherwise the rate cap would be
|
|
43
|
+
* stuck until the clock caught up to the old window start, potentially
|
|
44
|
+
* dropping logs for hours.
|
|
45
|
+
*
|
|
46
|
+
* Pre-init note: the counter increments here, before `_onReady` defers
|
|
47
|
+
* `_enqueue` to the init promise. If init resolves slowly and the user is
|
|
48
|
+
* later opted out, the counter has already consumed budget for records
|
|
49
|
+
* that won't enqueue. Cosmetic — no record is "lost" beyond what's
|
|
50
|
+
* already gated, and the window rolls on its own.
|
|
51
|
+
*/
|
|
52
|
+
private _checkRateLimit;
|
|
53
|
+
/**
|
|
54
|
+
* Drains `LogsQueue` in `maxBatchRecordsPerPost` slices, POSTing each as an
|
|
55
|
+
* OTLP payload.
|
|
56
|
+
* - Network error → keep items in queue, re-throw (caller retries later)
|
|
57
|
+
* - 413 → halve batch size, retry same records (do not advance)
|
|
58
|
+
* - Any other error → drop the batch (avoid infinite loop on malformed data),
|
|
59
|
+
* re-throw so callers can log/report
|
|
60
|
+
* Concurrent calls are serialized through `_flushPromise` so records at the
|
|
61
|
+
* head of the queue can't be sent twice.
|
|
62
|
+
*/
|
|
63
|
+
flush(): Promise<void>;
|
|
64
|
+
private _flushInner;
|
|
65
|
+
private _persistQueueAdvance;
|
|
66
|
+
/**
|
|
67
|
+
* OTLP resource attributes for every batch.
|
|
68
|
+
*
|
|
69
|
+
* Layout: user `resourceAttributes` spread first, then SDK-controlled
|
|
70
|
+
* keys layered on top so users cannot accidentally clobber them. Most logs
|
|
71
|
+
* backends index on `service.name` and `telemetry.sdk.*` for routing,
|
|
72
|
+
* SDK-version dashboards, and bug-correlation; letting a stray user key
|
|
73
|
+
* overwrite them silently breaks ingestion attribution. The dedicated
|
|
74
|
+
* `serviceName` / `environment` / `serviceVersion` config fields are the
|
|
75
|
+
* supported way to override `service.name` / `deployment.environment` /
|
|
76
|
+
* `service.version`.
|
|
77
|
+
*/
|
|
78
|
+
private _buildResourceAttributes;
|
|
15
79
|
private _enqueue;
|
|
80
|
+
/**
|
|
81
|
+
* Stops the timer-based flush and sends anything still in the queue.
|
|
82
|
+
* Intended for process-teardown paths (RN `_shutdown` override). Swallows
|
|
83
|
+
* errors so a failing final flush can't block the broader shutdown.
|
|
84
|
+
*
|
|
85
|
+
* If `timeoutMs` is provided, the final flush races against that budget so
|
|
86
|
+
* a slow network/storage can't hold up shutdown indefinitely. Without it,
|
|
87
|
+
* flush time is bounded only by `fetchRetryCount * (requestTimeout +
|
|
88
|
+
* fetchRetryDelay)`, which can exceed the caller's shutdown SLA.
|
|
89
|
+
*/
|
|
90
|
+
shutdown(timeoutMs?: number): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Time-bounded flush for transient lifecycle events (e.g. RN
|
|
93
|
+
* foreground→background) that must complete inside an OS-imposed window.
|
|
94
|
+
* Unlike `shutdown`, this leaves the periodic flush timer in place so the
|
|
95
|
+
* pipeline keeps draining if the process is resumed instead of suspended.
|
|
96
|
+
*
|
|
97
|
+
* Errors propagate so the host SDK can route them through its standard
|
|
98
|
+
* lifecycle error handler (e.g. RN's `logFlushError`). If the timer wins
|
|
99
|
+
* the race, a late rejection from the in-flight flush is silenced via a
|
|
100
|
+
* no-op handler attached after the race settles, to avoid noisy
|
|
101
|
+
* unhandled-rejection logs — the next regular flush cycle will retry.
|
|
102
|
+
*/
|
|
103
|
+
flushWithTimeout(timeoutMs: number): Promise<void>;
|
|
104
|
+
private _flushInBackground;
|
|
105
|
+
private _clearFlushTimer;
|
|
16
106
|
}
|
|
17
107
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/logs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/logs/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/logs/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAA4B,MAAM,UAAU,CAAA;AAC3D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AAErE,OAAO,KAAK,EAGV,iBAAiB,EACjB,aAAa,EACb,yBAAyB,EAC1B,MAAM,SAAS,CAAA;AAEhB,qBAAa,WAAW;IAuBpB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAMzB,OAAO,CAAC,QAAQ,CAAC,sBAAsB;IAhCzC,OAAO,CAAC,cAAc,CAAQ;IAC9B,OAAO,CAAC,gBAAgB,CAAQ;IAIhC,OAAO,CAAC,uBAAuB,CAAQ;IACvC,OAAO,CAAC,WAAW,CAAC,CAAmC;IAGvD,OAAO,CAAC,aAAa,CAA6B;IAMlD,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,mBAAmB,CAAC,CAAQ;IACpC,OAAO,CAAC,oBAAoB,CAAI;IAChC,OAAO,CAAC,iBAAiB,CAAI;IAC7B,OAAO,CAAC,cAAc,CAAQ;gBAGX,SAAS,EAAE,oBAAoB,EAC/B,OAAO,EAAE,yBAAyB,EAClC,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,aAAa,EAChC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,EAMlC,sBAAsB,GAAE,MAAM,OAAO,CAAC,IAAI,CAA2B;IASxF,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI;IAgC5C;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc;IAwBtB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,OAAO,CAAC,eAAe;IAwBvB;;;;;;;;;OASG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAUd,WAAW;YAiEX,oBAAoB;IAUlC;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,wBAAwB;IAWhC,OAAO,CAAC,QAAQ;IAgChB;;;;;;;;;OASG;IACG,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAcjD;;;;;;;;;;;OAWG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBxD,OAAO,CAAC,kBAAkB;IAM1B,OAAO,CAAC,gBAAgB;CAMzB"}
|
package/dist/logs/index.js
CHANGED
|
@@ -28,26 +28,128 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
28
28
|
});
|
|
29
29
|
const external_logs_utils_js_namespaceObject = require("./logs-utils.js");
|
|
30
30
|
const external_types_js_namespaceObject = require("../types.js");
|
|
31
|
+
const index_js_namespaceObject = require("../utils/index.js");
|
|
31
32
|
class PostHogLogs {
|
|
32
|
-
constructor(_instance, _config, _logger, _getContext, _onReady){
|
|
33
|
+
constructor(_instance, _config, _logger, _getContext, _onReady, _waitForStoragePersist = ()=>Promise.resolve()){
|
|
33
34
|
this._instance = _instance;
|
|
34
35
|
this._config = _config;
|
|
35
36
|
this._logger = _logger;
|
|
36
37
|
this._getContext = _getContext;
|
|
37
38
|
this._onReady = _onReady;
|
|
38
|
-
this.
|
|
39
|
+
this._waitForStoragePersist = _waitForStoragePersist;
|
|
40
|
+
this._flushPromise = null;
|
|
41
|
+
this._intervalWindowStart = 0;
|
|
42
|
+
this._intervalLogCount = 0;
|
|
43
|
+
this._droppedWarned = false;
|
|
39
44
|
this._maxBufferSize = _config.maxBufferSize;
|
|
45
|
+
this._flushIntervalMs = _config.flushIntervalMs;
|
|
46
|
+
this._maxBatchRecordsPerPost = _config.maxBatchRecordsPerPost;
|
|
47
|
+
this._rateCapWindowMs = _config.rateCapWindowMs;
|
|
48
|
+
this._maxLogsPerInterval = _config.maxLogsPerInterval;
|
|
40
49
|
}
|
|
41
50
|
captureLog(options) {
|
|
42
|
-
if (!this._localEnabled) return;
|
|
43
51
|
if (this._instance.optedOut) return;
|
|
44
52
|
if (!options?.body) return;
|
|
45
|
-
const
|
|
53
|
+
const filtered = this._runBeforeSend(options);
|
|
54
|
+
if (null === filtered) return;
|
|
55
|
+
if (!filtered.body) return;
|
|
56
|
+
if (!this._checkRateLimit()) return;
|
|
57
|
+
const record = (0, external_logs_utils_js_namespaceObject.buildOtlpLogRecord)(filtered, this._getContext());
|
|
46
58
|
const entry = {
|
|
47
59
|
record
|
|
48
60
|
};
|
|
49
61
|
this._onReady(()=>this._enqueue(entry));
|
|
50
62
|
}
|
|
63
|
+
_runBeforeSend(options) {
|
|
64
|
+
const beforeSend = this._config.beforeSend;
|
|
65
|
+
if (!beforeSend) return options;
|
|
66
|
+
const fns = (0, index_js_namespaceObject.isArray)(beforeSend) ? beforeSend : [
|
|
67
|
+
beforeSend
|
|
68
|
+
];
|
|
69
|
+
let result = options;
|
|
70
|
+
for (const fn of fns)try {
|
|
71
|
+
const next = fn(result);
|
|
72
|
+
if (!next) {
|
|
73
|
+
this._logger.info("Log was rejected in beforeSend function");
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
result = next;
|
|
77
|
+
} catch (e) {
|
|
78
|
+
this._logger.error("Error in beforeSend function for log:", e);
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
_checkRateLimit() {
|
|
83
|
+
if (void 0 === this._maxLogsPerInterval) return true;
|
|
84
|
+
const now = Date.now();
|
|
85
|
+
const elapsed = now - this._intervalWindowStart;
|
|
86
|
+
if (elapsed >= this._rateCapWindowMs || elapsed < 0) {
|
|
87
|
+
this._intervalWindowStart = now;
|
|
88
|
+
this._intervalLogCount = 0;
|
|
89
|
+
this._droppedWarned = false;
|
|
90
|
+
}
|
|
91
|
+
if (this._intervalLogCount >= this._maxLogsPerInterval) {
|
|
92
|
+
if (!this._droppedWarned) {
|
|
93
|
+
this._logger.warn(`captureLog dropping logs: exceeded ${this._maxLogsPerInterval} logs per ${this._rateCapWindowMs}ms`);
|
|
94
|
+
this._droppedWarned = true;
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
this._intervalLogCount++;
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
async flush() {
|
|
102
|
+
if (this._flushPromise) return this._flushPromise;
|
|
103
|
+
this._flushPromise = this._flushInner().finally(()=>{
|
|
104
|
+
this._flushPromise = null;
|
|
105
|
+
});
|
|
106
|
+
return this._flushPromise;
|
|
107
|
+
}
|
|
108
|
+
async _flushInner() {
|
|
109
|
+
this._clearFlushTimer();
|
|
110
|
+
let queue = this._instance.getPersistedProperty(external_types_js_namespaceObject.PostHogPersistedProperty.LogsQueue) ?? [];
|
|
111
|
+
if (0 === queue.length) return;
|
|
112
|
+
const originalQueueLength = queue.length;
|
|
113
|
+
let sentCount = 0;
|
|
114
|
+
while(queue.length > 0 && sentCount < originalQueueLength){
|
|
115
|
+
const batchSize = Math.min(queue.length, this._maxBatchRecordsPerPost);
|
|
116
|
+
const batch = queue.slice(0, batchSize);
|
|
117
|
+
const records = batch.map((e)=>e.record);
|
|
118
|
+
const payload = (0, external_logs_utils_js_namespaceObject.buildOtlpLogsPayload)(records, this._buildResourceAttributes(), this._instance.getLibraryId(), this._instance.getLibraryVersion());
|
|
119
|
+
const outcome = await this._instance._sendLogsBatch(payload);
|
|
120
|
+
if ('too-large' === outcome.kind && batch.length > 1) {
|
|
121
|
+
this._maxBatchRecordsPerPost = Math.max(1, Math.floor(batch.length / 2));
|
|
122
|
+
this._logger.warn(`Received 413 when sending logs batch of size ${batch.length}, reducing batch size to ${this._maxBatchRecordsPerPost}`);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if ('retry-later' === outcome.kind) throw outcome.error;
|
|
126
|
+
if ('too-large' === outcome.kind) this._logger.warn("Dropping a single log record after 413 with batch size 1 \u2014 the record is larger than the server cap and cannot be split further.");
|
|
127
|
+
else if ('ok' === outcome.kind && this._maxBatchRecordsPerPost < this._config.maxBatchRecordsPerPost) this._maxBatchRecordsPerPost = Math.min(this._config.maxBatchRecordsPerPost, this._maxBatchRecordsPerPost + 1);
|
|
128
|
+
await this._persistQueueAdvance(batch.length);
|
|
129
|
+
queue = this._instance.getPersistedProperty(external_types_js_namespaceObject.PostHogPersistedProperty.LogsQueue) ?? [];
|
|
130
|
+
sentCount += batch.length;
|
|
131
|
+
if ('fatal' === outcome.kind) throw outcome.error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async _persistQueueAdvance(consumed) {
|
|
135
|
+
const refreshed = this._instance.getPersistedProperty(external_types_js_namespaceObject.PostHogPersistedProperty.LogsQueue) ?? [];
|
|
136
|
+
this._instance.setPersistedProperty(external_types_js_namespaceObject.PostHogPersistedProperty.LogsQueue, refreshed.slice(consumed));
|
|
137
|
+
await this._waitForStoragePersist();
|
|
138
|
+
}
|
|
139
|
+
_buildResourceAttributes() {
|
|
140
|
+
return {
|
|
141
|
+
...this._config.resourceAttributes,
|
|
142
|
+
'service.name': this._config.serviceName || 'unknown_service',
|
|
143
|
+
...this._config.environment && {
|
|
144
|
+
'deployment.environment': this._config.environment
|
|
145
|
+
},
|
|
146
|
+
...this._config.serviceVersion && {
|
|
147
|
+
'service.version': this._config.serviceVersion
|
|
148
|
+
},
|
|
149
|
+
'telemetry.sdk.name': this._instance.getLibraryId(),
|
|
150
|
+
'telemetry.sdk.version': this._instance.getLibraryVersion()
|
|
151
|
+
};
|
|
152
|
+
}
|
|
51
153
|
_enqueue(entry) {
|
|
52
154
|
if (this._instance.optedOut) return;
|
|
53
155
|
const queue = this._instance.getPersistedProperty(external_types_js_namespaceObject.PostHogPersistedProperty.LogsQueue) ?? [];
|
|
@@ -57,6 +159,47 @@ class PostHogLogs {
|
|
|
57
159
|
}
|
|
58
160
|
queue.push(entry);
|
|
59
161
|
this._instance.setPersistedProperty(external_types_js_namespaceObject.PostHogPersistedProperty.LogsQueue, queue);
|
|
162
|
+
if (queue.length >= this._maxBufferSize) return void this._flushInBackground();
|
|
163
|
+
if (!this._flushTimer) this._flushTimer = (0, index_js_namespaceObject.safeSetTimeout)(()=>{
|
|
164
|
+
this._flushTimer = void 0;
|
|
165
|
+
this._flushInBackground();
|
|
166
|
+
}, this._flushIntervalMs);
|
|
167
|
+
}
|
|
168
|
+
async shutdown(timeoutMs) {
|
|
169
|
+
this._clearFlushTimer();
|
|
170
|
+
const flushPromise = this.flush().catch(()=>{});
|
|
171
|
+
if (void 0 === timeoutMs) return void await flushPromise;
|
|
172
|
+
await Promise.race([
|
|
173
|
+
flushPromise,
|
|
174
|
+
new Promise((resolve)=>(0, index_js_namespaceObject.safeSetTimeout)(resolve, timeoutMs))
|
|
175
|
+
]);
|
|
176
|
+
}
|
|
177
|
+
async flushWithTimeout(timeoutMs) {
|
|
178
|
+
let timedOut = false;
|
|
179
|
+
const flushPromise = this.flush();
|
|
180
|
+
const timerPromise = new Promise((resolve)=>(0, index_js_namespaceObject.safeSetTimeout)(()=>{
|
|
181
|
+
timedOut = true;
|
|
182
|
+
resolve();
|
|
183
|
+
}, timeoutMs));
|
|
184
|
+
try {
|
|
185
|
+
await Promise.race([
|
|
186
|
+
flushPromise,
|
|
187
|
+
timerPromise
|
|
188
|
+
]);
|
|
189
|
+
} finally{
|
|
190
|
+
if (timedOut) flushPromise.catch(()=>{});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
_flushInBackground() {
|
|
194
|
+
this.flush().catch((err)=>{
|
|
195
|
+
this._logger.error('PostHog logs flush failed:', err);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
_clearFlushTimer() {
|
|
199
|
+
if (this._flushTimer) {
|
|
200
|
+
clearTimeout(this._flushTimer);
|
|
201
|
+
this._flushTimer = void 0;
|
|
202
|
+
}
|
|
60
203
|
}
|
|
61
204
|
}
|
|
62
205
|
exports.PostHogLogs = __webpack_exports__.PostHogLogs;
|
package/dist/logs/index.mjs
CHANGED
|
@@ -1,25 +1,127 @@
|
|
|
1
|
-
import { buildOtlpLogRecord } from "./logs-utils.mjs";
|
|
1
|
+
import { buildOtlpLogRecord, buildOtlpLogsPayload } from "./logs-utils.mjs";
|
|
2
2
|
import { PostHogPersistedProperty } from "../types.mjs";
|
|
3
|
+
import { isArray, safeSetTimeout } from "../utils/index.mjs";
|
|
3
4
|
class PostHogLogs {
|
|
4
|
-
constructor(_instance, _config, _logger, _getContext, _onReady){
|
|
5
|
+
constructor(_instance, _config, _logger, _getContext, _onReady, _waitForStoragePersist = ()=>Promise.resolve()){
|
|
5
6
|
this._instance = _instance;
|
|
6
7
|
this._config = _config;
|
|
7
8
|
this._logger = _logger;
|
|
8
9
|
this._getContext = _getContext;
|
|
9
10
|
this._onReady = _onReady;
|
|
10
|
-
this.
|
|
11
|
+
this._waitForStoragePersist = _waitForStoragePersist;
|
|
12
|
+
this._flushPromise = null;
|
|
13
|
+
this._intervalWindowStart = 0;
|
|
14
|
+
this._intervalLogCount = 0;
|
|
15
|
+
this._droppedWarned = false;
|
|
11
16
|
this._maxBufferSize = _config.maxBufferSize;
|
|
17
|
+
this._flushIntervalMs = _config.flushIntervalMs;
|
|
18
|
+
this._maxBatchRecordsPerPost = _config.maxBatchRecordsPerPost;
|
|
19
|
+
this._rateCapWindowMs = _config.rateCapWindowMs;
|
|
20
|
+
this._maxLogsPerInterval = _config.maxLogsPerInterval;
|
|
12
21
|
}
|
|
13
22
|
captureLog(options) {
|
|
14
|
-
if (!this._localEnabled) return;
|
|
15
23
|
if (this._instance.optedOut) return;
|
|
16
24
|
if (!options?.body) return;
|
|
17
|
-
const
|
|
25
|
+
const filtered = this._runBeforeSend(options);
|
|
26
|
+
if (null === filtered) return;
|
|
27
|
+
if (!filtered.body) return;
|
|
28
|
+
if (!this._checkRateLimit()) return;
|
|
29
|
+
const record = buildOtlpLogRecord(filtered, this._getContext());
|
|
18
30
|
const entry = {
|
|
19
31
|
record
|
|
20
32
|
};
|
|
21
33
|
this._onReady(()=>this._enqueue(entry));
|
|
22
34
|
}
|
|
35
|
+
_runBeforeSend(options) {
|
|
36
|
+
const beforeSend = this._config.beforeSend;
|
|
37
|
+
if (!beforeSend) return options;
|
|
38
|
+
const fns = isArray(beforeSend) ? beforeSend : [
|
|
39
|
+
beforeSend
|
|
40
|
+
];
|
|
41
|
+
let result = options;
|
|
42
|
+
for (const fn of fns)try {
|
|
43
|
+
const next = fn(result);
|
|
44
|
+
if (!next) {
|
|
45
|
+
this._logger.info("Log was rejected in beforeSend function");
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
result = next;
|
|
49
|
+
} catch (e) {
|
|
50
|
+
this._logger.error("Error in beforeSend function for log:", e);
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
_checkRateLimit() {
|
|
55
|
+
if (void 0 === this._maxLogsPerInterval) return true;
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
const elapsed = now - this._intervalWindowStart;
|
|
58
|
+
if (elapsed >= this._rateCapWindowMs || elapsed < 0) {
|
|
59
|
+
this._intervalWindowStart = now;
|
|
60
|
+
this._intervalLogCount = 0;
|
|
61
|
+
this._droppedWarned = false;
|
|
62
|
+
}
|
|
63
|
+
if (this._intervalLogCount >= this._maxLogsPerInterval) {
|
|
64
|
+
if (!this._droppedWarned) {
|
|
65
|
+
this._logger.warn(`captureLog dropping logs: exceeded ${this._maxLogsPerInterval} logs per ${this._rateCapWindowMs}ms`);
|
|
66
|
+
this._droppedWarned = true;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
this._intervalLogCount++;
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
async flush() {
|
|
74
|
+
if (this._flushPromise) return this._flushPromise;
|
|
75
|
+
this._flushPromise = this._flushInner().finally(()=>{
|
|
76
|
+
this._flushPromise = null;
|
|
77
|
+
});
|
|
78
|
+
return this._flushPromise;
|
|
79
|
+
}
|
|
80
|
+
async _flushInner() {
|
|
81
|
+
this._clearFlushTimer();
|
|
82
|
+
let queue = this._instance.getPersistedProperty(PostHogPersistedProperty.LogsQueue) ?? [];
|
|
83
|
+
if (0 === queue.length) return;
|
|
84
|
+
const originalQueueLength = queue.length;
|
|
85
|
+
let sentCount = 0;
|
|
86
|
+
while(queue.length > 0 && sentCount < originalQueueLength){
|
|
87
|
+
const batchSize = Math.min(queue.length, this._maxBatchRecordsPerPost);
|
|
88
|
+
const batch = queue.slice(0, batchSize);
|
|
89
|
+
const records = batch.map((e)=>e.record);
|
|
90
|
+
const payload = buildOtlpLogsPayload(records, this._buildResourceAttributes(), this._instance.getLibraryId(), this._instance.getLibraryVersion());
|
|
91
|
+
const outcome = await this._instance._sendLogsBatch(payload);
|
|
92
|
+
if ('too-large' === outcome.kind && batch.length > 1) {
|
|
93
|
+
this._maxBatchRecordsPerPost = Math.max(1, Math.floor(batch.length / 2));
|
|
94
|
+
this._logger.warn(`Received 413 when sending logs batch of size ${batch.length}, reducing batch size to ${this._maxBatchRecordsPerPost}`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if ('retry-later' === outcome.kind) throw outcome.error;
|
|
98
|
+
if ('too-large' === outcome.kind) this._logger.warn("Dropping a single log record after 413 with batch size 1 \u2014 the record is larger than the server cap and cannot be split further.");
|
|
99
|
+
else if ('ok' === outcome.kind && this._maxBatchRecordsPerPost < this._config.maxBatchRecordsPerPost) this._maxBatchRecordsPerPost = Math.min(this._config.maxBatchRecordsPerPost, this._maxBatchRecordsPerPost + 1);
|
|
100
|
+
await this._persistQueueAdvance(batch.length);
|
|
101
|
+
queue = this._instance.getPersistedProperty(PostHogPersistedProperty.LogsQueue) ?? [];
|
|
102
|
+
sentCount += batch.length;
|
|
103
|
+
if ('fatal' === outcome.kind) throw outcome.error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async _persistQueueAdvance(consumed) {
|
|
107
|
+
const refreshed = this._instance.getPersistedProperty(PostHogPersistedProperty.LogsQueue) ?? [];
|
|
108
|
+
this._instance.setPersistedProperty(PostHogPersistedProperty.LogsQueue, refreshed.slice(consumed));
|
|
109
|
+
await this._waitForStoragePersist();
|
|
110
|
+
}
|
|
111
|
+
_buildResourceAttributes() {
|
|
112
|
+
return {
|
|
113
|
+
...this._config.resourceAttributes,
|
|
114
|
+
'service.name': this._config.serviceName || 'unknown_service',
|
|
115
|
+
...this._config.environment && {
|
|
116
|
+
'deployment.environment': this._config.environment
|
|
117
|
+
},
|
|
118
|
+
...this._config.serviceVersion && {
|
|
119
|
+
'service.version': this._config.serviceVersion
|
|
120
|
+
},
|
|
121
|
+
'telemetry.sdk.name': this._instance.getLibraryId(),
|
|
122
|
+
'telemetry.sdk.version': this._instance.getLibraryVersion()
|
|
123
|
+
};
|
|
124
|
+
}
|
|
23
125
|
_enqueue(entry) {
|
|
24
126
|
if (this._instance.optedOut) return;
|
|
25
127
|
const queue = this._instance.getPersistedProperty(PostHogPersistedProperty.LogsQueue) ?? [];
|
|
@@ -29,6 +131,47 @@ class PostHogLogs {
|
|
|
29
131
|
}
|
|
30
132
|
queue.push(entry);
|
|
31
133
|
this._instance.setPersistedProperty(PostHogPersistedProperty.LogsQueue, queue);
|
|
134
|
+
if (queue.length >= this._maxBufferSize) return void this._flushInBackground();
|
|
135
|
+
if (!this._flushTimer) this._flushTimer = safeSetTimeout(()=>{
|
|
136
|
+
this._flushTimer = void 0;
|
|
137
|
+
this._flushInBackground();
|
|
138
|
+
}, this._flushIntervalMs);
|
|
139
|
+
}
|
|
140
|
+
async shutdown(timeoutMs) {
|
|
141
|
+
this._clearFlushTimer();
|
|
142
|
+
const flushPromise = this.flush().catch(()=>{});
|
|
143
|
+
if (void 0 === timeoutMs) return void await flushPromise;
|
|
144
|
+
await Promise.race([
|
|
145
|
+
flushPromise,
|
|
146
|
+
new Promise((resolve)=>safeSetTimeout(resolve, timeoutMs))
|
|
147
|
+
]);
|
|
148
|
+
}
|
|
149
|
+
async flushWithTimeout(timeoutMs) {
|
|
150
|
+
let timedOut = false;
|
|
151
|
+
const flushPromise = this.flush();
|
|
152
|
+
const timerPromise = new Promise((resolve)=>safeSetTimeout(()=>{
|
|
153
|
+
timedOut = true;
|
|
154
|
+
resolve();
|
|
155
|
+
}, timeoutMs));
|
|
156
|
+
try {
|
|
157
|
+
await Promise.race([
|
|
158
|
+
flushPromise,
|
|
159
|
+
timerPromise
|
|
160
|
+
]);
|
|
161
|
+
} finally{
|
|
162
|
+
if (timedOut) flushPromise.catch(()=>{});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
_flushInBackground() {
|
|
166
|
+
this.flush().catch((err)=>{
|
|
167
|
+
this._logger.error('PostHog logs flush failed:', err);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
_clearFlushTimer() {
|
|
171
|
+
if (this._flushTimer) {
|
|
172
|
+
clearTimeout(this._flushTimer);
|
|
173
|
+
this._flushTimer = void 0;
|
|
174
|
+
}
|
|
32
175
|
}
|
|
33
176
|
}
|
|
34
177
|
export { PostHogLogs };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { CaptureLogOptions, LogAttributeValue,
|
|
1
|
+
import type { CaptureLogOptions, LogAttributeValue, LogSeverityLevel, OtlpAnyValue, OtlpKeyValue, OtlpLogRecord, OtlpLogsPayload, OtlpSeverityText } from '@posthog/types';
|
|
2
|
+
import type { LogSdkContext } from './types';
|
|
2
3
|
export declare function getOtlpSeverityText(level: LogSeverityLevel): OtlpSeverityText;
|
|
3
4
|
export declare function getOtlpSeverityNumber(level: LogSeverityLevel): number;
|
|
4
5
|
export declare function toOtlpAnyValue(value: LogAttributeValue): OtlpAnyValue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logs-utils.d.ts","sourceRoot":"","sources":["../../src/logs/logs-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,
|
|
1
|
+
{"version":3,"file":"logs-utils.d.ts","sourceRoot":"","sources":["../../src/logs/logs-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,eAAe,EAEf,gBAAgB,EACjB,MAAM,gBAAgB,CAAA;AACvB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAkB5C,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,CAE7E;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAErE;AAMD,wBAAgB,cAAc,CAAC,KAAK,EAAE,iBAAiB,GAAG,YAAY,CAiCrE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAAG,YAAY,EAAE,CAU3F;AAiBD;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,aAAa,GAAG,aAAa,CAmDvG;AAMD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,aAAa,EAAE,EAC3B,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACrD,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,GACnB,eAAe,CAcjB"}
|