@redthreadlabs/tracelog-client 1.4.0 → 1.6.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,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EventBuilder = void 0;
4
+ const util_1 = require("./util");
4
5
  class EventBuilder {
5
6
  constructor(enqueue, type) {
6
7
  this._level = 'info';
@@ -82,6 +83,7 @@ class EventBuilder {
82
83
  timestamp: Date.now(),
83
84
  level: this._level,
84
85
  message: this._message,
86
+ tz_offset: (0, util_1.tzOffsetMinutes)(),
85
87
  };
86
88
  if (this._duration !== undefined)
87
89
  event.duration = this._duration;
@@ -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
@@ -2,8 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LogClient = void 0;
4
4
  const EventBuilder_1 = require("./EventBuilder");
5
- // Default constants (matching the original AppLogRecorder)
6
- const DEFAULT_FLUSH_INTERVAL_MS = 5000;
5
+ const util_1 = require("./util");
6
+ // Defaults (all overridable via LogClientOptions)
7
+ const DEFAULT_FLUSH_CADENCE_MS = 5000;
7
8
  const DEFAULT_MAX_BUFFER_SIZE = 100;
8
9
  const DEFAULT_MAX_CHUNK_SIZE = 50;
9
10
  const DEFAULT_MAX_CHUNK_BYTES = 512 * 1024;
@@ -14,75 +15,77 @@ const PERSIST_DEBOUNCE_MS = 100;
14
15
  class LogClient {
15
16
  constructor(opts) {
16
17
  this._eventBuffer = [];
17
- this._timerBuffer = [];
18
- this._activeTimers = new Map();
19
- this._flushTimer = null;
20
- this._persistTimer = null;
18
+ this._perfBuffer = [];
19
+ this._activePerfs = new Map();
20
+ this._flushHandle = null;
21
+ this._persistHandle = null;
21
22
  this._disposed = false;
22
23
  this._flushing = false;
23
24
  this._opts = opts;
24
25
  this._loadPersistedLogs();
25
- this._flushTimer = setInterval(() => this.flush(), opts.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS);
26
+ this._flushHandle = setInterval(() => this.flush(), opts.flushCadenceMs ?? DEFAULT_FLUSH_CADENCE_MS);
26
27
  }
27
28
  // ---- Fluent event builder ----
28
29
  event(type = 'client-log') {
29
30
  return new EventBuilder_1.EventBuilder((evt) => this._enqueueEvent(evt), type);
30
31
  }
31
32
  // ---- Perf timing ----
32
- startTimer(name, parent) {
33
+ startPerf(name, parent) {
33
34
  const id = randomHex(16);
34
35
  const trace_id = parent ? parent.trace_id : randomHex(32);
35
36
  const root_id = parent ? parent.root_id : id;
36
- const token = { id, trace_id, root_id, key: name };
37
+ const token = { id, trace_id, root_id, name };
37
38
  const active = {
38
39
  token,
39
40
  startTime: now(),
41
+ tzOffset: (0, util_1.tzOffsetMinutes)(),
40
42
  parentToken: parent,
41
43
  children: [],
42
44
  };
43
- this._activeTimers.set(id, active);
45
+ this._activePerfs.set(id, active);
44
46
  // Register as child of parent
45
47
  if (parent) {
46
- const parentActive = this._activeTimers.get(parent.id);
48
+ const parentActive = this._activePerfs.get(parent.id);
47
49
  if (parentActive) {
48
50
  parentActive.children.push(id);
49
51
  }
50
52
  }
51
53
  return token;
52
54
  }
53
- endTimer(token, context) {
54
- const active = this._activeTimers.get(token.id);
55
+ endPerf(token, context) {
56
+ const active = this._activePerfs.get(token.id);
55
57
  if (!active)
56
58
  return;
57
59
  const duration = now() - active.startTime;
58
60
  // Auto-close children that haven't been ended yet
59
61
  for (const childId of active.children) {
60
- const childActive = this._activeTimers.get(childId);
62
+ const childActive = this._activePerfs.get(childId);
61
63
  if (childActive) {
62
- this.endTimer(childActive.token);
64
+ this.endPerf(childActive.token);
63
65
  }
64
66
  }
65
- const timer = {
67
+ const perf = {
66
68
  id: token.id,
67
69
  trace_id: token.trace_id,
68
70
  root_id: token.root_id,
69
- name: token.key,
71
+ name: token.name,
70
72
  type: 'client-perf',
71
73
  timestamp: Math.round(active.startTime),
72
74
  duration: Math.round(duration),
73
75
  outcome: 'success',
76
+ tz_offset: active.tzOffset,
74
77
  };
75
78
  if (active.parentToken) {
76
- timer.parent_id = active.parentToken.id;
79
+ perf.parent_id = active.parentToken.id;
77
80
  }
78
81
  if (context && Object.keys(context).length > 0) {
79
- timer.context = { tags: context };
82
+ perf.context = { tags: context };
80
83
  }
81
- this._timerBuffer.push(timer);
82
- this._activeTimers.delete(token.id);
84
+ this._perfBuffer.push(perf);
85
+ this._activePerfs.delete(token.id);
83
86
  this._schedulePersist();
84
87
  // Force flush if buffer is getting large
85
- if (this._timerBuffer.length + this._eventBuffer.length >= (this._opts.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE)) {
88
+ if (this._perfBuffer.length + this._eventBuffer.length >= (this._opts.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE)) {
86
89
  this.flush();
87
90
  }
88
91
  }
@@ -90,13 +93,13 @@ class LogClient {
90
93
  async flush() {
91
94
  if (this._disposed || this._flushing)
92
95
  return;
93
- if (this._eventBuffer.length === 0 && this._timerBuffer.length === 0)
96
+ if (this._eventBuffer.length === 0 && this._perfBuffer.length === 0)
94
97
  return;
95
98
  this._flushing = true;
96
99
  try {
97
100
  const events = this._eventBuffer.splice(0);
98
- const timers = this._timerBuffer.splice(0);
99
- await this._sendInChunks(events, timers);
101
+ const perfs = this._perfBuffer.splice(0);
102
+ await this._sendInChunks(events, perfs);
100
103
  }
101
104
  finally {
102
105
  this._flushing = false;
@@ -107,13 +110,13 @@ class LogClient {
107
110
  if (this._disposed)
108
111
  return;
109
112
  this._disposed = true;
110
- if (this._flushTimer) {
111
- clearInterval(this._flushTimer);
112
- this._flushTimer = null;
113
+ if (this._flushHandle) {
114
+ clearInterval(this._flushHandle);
115
+ this._flushHandle = null;
113
116
  }
114
- if (this._persistTimer) {
115
- clearTimeout(this._persistTimer);
116
- this._persistTimer = null;
117
+ if (this._persistHandle) {
118
+ clearTimeout(this._persistHandle);
119
+ this._persistHandle = null;
117
120
  }
118
121
  // Persist anything remaining
119
122
  this._persistNow();
@@ -129,23 +132,23 @@ class LogClient {
129
132
  }
130
133
  }
131
134
  // ---- Internal: chunked sending ----
132
- async _sendInChunks(events, timers) {
135
+ async _sendInChunks(events, perfs) {
133
136
  const maxChunkSize = this._opts.maxChunkSize ?? DEFAULT_MAX_CHUNK_SIZE;
134
137
  const maxChunkBytes = this._opts.maxChunkBytes ?? DEFAULT_MAX_CHUNK_BYTES;
135
- // Combine events and timers into chunks that respect size limits
138
+ // Combine events and perfs into chunks that respect size limits
136
139
  let eventIdx = 0;
137
- let timerIdx = 0;
140
+ let perfIdx = 0;
138
141
  let isFirstChunk = true;
139
- while (eventIdx < events.length || timerIdx < timers.length) {
142
+ while (eventIdx < events.length || perfIdx < perfs.length) {
140
143
  if (!isFirstChunk) {
141
144
  await delay(INTER_CHUNK_DELAY_MS);
142
145
  }
143
146
  isFirstChunk = false;
144
147
  const chunkEvents = [];
145
- const chunkTimers = [];
148
+ const chunkPerfs = [];
146
149
  let estimatedBytes = 200; // base overhead for batch envelope
147
150
  // Fill chunk with events
148
- while (eventIdx < events.length && chunkEvents.length + chunkTimers.length < maxChunkSize) {
151
+ while (eventIdx < events.length && chunkEvents.length + chunkPerfs.length < maxChunkSize) {
149
152
  const itemBytes = estimateJsonSize(events[eventIdx]);
150
153
  if (estimatedBytes + itemBytes > maxChunkBytes && chunkEvents.length > 0)
151
154
  break;
@@ -153,24 +156,24 @@ class LogClient {
153
156
  estimatedBytes += itemBytes;
154
157
  eventIdx++;
155
158
  }
156
- // Fill chunk with timers
157
- while (timerIdx < timers.length && chunkEvents.length + chunkTimers.length < maxChunkSize) {
158
- const itemBytes = estimateJsonSize(timers[timerIdx]);
159
- if (estimatedBytes + itemBytes > maxChunkBytes && (chunkEvents.length + chunkTimers.length) > 0)
159
+ // Fill chunk with perfs
160
+ while (perfIdx < perfs.length && chunkEvents.length + chunkPerfs.length < maxChunkSize) {
161
+ const itemBytes = estimateJsonSize(perfs[perfIdx]);
162
+ if (estimatedBytes + itemBytes > maxChunkBytes && (chunkEvents.length + chunkPerfs.length) > 0)
160
163
  break;
161
- chunkTimers.push(timers[timerIdx]);
164
+ chunkPerfs.push(perfs[perfIdx]);
162
165
  estimatedBytes += itemBytes;
163
- timerIdx++;
166
+ perfIdx++;
164
167
  }
165
- if (chunkEvents.length === 0 && chunkTimers.length === 0)
168
+ if (chunkEvents.length === 0 && chunkPerfs.length === 0)
166
169
  break;
167
- await this._sendChunkWithRetry(chunkEvents, chunkTimers);
170
+ await this._sendChunkWithRetry(chunkEvents, chunkPerfs);
168
171
  }
169
172
  }
170
- async _sendChunkWithRetry(events, timers) {
173
+ async _sendChunkWithRetry(events, perfs) {
171
174
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
172
175
  try {
173
- await this._sendChunk(events, timers);
176
+ await this._sendChunk(events, perfs);
174
177
  return;
175
178
  }
176
179
  catch (err) {
@@ -180,17 +183,17 @@ class LogClient {
180
183
  else {
181
184
  // Max retries exceeded — put items back for persistence
182
185
  this._eventBuffer.push(...events);
183
- this._timerBuffer.push(...timers);
186
+ this._perfBuffer.push(...perfs);
184
187
  this._schedulePersist();
185
188
  }
186
189
  }
187
190
  }
188
191
  }
189
- async _sendChunk(events, timers) {
192
+ async _sendChunk(events, perfs) {
190
193
  const batch = {
191
194
  client: this._opts.client,
192
195
  events,
193
- timers,
196
+ perfs,
194
197
  };
195
198
  const userId = this._opts.getUserId?.();
196
199
  const sessionRef = this._opts.getSessionRef?.();
@@ -216,23 +219,23 @@ class LogClient {
216
219
  }
217
220
  // ---- Internal: persistence ----
218
221
  _schedulePersist() {
219
- if (this._persistTimer || !this._opts.persistLogs)
222
+ if (this._persistHandle || !this._opts.persistLogs)
220
223
  return;
221
- this._persistTimer = setTimeout(() => {
222
- this._persistTimer = null;
224
+ this._persistHandle = setTimeout(() => {
225
+ this._persistHandle = null;
223
226
  this._persistNow();
224
227
  }, PERSIST_DEBOUNCE_MS);
225
228
  }
226
229
  _persistNow() {
227
230
  if (!this._opts.persistLogs)
228
231
  return;
229
- if (this._eventBuffer.length === 0 && this._timerBuffer.length === 0) {
232
+ if (this._eventBuffer.length === 0 && this._perfBuffer.length === 0) {
230
233
  this._opts.persistLogs('').catch(() => { });
231
234
  return;
232
235
  }
233
236
  const data = JSON.stringify({
234
237
  events: this._eventBuffer,
235
- timers: this._timerBuffer,
238
+ perfs: this._perfBuffer,
236
239
  });
237
240
  this._opts.persistLogs(data).catch(() => { });
238
241
  }
@@ -247,8 +250,8 @@ class LogClient {
247
250
  if (Array.isArray(parsed.events)) {
248
251
  this._eventBuffer.push(...parsed.events);
249
252
  }
250
- if (Array.isArray(parsed.timers)) {
251
- this._timerBuffer.push(...parsed.timers);
253
+ if (Array.isArray(parsed.perfs)) {
254
+ this._perfBuffer.push(...parsed.perfs);
252
255
  }
253
256
  // Clear persisted data now that it's loaded
254
257
  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,94 +1,14 @@
1
- export type JsonValue = string | number | boolean | null | JsonValue[] | {
2
- [key: string]: JsonValue;
3
- };
4
- export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
5
- export interface LogBatch {
6
- client: ClientInfo;
7
- user_id?: string;
8
- session_ref?: string;
9
- device_id?: string;
10
- events: LogEventItem[];
11
- timers: TimerItem[];
12
- }
13
- export interface LogEventItem {
14
- /** Event category, e.g. 'auth', 'billing', 'startup'. Default: 'client-log' */
15
- type: string;
16
- /** Epoch milliseconds */
17
- timestamp: number;
18
- level: LogLevel;
19
- message: string;
20
- /** Duration in milliseconds (for timed events that aren't span-shaped) */
21
- duration?: number;
22
- /** Serialized error info. `code` is the structured error code (ShareDB,
23
- * Node `err.code`, etc.) — facetable downstream, unlike a code folded
24
- * into the message text. */
25
- error?: {
26
- message: string;
27
- type?: string;
28
- code?: string;
29
- stack?: string;
30
- };
31
- /** Arbitrary key-value event data */
32
- params?: Record<string, JsonValue>;
33
- }
34
- export interface TimerItem {
35
- /** 16-char hex ID, generated client-side */
1
+ import type { ClientInfo } 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 */
36
5
  id: string;
37
- /** 32-char hex trace ID, shared by parent + children */
6
+ /** 32-char hex trace ID (shared across the entire perf tree) */
38
7
  trace_id: string;
39
- /** ID of the root timer in this trace (for transaction_id on spans) */
8
+ /** ID of the root perf in this trace */
40
9
  root_id: string;
41
- /** 16-char hex ID of parent timer (absent for root timers) */
42
- parent_id?: string;
43
- /** Operation name, e.g. 'content-store-startup' */
44
- name: string;
45
- /** Timer category. Default: 'client-perf' */
46
- type: string;
47
- /** Start time, epoch milliseconds */
48
- timestamp: number;
49
- /** Duration in milliseconds */
50
- duration: number;
51
- outcome: 'success' | 'failure' | 'unknown';
52
- context?: {
53
- tags?: Record<string, JsonValue>;
54
- };
55
- }
56
- export interface ClientInfo {
57
- /** Application name, e.g. 'duiduidui-app' */
10
+ /** Name of the operation being measured */
58
11
  name: string;
59
- /** Application version */
60
- version: string;
61
- os: {
62
- name: string;
63
- version: string;
64
- };
65
- device: {
66
- model?: string;
67
- brand?: string;
68
- type: string;
69
- };
70
- runtime: {
71
- name: string;
72
- version: string;
73
- };
74
- screen?: {
75
- width: number;
76
- height: number;
77
- pixel_ratio: number;
78
- };
79
- locale?: string;
80
- timezone?: string;
81
- device_year_class?: number;
82
- }
83
- export interface TimerToken {
84
- /** 16-char hex ID for this timer */
85
- id: string;
86
- /** 32-char hex trace ID (shared across the entire timer tree) */
87
- trace_id: string;
88
- /** ID of the root timer in this trace */
89
- root_id: string;
90
- /** Key/name of the operation being timed */
91
- key: string;
92
12
  }
93
13
  export interface LogClientOptions {
94
14
  /** Server endpoint URL for log submission */
@@ -103,8 +23,8 @@ export interface LogClientOptions {
103
23
  getSessionRef?: () => string | undefined;
104
24
  /** Returns native device identifier */
105
25
  getDeviceId?: () => string | undefined;
106
- /** Flush interval in ms. Default: 5000 */
107
- flushIntervalMs?: number;
26
+ /** Flush cadence in ms. Default: 5000 */
27
+ flushCadenceMs?: number;
108
28
  /** Max events buffered before forced flush. Default: 100 */
109
29
  maxBufferSize?: number;
110
30
  /** Max events per HTTP request. Default: 50 */
package/dist/types.js CHANGED
@@ -1,3 +1,2 @@
1
1
  "use strict";
2
- // ---- JSON value type (any valid JSON) ----
3
2
  Object.defineProperty(exports, "__esModule", { value: true });
package/dist/util.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Minutes east of UTC right now (ISO-8601 sign: localWallClock = UTC + offset).
3
+ * `Date.prototype.getTimezoneOffset()` returns minutes to ADD to local to get
4
+ * UTC (so it's inverted, e.g. +300 for EST); negating gives the conventional
5
+ * eastward offset (EST = -300, IST = +330). Captured at record time so a
6
+ * buffered event reflects where/when it actually happened.
7
+ */
8
+ export declare function tzOffsetMinutes(): number;
package/dist/util.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.tzOffsetMinutes = tzOffsetMinutes;
4
+ /**
5
+ * Minutes east of UTC right now (ISO-8601 sign: localWallClock = UTC + offset).
6
+ * `Date.prototype.getTimezoneOffset()` returns minutes to ADD to local to get
7
+ * UTC (so it's inverted, e.g. +300 for EST); negating gives the conventional
8
+ * eastward offset (EST = -300, IST = +330). Captured at record time so a
9
+ * buffered event reflects where/when it actually happened.
10
+ */
11
+ function tzOffsetMinutes() {
12
+ return -new Date().getTimezoneOffset();
13
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redthreadlabs/tracelog-client",
3
- "version": "1.4.0",
3
+ "version": "1.6.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/",
@@ -19,5 +19,8 @@
19
19
  },
20
20
  "devDependencies": {
21
21
  "typescript": "latest"
22
+ },
23
+ "dependencies": {
24
+ "@redthreadlabs/tracelog-schema": "^0.3.0"
22
25
  }
23
26
  }