@redthreadlabs/tracelog-client 1.3.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/EventBuilder.d.ts +21 -0
- package/dist/EventBuilder.js +79 -0
- package/dist/LogClient.d.ts +25 -0
- package/dist/LogClient.js +288 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +7 -0
- package/dist/types.d.ts +115 -0
- package/dist/types.js +3 -0
- package/package.json +21 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { JsonValue, LogEventItem } from './types';
|
|
2
|
+
export type EventEnqueuer = (event: LogEventItem) => void;
|
|
3
|
+
export declare class EventBuilder {
|
|
4
|
+
private _enqueue;
|
|
5
|
+
private _type;
|
|
6
|
+
private _level;
|
|
7
|
+
private _message;
|
|
8
|
+
private _duration?;
|
|
9
|
+
private _error?;
|
|
10
|
+
private _params?;
|
|
11
|
+
constructor(enqueue: EventEnqueuer, type: string);
|
|
12
|
+
info(message: string): this;
|
|
13
|
+
warn(message: string): this;
|
|
14
|
+
error(message: string): this;
|
|
15
|
+
debug(message: string): this;
|
|
16
|
+
withParam(key: string, value: JsonValue): this;
|
|
17
|
+
withParams(params: Record<string, JsonValue>): this;
|
|
18
|
+
withError(err: any): this;
|
|
19
|
+
withDuration(ms: number): this;
|
|
20
|
+
send(): void;
|
|
21
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EventBuilder = void 0;
|
|
4
|
+
class EventBuilder {
|
|
5
|
+
constructor(enqueue, type) {
|
|
6
|
+
this._level = 'info';
|
|
7
|
+
this._message = '';
|
|
8
|
+
this._enqueue = enqueue;
|
|
9
|
+
this._type = type;
|
|
10
|
+
}
|
|
11
|
+
info(message) {
|
|
12
|
+
this._level = 'info';
|
|
13
|
+
this._message = message;
|
|
14
|
+
return this;
|
|
15
|
+
}
|
|
16
|
+
warn(message) {
|
|
17
|
+
this._level = 'warn';
|
|
18
|
+
this._message = message;
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
error(message) {
|
|
22
|
+
this._level = 'error';
|
|
23
|
+
this._message = message;
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
debug(message) {
|
|
27
|
+
this._level = 'debug';
|
|
28
|
+
this._message = message;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
withParam(key, value) {
|
|
32
|
+
if (!this._params)
|
|
33
|
+
this._params = {};
|
|
34
|
+
this._params[key] = value;
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
withParams(params) {
|
|
38
|
+
if (!this._params)
|
|
39
|
+
this._params = {};
|
|
40
|
+
Object.assign(this._params, params);
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
withError(err) {
|
|
44
|
+
if (err instanceof Error) {
|
|
45
|
+
this._error = { message: err.message };
|
|
46
|
+
if (err.name)
|
|
47
|
+
this._error.type = err.name;
|
|
48
|
+
if (err.stack)
|
|
49
|
+
this._error.stack = err.stack;
|
|
50
|
+
}
|
|
51
|
+
else if (typeof err === 'string') {
|
|
52
|
+
this._error = { message: err };
|
|
53
|
+
}
|
|
54
|
+
else if (err != null) {
|
|
55
|
+
this._error = { message: String(err) };
|
|
56
|
+
}
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
withDuration(ms) {
|
|
60
|
+
this._duration = ms;
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
send() {
|
|
64
|
+
const event = {
|
|
65
|
+
type: this._type,
|
|
66
|
+
timestamp: Date.now(),
|
|
67
|
+
level: this._level,
|
|
68
|
+
message: this._message,
|
|
69
|
+
};
|
|
70
|
+
if (this._duration !== undefined)
|
|
71
|
+
event.duration = this._duration;
|
|
72
|
+
if (this._error)
|
|
73
|
+
event.error = this._error;
|
|
74
|
+
if (this._params)
|
|
75
|
+
event.params = this._params;
|
|
76
|
+
this._enqueue(event);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
exports.EventBuilder = EventBuilder;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { EventBuilder } from './EventBuilder';
|
|
2
|
+
import { JsonValue, LogClientOptions, TimerToken } from './types';
|
|
3
|
+
export declare class LogClient {
|
|
4
|
+
private _opts;
|
|
5
|
+
private _eventBuffer;
|
|
6
|
+
private _timerBuffer;
|
|
7
|
+
private _activeTimers;
|
|
8
|
+
private _flushTimer;
|
|
9
|
+
private _persistTimer;
|
|
10
|
+
private _disposed;
|
|
11
|
+
private _flushing;
|
|
12
|
+
constructor(opts: LogClientOptions);
|
|
13
|
+
event(type?: string): EventBuilder;
|
|
14
|
+
startTimer(name: string, parent?: TimerToken): TimerToken;
|
|
15
|
+
endTimer(token: TimerToken, context?: Record<string, JsonValue>): void;
|
|
16
|
+
flush(): Promise<void>;
|
|
17
|
+
dispose(): void;
|
|
18
|
+
private _enqueueEvent;
|
|
19
|
+
private _sendInChunks;
|
|
20
|
+
private _sendChunkWithRetry;
|
|
21
|
+
private _sendChunk;
|
|
22
|
+
private _schedulePersist;
|
|
23
|
+
private _persistNow;
|
|
24
|
+
private _loadPersistedLogs;
|
|
25
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LogClient = void 0;
|
|
4
|
+
const EventBuilder_1 = require("./EventBuilder");
|
|
5
|
+
// Default constants (matching the original AppLogRecorder)
|
|
6
|
+
const DEFAULT_FLUSH_INTERVAL_MS = 5000;
|
|
7
|
+
const DEFAULT_MAX_BUFFER_SIZE = 100;
|
|
8
|
+
const DEFAULT_MAX_CHUNK_SIZE = 50;
|
|
9
|
+
const DEFAULT_MAX_CHUNK_BYTES = 512 * 1024;
|
|
10
|
+
const INTER_CHUNK_DELAY_MS = 200;
|
|
11
|
+
const MAX_RETRIES = 3;
|
|
12
|
+
const BASE_RETRY_DELAY_MS = 1000;
|
|
13
|
+
const PERSIST_DEBOUNCE_MS = 100;
|
|
14
|
+
class LogClient {
|
|
15
|
+
constructor(opts) {
|
|
16
|
+
this._eventBuffer = [];
|
|
17
|
+
this._timerBuffer = [];
|
|
18
|
+
this._activeTimers = new Map();
|
|
19
|
+
this._flushTimer = null;
|
|
20
|
+
this._persistTimer = null;
|
|
21
|
+
this._disposed = false;
|
|
22
|
+
this._flushing = false;
|
|
23
|
+
this._opts = opts;
|
|
24
|
+
this._loadPersistedLogs();
|
|
25
|
+
this._flushTimer = setInterval(() => this.flush(), opts.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS);
|
|
26
|
+
}
|
|
27
|
+
// ---- Fluent event builder ----
|
|
28
|
+
event(type = 'client-log') {
|
|
29
|
+
return new EventBuilder_1.EventBuilder((evt) => this._enqueueEvent(evt), type);
|
|
30
|
+
}
|
|
31
|
+
// ---- Perf timing ----
|
|
32
|
+
startTimer(name, parent) {
|
|
33
|
+
const id = randomHex(16);
|
|
34
|
+
const trace_id = parent ? parent.trace_id : randomHex(32);
|
|
35
|
+
const root_id = parent ? parent.root_id : id;
|
|
36
|
+
const token = { id, trace_id, root_id, key: name };
|
|
37
|
+
const active = {
|
|
38
|
+
token,
|
|
39
|
+
startTime: now(),
|
|
40
|
+
parentToken: parent,
|
|
41
|
+
children: [],
|
|
42
|
+
};
|
|
43
|
+
this._activeTimers.set(id, active);
|
|
44
|
+
// Register as child of parent
|
|
45
|
+
if (parent) {
|
|
46
|
+
const parentActive = this._activeTimers.get(parent.id);
|
|
47
|
+
if (parentActive) {
|
|
48
|
+
parentActive.children.push(id);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return token;
|
|
52
|
+
}
|
|
53
|
+
endTimer(token, context) {
|
|
54
|
+
const active = this._activeTimers.get(token.id);
|
|
55
|
+
if (!active)
|
|
56
|
+
return;
|
|
57
|
+
const duration = now() - active.startTime;
|
|
58
|
+
// Auto-close children that haven't been ended yet
|
|
59
|
+
for (const childId of active.children) {
|
|
60
|
+
const childActive = this._activeTimers.get(childId);
|
|
61
|
+
if (childActive) {
|
|
62
|
+
this.endTimer(childActive.token);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const timer = {
|
|
66
|
+
id: token.id,
|
|
67
|
+
trace_id: token.trace_id,
|
|
68
|
+
root_id: token.root_id,
|
|
69
|
+
name: token.key,
|
|
70
|
+
type: 'client-perf',
|
|
71
|
+
timestamp: Math.round(active.startTime),
|
|
72
|
+
duration: Math.round(duration),
|
|
73
|
+
outcome: 'success',
|
|
74
|
+
};
|
|
75
|
+
if (active.parentToken) {
|
|
76
|
+
timer.parent_id = active.parentToken.id;
|
|
77
|
+
}
|
|
78
|
+
if (context && Object.keys(context).length > 0) {
|
|
79
|
+
timer.context = { tags: context };
|
|
80
|
+
}
|
|
81
|
+
this._timerBuffer.push(timer);
|
|
82
|
+
this._activeTimers.delete(token.id);
|
|
83
|
+
this._schedulePersist();
|
|
84
|
+
// Force flush if buffer is getting large
|
|
85
|
+
if (this._timerBuffer.length + this._eventBuffer.length >= (this._opts.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE)) {
|
|
86
|
+
this.flush();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// ---- Transport ----
|
|
90
|
+
async flush() {
|
|
91
|
+
if (this._disposed || this._flushing)
|
|
92
|
+
return;
|
|
93
|
+
if (this._eventBuffer.length === 0 && this._timerBuffer.length === 0)
|
|
94
|
+
return;
|
|
95
|
+
this._flushing = true;
|
|
96
|
+
try {
|
|
97
|
+
const events = this._eventBuffer.splice(0);
|
|
98
|
+
const timers = this._timerBuffer.splice(0);
|
|
99
|
+
await this._sendInChunks(events, timers);
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
this._flushing = false;
|
|
103
|
+
this._schedulePersist();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
dispose() {
|
|
107
|
+
if (this._disposed)
|
|
108
|
+
return;
|
|
109
|
+
this._disposed = true;
|
|
110
|
+
if (this._flushTimer) {
|
|
111
|
+
clearInterval(this._flushTimer);
|
|
112
|
+
this._flushTimer = null;
|
|
113
|
+
}
|
|
114
|
+
if (this._persistTimer) {
|
|
115
|
+
clearTimeout(this._persistTimer);
|
|
116
|
+
this._persistTimer = null;
|
|
117
|
+
}
|
|
118
|
+
// Persist anything remaining
|
|
119
|
+
this._persistNow();
|
|
120
|
+
}
|
|
121
|
+
// ---- Internal: event buffering ----
|
|
122
|
+
_enqueueEvent(event) {
|
|
123
|
+
if (this._disposed)
|
|
124
|
+
return;
|
|
125
|
+
this._eventBuffer.push(event);
|
|
126
|
+
this._schedulePersist();
|
|
127
|
+
if (this._eventBuffer.length >= (this._opts.maxBufferSize ?? DEFAULT_MAX_BUFFER_SIZE)) {
|
|
128
|
+
this.flush();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// ---- Internal: chunked sending ----
|
|
132
|
+
async _sendInChunks(events, timers) {
|
|
133
|
+
const maxChunkSize = this._opts.maxChunkSize ?? DEFAULT_MAX_CHUNK_SIZE;
|
|
134
|
+
const maxChunkBytes = this._opts.maxChunkBytes ?? DEFAULT_MAX_CHUNK_BYTES;
|
|
135
|
+
// Combine events and timers into chunks that respect size limits
|
|
136
|
+
let eventIdx = 0;
|
|
137
|
+
let timerIdx = 0;
|
|
138
|
+
let isFirstChunk = true;
|
|
139
|
+
while (eventIdx < events.length || timerIdx < timers.length) {
|
|
140
|
+
if (!isFirstChunk) {
|
|
141
|
+
await delay(INTER_CHUNK_DELAY_MS);
|
|
142
|
+
}
|
|
143
|
+
isFirstChunk = false;
|
|
144
|
+
const chunkEvents = [];
|
|
145
|
+
const chunkTimers = [];
|
|
146
|
+
let estimatedBytes = 200; // base overhead for batch envelope
|
|
147
|
+
// Fill chunk with events
|
|
148
|
+
while (eventIdx < events.length && chunkEvents.length + chunkTimers.length < maxChunkSize) {
|
|
149
|
+
const itemBytes = estimateJsonSize(events[eventIdx]);
|
|
150
|
+
if (estimatedBytes + itemBytes > maxChunkBytes && chunkEvents.length > 0)
|
|
151
|
+
break;
|
|
152
|
+
chunkEvents.push(events[eventIdx]);
|
|
153
|
+
estimatedBytes += itemBytes;
|
|
154
|
+
eventIdx++;
|
|
155
|
+
}
|
|
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)
|
|
160
|
+
break;
|
|
161
|
+
chunkTimers.push(timers[timerIdx]);
|
|
162
|
+
estimatedBytes += itemBytes;
|
|
163
|
+
timerIdx++;
|
|
164
|
+
}
|
|
165
|
+
if (chunkEvents.length === 0 && chunkTimers.length === 0)
|
|
166
|
+
break;
|
|
167
|
+
await this._sendChunkWithRetry(chunkEvents, chunkTimers);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async _sendChunkWithRetry(events, timers) {
|
|
171
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
172
|
+
try {
|
|
173
|
+
await this._sendChunk(events, timers);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
if (attempt < MAX_RETRIES) {
|
|
178
|
+
await delay(BASE_RETRY_DELAY_MS * Math.pow(2, attempt));
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
// Max retries exceeded — put items back for persistence
|
|
182
|
+
this._eventBuffer.push(...events);
|
|
183
|
+
this._timerBuffer.push(...timers);
|
|
184
|
+
this._schedulePersist();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async _sendChunk(events, timers) {
|
|
190
|
+
const batch = {
|
|
191
|
+
client: this._opts.client,
|
|
192
|
+
events,
|
|
193
|
+
timers,
|
|
194
|
+
};
|
|
195
|
+
const userId = this._opts.getUserId?.();
|
|
196
|
+
const sessionRef = this._opts.getSessionRef?.();
|
|
197
|
+
const deviceId = this._opts.getDeviceId?.();
|
|
198
|
+
if (userId)
|
|
199
|
+
batch.user_id = userId;
|
|
200
|
+
if (sessionRef)
|
|
201
|
+
batch.session_ref = sessionRef;
|
|
202
|
+
if (deviceId)
|
|
203
|
+
batch.device_id = deviceId;
|
|
204
|
+
const headers = await Promise.resolve(this._opts.getAuthHeaders());
|
|
205
|
+
const response = await fetch(this._opts.endpoint, {
|
|
206
|
+
method: 'POST',
|
|
207
|
+
headers: {
|
|
208
|
+
'Content-Type': 'application/json',
|
|
209
|
+
...headers,
|
|
210
|
+
},
|
|
211
|
+
body: JSON.stringify(batch),
|
|
212
|
+
});
|
|
213
|
+
if (!response.ok) {
|
|
214
|
+
throw new Error(`Server returned ${response.status}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// ---- Internal: persistence ----
|
|
218
|
+
_schedulePersist() {
|
|
219
|
+
if (this._persistTimer || !this._opts.persistLogs)
|
|
220
|
+
return;
|
|
221
|
+
this._persistTimer = setTimeout(() => {
|
|
222
|
+
this._persistTimer = null;
|
|
223
|
+
this._persistNow();
|
|
224
|
+
}, PERSIST_DEBOUNCE_MS);
|
|
225
|
+
}
|
|
226
|
+
_persistNow() {
|
|
227
|
+
if (!this._opts.persistLogs)
|
|
228
|
+
return;
|
|
229
|
+
if (this._eventBuffer.length === 0 && this._timerBuffer.length === 0) {
|
|
230
|
+
this._opts.persistLogs('').catch(() => { });
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const data = JSON.stringify({
|
|
234
|
+
events: this._eventBuffer,
|
|
235
|
+
timers: this._timerBuffer,
|
|
236
|
+
});
|
|
237
|
+
this._opts.persistLogs(data).catch(() => { });
|
|
238
|
+
}
|
|
239
|
+
async _loadPersistedLogs() {
|
|
240
|
+
if (!this._opts.loadPersistedLogs)
|
|
241
|
+
return;
|
|
242
|
+
try {
|
|
243
|
+
const data = await this._opts.loadPersistedLogs();
|
|
244
|
+
if (!data)
|
|
245
|
+
return;
|
|
246
|
+
const parsed = JSON.parse(data);
|
|
247
|
+
if (Array.isArray(parsed.events)) {
|
|
248
|
+
this._eventBuffer.push(...parsed.events);
|
|
249
|
+
}
|
|
250
|
+
if (Array.isArray(parsed.timers)) {
|
|
251
|
+
this._timerBuffer.push(...parsed.timers);
|
|
252
|
+
}
|
|
253
|
+
// Clear persisted data now that it's loaded
|
|
254
|
+
this._opts.persistLogs?.('').catch(() => { });
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
// Ignore parse errors from corrupted persisted data
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
exports.LogClient = LogClient;
|
|
262
|
+
// ---- Helpers ----
|
|
263
|
+
function randomHex(length) {
|
|
264
|
+
const chars = '0123456789abcdef';
|
|
265
|
+
let result = '';
|
|
266
|
+
for (let i = 0; i < length; i++) {
|
|
267
|
+
result += chars[Math.floor(Math.random() * 16)];
|
|
268
|
+
}
|
|
269
|
+
return result;
|
|
270
|
+
}
|
|
271
|
+
function now() {
|
|
272
|
+
if (typeof performance !== 'undefined' && performance.now) {
|
|
273
|
+
// Use performance.now() for high-res timing, but we need wall-clock for timestamps
|
|
274
|
+
// Store the offset on first call
|
|
275
|
+
if (!now._offset) {
|
|
276
|
+
now._offset = Date.now() - performance.now();
|
|
277
|
+
}
|
|
278
|
+
return now._offset + performance.now();
|
|
279
|
+
}
|
|
280
|
+
return Date.now();
|
|
281
|
+
}
|
|
282
|
+
function delay(ms) {
|
|
283
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
284
|
+
}
|
|
285
|
+
function estimateJsonSize(obj) {
|
|
286
|
+
// Fast estimate — avoid full serialization during chunking
|
|
287
|
+
return JSON.stringify(obj).length;
|
|
288
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EventBuilder = exports.LogClient = void 0;
|
|
4
|
+
var LogClient_1 = require("./LogClient");
|
|
5
|
+
Object.defineProperty(exports, "LogClient", { enumerable: true, get: function () { return LogClient_1.LogClient; } });
|
|
6
|
+
var EventBuilder_1 = require("./EventBuilder");
|
|
7
|
+
Object.defineProperty(exports, "EventBuilder", { enumerable: true, get: function () { return EventBuilder_1.EventBuilder; } });
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
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 (message, type, stack) */
|
|
23
|
+
error?: {
|
|
24
|
+
message: string;
|
|
25
|
+
type?: string;
|
|
26
|
+
stack?: string;
|
|
27
|
+
};
|
|
28
|
+
/** Arbitrary key-value event data */
|
|
29
|
+
params?: Record<string, JsonValue>;
|
|
30
|
+
}
|
|
31
|
+
export interface TimerItem {
|
|
32
|
+
/** 16-char hex ID, generated client-side */
|
|
33
|
+
id: string;
|
|
34
|
+
/** 32-char hex trace ID, shared by parent + children */
|
|
35
|
+
trace_id: string;
|
|
36
|
+
/** ID of the root timer in this trace (for transaction_id on spans) */
|
|
37
|
+
root_id: string;
|
|
38
|
+
/** 16-char hex ID of parent timer (absent for root timers) */
|
|
39
|
+
parent_id?: string;
|
|
40
|
+
/** Operation name, e.g. 'content-store-startup' */
|
|
41
|
+
name: string;
|
|
42
|
+
/** Timer category. Default: 'client-perf' */
|
|
43
|
+
type: string;
|
|
44
|
+
/** Start time, epoch milliseconds */
|
|
45
|
+
timestamp: number;
|
|
46
|
+
/** Duration in milliseconds */
|
|
47
|
+
duration: number;
|
|
48
|
+
outcome: 'success' | 'failure' | 'unknown';
|
|
49
|
+
context?: {
|
|
50
|
+
tags?: Record<string, JsonValue>;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export interface ClientInfo {
|
|
54
|
+
/** Application name, e.g. 'duiduidui-app' */
|
|
55
|
+
name: string;
|
|
56
|
+
/** Application version */
|
|
57
|
+
version: string;
|
|
58
|
+
os: {
|
|
59
|
+
name: string;
|
|
60
|
+
version: string;
|
|
61
|
+
};
|
|
62
|
+
device: {
|
|
63
|
+
model?: string;
|
|
64
|
+
brand?: string;
|
|
65
|
+
type: string;
|
|
66
|
+
};
|
|
67
|
+
runtime: {
|
|
68
|
+
name: string;
|
|
69
|
+
version: string;
|
|
70
|
+
};
|
|
71
|
+
screen?: {
|
|
72
|
+
width: number;
|
|
73
|
+
height: number;
|
|
74
|
+
pixel_ratio: number;
|
|
75
|
+
};
|
|
76
|
+
locale?: string;
|
|
77
|
+
timezone?: string;
|
|
78
|
+
device_year_class?: number;
|
|
79
|
+
}
|
|
80
|
+
export interface TimerToken {
|
|
81
|
+
/** 16-char hex ID for this timer */
|
|
82
|
+
id: string;
|
|
83
|
+
/** 32-char hex trace ID (shared across the entire timer tree) */
|
|
84
|
+
trace_id: string;
|
|
85
|
+
/** ID of the root timer in this trace */
|
|
86
|
+
root_id: string;
|
|
87
|
+
/** Key/name of the operation being timed */
|
|
88
|
+
key: string;
|
|
89
|
+
}
|
|
90
|
+
export interface LogClientOptions {
|
|
91
|
+
/** Server endpoint URL for log submission */
|
|
92
|
+
endpoint: string;
|
|
93
|
+
/** Returns auth headers (e.g. { Authorization: 'Bearer ...' }) */
|
|
94
|
+
getAuthHeaders: () => Promise<Record<string, string>> | Record<string, string>;
|
|
95
|
+
/** Static client/device info, sent once per batch */
|
|
96
|
+
client: ClientInfo;
|
|
97
|
+
/** Returns current user ID, if logged in */
|
|
98
|
+
getUserId?: () => string | undefined;
|
|
99
|
+
/** Returns current session reference */
|
|
100
|
+
getSessionRef?: () => string | undefined;
|
|
101
|
+
/** Returns native device identifier */
|
|
102
|
+
getDeviceId?: () => string | undefined;
|
|
103
|
+
/** Flush interval in ms. Default: 5000 */
|
|
104
|
+
flushIntervalMs?: number;
|
|
105
|
+
/** Max events buffered before forced flush. Default: 100 */
|
|
106
|
+
maxBufferSize?: number;
|
|
107
|
+
/** Max events per HTTP request. Default: 50 */
|
|
108
|
+
maxChunkSize?: number;
|
|
109
|
+
/** Max bytes per HTTP request. Default: 524288 (512KB) */
|
|
110
|
+
maxChunkBytes?: number;
|
|
111
|
+
/** Persist pending logs (e.g. to AsyncStorage). Called with JSON string. */
|
|
112
|
+
persistLogs?: (data: string) => Promise<void>;
|
|
113
|
+
/** Load previously persisted logs. Returns JSON string or null. */
|
|
114
|
+
loadPersistedLogs?: () => Promise<string | null>;
|
|
115
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@redthreadlabs/tracelog-client",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "Lightweight logging client for tracelog — works in React Native and browsers",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"registry": "https://registry.npmjs.org/",
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"typescript": "latest"
|
|
20
|
+
}
|
|
21
|
+
}
|