@redthreadlabs/tracelog-client 1.5.0 → 1.7.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.
@@ -1,18 +1,18 @@
1
1
  import { EventBuilder } from './EventBuilder';
2
- import { JsonValue, LogClientOptions, TimerToken } from './types';
2
+ import { JsonValue, LogClientOptions, PerfToken } from './types';
3
3
  export declare class LogClient {
4
4
  private _opts;
5
5
  private _eventBuffer;
6
- private _timerBuffer;
7
- private _activeTimers;
8
- private _flushTimer;
9
- private _persistTimer;
6
+ private _perfBuffer;
7
+ private _activePerfs;
8
+ private _flushHandle;
9
+ private _persistHandle;
10
10
  private _disposed;
11
11
  private _flushing;
12
12
  constructor(opts: LogClientOptions);
13
13
  event(type?: string): EventBuilder;
14
- startTimer(name: string, parent?: TimerToken): TimerToken;
15
- endTimer(token: TimerToken, context?: Record<string, JsonValue>): void;
14
+ startPerf(name: string, parent?: PerfToken): PerfToken;
15
+ endPerf(token: PerfToken, context?: Record<string, JsonValue>): void;
16
16
  flush(): Promise<void>;
17
17
  dispose(): void;
18
18
  private _enqueueEvent;
package/dist/LogClient.js CHANGED
@@ -3,8 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LogClient = void 0;
4
4
  const EventBuilder_1 = require("./EventBuilder");
5
5
  const util_1 = require("./util");
6
- // Default constants (matching the original AppLogRecorder)
7
- const DEFAULT_FLUSH_INTERVAL_MS = 5000;
6
+ // Level ordering for the optional getMinLevel gate. An event is dropped when
7
+ // its level ranks below the host-supplied minimum.
8
+ const LEVEL_RANK = { debug: 0, info: 1, warn: 2, error: 3 };
9
+ // Defaults (all overridable via LogClientOptions)
10
+ const DEFAULT_FLUSH_CADENCE_MS = 5000;
8
11
  const DEFAULT_MAX_BUFFER_SIZE = 100;
9
12
  const DEFAULT_MAX_CHUNK_SIZE = 50;
10
13
  const DEFAULT_MAX_CHUNK_BYTES = 512 * 1024;
@@ -15,26 +18,26 @@ const PERSIST_DEBOUNCE_MS = 100;
15
18
  class LogClient {
16
19
  constructor(opts) {
17
20
  this._eventBuffer = [];
18
- this._timerBuffer = [];
19
- this._activeTimers = new Map();
20
- this._flushTimer = null;
21
- this._persistTimer = null;
21
+ this._perfBuffer = [];
22
+ this._activePerfs = new Map();
23
+ this._flushHandle = null;
24
+ this._persistHandle = null;
22
25
  this._disposed = false;
23
26
  this._flushing = false;
24
27
  this._opts = opts;
25
28
  this._loadPersistedLogs();
26
- this._flushTimer = setInterval(() => this.flush(), opts.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS);
29
+ this._flushHandle = setInterval(() => this.flush(), opts.flushCadenceMs ?? DEFAULT_FLUSH_CADENCE_MS);
27
30
  }
28
31
  // ---- Fluent event builder ----
29
32
  event(type = 'client-log') {
30
33
  return new EventBuilder_1.EventBuilder((evt) => this._enqueueEvent(evt), type);
31
34
  }
32
35
  // ---- Perf timing ----
33
- startTimer(name, parent) {
36
+ startPerf(name, parent) {
34
37
  const id = randomHex(16);
35
38
  const trace_id = parent ? parent.trace_id : randomHex(32);
36
39
  const root_id = parent ? parent.root_id : id;
37
- const token = { id, trace_id, root_id, key: name };
40
+ const token = { id, trace_id, root_id, name };
38
41
  const active = {
39
42
  token,
40
43
  startTime: now(),
@@ -42,33 +45,33 @@ class LogClient {
42
45
  parentToken: parent,
43
46
  children: [],
44
47
  };
45
- this._activeTimers.set(id, active);
48
+ this._activePerfs.set(id, active);
46
49
  // Register as child of parent
47
50
  if (parent) {
48
- const parentActive = this._activeTimers.get(parent.id);
51
+ const parentActive = this._activePerfs.get(parent.id);
49
52
  if (parentActive) {
50
53
  parentActive.children.push(id);
51
54
  }
52
55
  }
53
56
  return token;
54
57
  }
55
- endTimer(token, context) {
56
- const active = this._activeTimers.get(token.id);
58
+ endPerf(token, context) {
59
+ const active = this._activePerfs.get(token.id);
57
60
  if (!active)
58
61
  return;
59
62
  const duration = now() - active.startTime;
60
63
  // Auto-close children that haven't been ended yet
61
64
  for (const childId of active.children) {
62
- const childActive = this._activeTimers.get(childId);
65
+ const childActive = this._activePerfs.get(childId);
63
66
  if (childActive) {
64
- this.endTimer(childActive.token);
67
+ this.endPerf(childActive.token);
65
68
  }
66
69
  }
67
- const timer = {
70
+ const perf = {
68
71
  id: token.id,
69
72
  trace_id: token.trace_id,
70
73
  root_id: token.root_id,
71
- name: token.key,
74
+ name: token.name,
72
75
  type: 'client-perf',
73
76
  timestamp: Math.round(active.startTime),
74
77
  duration: Math.round(duration),
@@ -76,16 +79,16 @@ class LogClient {
76
79
  tz_offset: active.tzOffset,
77
80
  };
78
81
  if (active.parentToken) {
79
- timer.parent_id = active.parentToken.id;
82
+ perf.parent_id = active.parentToken.id;
80
83
  }
81
84
  if (context && Object.keys(context).length > 0) {
82
- timer.context = { tags: context };
85
+ perf.context = { tags: context };
83
86
  }
84
- this._timerBuffer.push(timer);
85
- this._activeTimers.delete(token.id);
87
+ this._perfBuffer.push(perf);
88
+ this._activePerfs.delete(token.id);
86
89
  this._schedulePersist();
87
90
  // Force flush if buffer is getting large
88
- if (this._timerBuffer.length + this._eventBuffer.length >= (this._opts.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE)) {
91
+ if (this._perfBuffer.length + this._eventBuffer.length >= (this._opts.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE)) {
89
92
  this.flush();
90
93
  }
91
94
  }
@@ -93,13 +96,13 @@ class LogClient {
93
96
  async flush() {
94
97
  if (this._disposed || this._flushing)
95
98
  return;
96
- if (this._eventBuffer.length === 0 && this._timerBuffer.length === 0)
99
+ if (this._eventBuffer.length === 0 && this._perfBuffer.length === 0)
97
100
  return;
98
101
  this._flushing = true;
99
102
  try {
100
103
  const events = this._eventBuffer.splice(0);
101
- const timers = this._timerBuffer.splice(0);
102
- await this._sendInChunks(events, timers);
104
+ const perfs = this._perfBuffer.splice(0);
105
+ await this._sendInChunks(events, perfs);
103
106
  }
104
107
  finally {
105
108
  this._flushing = false;
@@ -110,13 +113,13 @@ class LogClient {
110
113
  if (this._disposed)
111
114
  return;
112
115
  this._disposed = true;
113
- if (this._flushTimer) {
114
- clearInterval(this._flushTimer);
115
- this._flushTimer = null;
116
+ if (this._flushHandle) {
117
+ clearInterval(this._flushHandle);
118
+ this._flushHandle = null;
116
119
  }
117
- if (this._persistTimer) {
118
- clearTimeout(this._persistTimer);
119
- this._persistTimer = null;
120
+ if (this._persistHandle) {
121
+ clearTimeout(this._persistHandle);
122
+ this._persistHandle = null;
120
123
  }
121
124
  // Persist anything remaining
122
125
  this._persistNow();
@@ -125,6 +128,11 @@ class LogClient {
125
128
  _enqueueEvent(event) {
126
129
  if (this._disposed)
127
130
  return;
131
+ // Level gate: drop events below the host-supplied minimum before they ever
132
+ // buffer, persist, or ship. No callback ⇒ emit everything (back-compat).
133
+ const minLevel = this._opts.getMinLevel?.();
134
+ if (minLevel && LEVEL_RANK[event.level] < LEVEL_RANK[minLevel])
135
+ return;
128
136
  this._eventBuffer.push(event);
129
137
  this._schedulePersist();
130
138
  if (this._eventBuffer.length >= (this._opts.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE)) {
@@ -132,23 +140,23 @@ class LogClient {
132
140
  }
133
141
  }
134
142
  // ---- Internal: chunked sending ----
135
- async _sendInChunks(events, timers) {
143
+ async _sendInChunks(events, perfs) {
136
144
  const maxChunkSize = this._opts.maxChunkSize ?? DEFAULT_MAX_CHUNK_SIZE;
137
145
  const maxChunkBytes = this._opts.maxChunkBytes ?? DEFAULT_MAX_CHUNK_BYTES;
138
- // Combine events and timers into chunks that respect size limits
146
+ // Combine events and perfs into chunks that respect size limits
139
147
  let eventIdx = 0;
140
- let timerIdx = 0;
148
+ let perfIdx = 0;
141
149
  let isFirstChunk = true;
142
- while (eventIdx < events.length || timerIdx < timers.length) {
150
+ while (eventIdx < events.length || perfIdx < perfs.length) {
143
151
  if (!isFirstChunk) {
144
152
  await delay(INTER_CHUNK_DELAY_MS);
145
153
  }
146
154
  isFirstChunk = false;
147
155
  const chunkEvents = [];
148
- const chunkTimers = [];
156
+ const chunkPerfs = [];
149
157
  let estimatedBytes = 200; // base overhead for batch envelope
150
158
  // Fill chunk with events
151
- while (eventIdx < events.length && chunkEvents.length + chunkTimers.length < maxChunkSize) {
159
+ while (eventIdx < events.length && chunkEvents.length + chunkPerfs.length < maxChunkSize) {
152
160
  const itemBytes = estimateJsonSize(events[eventIdx]);
153
161
  if (estimatedBytes + itemBytes > maxChunkBytes && chunkEvents.length > 0)
154
162
  break;
@@ -156,24 +164,24 @@ class LogClient {
156
164
  estimatedBytes += itemBytes;
157
165
  eventIdx++;
158
166
  }
159
- // Fill chunk with timers
160
- while (timerIdx < timers.length && chunkEvents.length + chunkTimers.length < maxChunkSize) {
161
- const itemBytes = estimateJsonSize(timers[timerIdx]);
162
- if (estimatedBytes + itemBytes > maxChunkBytes && (chunkEvents.length + chunkTimers.length) > 0)
167
+ // Fill chunk with perfs
168
+ while (perfIdx < perfs.length && chunkEvents.length + chunkPerfs.length < maxChunkSize) {
169
+ const itemBytes = estimateJsonSize(perfs[perfIdx]);
170
+ if (estimatedBytes + itemBytes > maxChunkBytes && (chunkEvents.length + chunkPerfs.length) > 0)
163
171
  break;
164
- chunkTimers.push(timers[timerIdx]);
172
+ chunkPerfs.push(perfs[perfIdx]);
165
173
  estimatedBytes += itemBytes;
166
- timerIdx++;
174
+ perfIdx++;
167
175
  }
168
- if (chunkEvents.length === 0 && chunkTimers.length === 0)
176
+ if (chunkEvents.length === 0 && chunkPerfs.length === 0)
169
177
  break;
170
- await this._sendChunkWithRetry(chunkEvents, chunkTimers);
178
+ await this._sendChunkWithRetry(chunkEvents, chunkPerfs);
171
179
  }
172
180
  }
173
- async _sendChunkWithRetry(events, timers) {
181
+ async _sendChunkWithRetry(events, perfs) {
174
182
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
175
183
  try {
176
- await this._sendChunk(events, timers);
184
+ await this._sendChunk(events, perfs);
177
185
  return;
178
186
  }
179
187
  catch (err) {
@@ -183,17 +191,17 @@ class LogClient {
183
191
  else {
184
192
  // Max retries exceeded — put items back for persistence
185
193
  this._eventBuffer.push(...events);
186
- this._timerBuffer.push(...timers);
194
+ this._perfBuffer.push(...perfs);
187
195
  this._schedulePersist();
188
196
  }
189
197
  }
190
198
  }
191
199
  }
192
- async _sendChunk(events, timers) {
200
+ async _sendChunk(events, perfs) {
193
201
  const batch = {
194
202
  client: this._opts.client,
195
203
  events,
196
- timers,
204
+ perfs,
197
205
  };
198
206
  const userId = this._opts.getUserId?.();
199
207
  const sessionRef = this._opts.getSessionRef?.();
@@ -219,23 +227,23 @@ class LogClient {
219
227
  }
220
228
  // ---- Internal: persistence ----
221
229
  _schedulePersist() {
222
- if (this._persistTimer || !this._opts.persistLogs)
230
+ if (this._persistHandle || !this._opts.persistLogs)
223
231
  return;
224
- this._persistTimer = setTimeout(() => {
225
- this._persistTimer = null;
232
+ this._persistHandle = setTimeout(() => {
233
+ this._persistHandle = null;
226
234
  this._persistNow();
227
235
  }, PERSIST_DEBOUNCE_MS);
228
236
  }
229
237
  _persistNow() {
230
238
  if (!this._opts.persistLogs)
231
239
  return;
232
- if (this._eventBuffer.length === 0 && this._timerBuffer.length === 0) {
240
+ if (this._eventBuffer.length === 0 && this._perfBuffer.length === 0) {
233
241
  this._opts.persistLogs('').catch(() => { });
234
242
  return;
235
243
  }
236
244
  const data = JSON.stringify({
237
245
  events: this._eventBuffer,
238
- timers: this._timerBuffer,
246
+ perfs: this._perfBuffer,
239
247
  });
240
248
  this._opts.persistLogs(data).catch(() => { });
241
249
  }
@@ -250,8 +258,8 @@ class LogClient {
250
258
  if (Array.isArray(parsed.events)) {
251
259
  this._eventBuffer.push(...parsed.events);
252
260
  }
253
- if (Array.isArray(parsed.timers)) {
254
- this._timerBuffer.push(...parsed.timers);
261
+ if (Array.isArray(parsed.perfs)) {
262
+ this._perfBuffer.push(...parsed.perfs);
255
263
  }
256
264
  // Clear persisted data now that it's loaded
257
265
  this._opts.persistLogs?.('').catch(() => { });
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { LogClient } from './LogClient';
2
2
  export { EventBuilder } from './EventBuilder';
3
- export { LogBatch, LogEventItem, TimerItem, ClientInfo, TimerToken, LogClientOptions, LogLevel, JsonValue, } from './types';
3
+ export { LogBatch, LogEventItem, LogPerfItem, ClientInfo, PerfToken, LogClientOptions, LogLevel, JsonValue, } from './types';
package/dist/types.d.ts CHANGED
@@ -1,14 +1,14 @@
1
- import type { ClientInfo } from '@redthreadlabs/tracelog-schema';
2
- export type { JsonValue, LogLevel, LogBatch, LogEventItem, TimerItem, ClientInfo, } from '@redthreadlabs/tracelog-schema';
3
- export interface TimerToken {
4
- /** 16-char hex ID for this timer */
1
+ import type { ClientInfo, LogLevel } from '@redthreadlabs/tracelog-schema';
2
+ export type { JsonValue, LogLevel, LogBatch, LogEventItem, LogPerfItem, ClientInfo, } from '@redthreadlabs/tracelog-schema';
3
+ export interface PerfToken {
4
+ /** 16-char hex ID for this perf */
5
5
  id: string;
6
- /** 32-char hex trace ID (shared across the entire timer tree) */
6
+ /** 32-char hex trace ID (shared across the entire perf tree) */
7
7
  trace_id: string;
8
- /** ID of the root timer in this trace */
8
+ /** ID of the root perf in this trace */
9
9
  root_id: string;
10
- /** Key/name of the operation being timed */
11
- key: string;
10
+ /** Name of the operation being measured */
11
+ name: string;
12
12
  }
13
13
  export interface LogClientOptions {
14
14
  /** Server endpoint URL for log submission */
@@ -23,8 +23,15 @@ export interface LogClientOptions {
23
23
  getSessionRef?: () => string | undefined;
24
24
  /** Returns native device identifier */
25
25
  getDeviceId?: () => string | undefined;
26
- /** Flush interval in ms. Default: 5000 */
27
- flushIntervalMs?: number;
26
+ /**
27
+ * Returns the minimum level to emit. Events whose level ranks below this
28
+ * (debug < info < warn < error) are dropped before buffering — they never
29
+ * persist or ship. Called once per event, so keep it cheap and synchronous.
30
+ * Omit to emit every level (default). Perfs are unaffected.
31
+ */
32
+ getMinLevel?: () => LogLevel;
33
+ /** Flush cadence in ms. Default: 5000 */
34
+ flushCadenceMs?: number;
28
35
  /** Max events buffered before forced flush. Default: 100 */
29
36
  maxBufferSize?: number;
30
37
  /** Max events per HTTP request. Default: 50 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redthreadlabs/tracelog-client",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "Lightweight logging client for tracelog — works in React Native and browsers",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/",
@@ -21,6 +21,6 @@
21
21
  "typescript": "latest"
22
22
  },
23
23
  "dependencies": {
24
- "@redthreadlabs/tracelog-schema": "^0.1.0"
24
+ "@redthreadlabs/tracelog-schema": "^0.3.0"
25
25
  }
26
26
  }