@logtide/sdk-node 0.1.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.cjs ADDED
@@ -0,0 +1,408 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var crypto = require('crypto');
6
+
7
+ // src/index.ts
8
+ var CircuitBreaker = class {
9
+ constructor(threshold, resetMs) {
10
+ this.threshold = threshold;
11
+ this.resetMs = resetMs;
12
+ }
13
+ state = "CLOSED" /* CLOSED */;
14
+ failureCount = 0;
15
+ lastFailureTime = null;
16
+ recordSuccess() {
17
+ this.failureCount = 0;
18
+ this.state = "CLOSED" /* CLOSED */;
19
+ }
20
+ recordFailure() {
21
+ this.failureCount++;
22
+ this.lastFailureTime = Date.now();
23
+ if (this.failureCount >= this.threshold) {
24
+ this.state = "OPEN" /* OPEN */;
25
+ }
26
+ }
27
+ canAttempt() {
28
+ if (this.state === "CLOSED" /* CLOSED */) {
29
+ return true;
30
+ }
31
+ if (this.state === "OPEN" /* OPEN */) {
32
+ const now = Date.now();
33
+ if (this.lastFailureTime && now - this.lastFailureTime >= this.resetMs) {
34
+ this.state = "HALF_OPEN" /* HALF_OPEN */;
35
+ return true;
36
+ }
37
+ return false;
38
+ }
39
+ return true;
40
+ }
41
+ getState() {
42
+ return this.state;
43
+ }
44
+ };
45
+ function isValidUUID(str) {
46
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
47
+ return uuidRegex.test(str);
48
+ }
49
+ function normalizeTraceId(traceId, debug) {
50
+ if (!traceId) {
51
+ return void 0;
52
+ }
53
+ if (isValidUUID(traceId)) {
54
+ return traceId;
55
+ }
56
+ const newTraceId = crypto.randomUUID();
57
+ if (debug) {
58
+ console.warn(
59
+ `[LogTide] Invalid trace_id "${traceId}" (must be UUID v4). Generated new UUID: ${newTraceId}`
60
+ );
61
+ }
62
+ return newTraceId;
63
+ }
64
+ function serializeError(error) {
65
+ if (error instanceof Error) {
66
+ const result = {
67
+ name: error.name,
68
+ message: error.message,
69
+ stack: error.stack
70
+ };
71
+ if (error.cause) {
72
+ result.cause = serializeError(error.cause);
73
+ }
74
+ return result;
75
+ }
76
+ if (typeof error === "string") {
77
+ return { message: error };
78
+ }
79
+ if (typeof error === "object" && error !== null) {
80
+ return error;
81
+ }
82
+ return { message: String(error) };
83
+ }
84
+ var LogTideClient = class {
85
+ apiUrl;
86
+ apiKey;
87
+ batchSize;
88
+ flushInterval;
89
+ maxBufferSize;
90
+ maxRetries;
91
+ retryDelayMs;
92
+ enableMetrics;
93
+ debugMode;
94
+ globalMetadata;
95
+ autoTraceId;
96
+ buffer = [];
97
+ timer = null;
98
+ circuitBreaker;
99
+ // Metrics
100
+ metrics = {
101
+ logsSent: 0,
102
+ logsDropped: 0,
103
+ errors: 0,
104
+ retries: 0,
105
+ avgLatencyMs: 0,
106
+ circuitBreakerTrips: 0
107
+ };
108
+ latencies = [];
109
+ // Context tracking
110
+ currentTraceId = null;
111
+ constructor(options) {
112
+ this.apiUrl = options.apiUrl.replace(/\/$/, "");
113
+ this.apiKey = options.apiKey;
114
+ this.batchSize = options.batchSize || 100;
115
+ this.flushInterval = options.flushInterval || 5e3;
116
+ this.maxBufferSize = options.maxBufferSize || 1e4;
117
+ this.maxRetries = options.maxRetries || 3;
118
+ this.retryDelayMs = options.retryDelayMs || 1e3;
119
+ this.enableMetrics = options.enableMetrics ?? true;
120
+ this.debugMode = options.debug ?? false;
121
+ this.globalMetadata = options.globalMetadata || {};
122
+ this.autoTraceId = options.autoTraceId ?? false;
123
+ this.circuitBreaker = new CircuitBreaker(
124
+ options.circuitBreakerThreshold || 5,
125
+ options.circuitBreakerResetMs || 3e4
126
+ );
127
+ this.startFlushTimer();
128
+ }
129
+ // ==================== Context Helpers ====================
130
+ /**
131
+ * Set trace ID for subsequent logs
132
+ * Automatically validates and normalizes to UUID v4
133
+ */
134
+ setTraceId(traceId) {
135
+ this.currentTraceId = normalizeTraceId(traceId, this.debugMode) || null;
136
+ }
137
+ /**
138
+ * Get current trace ID
139
+ */
140
+ getTraceId() {
141
+ return this.currentTraceId;
142
+ }
143
+ /**
144
+ * Execute function with a specific trace ID context
145
+ */
146
+ withTraceId(traceId, fn) {
147
+ const previousTraceId = this.currentTraceId;
148
+ this.currentTraceId = traceId;
149
+ try {
150
+ return fn();
151
+ } finally {
152
+ this.currentTraceId = previousTraceId;
153
+ }
154
+ }
155
+ /**
156
+ * Execute function with a new auto-generated trace ID
157
+ */
158
+ withNewTraceId(fn) {
159
+ return this.withTraceId(crypto.randomUUID(), fn);
160
+ }
161
+ // ==================== Logging Methods ====================
162
+ startFlushTimer() {
163
+ this.timer = setInterval(() => {
164
+ this.flush();
165
+ }, this.flushInterval);
166
+ }
167
+ log(entry) {
168
+ if (this.buffer.length >= this.maxBufferSize) {
169
+ this.metrics.logsDropped++;
170
+ if (this.debugMode) {
171
+ console.warn(`[LogTide] Buffer full, dropping log: ${entry.message}`);
172
+ }
173
+ return;
174
+ }
175
+ const normalizedTraceId = normalizeTraceId(entry.trace_id, this.debugMode) || normalizeTraceId(this.currentTraceId, this.debugMode) || (this.autoTraceId ? crypto.randomUUID() : void 0);
176
+ const internalEntry = {
177
+ ...entry,
178
+ time: entry.time || (/* @__PURE__ */ new Date()).toISOString(),
179
+ metadata: {
180
+ ...this.globalMetadata,
181
+ ...entry.metadata
182
+ },
183
+ trace_id: normalizedTraceId
184
+ };
185
+ this.buffer.push(internalEntry);
186
+ if (this.buffer.length >= this.batchSize) {
187
+ this.flush();
188
+ }
189
+ }
190
+ debug(service, message, metadata) {
191
+ this.log({ service, level: "debug", message, metadata });
192
+ }
193
+ info(service, message, metadata) {
194
+ this.log({ service, level: "info", message, metadata });
195
+ }
196
+ warn(service, message, metadata) {
197
+ this.log({ service, level: "warn", message, metadata });
198
+ }
199
+ error(service, message, metadataOrError) {
200
+ let metadata = {};
201
+ if (metadataOrError instanceof Error) {
202
+ metadata = { error: serializeError(metadataOrError) };
203
+ } else if (metadataOrError) {
204
+ metadata = metadataOrError;
205
+ }
206
+ this.log({ service, level: "error", message, metadata });
207
+ }
208
+ critical(service, message, metadataOrError) {
209
+ let metadata = {};
210
+ if (metadataOrError instanceof Error) {
211
+ metadata = { error: serializeError(metadataOrError) };
212
+ } else if (metadataOrError) {
213
+ metadata = metadataOrError;
214
+ }
215
+ this.log({ service, level: "critical", message, metadata });
216
+ }
217
+ // ==================== Flush with Retry & Circuit Breaker ====================
218
+ async flush() {
219
+ if (this.buffer.length === 0) return;
220
+ if (!this.circuitBreaker.canAttempt()) {
221
+ this.metrics.circuitBreakerTrips++;
222
+ if (this.debugMode) {
223
+ console.warn("[LogTide] Circuit breaker OPEN, skipping flush");
224
+ }
225
+ return;
226
+ }
227
+ const logs = [...this.buffer];
228
+ this.buffer = [];
229
+ const startTime = Date.now();
230
+ let lastError = null;
231
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
232
+ try {
233
+ const response = await fetch(`${this.apiUrl}/api/v1/ingest`, {
234
+ method: "POST",
235
+ headers: {
236
+ "Content-Type": "application/json",
237
+ "X-API-Key": this.apiKey
238
+ },
239
+ body: JSON.stringify({ logs })
240
+ });
241
+ if (!response.ok) {
242
+ const errorText = await response.text();
243
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
244
+ }
245
+ this.circuitBreaker.recordSuccess();
246
+ this.metrics.logsSent += logs.length;
247
+ if (this.enableMetrics) {
248
+ const latency = Date.now() - startTime;
249
+ this.latencies.push(latency);
250
+ if (this.latencies.length > 100) {
251
+ this.latencies.shift();
252
+ }
253
+ this.metrics.avgLatencyMs = this.latencies.reduce((a, b) => a + b, 0) / this.latencies.length;
254
+ }
255
+ if (this.debugMode) {
256
+ console.log(`[LogTide] Sent ${logs.length} logs successfully`);
257
+ }
258
+ return;
259
+ } catch (error) {
260
+ lastError = error instanceof Error ? error : new Error(String(error));
261
+ this.metrics.errors++;
262
+ if (attempt < this.maxRetries) {
263
+ this.metrics.retries++;
264
+ const delay = this.retryDelayMs * Math.pow(2, attempt);
265
+ if (this.debugMode) {
266
+ console.warn(
267
+ `[LogTide] Retry ${attempt + 1}/${this.maxRetries} after ${delay}ms: ${lastError.message}`
268
+ );
269
+ }
270
+ await this.sleep(delay);
271
+ }
272
+ }
273
+ }
274
+ this.circuitBreaker.recordFailure();
275
+ if (this.debugMode) {
276
+ console.error(`[LogTide] Failed to send logs after ${this.maxRetries} retries:`, lastError);
277
+ }
278
+ if (this.buffer.length + logs.length <= this.maxBufferSize) {
279
+ this.buffer.unshift(...logs);
280
+ } else {
281
+ this.metrics.logsDropped += logs.length;
282
+ }
283
+ }
284
+ sleep(ms) {
285
+ return new Promise((resolve) => setTimeout(resolve, ms));
286
+ }
287
+ // ==================== Query Methods ====================
288
+ async query(options = {}) {
289
+ const params = new URLSearchParams();
290
+ if (options.service) params.append("service", options.service);
291
+ if (options.level) params.append("level", options.level);
292
+ if (options.from) {
293
+ const from = options.from instanceof Date ? options.from.toISOString() : options.from;
294
+ params.append("from", from);
295
+ }
296
+ if (options.to) {
297
+ const to = options.to instanceof Date ? options.to.toISOString() : options.to;
298
+ params.append("to", to);
299
+ }
300
+ if (options.q) params.append("q", options.q);
301
+ if (options.limit) params.append("limit", String(options.limit));
302
+ if (options.offset) params.append("offset", String(options.offset));
303
+ const url = `${this.apiUrl}/api/v1/logs?${params.toString()}`;
304
+ const response = await fetch(url, {
305
+ headers: {
306
+ "X-API-Key": this.apiKey
307
+ }
308
+ });
309
+ if (!response.ok) {
310
+ const errorText = await response.text();
311
+ throw new Error(`Query failed: HTTP ${response.status}: ${errorText}`);
312
+ }
313
+ return await response.json();
314
+ }
315
+ async getByTraceId(traceId) {
316
+ const url = `${this.apiUrl}/api/v1/logs/trace/${traceId}`;
317
+ const response = await fetch(url, {
318
+ headers: {
319
+ "X-API-Key": this.apiKey
320
+ }
321
+ });
322
+ if (!response.ok) {
323
+ const errorText = await response.text();
324
+ throw new Error(`Get by trace ID failed: HTTP ${response.status}: ${errorText}`);
325
+ }
326
+ const data = await response.json();
327
+ return data.logs || [];
328
+ }
329
+ async getAggregatedStats(options) {
330
+ const params = new URLSearchParams();
331
+ const from = options.from instanceof Date ? options.from.toISOString() : options.from;
332
+ const to = options.to instanceof Date ? options.to.toISOString() : options.to;
333
+ params.append("from", from);
334
+ params.append("to", to);
335
+ if (options.interval) params.append("interval", options.interval);
336
+ if (options.service) params.append("service", options.service);
337
+ const url = `${this.apiUrl}/api/v1/logs/aggregated?${params.toString()}`;
338
+ const response = await fetch(url, {
339
+ headers: {
340
+ "X-API-Key": this.apiKey
341
+ }
342
+ });
343
+ if (!response.ok) {
344
+ const errorText = await response.text();
345
+ throw new Error(`Get aggregated stats failed: HTTP ${response.status}: ${errorText}`);
346
+ }
347
+ return await response.json();
348
+ }
349
+ // ==================== Live Tail (SSE) ====================
350
+ stream(options) {
351
+ const params = new URLSearchParams();
352
+ params.append("token", this.apiKey);
353
+ if (options.service) params.append("service", options.service);
354
+ if (options.level) params.append("level", options.level);
355
+ const url = `${this.apiUrl}/api/v1/logs/stream?${params.toString()}`;
356
+ const eventSource = new EventSource(url);
357
+ eventSource.addEventListener("log", (event) => {
358
+ try {
359
+ const messageEvent = event;
360
+ const log = JSON.parse(messageEvent.data);
361
+ options.onLog(log);
362
+ } catch (error) {
363
+ const err = error instanceof Error ? error : new Error(String(error));
364
+ options.onError?.(err);
365
+ }
366
+ });
367
+ eventSource.addEventListener("error", () => {
368
+ const error = new Error("SSE connection error");
369
+ options.onError?.(error);
370
+ });
371
+ return () => {
372
+ eventSource.close();
373
+ };
374
+ }
375
+ // ==================== Metrics ====================
376
+ getMetrics() {
377
+ return { ...this.metrics };
378
+ }
379
+ resetMetrics() {
380
+ this.metrics = {
381
+ logsSent: 0,
382
+ logsDropped: 0,
383
+ errors: 0,
384
+ retries: 0,
385
+ avgLatencyMs: 0,
386
+ circuitBreakerTrips: 0
387
+ };
388
+ this.latencies = [];
389
+ }
390
+ getCircuitBreakerState() {
391
+ return this.circuitBreaker.getState();
392
+ }
393
+ // ==================== Cleanup ====================
394
+ async close() {
395
+ if (this.timer) {
396
+ clearInterval(this.timer);
397
+ this.timer = null;
398
+ }
399
+ await this.flush();
400
+ }
401
+ };
402
+ var index_default = LogTideClient;
403
+
404
+ exports.LogTideClient = LogTideClient;
405
+ exports.default = index_default;
406
+ exports.serializeError = serializeError;
407
+ //# sourceMappingURL=index.cjs.map
408
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":["randomUUID"],"mappings":"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","file":"index.cjs","sourcesContent":["import { randomUUID } from 'crypto';\n\n// ==================== Types ====================\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'critical';\n\nexport interface LogTideClientOptions {\n apiUrl: string;\n apiKey: string;\n batchSize?: number;\n flushInterval?: number;\n maxBufferSize?: number;\n maxRetries?: number;\n retryDelayMs?: number;\n circuitBreakerThreshold?: number;\n circuitBreakerResetMs?: number;\n enableMetrics?: boolean;\n debug?: boolean;\n globalMetadata?: Record<string, unknown>;\n autoTraceId?: boolean;\n}\n\nexport interface LogEntry {\n service: string;\n level: LogLevel;\n message: string;\n time?: string;\n metadata?: Record<string, unknown>;\n trace_id?: string;\n}\n\nexport interface InternalLogEntry extends LogEntry {\n time: string;\n}\n\nexport interface QueryOptions {\n service?: string;\n level?: LogLevel;\n from?: Date | string;\n to?: Date | string;\n q?: string;\n limit?: number;\n offset?: number;\n}\n\nexport interface LogsResponse {\n logs: InternalLogEntry[];\n total: number;\n limit: number;\n offset: number;\n}\n\nexport interface AggregatedStatsOptions {\n from: Date | string;\n to: Date | string;\n interval?: '1m' | '5m' | '1h' | '1d';\n service?: string;\n}\n\nexport interface AggregatedStatsResponse {\n timeseries: Array<{\n bucket: string;\n total: number;\n by_level: Record<string, number>;\n }>;\n top_services: Array<{ service: string; count: number }>;\n top_errors: Array<{ message: string; count: number }>;\n}\n\nexport interface ClientMetrics {\n logsSent: number;\n logsDropped: number;\n errors: number;\n retries: number;\n avgLatencyMs: number;\n circuitBreakerTrips: number;\n}\n\nexport interface StreamOptions {\n service?: string;\n level?: LogLevel;\n onLog: (log: InternalLogEntry) => void;\n onError?: (error: Error) => void;\n}\n\n// ==================== Circuit Breaker ====================\n\nenum CircuitState {\n CLOSED = 'CLOSED',\n OPEN = 'OPEN',\n HALF_OPEN = 'HALF_OPEN',\n}\n\nclass CircuitBreaker {\n private state: CircuitState = CircuitState.CLOSED;\n private failureCount = 0;\n private lastFailureTime: number | null = null;\n\n constructor(\n private threshold: number,\n private resetMs: number,\n ) {}\n\n recordSuccess() {\n this.failureCount = 0;\n this.state = CircuitState.CLOSED;\n }\n\n recordFailure() {\n this.failureCount++;\n this.lastFailureTime = Date.now();\n\n if (this.failureCount >= this.threshold) {\n this.state = CircuitState.OPEN;\n }\n }\n\n canAttempt(): boolean {\n if (this.state === CircuitState.CLOSED) {\n return true;\n }\n\n if (this.state === CircuitState.OPEN) {\n const now = Date.now();\n if (this.lastFailureTime && now - this.lastFailureTime >= this.resetMs) {\n this.state = CircuitState.HALF_OPEN;\n return true;\n }\n return false;\n }\n\n // HALF_OPEN state - allow one attempt\n return true;\n }\n\n getState(): CircuitState {\n return this.state;\n }\n}\n\n// ==================== UUID Validation ====================\n\n/**\n * Validates if a string is a valid UUID (v4)\n */\nfunction isValidUUID(str: string): boolean {\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n return uuidRegex.test(str);\n}\n\n/**\n * Normalizes a trace_id to ensure it's a valid UUID.\n * If invalid, generates a new UUID and warns in debug mode.\n */\nfunction normalizeTraceId(traceId: string | null | undefined, debug: boolean): string | undefined {\n if (!traceId) {\n return undefined;\n }\n\n if (isValidUUID(traceId)) {\n return traceId;\n }\n\n // Invalid UUID - generate a new one\n const newTraceId = randomUUID();\n if (debug) {\n console.warn(\n `[LogTide] Invalid trace_id \"${traceId}\" (must be UUID v4). Generated new UUID: ${newTraceId}`,\n );\n }\n return newTraceId;\n}\n\n// ==================== Error Serialization ====================\n\nexport function serializeError(error: unknown): Record<string, unknown> {\n if (error instanceof Error) {\n const result: Record<string, unknown> = {\n name: error.name,\n message: error.message,\n stack: error.stack,\n };\n\n if (error.cause) {\n result.cause = serializeError(error.cause);\n }\n\n return result;\n }\n\n if (typeof error === 'string') {\n return { message: error };\n }\n\n if (typeof error === 'object' && error !== null) {\n return error as Record<string, unknown>;\n }\n\n return { message: String(error) };\n}\n\n// ==================== Main Client ====================\n\nexport class LogTideClient {\n private apiUrl: string;\n private apiKey: string;\n private batchSize: number;\n private flushInterval: number;\n private maxBufferSize: number;\n private maxRetries: number;\n private retryDelayMs: number;\n private enableMetrics: boolean;\n private debugMode: boolean;\n private globalMetadata: Record<string, unknown>;\n private autoTraceId: boolean;\n\n private buffer: InternalLogEntry[] = [];\n private timer: NodeJS.Timeout | null = null;\n private circuitBreaker: CircuitBreaker;\n\n // Metrics\n private metrics: ClientMetrics = {\n logsSent: 0,\n logsDropped: 0,\n errors: 0,\n retries: 0,\n avgLatencyMs: 0,\n circuitBreakerTrips: 0,\n };\n private latencies: number[] = [];\n\n // Context tracking\n private currentTraceId: string | null = null;\n\n constructor(options: LogTideClientOptions) {\n this.apiUrl = options.apiUrl.replace(/\\/$/, '');\n this.apiKey = options.apiKey;\n this.batchSize = options.batchSize || 100;\n this.flushInterval = options.flushInterval || 5000;\n this.maxBufferSize = options.maxBufferSize || 10000;\n this.maxRetries = options.maxRetries || 3;\n this.retryDelayMs = options.retryDelayMs || 1000;\n this.enableMetrics = options.enableMetrics ?? true;\n this.debugMode = options.debug ?? false;\n this.globalMetadata = options.globalMetadata || {};\n this.autoTraceId = options.autoTraceId ?? false;\n\n this.circuitBreaker = new CircuitBreaker(\n options.circuitBreakerThreshold || 5,\n options.circuitBreakerResetMs || 30000,\n );\n\n this.startFlushTimer();\n }\n\n // ==================== Context Helpers ====================\n\n /**\n * Set trace ID for subsequent logs\n * Automatically validates and normalizes to UUID v4\n */\n setTraceId(traceId: string | null) {\n this.currentTraceId = normalizeTraceId(traceId, this.debugMode) || null;\n }\n\n /**\n * Get current trace ID\n */\n getTraceId(): string | null {\n return this.currentTraceId;\n }\n\n /**\n * Execute function with a specific trace ID context\n */\n withTraceId<T>(traceId: string, fn: () => T): T {\n const previousTraceId = this.currentTraceId;\n this.currentTraceId = traceId;\n try {\n return fn();\n } finally {\n this.currentTraceId = previousTraceId;\n }\n }\n\n /**\n * Execute function with a new auto-generated trace ID\n */\n withNewTraceId<T>(fn: () => T): T {\n return this.withTraceId(randomUUID(), fn);\n }\n\n // ==================== Logging Methods ====================\n\n private startFlushTimer() {\n this.timer = setInterval(() => {\n this.flush();\n }, this.flushInterval);\n }\n\n log(entry: LogEntry) {\n // Check buffer size limit\n if (this.buffer.length >= this.maxBufferSize) {\n this.metrics.logsDropped++;\n if (this.debugMode) {\n console.warn(`[LogTide] Buffer full, dropping log: ${entry.message}`);\n }\n return;\n }\n\n // Normalize trace_id to ensure it's a valid UUID\n const normalizedTraceId =\n normalizeTraceId(entry.trace_id, this.debugMode) ||\n normalizeTraceId(this.currentTraceId, this.debugMode) ||\n (this.autoTraceId ? randomUUID() : undefined);\n\n const internalEntry: InternalLogEntry = {\n ...entry,\n time: entry.time || new Date().toISOString(),\n metadata: {\n ...this.globalMetadata,\n ...entry.metadata,\n },\n trace_id: normalizedTraceId,\n };\n\n this.buffer.push(internalEntry);\n\n if (this.buffer.length >= this.batchSize) {\n this.flush();\n }\n }\n\n debug(service: string, message: string, metadata?: Record<string, unknown>) {\n this.log({ service, level: 'debug', message, metadata });\n }\n\n info(service: string, message: string, metadata?: Record<string, unknown>) {\n this.log({ service, level: 'info', message, metadata });\n }\n\n warn(service: string, message: string, metadata?: Record<string, unknown>) {\n this.log({ service, level: 'warn', message, metadata });\n }\n\n error(service: string, message: string, metadataOrError?: Record<string, unknown> | Error) {\n let metadata: Record<string, unknown> = {};\n\n if (metadataOrError instanceof Error) {\n metadata = { error: serializeError(metadataOrError) };\n } else if (metadataOrError) {\n metadata = metadataOrError;\n }\n\n this.log({ service, level: 'error', message, metadata });\n }\n\n critical(service: string, message: string, metadataOrError?: Record<string, unknown> | Error) {\n let metadata: Record<string, unknown> = {};\n\n if (metadataOrError instanceof Error) {\n metadata = { error: serializeError(metadataOrError) };\n } else if (metadataOrError) {\n metadata = metadataOrError;\n }\n\n this.log({ service, level: 'critical', message, metadata });\n }\n\n // ==================== Flush with Retry & Circuit Breaker ====================\n\n async flush() {\n if (this.buffer.length === 0) return;\n\n // Check circuit breaker\n if (!this.circuitBreaker.canAttempt()) {\n this.metrics.circuitBreakerTrips++;\n if (this.debugMode) {\n console.warn('[LogTide] Circuit breaker OPEN, skipping flush');\n }\n return;\n }\n\n const logs = [...this.buffer];\n this.buffer = [];\n\n const startTime = Date.now();\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n try {\n const response = await fetch(`${this.apiUrl}/api/v1/ingest`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'X-API-Key': this.apiKey,\n },\n body: JSON.stringify({ logs }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`HTTP ${response.status}: ${errorText}`);\n }\n\n // Success\n this.circuitBreaker.recordSuccess();\n this.metrics.logsSent += logs.length;\n\n if (this.enableMetrics) {\n const latency = Date.now() - startTime;\n this.latencies.push(latency);\n if (this.latencies.length > 100) {\n this.latencies.shift();\n }\n this.metrics.avgLatencyMs =\n this.latencies.reduce((a, b) => a + b, 0) / this.latencies.length;\n }\n\n if (this.debugMode) {\n console.log(`[LogTide] Sent ${logs.length} logs successfully`);\n }\n\n return;\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n this.metrics.errors++;\n\n if (attempt < this.maxRetries) {\n this.metrics.retries++;\n const delay = this.retryDelayMs * Math.pow(2, attempt);\n if (this.debugMode) {\n console.warn(\n `[LogTide] Retry ${attempt + 1}/${this.maxRetries} after ${delay}ms: ${lastError.message}`,\n );\n }\n await this.sleep(delay);\n }\n }\n }\n\n // All retries failed\n this.circuitBreaker.recordFailure();\n\n if (this.debugMode) {\n console.error(`[LogTide] Failed to send logs after ${this.maxRetries} retries:`, lastError);\n }\n\n // Re-add logs to buffer if not full\n if (this.buffer.length + logs.length <= this.maxBufferSize) {\n this.buffer.unshift(...logs);\n } else {\n this.metrics.logsDropped += logs.length;\n }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n\n // ==================== Query Methods ====================\n\n async query(options: QueryOptions = {}): Promise<LogsResponse> {\n const params = new URLSearchParams();\n\n if (options.service) params.append('service', options.service);\n if (options.level) params.append('level', options.level);\n if (options.from) {\n const from = options.from instanceof Date ? options.from.toISOString() : options.from;\n params.append('from', from);\n }\n if (options.to) {\n const to = options.to instanceof Date ? options.to.toISOString() : options.to;\n params.append('to', to);\n }\n if (options.q) params.append('q', options.q);\n if (options.limit) params.append('limit', String(options.limit));\n if (options.offset) params.append('offset', String(options.offset));\n\n const url = `${this.apiUrl}/api/v1/logs?${params.toString()}`;\n\n const response = await fetch(url, {\n headers: {\n 'X-API-Key': this.apiKey,\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Query failed: HTTP ${response.status}: ${errorText}`);\n }\n\n return (await response.json()) as LogsResponse;\n }\n\n async getByTraceId(traceId: string): Promise<InternalLogEntry[]> {\n const url = `${this.apiUrl}/api/v1/logs/trace/${traceId}`;\n\n const response = await fetch(url, {\n headers: {\n 'X-API-Key': this.apiKey,\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Get by trace ID failed: HTTP ${response.status}: ${errorText}`);\n }\n\n const data = (await response.json()) as { logs?: InternalLogEntry[] };\n return data.logs || [];\n }\n\n async getAggregatedStats(options: AggregatedStatsOptions): Promise<AggregatedStatsResponse> {\n const params = new URLSearchParams();\n\n const from = options.from instanceof Date ? options.from.toISOString() : options.from;\n const to = options.to instanceof Date ? options.to.toISOString() : options.to;\n\n params.append('from', from);\n params.append('to', to);\n if (options.interval) params.append('interval', options.interval);\n if (options.service) params.append('service', options.service);\n\n const url = `${this.apiUrl}/api/v1/logs/aggregated?${params.toString()}`;\n\n const response = await fetch(url, {\n headers: {\n 'X-API-Key': this.apiKey,\n },\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Get aggregated stats failed: HTTP ${response.status}: ${errorText}`);\n }\n\n return (await response.json()) as AggregatedStatsResponse;\n }\n\n // ==================== Live Tail (SSE) ====================\n\n stream(options: StreamOptions): () => void {\n const params = new URLSearchParams();\n params.append('token', this.apiKey);\n if (options.service) params.append('service', options.service);\n if (options.level) params.append('level', options.level);\n\n const url = `${this.apiUrl}/api/v1/logs/stream?${params.toString()}`;\n\n const eventSource = new EventSource(url);\n\n eventSource.addEventListener('log', (event: Event) => {\n try {\n const messageEvent = event as MessageEvent;\n const log = JSON.parse(messageEvent.data) as InternalLogEntry;\n options.onLog(log);\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n options.onError?.(err);\n }\n });\n\n eventSource.addEventListener('error', () => {\n const error = new Error('SSE connection error');\n options.onError?.(error);\n });\n\n // Return cleanup function\n return () => {\n eventSource.close();\n };\n }\n\n // ==================== Metrics ====================\n\n getMetrics(): ClientMetrics {\n return { ...this.metrics };\n }\n\n resetMetrics() {\n this.metrics = {\n logsSent: 0,\n logsDropped: 0,\n errors: 0,\n retries: 0,\n avgLatencyMs: 0,\n circuitBreakerTrips: 0,\n };\n this.latencies = [];\n }\n\n getCircuitBreakerState(): string {\n return this.circuitBreaker.getState();\n }\n\n // ==================== Cleanup ====================\n\n async close() {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n await this.flush();\n }\n}\n\nexport default LogTideClient;\n"]}
@@ -0,0 +1,134 @@
1
+ type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'critical';
2
+ interface LogTideClientOptions {
3
+ apiUrl: string;
4
+ apiKey: string;
5
+ batchSize?: number;
6
+ flushInterval?: number;
7
+ maxBufferSize?: number;
8
+ maxRetries?: number;
9
+ retryDelayMs?: number;
10
+ circuitBreakerThreshold?: number;
11
+ circuitBreakerResetMs?: number;
12
+ enableMetrics?: boolean;
13
+ debug?: boolean;
14
+ globalMetadata?: Record<string, unknown>;
15
+ autoTraceId?: boolean;
16
+ }
17
+ interface LogEntry {
18
+ service: string;
19
+ level: LogLevel;
20
+ message: string;
21
+ time?: string;
22
+ metadata?: Record<string, unknown>;
23
+ trace_id?: string;
24
+ }
25
+ interface InternalLogEntry extends LogEntry {
26
+ time: string;
27
+ }
28
+ interface QueryOptions {
29
+ service?: string;
30
+ level?: LogLevel;
31
+ from?: Date | string;
32
+ to?: Date | string;
33
+ q?: string;
34
+ limit?: number;
35
+ offset?: number;
36
+ }
37
+ interface LogsResponse {
38
+ logs: InternalLogEntry[];
39
+ total: number;
40
+ limit: number;
41
+ offset: number;
42
+ }
43
+ interface AggregatedStatsOptions {
44
+ from: Date | string;
45
+ to: Date | string;
46
+ interval?: '1m' | '5m' | '1h' | '1d';
47
+ service?: string;
48
+ }
49
+ interface AggregatedStatsResponse {
50
+ timeseries: Array<{
51
+ bucket: string;
52
+ total: number;
53
+ by_level: Record<string, number>;
54
+ }>;
55
+ top_services: Array<{
56
+ service: string;
57
+ count: number;
58
+ }>;
59
+ top_errors: Array<{
60
+ message: string;
61
+ count: number;
62
+ }>;
63
+ }
64
+ interface ClientMetrics {
65
+ logsSent: number;
66
+ logsDropped: number;
67
+ errors: number;
68
+ retries: number;
69
+ avgLatencyMs: number;
70
+ circuitBreakerTrips: number;
71
+ }
72
+ interface StreamOptions {
73
+ service?: string;
74
+ level?: LogLevel;
75
+ onLog: (log: InternalLogEntry) => void;
76
+ onError?: (error: Error) => void;
77
+ }
78
+ declare function serializeError(error: unknown): Record<string, unknown>;
79
+ declare class LogTideClient {
80
+ private apiUrl;
81
+ private apiKey;
82
+ private batchSize;
83
+ private flushInterval;
84
+ private maxBufferSize;
85
+ private maxRetries;
86
+ private retryDelayMs;
87
+ private enableMetrics;
88
+ private debugMode;
89
+ private globalMetadata;
90
+ private autoTraceId;
91
+ private buffer;
92
+ private timer;
93
+ private circuitBreaker;
94
+ private metrics;
95
+ private latencies;
96
+ private currentTraceId;
97
+ constructor(options: LogTideClientOptions);
98
+ /**
99
+ * Set trace ID for subsequent logs
100
+ * Automatically validates and normalizes to UUID v4
101
+ */
102
+ setTraceId(traceId: string | null): void;
103
+ /**
104
+ * Get current trace ID
105
+ */
106
+ getTraceId(): string | null;
107
+ /**
108
+ * Execute function with a specific trace ID context
109
+ */
110
+ withTraceId<T>(traceId: string, fn: () => T): T;
111
+ /**
112
+ * Execute function with a new auto-generated trace ID
113
+ */
114
+ withNewTraceId<T>(fn: () => T): T;
115
+ private startFlushTimer;
116
+ log(entry: LogEntry): void;
117
+ debug(service: string, message: string, metadata?: Record<string, unknown>): void;
118
+ info(service: string, message: string, metadata?: Record<string, unknown>): void;
119
+ warn(service: string, message: string, metadata?: Record<string, unknown>): void;
120
+ error(service: string, message: string, metadataOrError?: Record<string, unknown> | Error): void;
121
+ critical(service: string, message: string, metadataOrError?: Record<string, unknown> | Error): void;
122
+ flush(): Promise<void>;
123
+ private sleep;
124
+ query(options?: QueryOptions): Promise<LogsResponse>;
125
+ getByTraceId(traceId: string): Promise<InternalLogEntry[]>;
126
+ getAggregatedStats(options: AggregatedStatsOptions): Promise<AggregatedStatsResponse>;
127
+ stream(options: StreamOptions): () => void;
128
+ getMetrics(): ClientMetrics;
129
+ resetMetrics(): void;
130
+ getCircuitBreakerState(): string;
131
+ close(): Promise<void>;
132
+ }
133
+
134
+ export { type AggregatedStatsOptions, type AggregatedStatsResponse, type ClientMetrics, type InternalLogEntry, type LogEntry, type LogLevel, LogTideClient, type LogTideClientOptions, type LogsResponse, type QueryOptions, type StreamOptions, LogTideClient as default, serializeError };