@posthog/core 1.27.8 → 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.
Files changed (51) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/logs/index.d.ts +94 -4
  4. package/dist/logs/index.d.ts.map +1 -1
  5. package/dist/logs/index.js +147 -4
  6. package/dist/logs/index.mjs +148 -5
  7. package/dist/logs/logs-utils.d.ts +2 -1
  8. package/dist/logs/logs-utils.d.ts.map +1 -1
  9. package/dist/logs/types.d.ts +140 -8
  10. package/dist/logs/types.d.ts.map +1 -1
  11. package/dist/posthog-core-stateless.d.ts +34 -0
  12. package/dist/posthog-core-stateless.d.ts.map +1 -1
  13. package/dist/posthog-core-stateless.js +39 -0
  14. package/dist/posthog-core-stateless.mjs +39 -0
  15. package/dist/surveys/events.d.ts +22 -0
  16. package/dist/surveys/events.d.ts.map +1 -0
  17. package/dist/surveys/events.js +95 -0
  18. package/dist/surveys/events.mjs +43 -0
  19. package/dist/surveys/index.d.ts +4 -0
  20. package/dist/surveys/index.d.ts.map +1 -0
  21. package/dist/surveys/index.js +83 -0
  22. package/dist/surveys/index.mjs +4 -0
  23. package/dist/surveys/translations.d.ts +38 -0
  24. package/dist/surveys/translations.d.ts.map +1 -0
  25. package/dist/surveys/translations.js +207 -0
  26. package/dist/surveys/translations.mjs +158 -0
  27. package/dist/testing/test-utils.d.ts.map +1 -1
  28. package/dist/testing/test-utils.js +1 -0
  29. package/dist/testing/test-utils.mjs +1 -0
  30. package/dist/types.d.ts +31 -2
  31. package/dist/types.d.ts.map +1 -1
  32. package/dist/utils/logger.d.ts +1 -1
  33. package/dist/utils/logger.d.ts.map +1 -1
  34. package/dist/utils/logger.js +3 -0
  35. package/dist/utils/logger.mjs +3 -0
  36. package/package.json +26 -2
  37. package/src/index.ts +12 -1
  38. package/src/logs/index.spec.ts +891 -17
  39. package/src/logs/index.ts +337 -13
  40. package/src/logs/logs-utils.spec.ts +2 -1
  41. package/src/logs/logs-utils.ts +1 -1
  42. package/src/logs/types.ts +150 -25
  43. package/src/posthog-core-stateless.ts +64 -0
  44. package/src/surveys/events.spec.ts +52 -0
  45. package/src/surveys/events.ts +80 -0
  46. package/src/surveys/index.ts +18 -0
  47. package/src/surveys/translations.spec.ts +205 -0
  48. package/src/surveys/translations.ts +244 -0
  49. package/src/testing/test-utils.ts +1 -0
  50. package/src/types.ts +38 -2
  51. 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';
@@ -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,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA;AAClG,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"}
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"}
@@ -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 _localEnabled;
10
+ private readonly _waitForStoragePersist;
12
11
  private _maxBufferSize;
13
- constructor(_instance: PostHogCoreStateless, _config: ResolvedPostHogLogsConfig, _logger: Logger, _getContext: () => LogSdkContext, _onReady: (fn: () => void) => void);
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/logs/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,EAAE,MAAM,EAA4B,MAAM,UAAU,CAAA;AAC3D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AACrE,OAAO,KAAK,EAAoB,iBAAiB,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AAE7F,qBAAa,WAAW;IAKpB,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;IAR3B,OAAO,CAAC,aAAa,CAAS;IAC9B,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;IAMrD,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI;IAoB5C,OAAO,CAAC,QAAQ;CAgBjB"}
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"}
@@ -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._localEnabled = false !== _config.enabled;
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 record = (0, external_logs_utils_js_namespaceObject.buildOtlpLogRecord)(options, this._getContext());
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;
@@ -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._localEnabled = false !== _config.enabled;
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 record = buildOtlpLogRecord(options, this._getContext());
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, LogSdkContext, LogSeverityLevel, OtlpAnyValue, OtlpKeyValue, OtlpLogRecord, OtlpLogsPayload, OtlpSeverityText } from '@posthog/types';
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,aAAa,EACb,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,eAAe,EAEf,gBAAgB,EACjB,MAAM,gBAAgB,CAAA;AAkBvB,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"}
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"}