logwell 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/LICENSE +21 -0
- package/README.md +253 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +227 -0
- package/dist/index.d.ts +227 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 divkix
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# logwell
|
|
2
|
+
|
|
3
|
+
Official TypeScript SDK for [Logwell](https://github.com/divkix/logwell) - a self-hosted logging platform with real-time streaming and full-text search.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Zero runtime dependencies** - Uses native `fetch`
|
|
8
|
+
- **Universal runtime support** - Node.js 18+, browsers, and edge runtimes
|
|
9
|
+
- **TypeScript-first** - Full type definitions included
|
|
10
|
+
- **Automatic batching** - Configurable batch size and flush intervals
|
|
11
|
+
- **Retry with backoff** - Exponential backoff on transient failures
|
|
12
|
+
- **Child loggers** - Request-scoped context propagation
|
|
13
|
+
- **Lightweight** - < 10KB gzipped
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install logwell
|
|
19
|
+
# or
|
|
20
|
+
bun add logwell
|
|
21
|
+
# or
|
|
22
|
+
pnpm add logwell
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { Logwell } from 'logwell';
|
|
29
|
+
|
|
30
|
+
const logger = new Logwell({
|
|
31
|
+
apiKey: 'lw_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
|
32
|
+
endpoint: 'https://logs.example.com',
|
|
33
|
+
service: 'my-app',
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Log at different levels
|
|
37
|
+
logger.debug('Debug message');
|
|
38
|
+
logger.info('User logged in', { userId: '123' });
|
|
39
|
+
logger.warn('Deprecated API called');
|
|
40
|
+
logger.error('Database connection failed', { host: 'db.local' });
|
|
41
|
+
logger.fatal('Unrecoverable error');
|
|
42
|
+
|
|
43
|
+
// Flush before shutdown
|
|
44
|
+
await logger.shutdown();
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Configuration
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
interface LogwellConfig {
|
|
51
|
+
// Required
|
|
52
|
+
apiKey: string; // API key (format: lw_[32chars])
|
|
53
|
+
endpoint: string; // Logwell server URL
|
|
54
|
+
|
|
55
|
+
// Optional
|
|
56
|
+
service?: string; // Default service name for all logs
|
|
57
|
+
batchSize?: number; // Logs per batch (default: 50)
|
|
58
|
+
flushInterval?: number; // Auto-flush interval in ms (default: 5000)
|
|
59
|
+
maxQueueSize?: number; // Max queue size (default: 1000)
|
|
60
|
+
maxRetries?: number; // Retry attempts (default: 3)
|
|
61
|
+
|
|
62
|
+
// Callbacks
|
|
63
|
+
onError?: (error: Error) => void; // Called on send failures
|
|
64
|
+
onFlush?: (count: number) => void; // Called after successful flush
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## API Reference
|
|
69
|
+
|
|
70
|
+
### Log Methods
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
logger.debug(message: string, metadata?: Record<string, unknown>): void
|
|
74
|
+
logger.info(message: string, metadata?: Record<string, unknown>): void
|
|
75
|
+
logger.warn(message: string, metadata?: Record<string, unknown>): void
|
|
76
|
+
logger.error(message: string, metadata?: Record<string, unknown>): void
|
|
77
|
+
logger.fatal(message: string, metadata?: Record<string, unknown>): void
|
|
78
|
+
|
|
79
|
+
// Generic log method
|
|
80
|
+
logger.log(entry: LogEntry): void
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Lifecycle Methods
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// Force immediate flush of queued logs
|
|
87
|
+
await logger.flush(): Promise<IngestResponse | null>
|
|
88
|
+
|
|
89
|
+
// Flush and stop (call before process exit)
|
|
90
|
+
await logger.shutdown(): Promise<void>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Child Loggers
|
|
94
|
+
|
|
95
|
+
Create child loggers with additional context:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
const requestLogger = logger.child({
|
|
99
|
+
service: 'api-handler',
|
|
100
|
+
metadata: { requestId: req.id },
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// All logs include requestId automatically
|
|
104
|
+
requestLogger.info('Request received', { path: req.path });
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Properties
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// Current queue size
|
|
111
|
+
logger.queueSize: number
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Usage Examples
|
|
115
|
+
|
|
116
|
+
### Express.js Middleware
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { Logwell } from 'logwell';
|
|
120
|
+
import express from 'express';
|
|
121
|
+
|
|
122
|
+
const logger = new Logwell({
|
|
123
|
+
apiKey: process.env.LOGWELL_API_KEY!,
|
|
124
|
+
endpoint: process.env.LOGWELL_ENDPOINT!,
|
|
125
|
+
service: 'express-app',
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const app = express();
|
|
129
|
+
|
|
130
|
+
app.use((req, res, next) => {
|
|
131
|
+
const requestId = crypto.randomUUID();
|
|
132
|
+
req.log = logger.child({ metadata: { requestId } });
|
|
133
|
+
|
|
134
|
+
const start = Date.now();
|
|
135
|
+
res.on('finish', () => {
|
|
136
|
+
req.log.info('Request completed', {
|
|
137
|
+
method: req.method,
|
|
138
|
+
path: req.path,
|
|
139
|
+
status: res.statusCode,
|
|
140
|
+
durationMs: Date.now() - start,
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
next();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Graceful shutdown
|
|
148
|
+
process.on('SIGTERM', async () => {
|
|
149
|
+
await logger.shutdown();
|
|
150
|
+
process.exit(0);
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Cloudflare Workers
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { Logwell } from 'logwell';
|
|
158
|
+
|
|
159
|
+
export default {
|
|
160
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
161
|
+
const logger = new Logwell({
|
|
162
|
+
apiKey: env.LOGWELL_API_KEY,
|
|
163
|
+
endpoint: env.LOGWELL_ENDPOINT,
|
|
164
|
+
batchSize: 1, // Immediate flush for short-lived workers
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
logger.info('Worker invoked', { url: request.url });
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
const response = await handleRequest(request);
|
|
171
|
+
await logger.flush();
|
|
172
|
+
return response;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
logger.error('Worker error', { error: String(error) });
|
|
175
|
+
await logger.flush();
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Browser
|
|
183
|
+
|
|
184
|
+
```html
|
|
185
|
+
<script type="module">
|
|
186
|
+
import { Logwell } from 'https://esm.sh/logwell';
|
|
187
|
+
|
|
188
|
+
const logger = new Logwell({
|
|
189
|
+
apiKey: 'lw_xxx',
|
|
190
|
+
endpoint: 'https://logs.example.com',
|
|
191
|
+
batchSize: 10,
|
|
192
|
+
flushInterval: 10000,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Log user actions
|
|
196
|
+
document.getElementById('submit')?.addEventListener('click', () => {
|
|
197
|
+
logger.info('Form submitted', { formId: 'signup' });
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Flush before page unload
|
|
201
|
+
window.addEventListener('beforeunload', () => logger.flush());
|
|
202
|
+
</script>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Error Handling
|
|
206
|
+
|
|
207
|
+
The SDK throws `LogwellError` on failures:
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import { Logwell, LogwellError } from 'logwell';
|
|
211
|
+
|
|
212
|
+
const logger = new Logwell({
|
|
213
|
+
apiKey: 'lw_xxx',
|
|
214
|
+
endpoint: 'https://logs.example.com',
|
|
215
|
+
onError: (error) => {
|
|
216
|
+
if (error instanceof LogwellError) {
|
|
217
|
+
console.error(`Logwell error [${error.code}]: ${error.message}`);
|
|
218
|
+
if (error.retryable) {
|
|
219
|
+
// Will be retried automatically
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Error codes:
|
|
227
|
+
- `NETWORK_ERROR` - Network failure (retryable)
|
|
228
|
+
- `UNAUTHORIZED` - Invalid API key
|
|
229
|
+
- `VALIDATION_ERROR` - Invalid log format
|
|
230
|
+
- `RATE_LIMITED` - Too many requests (retryable)
|
|
231
|
+
- `SERVER_ERROR` - Server error (retryable)
|
|
232
|
+
- `QUEUE_OVERFLOW` - Queue full, oldest logs dropped
|
|
233
|
+
- `INVALID_CONFIG` - Invalid configuration
|
|
234
|
+
|
|
235
|
+
## TypeScript
|
|
236
|
+
|
|
237
|
+
Full type definitions are included:
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
import type {
|
|
241
|
+
Logwell,
|
|
242
|
+
LogwellConfig,
|
|
243
|
+
LogLevel,
|
|
244
|
+
LogEntry,
|
|
245
|
+
IngestResponse,
|
|
246
|
+
LogwellError,
|
|
247
|
+
LogwellErrorCode,
|
|
248
|
+
} from 'logwell';
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## License
|
|
252
|
+
|
|
253
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var i=class r extends Error{constructor(t,n,o,h=false){super(t);this.code=n;this.statusCode=o;this.retryable=h;this.name="LogwellError",Error.captureStackTrace?.(this,r);}};var s={batchSize:50,flushInterval:5e3,maxQueueSize:1e3,maxRetries:3},d=/^lw_[A-Za-z0-9_-]{32}$/;function f(r){return !r||typeof r!="string"?false:d.test(r)}function c(r){try{return new URL(r),!0}catch{return false}}function p(r){if(!r.apiKey)throw new i("apiKey is required","INVALID_CONFIG");if(!r.endpoint)throw new i("endpoint is required","INVALID_CONFIG");if(!f(r.apiKey))throw new i("Invalid API key format. Expected: lw_[32 characters]","INVALID_CONFIG");if(!c(r.endpoint))throw new i("Invalid endpoint URL","INVALID_CONFIG");if(r.batchSize!==void 0&&r.batchSize<=0)throw new i("batchSize must be positive","INVALID_CONFIG");if(r.flushInterval!==void 0&&r.flushInterval<=0)throw new i("flushInterval must be positive","INVALID_CONFIG");if(r.maxQueueSize!==void 0&&r.maxQueueSize<=0)throw new i("maxQueueSize must be positive","INVALID_CONFIG");if(r.maxRetries!==void 0&&r.maxRetries<0)throw new i("maxRetries must be non-negative","INVALID_CONFIG");return {apiKey:r.apiKey,endpoint:r.endpoint,service:r.service,batchSize:r.batchSize??s.batchSize,flushInterval:r.flushInterval??s.flushInterval,maxQueueSize:r.maxQueueSize??s.maxQueueSize,maxRetries:r.maxRetries??s.maxRetries,onError:r.onError,onFlush:r.onFlush}}var a=class{constructor(e,t){this.sendBatch=e;this.config=t;}queue=[];flushTimer=null;flushing=false;stopped=false;get size(){return this.queue.length}add(e){if(!this.stopped){if(this.queue.length>=this.config.maxQueueSize){let t=this.queue.shift();this.config.onError?.(new i(`Queue overflow. Dropped log: ${t?.message.substring(0,50)}...`,"QUEUE_OVERFLOW"));}this.queue.push(e),!this.flushTimer&&!this.stopped&&this.startTimer(),this.queue.length>=this.config.batchSize&&this.flush();}}async flush(){if(this.flushing||this.queue.length===0)return null;this.flushing=true,this.stopTimer();let e=this.queue.splice(0),t=e.length;try{let n=await this.sendBatch(e);return this.config.onFlush?.(t),this.queue.length>0&&!this.stopped&&this.startTimer(),n}catch(n){return this.queue.unshift(...e),this.config.onError?.(n),this.stopped||this.startTimer(),null}finally{this.flushing=false;}}async shutdown(){this.stopped||(this.stopped=true,this.stopTimer(),this.queue.length>0&&(this.flushing=false,await this.flush()));}startTimer(){this.flushTimer=setTimeout(()=>{this.flush();},this.config.flushInterval);}stopTimer(){this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null);}};function g(r,e=100){let t=Math.min(e*2**r,1e4),n=Math.random()*t*.3;return new Promise(o=>setTimeout(o,t+n))}var u=class{constructor(e){this.config=e;this.ingestUrl=`${e.endpoint}/v1/ingest`;}ingestUrl;async send(e){let t=null;for(let n=0;n<=this.config.maxRetries;n++)try{return await this.doRequest(e)}catch(o){if(t=o,!t.retryable)throw t;n<this.config.maxRetries&&await g(n);}throw t}async doRequest(e){let t;try{t=await fetch(this.ingestUrl,{method:"POST",headers:{Authorization:`Bearer ${this.config.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(e)});}catch(n){throw new i(`Network error: ${n.message}`,"NETWORK_ERROR",void 0,true)}if(!t.ok){let n=await this.tryParseError(t);throw this.createError(t.status,n)}return await t.json()}async tryParseError(e){try{let t=await e.json();return t.message||t.error||"Unknown error"}catch{return `HTTP ${e.status}`}}createError(e,t){switch(e){case 401:return new i(`Unauthorized: ${t}`,"UNAUTHORIZED",e,false);case 400:return new i(`Validation error: ${t}`,"VALIDATION_ERROR",e,false);case 429:return new i(`Rate limited: ${t}`,"RATE_LIMITED",e,true);default:return e>=500?new i(`Server error: ${t}`,"SERVER_ERROR",e,true):new i(`HTTP error ${e}: ${t}`,"SERVER_ERROR",e,false)}}};var l=class r{config;queue;transport;parentMetadata;stopped=false;constructor(e,t,n){if(this.config=p(e),this.parentMetadata=n,this.transport=new u({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxRetries:this.config.maxRetries}),t)this.queue=t;else {let o={batchSize:this.config.batchSize,flushInterval:this.config.flushInterval,maxQueueSize:this.config.maxQueueSize,onError:this.config.onError,onFlush:this.config.onFlush};this.queue=new a(h=>this.transport.send(h),o);}}get queueSize(){return this.queue.size}log(e){if(this.stopped)return;let t={...e,timestamp:e.timestamp??new Date().toISOString(),service:e.service??this.config.service,metadata:this.mergeMetadata(e.metadata)};this.queue.add(t);}debug(e,t){this.log({level:"debug",message:e,metadata:t});}info(e,t){this.log({level:"info",message:e,metadata:t});}warn(e,t){this.log({level:"warn",message:e,metadata:t});}error(e,t){this.log({level:"error",message:e,metadata:t});}fatal(e,t){this.log({level:"fatal",message:e,metadata:t});}async flush(){return this.queue.flush()}async shutdown(){this.stopped=true,await this.queue.shutdown();}child(e){let t={...this.config,service:e.service??this.config.service},n={...this.parentMetadata,...e.metadata};return new r(t,this.queue,n)}mergeMetadata(e){if(!(!this.parentMetadata&&!e))return {...this.parentMetadata,...e}}};exports.Logwell=l;exports.LogwellError=i;//# sourceMappingURL=index.cjs.map
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/config.ts","../src/queue.ts","../src/transport.ts","../src/client.ts"],"names":["LogwellError","_LogwellError","message","code","statusCode","retryable","DEFAULT_CONFIG","API_KEY_REGEX","validateApiKeyFormat","apiKey","isValidUrl","url","validateConfig","config","BatchQueue","sendBatch","entry","dropped","batch","count","response","error","delay","attempt","baseDelay","ms","jitter","resolve","HttpTransport","logs","lastError","errorBody","body","status","Logwell","_Logwell","existingQueue","parentMetadata","queueConfig","fullEntry","metadata","options","childConfig","childMetadata","entryMetadata"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Error codes for Logwell SDK errors\n */\nexport type LogwellErrorCode =\n | 'NETWORK_ERROR'\n | 'UNAUTHORIZED'\n | 'VALIDATION_ERROR'\n | 'RATE_LIMITED'\n | 'SERVER_ERROR'\n | 'QUEUE_OVERFLOW'\n | 'INVALID_CONFIG';\n\n/**\n * Custom error class for Logwell SDK errors\n *\n * @example\n * ```ts\n * throw new LogwellError('Invalid API key', 'UNAUTHORIZED', 401, false);\n * ```\n */\n// V8 specific type for captureStackTrace\ndeclare global {\n interface ErrorConstructor {\n captureStackTrace?(targetObject: object, constructorOpt?: NewableFunction): void;\n }\n}\n\nexport class LogwellError extends Error {\n /**\n * Creates a new LogwellError\n *\n * @param message - Human-readable error message\n * @param code - Error code for programmatic handling\n * @param statusCode - HTTP status code if applicable\n * @param retryable - Whether the operation can be retried\n */\n constructor(\n message: string,\n public readonly code: LogwellErrorCode,\n public readonly statusCode?: number,\n public readonly retryable: boolean = false,\n ) {\n super(message);\n this.name = 'LogwellError';\n\n // Maintains proper stack trace for where our error was thrown (V8 only)\n Error.captureStackTrace?.(this, LogwellError);\n }\n}\n","import { LogwellError } from './errors';\nimport type { LogwellConfig } from './types';\n\n/**\n * Default configuration values\n */\nexport const DEFAULT_CONFIG = {\n batchSize: 50,\n flushInterval: 5000,\n maxQueueSize: 1000,\n maxRetries: 3,\n} as const;\n\n/**\n * API key format regex: lw_[32 alphanumeric chars including - and _]\n */\nexport const API_KEY_REGEX = /^lw_[A-Za-z0-9_-]{32}$/;\n\n/**\n * Validates API key format\n *\n * @param apiKey - API key to validate\n * @returns true if valid format, false otherwise\n */\nexport function validateApiKeyFormat(apiKey: string): boolean {\n if (!apiKey || typeof apiKey !== 'string') {\n return false;\n }\n return API_KEY_REGEX.test(apiKey);\n}\n\n/**\n * Validates a URL string\n *\n * @param url - URL string to validate\n * @returns true if valid URL, false otherwise\n */\nfunction isValidUrl(url: string): boolean {\n try {\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Validates configuration and returns merged config with defaults\n *\n * @param config - Partial configuration to validate\n * @returns Complete configuration with defaults applied\n * @throws LogwellError if configuration is invalid\n */\nexport function validateConfig(config: Partial<LogwellConfig>): LogwellConfig {\n // Validate required fields\n if (!config.apiKey) {\n throw new LogwellError('apiKey is required', 'INVALID_CONFIG');\n }\n\n if (!config.endpoint) {\n throw new LogwellError('endpoint is required', 'INVALID_CONFIG');\n }\n\n // Validate API key format\n if (!validateApiKeyFormat(config.apiKey)) {\n throw new LogwellError(\n 'Invalid API key format. Expected: lw_[32 characters]',\n 'INVALID_CONFIG',\n );\n }\n\n // Validate endpoint URL\n if (!isValidUrl(config.endpoint)) {\n throw new LogwellError('Invalid endpoint URL', 'INVALID_CONFIG');\n }\n\n // Validate numeric options\n if (config.batchSize !== undefined && config.batchSize <= 0) {\n throw new LogwellError('batchSize must be positive', 'INVALID_CONFIG');\n }\n\n if (config.flushInterval !== undefined && config.flushInterval <= 0) {\n throw new LogwellError('flushInterval must be positive', 'INVALID_CONFIG');\n }\n\n if (config.maxQueueSize !== undefined && config.maxQueueSize <= 0) {\n throw new LogwellError('maxQueueSize must be positive', 'INVALID_CONFIG');\n }\n\n if (config.maxRetries !== undefined && config.maxRetries < 0) {\n throw new LogwellError('maxRetries must be non-negative', 'INVALID_CONFIG');\n }\n\n // Return merged config with defaults\n return {\n apiKey: config.apiKey,\n endpoint: config.endpoint,\n service: config.service,\n batchSize: config.batchSize ?? DEFAULT_CONFIG.batchSize,\n flushInterval: config.flushInterval ?? DEFAULT_CONFIG.flushInterval,\n maxQueueSize: config.maxQueueSize ?? DEFAULT_CONFIG.maxQueueSize,\n maxRetries: config.maxRetries ?? DEFAULT_CONFIG.maxRetries,\n onError: config.onError,\n onFlush: config.onFlush,\n };\n}\n","import { LogwellError } from './errors';\nimport type { IngestResponse, LogEntry } from './types';\n\n/**\n * Callback type for sending batched logs\n */\nexport type SendBatchFn = (logs: LogEntry[]) => Promise<IngestResponse>;\n\n/**\n * Queue configuration options\n */\nexport interface QueueConfig {\n batchSize: number;\n flushInterval: number;\n maxQueueSize: number;\n onError?: (error: Error) => void;\n onFlush?: (count: number) => void;\n}\n\n/**\n * Batch queue for buffering and sending logs\n *\n * Features:\n * - Automatic flush on batch size threshold\n * - Automatic flush on time interval\n * - Queue overflow protection (drops oldest)\n * - Re-queue on send failure\n * - Graceful shutdown\n */\nexport class BatchQueue {\n private queue: LogEntry[] = [];\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n private flushing = false;\n private stopped = false;\n\n constructor(\n private sendBatch: SendBatchFn,\n private config: QueueConfig,\n ) {}\n\n /**\n * Current number of logs in the queue\n */\n get size(): number {\n return this.queue.length;\n }\n\n /**\n * Add a log entry to the queue\n *\n * Triggers flush if batch size is reached.\n * Drops oldest log if queue overflows.\n */\n add(entry: LogEntry): void {\n if (this.stopped) {\n return;\n }\n\n // Handle queue overflow\n if (this.queue.length >= this.config.maxQueueSize) {\n const dropped = this.queue.shift();\n this.config.onError?.(\n new LogwellError(\n `Queue overflow. Dropped log: ${dropped?.message.substring(0, 50)}...`,\n 'QUEUE_OVERFLOW',\n ),\n );\n }\n\n this.queue.push(entry);\n\n // Start timer on first entry\n if (!this.flushTimer && !this.stopped) {\n this.startTimer();\n }\n\n // Flush immediately if batch size reached\n if (this.queue.length >= this.config.batchSize) {\n void this.flush();\n }\n }\n\n /**\n * Flush all queued logs immediately\n *\n * @returns Response from the server, or null if queue was empty\n */\n async flush(): Promise<IngestResponse | null> {\n // Prevent concurrent flushes\n if (this.flushing || this.queue.length === 0) {\n return null;\n }\n\n this.flushing = true;\n this.stopTimer();\n\n // Take current batch\n const batch = this.queue.splice(0);\n const count = batch.length;\n\n try {\n const response = await this.sendBatch(batch);\n this.config.onFlush?.(count);\n\n // Restart timer if more logs remain (added during flush)\n if (this.queue.length > 0 && !this.stopped) {\n this.startTimer();\n }\n\n return response;\n } catch (error) {\n // Re-queue failed logs at the front\n this.queue.unshift(...batch);\n this.config.onError?.(error as Error);\n\n // Restart timer to retry\n if (!this.stopped) {\n this.startTimer();\n }\n\n return null;\n } finally {\n this.flushing = false;\n }\n }\n\n /**\n * Flush remaining logs and stop the queue\n */\n async shutdown(): Promise<void> {\n if (this.stopped) {\n return;\n }\n\n this.stopped = true;\n this.stopTimer();\n\n // Flush all remaining logs\n if (this.queue.length > 0) {\n this.flushing = false; // Reset flushing flag\n await this.flush();\n }\n }\n\n private startTimer(): void {\n this.flushTimer = setTimeout(() => {\n void this.flush();\n }, this.config.flushInterval);\n }\n\n private stopTimer(): void {\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n }\n}\n","import { LogwellError } from './errors';\nimport type { IngestResponse, LogEntry } from './types';\n\n/**\n * Transport configuration\n */\nexport interface TransportConfig {\n endpoint: string;\n apiKey: string;\n maxRetries: number;\n timeout?: number;\n}\n\n/**\n * Delay helper with exponential backoff\n */\nfunction delay(attempt: number, baseDelay = 100): Promise<void> {\n const ms = Math.min(baseDelay * 2 ** attempt, 10000);\n const jitter = Math.random() * ms * 0.3;\n return new Promise((resolve) => setTimeout(resolve, ms + jitter));\n}\n\n/**\n * Check if error is retryable based on status code\n */\nfunction isRetryableStatus(status: number): boolean {\n return status >= 500 || status === 429;\n}\n\n/**\n * HTTP transport for sending logs to Logwell server\n *\n * Features:\n * - Automatic retry with exponential backoff\n * - Error classification with retryable flag\n * - Proper error handling for all HTTP status codes\n */\nexport class HttpTransport {\n private readonly ingestUrl: string;\n\n constructor(private config: TransportConfig) {\n this.ingestUrl = `${config.endpoint}/v1/ingest`;\n }\n\n /**\n * Send logs to the Logwell server\n *\n * @param logs - Array of log entries to send\n * @returns Response with accepted/rejected counts\n * @throws LogwellError on failure after all retries\n */\n async send(logs: LogEntry[]): Promise<IngestResponse> {\n let lastError: LogwellError | null = null;\n\n for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {\n try {\n const response = await this.doRequest(logs);\n return response;\n } catch (error) {\n lastError = error as LogwellError;\n\n // Don't retry non-retryable errors\n if (!lastError.retryable) {\n throw lastError;\n }\n\n // Don't delay after the last attempt\n if (attempt < this.config.maxRetries) {\n await delay(attempt);\n }\n }\n }\n\n throw lastError!;\n }\n\n private async doRequest(logs: LogEntry[]): Promise<IngestResponse> {\n let response: Response;\n\n try {\n response = await fetch(this.ingestUrl, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.config.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(logs),\n });\n } catch (error) {\n // Network error (fetch failed)\n throw new LogwellError(\n `Network error: ${(error as Error).message}`,\n 'NETWORK_ERROR',\n undefined,\n true,\n );\n }\n\n // Handle error responses\n if (!response.ok) {\n const errorBody = await this.tryParseError(response);\n throw this.createError(response.status, errorBody);\n }\n\n // Parse successful response\n return (await response.json()) as IngestResponse;\n }\n\n private async tryParseError(response: Response): Promise<string> {\n try {\n const body = await response.json();\n return body.message || body.error || 'Unknown error';\n } catch {\n return `HTTP ${response.status}`;\n }\n }\n\n private createError(status: number, message: string): LogwellError {\n switch (status) {\n case 401:\n return new LogwellError(\n `Unauthorized: ${message}`,\n 'UNAUTHORIZED',\n status,\n false,\n );\n case 400:\n return new LogwellError(\n `Validation error: ${message}`,\n 'VALIDATION_ERROR',\n status,\n false,\n );\n case 429:\n return new LogwellError(\n `Rate limited: ${message}`,\n 'RATE_LIMITED',\n status,\n true,\n );\n default:\n if (status >= 500) {\n return new LogwellError(\n `Server error: ${message}`,\n 'SERVER_ERROR',\n status,\n true,\n );\n }\n return new LogwellError(\n `HTTP error ${status}: ${message}`,\n 'SERVER_ERROR',\n status,\n false,\n );\n }\n }\n}\n","import { validateConfig } from './config';\nimport { BatchQueue, type QueueConfig } from './queue';\nimport { HttpTransport } from './transport';\nimport type { IngestResponse, LogEntry, LogwellConfig } from './types';\n\n/**\n * Child logger options\n */\nexport interface ChildLoggerOptions {\n service?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Internal resolved config with all defaults applied\n */\ninterface ResolvedConfig {\n apiKey: string;\n endpoint: string;\n service?: string;\n batchSize: number;\n flushInterval: number;\n maxQueueSize: number;\n maxRetries: number;\n onError?: (error: Error) => void;\n onFlush?: (count: number) => void;\n}\n\n/**\n * Asserts that config has all required fields after validation\n */\nfunction asResolvedConfig(config: LogwellConfig): ResolvedConfig {\n return config as ResolvedConfig;\n}\n\n/**\n * Main Logwell client class\n *\n * Provides methods for logging at different levels with automatic\n * batching, retry, and queue management.\n *\n * @example\n * ```ts\n * const logger = new Logwell({\n * apiKey: 'lw_xxx',\n * endpoint: 'https://logs.example.com',\n * service: 'my-app',\n * });\n *\n * logger.info('User logged in', { userId: '123' });\n * await logger.shutdown();\n * ```\n */\nexport class Logwell {\n private readonly config: ResolvedConfig;\n private readonly queue: BatchQueue;\n private readonly transport: HttpTransport;\n private readonly parentMetadata?: Record<string, unknown>;\n private stopped = false;\n\n constructor(config: LogwellConfig);\n constructor(config: LogwellConfig, queue: BatchQueue, parentMetadata?: Record<string, unknown>);\n constructor(\n config: LogwellConfig,\n existingQueue?: BatchQueue,\n parentMetadata?: Record<string, unknown>,\n ) {\n // Validate and apply defaults\n this.config = asResolvedConfig(validateConfig(config));\n this.parentMetadata = parentMetadata;\n\n // Create transport\n this.transport = new HttpTransport({\n endpoint: this.config.endpoint,\n apiKey: this.config.apiKey,\n maxRetries: this.config.maxRetries,\n });\n\n // Use existing queue (for child loggers) or create new one\n if (existingQueue) {\n this.queue = existingQueue;\n } else {\n const queueConfig: QueueConfig = {\n batchSize: this.config.batchSize,\n flushInterval: this.config.flushInterval,\n maxQueueSize: this.config.maxQueueSize,\n onError: this.config.onError,\n onFlush: this.config.onFlush,\n };\n this.queue = new BatchQueue((logs) => this.transport.send(logs), queueConfig);\n }\n }\n\n /**\n * Current number of logs waiting in the queue\n */\n get queueSize(): number {\n return this.queue.size;\n }\n\n /**\n * Log a message at the specified level\n */\n log(entry: LogEntry): void {\n if (this.stopped) return;\n\n const fullEntry: LogEntry = {\n ...entry,\n timestamp: entry.timestamp ?? new Date().toISOString(),\n service: entry.service ?? this.config.service,\n metadata: this.mergeMetadata(entry.metadata),\n };\n\n this.queue.add(fullEntry);\n }\n\n /**\n * Log a debug message\n */\n debug(message: string, metadata?: Record<string, unknown>): void {\n this.log({ level: 'debug', message, metadata });\n }\n\n /**\n * Log an info message\n */\n info(message: string, metadata?: Record<string, unknown>): void {\n this.log({ level: 'info', message, metadata });\n }\n\n /**\n * Log a warning message\n */\n warn(message: string, metadata?: Record<string, unknown>): void {\n this.log({ level: 'warn', message, metadata });\n }\n\n /**\n * Log an error message\n */\n error(message: string, metadata?: Record<string, unknown>): void {\n this.log({ level: 'error', message, metadata });\n }\n\n /**\n * Log a fatal error message\n */\n fatal(message: string, metadata?: Record<string, unknown>): void {\n this.log({ level: 'fatal', message, metadata });\n }\n\n /**\n * Flush all queued logs immediately\n *\n * @returns Response from the server, or null if queue was empty\n */\n async flush(): Promise<IngestResponse | null> {\n return this.queue.flush();\n }\n\n /**\n * Flush remaining logs and stop the client\n *\n * Call this before process exit to ensure all logs are sent.\n */\n async shutdown(): Promise<void> {\n this.stopped = true;\n await this.queue.shutdown();\n }\n\n /**\n * Create a child logger with additional context\n *\n * Child loggers share the same queue as the parent,\n * but can have their own service name and default metadata.\n *\n * @example\n * ```ts\n * const requestLogger = logger.child({\n * metadata: { requestId: req.id },\n * });\n * requestLogger.info('Request received');\n * ```\n */\n child(options: ChildLoggerOptions): Logwell {\n const childConfig: LogwellConfig = {\n ...this.config,\n service: options.service ?? this.config.service,\n };\n\n const childMetadata = {\n ...this.parentMetadata,\n ...options.metadata,\n };\n\n return new Logwell(childConfig, this.queue, childMetadata);\n }\n\n private mergeMetadata(\n entryMetadata?: Record<string, unknown>,\n ): Record<string, unknown> | undefined {\n if (!this.parentMetadata && !entryMetadata) {\n return undefined;\n }\n return {\n ...this.parentMetadata,\n ...entryMetadata,\n };\n }\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Valid log levels matching Logwell server
|
|
3
|
+
*/
|
|
4
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
|
5
|
+
/**
|
|
6
|
+
* Single log entry for the simple API
|
|
7
|
+
*/
|
|
8
|
+
interface LogEntry {
|
|
9
|
+
level: LogLevel;
|
|
10
|
+
message: string;
|
|
11
|
+
timestamp?: string;
|
|
12
|
+
service?: string;
|
|
13
|
+
metadata?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* SDK configuration options
|
|
17
|
+
*/
|
|
18
|
+
interface LogwellConfig {
|
|
19
|
+
/** API key in format lw_[32chars] */
|
|
20
|
+
apiKey: string;
|
|
21
|
+
/** Logwell server endpoint URL */
|
|
22
|
+
endpoint: string;
|
|
23
|
+
/** Default service name for all logs */
|
|
24
|
+
service?: string;
|
|
25
|
+
/** Max logs before auto-flush (default: 50) */
|
|
26
|
+
batchSize?: number;
|
|
27
|
+
/** Auto-flush interval in ms (default: 5000) */
|
|
28
|
+
flushInterval?: number;
|
|
29
|
+
/** Max queue size before dropping oldest (default: 1000) */
|
|
30
|
+
maxQueueSize?: number;
|
|
31
|
+
/** Max retry attempts (default: 3) */
|
|
32
|
+
maxRetries?: number;
|
|
33
|
+
/** Called on send failures */
|
|
34
|
+
onError?: (error: Error) => void;
|
|
35
|
+
/** Called after successful flush */
|
|
36
|
+
onFlush?: (count: number) => void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Response from the ingest endpoint
|
|
40
|
+
*/
|
|
41
|
+
interface IngestResponse {
|
|
42
|
+
accepted: number;
|
|
43
|
+
rejected?: number;
|
|
44
|
+
errors?: string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Callback type for sending batched logs
|
|
49
|
+
*/
|
|
50
|
+
type SendBatchFn = (logs: LogEntry[]) => Promise<IngestResponse>;
|
|
51
|
+
/**
|
|
52
|
+
* Queue configuration options
|
|
53
|
+
*/
|
|
54
|
+
interface QueueConfig {
|
|
55
|
+
batchSize: number;
|
|
56
|
+
flushInterval: number;
|
|
57
|
+
maxQueueSize: number;
|
|
58
|
+
onError?: (error: Error) => void;
|
|
59
|
+
onFlush?: (count: number) => void;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Batch queue for buffering and sending logs
|
|
63
|
+
*
|
|
64
|
+
* Features:
|
|
65
|
+
* - Automatic flush on batch size threshold
|
|
66
|
+
* - Automatic flush on time interval
|
|
67
|
+
* - Queue overflow protection (drops oldest)
|
|
68
|
+
* - Re-queue on send failure
|
|
69
|
+
* - Graceful shutdown
|
|
70
|
+
*/
|
|
71
|
+
declare class BatchQueue {
|
|
72
|
+
private sendBatch;
|
|
73
|
+
private config;
|
|
74
|
+
private queue;
|
|
75
|
+
private flushTimer;
|
|
76
|
+
private flushing;
|
|
77
|
+
private stopped;
|
|
78
|
+
constructor(sendBatch: SendBatchFn, config: QueueConfig);
|
|
79
|
+
/**
|
|
80
|
+
* Current number of logs in the queue
|
|
81
|
+
*/
|
|
82
|
+
get size(): number;
|
|
83
|
+
/**
|
|
84
|
+
* Add a log entry to the queue
|
|
85
|
+
*
|
|
86
|
+
* Triggers flush if batch size is reached.
|
|
87
|
+
* Drops oldest log if queue overflows.
|
|
88
|
+
*/
|
|
89
|
+
add(entry: LogEntry): void;
|
|
90
|
+
/**
|
|
91
|
+
* Flush all queued logs immediately
|
|
92
|
+
*
|
|
93
|
+
* @returns Response from the server, or null if queue was empty
|
|
94
|
+
*/
|
|
95
|
+
flush(): Promise<IngestResponse | null>;
|
|
96
|
+
/**
|
|
97
|
+
* Flush remaining logs and stop the queue
|
|
98
|
+
*/
|
|
99
|
+
shutdown(): Promise<void>;
|
|
100
|
+
private startTimer;
|
|
101
|
+
private stopTimer;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Child logger options
|
|
106
|
+
*/
|
|
107
|
+
interface ChildLoggerOptions {
|
|
108
|
+
service?: string;
|
|
109
|
+
metadata?: Record<string, unknown>;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Main Logwell client class
|
|
113
|
+
*
|
|
114
|
+
* Provides methods for logging at different levels with automatic
|
|
115
|
+
* batching, retry, and queue management.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* const logger = new Logwell({
|
|
120
|
+
* apiKey: 'lw_xxx',
|
|
121
|
+
* endpoint: 'https://logs.example.com',
|
|
122
|
+
* service: 'my-app',
|
|
123
|
+
* });
|
|
124
|
+
*
|
|
125
|
+
* logger.info('User logged in', { userId: '123' });
|
|
126
|
+
* await logger.shutdown();
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
declare class Logwell {
|
|
130
|
+
private readonly config;
|
|
131
|
+
private readonly queue;
|
|
132
|
+
private readonly transport;
|
|
133
|
+
private readonly parentMetadata?;
|
|
134
|
+
private stopped;
|
|
135
|
+
constructor(config: LogwellConfig);
|
|
136
|
+
constructor(config: LogwellConfig, queue: BatchQueue, parentMetadata?: Record<string, unknown>);
|
|
137
|
+
/**
|
|
138
|
+
* Current number of logs waiting in the queue
|
|
139
|
+
*/
|
|
140
|
+
get queueSize(): number;
|
|
141
|
+
/**
|
|
142
|
+
* Log a message at the specified level
|
|
143
|
+
*/
|
|
144
|
+
log(entry: LogEntry): void;
|
|
145
|
+
/**
|
|
146
|
+
* Log a debug message
|
|
147
|
+
*/
|
|
148
|
+
debug(message: string, metadata?: Record<string, unknown>): void;
|
|
149
|
+
/**
|
|
150
|
+
* Log an info message
|
|
151
|
+
*/
|
|
152
|
+
info(message: string, metadata?: Record<string, unknown>): void;
|
|
153
|
+
/**
|
|
154
|
+
* Log a warning message
|
|
155
|
+
*/
|
|
156
|
+
warn(message: string, metadata?: Record<string, unknown>): void;
|
|
157
|
+
/**
|
|
158
|
+
* Log an error message
|
|
159
|
+
*/
|
|
160
|
+
error(message: string, metadata?: Record<string, unknown>): void;
|
|
161
|
+
/**
|
|
162
|
+
* Log a fatal error message
|
|
163
|
+
*/
|
|
164
|
+
fatal(message: string, metadata?: Record<string, unknown>): void;
|
|
165
|
+
/**
|
|
166
|
+
* Flush all queued logs immediately
|
|
167
|
+
*
|
|
168
|
+
* @returns Response from the server, or null if queue was empty
|
|
169
|
+
*/
|
|
170
|
+
flush(): Promise<IngestResponse | null>;
|
|
171
|
+
/**
|
|
172
|
+
* Flush remaining logs and stop the client
|
|
173
|
+
*
|
|
174
|
+
* Call this before process exit to ensure all logs are sent.
|
|
175
|
+
*/
|
|
176
|
+
shutdown(): Promise<void>;
|
|
177
|
+
/**
|
|
178
|
+
* Create a child logger with additional context
|
|
179
|
+
*
|
|
180
|
+
* Child loggers share the same queue as the parent,
|
|
181
|
+
* but can have their own service name and default metadata.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* const requestLogger = logger.child({
|
|
186
|
+
* metadata: { requestId: req.id },
|
|
187
|
+
* });
|
|
188
|
+
* requestLogger.info('Request received');
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
child(options: ChildLoggerOptions): Logwell;
|
|
192
|
+
private mergeMetadata;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Error codes for Logwell SDK errors
|
|
197
|
+
*/
|
|
198
|
+
type LogwellErrorCode = 'NETWORK_ERROR' | 'UNAUTHORIZED' | 'VALIDATION_ERROR' | 'RATE_LIMITED' | 'SERVER_ERROR' | 'QUEUE_OVERFLOW' | 'INVALID_CONFIG';
|
|
199
|
+
/**
|
|
200
|
+
* Custom error class for Logwell SDK errors
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```ts
|
|
204
|
+
* throw new LogwellError('Invalid API key', 'UNAUTHORIZED', 401, false);
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
declare global {
|
|
208
|
+
interface ErrorConstructor {
|
|
209
|
+
captureStackTrace?(targetObject: object, constructorOpt?: NewableFunction): void;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
declare class LogwellError extends Error {
|
|
213
|
+
readonly code: LogwellErrorCode;
|
|
214
|
+
readonly statusCode?: number | undefined;
|
|
215
|
+
readonly retryable: boolean;
|
|
216
|
+
/**
|
|
217
|
+
* Creates a new LogwellError
|
|
218
|
+
*
|
|
219
|
+
* @param message - Human-readable error message
|
|
220
|
+
* @param code - Error code for programmatic handling
|
|
221
|
+
* @param statusCode - HTTP status code if applicable
|
|
222
|
+
* @param retryable - Whether the operation can be retried
|
|
223
|
+
*/
|
|
224
|
+
constructor(message: string, code: LogwellErrorCode, statusCode?: number | undefined, retryable?: boolean);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export { type ChildLoggerOptions, type IngestResponse, type LogEntry, type LogLevel, Logwell, type LogwellConfig, LogwellError, type LogwellErrorCode };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Valid log levels matching Logwell server
|
|
3
|
+
*/
|
|
4
|
+
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
|
5
|
+
/**
|
|
6
|
+
* Single log entry for the simple API
|
|
7
|
+
*/
|
|
8
|
+
interface LogEntry {
|
|
9
|
+
level: LogLevel;
|
|
10
|
+
message: string;
|
|
11
|
+
timestamp?: string;
|
|
12
|
+
service?: string;
|
|
13
|
+
metadata?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* SDK configuration options
|
|
17
|
+
*/
|
|
18
|
+
interface LogwellConfig {
|
|
19
|
+
/** API key in format lw_[32chars] */
|
|
20
|
+
apiKey: string;
|
|
21
|
+
/** Logwell server endpoint URL */
|
|
22
|
+
endpoint: string;
|
|
23
|
+
/** Default service name for all logs */
|
|
24
|
+
service?: string;
|
|
25
|
+
/** Max logs before auto-flush (default: 50) */
|
|
26
|
+
batchSize?: number;
|
|
27
|
+
/** Auto-flush interval in ms (default: 5000) */
|
|
28
|
+
flushInterval?: number;
|
|
29
|
+
/** Max queue size before dropping oldest (default: 1000) */
|
|
30
|
+
maxQueueSize?: number;
|
|
31
|
+
/** Max retry attempts (default: 3) */
|
|
32
|
+
maxRetries?: number;
|
|
33
|
+
/** Called on send failures */
|
|
34
|
+
onError?: (error: Error) => void;
|
|
35
|
+
/** Called after successful flush */
|
|
36
|
+
onFlush?: (count: number) => void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Response from the ingest endpoint
|
|
40
|
+
*/
|
|
41
|
+
interface IngestResponse {
|
|
42
|
+
accepted: number;
|
|
43
|
+
rejected?: number;
|
|
44
|
+
errors?: string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Callback type for sending batched logs
|
|
49
|
+
*/
|
|
50
|
+
type SendBatchFn = (logs: LogEntry[]) => Promise<IngestResponse>;
|
|
51
|
+
/**
|
|
52
|
+
* Queue configuration options
|
|
53
|
+
*/
|
|
54
|
+
interface QueueConfig {
|
|
55
|
+
batchSize: number;
|
|
56
|
+
flushInterval: number;
|
|
57
|
+
maxQueueSize: number;
|
|
58
|
+
onError?: (error: Error) => void;
|
|
59
|
+
onFlush?: (count: number) => void;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Batch queue for buffering and sending logs
|
|
63
|
+
*
|
|
64
|
+
* Features:
|
|
65
|
+
* - Automatic flush on batch size threshold
|
|
66
|
+
* - Automatic flush on time interval
|
|
67
|
+
* - Queue overflow protection (drops oldest)
|
|
68
|
+
* - Re-queue on send failure
|
|
69
|
+
* - Graceful shutdown
|
|
70
|
+
*/
|
|
71
|
+
declare class BatchQueue {
|
|
72
|
+
private sendBatch;
|
|
73
|
+
private config;
|
|
74
|
+
private queue;
|
|
75
|
+
private flushTimer;
|
|
76
|
+
private flushing;
|
|
77
|
+
private stopped;
|
|
78
|
+
constructor(sendBatch: SendBatchFn, config: QueueConfig);
|
|
79
|
+
/**
|
|
80
|
+
* Current number of logs in the queue
|
|
81
|
+
*/
|
|
82
|
+
get size(): number;
|
|
83
|
+
/**
|
|
84
|
+
* Add a log entry to the queue
|
|
85
|
+
*
|
|
86
|
+
* Triggers flush if batch size is reached.
|
|
87
|
+
* Drops oldest log if queue overflows.
|
|
88
|
+
*/
|
|
89
|
+
add(entry: LogEntry): void;
|
|
90
|
+
/**
|
|
91
|
+
* Flush all queued logs immediately
|
|
92
|
+
*
|
|
93
|
+
* @returns Response from the server, or null if queue was empty
|
|
94
|
+
*/
|
|
95
|
+
flush(): Promise<IngestResponse | null>;
|
|
96
|
+
/**
|
|
97
|
+
* Flush remaining logs and stop the queue
|
|
98
|
+
*/
|
|
99
|
+
shutdown(): Promise<void>;
|
|
100
|
+
private startTimer;
|
|
101
|
+
private stopTimer;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Child logger options
|
|
106
|
+
*/
|
|
107
|
+
interface ChildLoggerOptions {
|
|
108
|
+
service?: string;
|
|
109
|
+
metadata?: Record<string, unknown>;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Main Logwell client class
|
|
113
|
+
*
|
|
114
|
+
* Provides methods for logging at different levels with automatic
|
|
115
|
+
* batching, retry, and queue management.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* const logger = new Logwell({
|
|
120
|
+
* apiKey: 'lw_xxx',
|
|
121
|
+
* endpoint: 'https://logs.example.com',
|
|
122
|
+
* service: 'my-app',
|
|
123
|
+
* });
|
|
124
|
+
*
|
|
125
|
+
* logger.info('User logged in', { userId: '123' });
|
|
126
|
+
* await logger.shutdown();
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
declare class Logwell {
|
|
130
|
+
private readonly config;
|
|
131
|
+
private readonly queue;
|
|
132
|
+
private readonly transport;
|
|
133
|
+
private readonly parentMetadata?;
|
|
134
|
+
private stopped;
|
|
135
|
+
constructor(config: LogwellConfig);
|
|
136
|
+
constructor(config: LogwellConfig, queue: BatchQueue, parentMetadata?: Record<string, unknown>);
|
|
137
|
+
/**
|
|
138
|
+
* Current number of logs waiting in the queue
|
|
139
|
+
*/
|
|
140
|
+
get queueSize(): number;
|
|
141
|
+
/**
|
|
142
|
+
* Log a message at the specified level
|
|
143
|
+
*/
|
|
144
|
+
log(entry: LogEntry): void;
|
|
145
|
+
/**
|
|
146
|
+
* Log a debug message
|
|
147
|
+
*/
|
|
148
|
+
debug(message: string, metadata?: Record<string, unknown>): void;
|
|
149
|
+
/**
|
|
150
|
+
* Log an info message
|
|
151
|
+
*/
|
|
152
|
+
info(message: string, metadata?: Record<string, unknown>): void;
|
|
153
|
+
/**
|
|
154
|
+
* Log a warning message
|
|
155
|
+
*/
|
|
156
|
+
warn(message: string, metadata?: Record<string, unknown>): void;
|
|
157
|
+
/**
|
|
158
|
+
* Log an error message
|
|
159
|
+
*/
|
|
160
|
+
error(message: string, metadata?: Record<string, unknown>): void;
|
|
161
|
+
/**
|
|
162
|
+
* Log a fatal error message
|
|
163
|
+
*/
|
|
164
|
+
fatal(message: string, metadata?: Record<string, unknown>): void;
|
|
165
|
+
/**
|
|
166
|
+
* Flush all queued logs immediately
|
|
167
|
+
*
|
|
168
|
+
* @returns Response from the server, or null if queue was empty
|
|
169
|
+
*/
|
|
170
|
+
flush(): Promise<IngestResponse | null>;
|
|
171
|
+
/**
|
|
172
|
+
* Flush remaining logs and stop the client
|
|
173
|
+
*
|
|
174
|
+
* Call this before process exit to ensure all logs are sent.
|
|
175
|
+
*/
|
|
176
|
+
shutdown(): Promise<void>;
|
|
177
|
+
/**
|
|
178
|
+
* Create a child logger with additional context
|
|
179
|
+
*
|
|
180
|
+
* Child loggers share the same queue as the parent,
|
|
181
|
+
* but can have their own service name and default metadata.
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```ts
|
|
185
|
+
* const requestLogger = logger.child({
|
|
186
|
+
* metadata: { requestId: req.id },
|
|
187
|
+
* });
|
|
188
|
+
* requestLogger.info('Request received');
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
child(options: ChildLoggerOptions): Logwell;
|
|
192
|
+
private mergeMetadata;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Error codes for Logwell SDK errors
|
|
197
|
+
*/
|
|
198
|
+
type LogwellErrorCode = 'NETWORK_ERROR' | 'UNAUTHORIZED' | 'VALIDATION_ERROR' | 'RATE_LIMITED' | 'SERVER_ERROR' | 'QUEUE_OVERFLOW' | 'INVALID_CONFIG';
|
|
199
|
+
/**
|
|
200
|
+
* Custom error class for Logwell SDK errors
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```ts
|
|
204
|
+
* throw new LogwellError('Invalid API key', 'UNAUTHORIZED', 401, false);
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
declare global {
|
|
208
|
+
interface ErrorConstructor {
|
|
209
|
+
captureStackTrace?(targetObject: object, constructorOpt?: NewableFunction): void;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
declare class LogwellError extends Error {
|
|
213
|
+
readonly code: LogwellErrorCode;
|
|
214
|
+
readonly statusCode?: number | undefined;
|
|
215
|
+
readonly retryable: boolean;
|
|
216
|
+
/**
|
|
217
|
+
* Creates a new LogwellError
|
|
218
|
+
*
|
|
219
|
+
* @param message - Human-readable error message
|
|
220
|
+
* @param code - Error code for programmatic handling
|
|
221
|
+
* @param statusCode - HTTP status code if applicable
|
|
222
|
+
* @param retryable - Whether the operation can be retried
|
|
223
|
+
*/
|
|
224
|
+
constructor(message: string, code: LogwellErrorCode, statusCode?: number | undefined, retryable?: boolean);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export { type ChildLoggerOptions, type IngestResponse, type LogEntry, type LogLevel, Logwell, type LogwellConfig, LogwellError, type LogwellErrorCode };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var i=class r extends Error{constructor(t,n,o,h=false){super(t);this.code=n;this.statusCode=o;this.retryable=h;this.name="LogwellError",Error.captureStackTrace?.(this,r);}};var s={batchSize:50,flushInterval:5e3,maxQueueSize:1e3,maxRetries:3},d=/^lw_[A-Za-z0-9_-]{32}$/;function f(r){return !r||typeof r!="string"?false:d.test(r)}function c(r){try{return new URL(r),!0}catch{return false}}function p(r){if(!r.apiKey)throw new i("apiKey is required","INVALID_CONFIG");if(!r.endpoint)throw new i("endpoint is required","INVALID_CONFIG");if(!f(r.apiKey))throw new i("Invalid API key format. Expected: lw_[32 characters]","INVALID_CONFIG");if(!c(r.endpoint))throw new i("Invalid endpoint URL","INVALID_CONFIG");if(r.batchSize!==void 0&&r.batchSize<=0)throw new i("batchSize must be positive","INVALID_CONFIG");if(r.flushInterval!==void 0&&r.flushInterval<=0)throw new i("flushInterval must be positive","INVALID_CONFIG");if(r.maxQueueSize!==void 0&&r.maxQueueSize<=0)throw new i("maxQueueSize must be positive","INVALID_CONFIG");if(r.maxRetries!==void 0&&r.maxRetries<0)throw new i("maxRetries must be non-negative","INVALID_CONFIG");return {apiKey:r.apiKey,endpoint:r.endpoint,service:r.service,batchSize:r.batchSize??s.batchSize,flushInterval:r.flushInterval??s.flushInterval,maxQueueSize:r.maxQueueSize??s.maxQueueSize,maxRetries:r.maxRetries??s.maxRetries,onError:r.onError,onFlush:r.onFlush}}var a=class{constructor(e,t){this.sendBatch=e;this.config=t;}queue=[];flushTimer=null;flushing=false;stopped=false;get size(){return this.queue.length}add(e){if(!this.stopped){if(this.queue.length>=this.config.maxQueueSize){let t=this.queue.shift();this.config.onError?.(new i(`Queue overflow. Dropped log: ${t?.message.substring(0,50)}...`,"QUEUE_OVERFLOW"));}this.queue.push(e),!this.flushTimer&&!this.stopped&&this.startTimer(),this.queue.length>=this.config.batchSize&&this.flush();}}async flush(){if(this.flushing||this.queue.length===0)return null;this.flushing=true,this.stopTimer();let e=this.queue.splice(0),t=e.length;try{let n=await this.sendBatch(e);return this.config.onFlush?.(t),this.queue.length>0&&!this.stopped&&this.startTimer(),n}catch(n){return this.queue.unshift(...e),this.config.onError?.(n),this.stopped||this.startTimer(),null}finally{this.flushing=false;}}async shutdown(){this.stopped||(this.stopped=true,this.stopTimer(),this.queue.length>0&&(this.flushing=false,await this.flush()));}startTimer(){this.flushTimer=setTimeout(()=>{this.flush();},this.config.flushInterval);}stopTimer(){this.flushTimer&&(clearTimeout(this.flushTimer),this.flushTimer=null);}};function g(r,e=100){let t=Math.min(e*2**r,1e4),n=Math.random()*t*.3;return new Promise(o=>setTimeout(o,t+n))}var u=class{constructor(e){this.config=e;this.ingestUrl=`${e.endpoint}/v1/ingest`;}ingestUrl;async send(e){let t=null;for(let n=0;n<=this.config.maxRetries;n++)try{return await this.doRequest(e)}catch(o){if(t=o,!t.retryable)throw t;n<this.config.maxRetries&&await g(n);}throw t}async doRequest(e){let t;try{t=await fetch(this.ingestUrl,{method:"POST",headers:{Authorization:`Bearer ${this.config.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify(e)});}catch(n){throw new i(`Network error: ${n.message}`,"NETWORK_ERROR",void 0,true)}if(!t.ok){let n=await this.tryParseError(t);throw this.createError(t.status,n)}return await t.json()}async tryParseError(e){try{let t=await e.json();return t.message||t.error||"Unknown error"}catch{return `HTTP ${e.status}`}}createError(e,t){switch(e){case 401:return new i(`Unauthorized: ${t}`,"UNAUTHORIZED",e,false);case 400:return new i(`Validation error: ${t}`,"VALIDATION_ERROR",e,false);case 429:return new i(`Rate limited: ${t}`,"RATE_LIMITED",e,true);default:return e>=500?new i(`Server error: ${t}`,"SERVER_ERROR",e,true):new i(`HTTP error ${e}: ${t}`,"SERVER_ERROR",e,false)}}};var l=class r{config;queue;transport;parentMetadata;stopped=false;constructor(e,t,n){if(this.config=p(e),this.parentMetadata=n,this.transport=new u({endpoint:this.config.endpoint,apiKey:this.config.apiKey,maxRetries:this.config.maxRetries}),t)this.queue=t;else {let o={batchSize:this.config.batchSize,flushInterval:this.config.flushInterval,maxQueueSize:this.config.maxQueueSize,onError:this.config.onError,onFlush:this.config.onFlush};this.queue=new a(h=>this.transport.send(h),o);}}get queueSize(){return this.queue.size}log(e){if(this.stopped)return;let t={...e,timestamp:e.timestamp??new Date().toISOString(),service:e.service??this.config.service,metadata:this.mergeMetadata(e.metadata)};this.queue.add(t);}debug(e,t){this.log({level:"debug",message:e,metadata:t});}info(e,t){this.log({level:"info",message:e,metadata:t});}warn(e,t){this.log({level:"warn",message:e,metadata:t});}error(e,t){this.log({level:"error",message:e,metadata:t});}fatal(e,t){this.log({level:"fatal",message:e,metadata:t});}async flush(){return this.queue.flush()}async shutdown(){this.stopped=true,await this.queue.shutdown();}child(e){let t={...this.config,service:e.service??this.config.service},n={...this.parentMetadata,...e.metadata};return new r(t,this.queue,n)}mergeMetadata(e){if(!(!this.parentMetadata&&!e))return {...this.parentMetadata,...e}}};export{l as Logwell,i as LogwellError};//# sourceMappingURL=index.js.map
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/config.ts","../src/queue.ts","../src/transport.ts","../src/client.ts"],"names":["LogwellError","_LogwellError","message","code","statusCode","retryable","DEFAULT_CONFIG","API_KEY_REGEX","validateApiKeyFormat","apiKey","isValidUrl","url","validateConfig","config","BatchQueue","sendBatch","entry","dropped","batch","count","response","error","delay","attempt","baseDelay","ms","jitter","resolve","HttpTransport","logs","lastError","errorBody","body","status","Logwell","_Logwell","existingQueue","parentMetadata","queueConfig","fullEntry","metadata","options","childConfig","childMetadata","entryMetadata"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Error codes for Logwell SDK errors\n */\nexport type LogwellErrorCode =\n | 'NETWORK_ERROR'\n | 'UNAUTHORIZED'\n | 'VALIDATION_ERROR'\n | 'RATE_LIMITED'\n | 'SERVER_ERROR'\n | 'QUEUE_OVERFLOW'\n | 'INVALID_CONFIG';\n\n/**\n * Custom error class for Logwell SDK errors\n *\n * @example\n * ```ts\n * throw new LogwellError('Invalid API key', 'UNAUTHORIZED', 401, false);\n * ```\n */\n// V8 specific type for captureStackTrace\ndeclare global {\n interface ErrorConstructor {\n captureStackTrace?(targetObject: object, constructorOpt?: NewableFunction): void;\n }\n}\n\nexport class LogwellError extends Error {\n /**\n * Creates a new LogwellError\n *\n * @param message - Human-readable error message\n * @param code - Error code for programmatic handling\n * @param statusCode - HTTP status code if applicable\n * @param retryable - Whether the operation can be retried\n */\n constructor(\n message: string,\n public readonly code: LogwellErrorCode,\n public readonly statusCode?: number,\n public readonly retryable: boolean = false,\n ) {\n super(message);\n this.name = 'LogwellError';\n\n // Maintains proper stack trace for where our error was thrown (V8 only)\n Error.captureStackTrace?.(this, LogwellError);\n }\n}\n","import { LogwellError } from './errors';\nimport type { LogwellConfig } from './types';\n\n/**\n * Default configuration values\n */\nexport const DEFAULT_CONFIG = {\n batchSize: 50,\n flushInterval: 5000,\n maxQueueSize: 1000,\n maxRetries: 3,\n} as const;\n\n/**\n * API key format regex: lw_[32 alphanumeric chars including - and _]\n */\nexport const API_KEY_REGEX = /^lw_[A-Za-z0-9_-]{32}$/;\n\n/**\n * Validates API key format\n *\n * @param apiKey - API key to validate\n * @returns true if valid format, false otherwise\n */\nexport function validateApiKeyFormat(apiKey: string): boolean {\n if (!apiKey || typeof apiKey !== 'string') {\n return false;\n }\n return API_KEY_REGEX.test(apiKey);\n}\n\n/**\n * Validates a URL string\n *\n * @param url - URL string to validate\n * @returns true if valid URL, false otherwise\n */\nfunction isValidUrl(url: string): boolean {\n try {\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Validates configuration and returns merged config with defaults\n *\n * @param config - Partial configuration to validate\n * @returns Complete configuration with defaults applied\n * @throws LogwellError if configuration is invalid\n */\nexport function validateConfig(config: Partial<LogwellConfig>): LogwellConfig {\n // Validate required fields\n if (!config.apiKey) {\n throw new LogwellError('apiKey is required', 'INVALID_CONFIG');\n }\n\n if (!config.endpoint) {\n throw new LogwellError('endpoint is required', 'INVALID_CONFIG');\n }\n\n // Validate API key format\n if (!validateApiKeyFormat(config.apiKey)) {\n throw new LogwellError(\n 'Invalid API key format. Expected: lw_[32 characters]',\n 'INVALID_CONFIG',\n );\n }\n\n // Validate endpoint URL\n if (!isValidUrl(config.endpoint)) {\n throw new LogwellError('Invalid endpoint URL', 'INVALID_CONFIG');\n }\n\n // Validate numeric options\n if (config.batchSize !== undefined && config.batchSize <= 0) {\n throw new LogwellError('batchSize must be positive', 'INVALID_CONFIG');\n }\n\n if (config.flushInterval !== undefined && config.flushInterval <= 0) {\n throw new LogwellError('flushInterval must be positive', 'INVALID_CONFIG');\n }\n\n if (config.maxQueueSize !== undefined && config.maxQueueSize <= 0) {\n throw new LogwellError('maxQueueSize must be positive', 'INVALID_CONFIG');\n }\n\n if (config.maxRetries !== undefined && config.maxRetries < 0) {\n throw new LogwellError('maxRetries must be non-negative', 'INVALID_CONFIG');\n }\n\n // Return merged config with defaults\n return {\n apiKey: config.apiKey,\n endpoint: config.endpoint,\n service: config.service,\n batchSize: config.batchSize ?? DEFAULT_CONFIG.batchSize,\n flushInterval: config.flushInterval ?? DEFAULT_CONFIG.flushInterval,\n maxQueueSize: config.maxQueueSize ?? DEFAULT_CONFIG.maxQueueSize,\n maxRetries: config.maxRetries ?? DEFAULT_CONFIG.maxRetries,\n onError: config.onError,\n onFlush: config.onFlush,\n };\n}\n","import { LogwellError } from './errors';\nimport type { IngestResponse, LogEntry } from './types';\n\n/**\n * Callback type for sending batched logs\n */\nexport type SendBatchFn = (logs: LogEntry[]) => Promise<IngestResponse>;\n\n/**\n * Queue configuration options\n */\nexport interface QueueConfig {\n batchSize: number;\n flushInterval: number;\n maxQueueSize: number;\n onError?: (error: Error) => void;\n onFlush?: (count: number) => void;\n}\n\n/**\n * Batch queue for buffering and sending logs\n *\n * Features:\n * - Automatic flush on batch size threshold\n * - Automatic flush on time interval\n * - Queue overflow protection (drops oldest)\n * - Re-queue on send failure\n * - Graceful shutdown\n */\nexport class BatchQueue {\n private queue: LogEntry[] = [];\n private flushTimer: ReturnType<typeof setTimeout> | null = null;\n private flushing = false;\n private stopped = false;\n\n constructor(\n private sendBatch: SendBatchFn,\n private config: QueueConfig,\n ) {}\n\n /**\n * Current number of logs in the queue\n */\n get size(): number {\n return this.queue.length;\n }\n\n /**\n * Add a log entry to the queue\n *\n * Triggers flush if batch size is reached.\n * Drops oldest log if queue overflows.\n */\n add(entry: LogEntry): void {\n if (this.stopped) {\n return;\n }\n\n // Handle queue overflow\n if (this.queue.length >= this.config.maxQueueSize) {\n const dropped = this.queue.shift();\n this.config.onError?.(\n new LogwellError(\n `Queue overflow. Dropped log: ${dropped?.message.substring(0, 50)}...`,\n 'QUEUE_OVERFLOW',\n ),\n );\n }\n\n this.queue.push(entry);\n\n // Start timer on first entry\n if (!this.flushTimer && !this.stopped) {\n this.startTimer();\n }\n\n // Flush immediately if batch size reached\n if (this.queue.length >= this.config.batchSize) {\n void this.flush();\n }\n }\n\n /**\n * Flush all queued logs immediately\n *\n * @returns Response from the server, or null if queue was empty\n */\n async flush(): Promise<IngestResponse | null> {\n // Prevent concurrent flushes\n if (this.flushing || this.queue.length === 0) {\n return null;\n }\n\n this.flushing = true;\n this.stopTimer();\n\n // Take current batch\n const batch = this.queue.splice(0);\n const count = batch.length;\n\n try {\n const response = await this.sendBatch(batch);\n this.config.onFlush?.(count);\n\n // Restart timer if more logs remain (added during flush)\n if (this.queue.length > 0 && !this.stopped) {\n this.startTimer();\n }\n\n return response;\n } catch (error) {\n // Re-queue failed logs at the front\n this.queue.unshift(...batch);\n this.config.onError?.(error as Error);\n\n // Restart timer to retry\n if (!this.stopped) {\n this.startTimer();\n }\n\n return null;\n } finally {\n this.flushing = false;\n }\n }\n\n /**\n * Flush remaining logs and stop the queue\n */\n async shutdown(): Promise<void> {\n if (this.stopped) {\n return;\n }\n\n this.stopped = true;\n this.stopTimer();\n\n // Flush all remaining logs\n if (this.queue.length > 0) {\n this.flushing = false; // Reset flushing flag\n await this.flush();\n }\n }\n\n private startTimer(): void {\n this.flushTimer = setTimeout(() => {\n void this.flush();\n }, this.config.flushInterval);\n }\n\n private stopTimer(): void {\n if (this.flushTimer) {\n clearTimeout(this.flushTimer);\n this.flushTimer = null;\n }\n }\n}\n","import { LogwellError } from './errors';\nimport type { IngestResponse, LogEntry } from './types';\n\n/**\n * Transport configuration\n */\nexport interface TransportConfig {\n endpoint: string;\n apiKey: string;\n maxRetries: number;\n timeout?: number;\n}\n\n/**\n * Delay helper with exponential backoff\n */\nfunction delay(attempt: number, baseDelay = 100): Promise<void> {\n const ms = Math.min(baseDelay * 2 ** attempt, 10000);\n const jitter = Math.random() * ms * 0.3;\n return new Promise((resolve) => setTimeout(resolve, ms + jitter));\n}\n\n/**\n * Check if error is retryable based on status code\n */\nfunction isRetryableStatus(status: number): boolean {\n return status >= 500 || status === 429;\n}\n\n/**\n * HTTP transport for sending logs to Logwell server\n *\n * Features:\n * - Automatic retry with exponential backoff\n * - Error classification with retryable flag\n * - Proper error handling for all HTTP status codes\n */\nexport class HttpTransport {\n private readonly ingestUrl: string;\n\n constructor(private config: TransportConfig) {\n this.ingestUrl = `${config.endpoint}/v1/ingest`;\n }\n\n /**\n * Send logs to the Logwell server\n *\n * @param logs - Array of log entries to send\n * @returns Response with accepted/rejected counts\n * @throws LogwellError on failure after all retries\n */\n async send(logs: LogEntry[]): Promise<IngestResponse> {\n let lastError: LogwellError | null = null;\n\n for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {\n try {\n const response = await this.doRequest(logs);\n return response;\n } catch (error) {\n lastError = error as LogwellError;\n\n // Don't retry non-retryable errors\n if (!lastError.retryable) {\n throw lastError;\n }\n\n // Don't delay after the last attempt\n if (attempt < this.config.maxRetries) {\n await delay(attempt);\n }\n }\n }\n\n throw lastError!;\n }\n\n private async doRequest(logs: LogEntry[]): Promise<IngestResponse> {\n let response: Response;\n\n try {\n response = await fetch(this.ingestUrl, {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${this.config.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(logs),\n });\n } catch (error) {\n // Network error (fetch failed)\n throw new LogwellError(\n `Network error: ${(error as Error).message}`,\n 'NETWORK_ERROR',\n undefined,\n true,\n );\n }\n\n // Handle error responses\n if (!response.ok) {\n const errorBody = await this.tryParseError(response);\n throw this.createError(response.status, errorBody);\n }\n\n // Parse successful response\n return (await response.json()) as IngestResponse;\n }\n\n private async tryParseError(response: Response): Promise<string> {\n try {\n const body = await response.json();\n return body.message || body.error || 'Unknown error';\n } catch {\n return `HTTP ${response.status}`;\n }\n }\n\n private createError(status: number, message: string): LogwellError {\n switch (status) {\n case 401:\n return new LogwellError(\n `Unauthorized: ${message}`,\n 'UNAUTHORIZED',\n status,\n false,\n );\n case 400:\n return new LogwellError(\n `Validation error: ${message}`,\n 'VALIDATION_ERROR',\n status,\n false,\n );\n case 429:\n return new LogwellError(\n `Rate limited: ${message}`,\n 'RATE_LIMITED',\n status,\n true,\n );\n default:\n if (status >= 500) {\n return new LogwellError(\n `Server error: ${message}`,\n 'SERVER_ERROR',\n status,\n true,\n );\n }\n return new LogwellError(\n `HTTP error ${status}: ${message}`,\n 'SERVER_ERROR',\n status,\n false,\n );\n }\n }\n}\n","import { validateConfig } from './config';\nimport { BatchQueue, type QueueConfig } from './queue';\nimport { HttpTransport } from './transport';\nimport type { IngestResponse, LogEntry, LogwellConfig } from './types';\n\n/**\n * Child logger options\n */\nexport interface ChildLoggerOptions {\n service?: string;\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Internal resolved config with all defaults applied\n */\ninterface ResolvedConfig {\n apiKey: string;\n endpoint: string;\n service?: string;\n batchSize: number;\n flushInterval: number;\n maxQueueSize: number;\n maxRetries: number;\n onError?: (error: Error) => void;\n onFlush?: (count: number) => void;\n}\n\n/**\n * Asserts that config has all required fields after validation\n */\nfunction asResolvedConfig(config: LogwellConfig): ResolvedConfig {\n return config as ResolvedConfig;\n}\n\n/**\n * Main Logwell client class\n *\n * Provides methods for logging at different levels with automatic\n * batching, retry, and queue management.\n *\n * @example\n * ```ts\n * const logger = new Logwell({\n * apiKey: 'lw_xxx',\n * endpoint: 'https://logs.example.com',\n * service: 'my-app',\n * });\n *\n * logger.info('User logged in', { userId: '123' });\n * await logger.shutdown();\n * ```\n */\nexport class Logwell {\n private readonly config: ResolvedConfig;\n private readonly queue: BatchQueue;\n private readonly transport: HttpTransport;\n private readonly parentMetadata?: Record<string, unknown>;\n private stopped = false;\n\n constructor(config: LogwellConfig);\n constructor(config: LogwellConfig, queue: BatchQueue, parentMetadata?: Record<string, unknown>);\n constructor(\n config: LogwellConfig,\n existingQueue?: BatchQueue,\n parentMetadata?: Record<string, unknown>,\n ) {\n // Validate and apply defaults\n this.config = asResolvedConfig(validateConfig(config));\n this.parentMetadata = parentMetadata;\n\n // Create transport\n this.transport = new HttpTransport({\n endpoint: this.config.endpoint,\n apiKey: this.config.apiKey,\n maxRetries: this.config.maxRetries,\n });\n\n // Use existing queue (for child loggers) or create new one\n if (existingQueue) {\n this.queue = existingQueue;\n } else {\n const queueConfig: QueueConfig = {\n batchSize: this.config.batchSize,\n flushInterval: this.config.flushInterval,\n maxQueueSize: this.config.maxQueueSize,\n onError: this.config.onError,\n onFlush: this.config.onFlush,\n };\n this.queue = new BatchQueue((logs) => this.transport.send(logs), queueConfig);\n }\n }\n\n /**\n * Current number of logs waiting in the queue\n */\n get queueSize(): number {\n return this.queue.size;\n }\n\n /**\n * Log a message at the specified level\n */\n log(entry: LogEntry): void {\n if (this.stopped) return;\n\n const fullEntry: LogEntry = {\n ...entry,\n timestamp: entry.timestamp ?? new Date().toISOString(),\n service: entry.service ?? this.config.service,\n metadata: this.mergeMetadata(entry.metadata),\n };\n\n this.queue.add(fullEntry);\n }\n\n /**\n * Log a debug message\n */\n debug(message: string, metadata?: Record<string, unknown>): void {\n this.log({ level: 'debug', message, metadata });\n }\n\n /**\n * Log an info message\n */\n info(message: string, metadata?: Record<string, unknown>): void {\n this.log({ level: 'info', message, metadata });\n }\n\n /**\n * Log a warning message\n */\n warn(message: string, metadata?: Record<string, unknown>): void {\n this.log({ level: 'warn', message, metadata });\n }\n\n /**\n * Log an error message\n */\n error(message: string, metadata?: Record<string, unknown>): void {\n this.log({ level: 'error', message, metadata });\n }\n\n /**\n * Log a fatal error message\n */\n fatal(message: string, metadata?: Record<string, unknown>): void {\n this.log({ level: 'fatal', message, metadata });\n }\n\n /**\n * Flush all queued logs immediately\n *\n * @returns Response from the server, or null if queue was empty\n */\n async flush(): Promise<IngestResponse | null> {\n return this.queue.flush();\n }\n\n /**\n * Flush remaining logs and stop the client\n *\n * Call this before process exit to ensure all logs are sent.\n */\n async shutdown(): Promise<void> {\n this.stopped = true;\n await this.queue.shutdown();\n }\n\n /**\n * Create a child logger with additional context\n *\n * Child loggers share the same queue as the parent,\n * but can have their own service name and default metadata.\n *\n * @example\n * ```ts\n * const requestLogger = logger.child({\n * metadata: { requestId: req.id },\n * });\n * requestLogger.info('Request received');\n * ```\n */\n child(options: ChildLoggerOptions): Logwell {\n const childConfig: LogwellConfig = {\n ...this.config,\n service: options.service ?? this.config.service,\n };\n\n const childMetadata = {\n ...this.parentMetadata,\n ...options.metadata,\n };\n\n return new Logwell(childConfig, this.queue, childMetadata);\n }\n\n private mergeMetadata(\n entryMetadata?: Record<string, unknown>,\n ): Record<string, unknown> | undefined {\n if (!this.parentMetadata && !entryMetadata) {\n return undefined;\n }\n return {\n ...this.parentMetadata,\n ...entryMetadata,\n };\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "logwell",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official TypeScript SDK for Logwell logging platform",
|
|
5
|
+
"keywords": ["logging", "logs", "observability", "logwell", "typescript"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Logwell",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/divkix/logwell",
|
|
11
|
+
"directory": "sdks/typescript"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/divkix/logwell/tree/main/sdks/typescript#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/divkix/logwell/issues"
|
|
16
|
+
},
|
|
17
|
+
"type": "module",
|
|
18
|
+
"main": "./dist/index.cjs",
|
|
19
|
+
"module": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"import": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"require": {
|
|
28
|
+
"types": "./dist/index.d.cts",
|
|
29
|
+
"default": "./dist/index.cjs"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"./package.json": "./package.json"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"sideEffects": false,
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18.0.0"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsup",
|
|
45
|
+
"dev": "tsup --watch",
|
|
46
|
+
"check": "tsc --noEmit",
|
|
47
|
+
"lint": "biome check .",
|
|
48
|
+
"lint:fix": "biome check --write .",
|
|
49
|
+
"test": "vitest run",
|
|
50
|
+
"test:unit": "vitest run tests/unit/",
|
|
51
|
+
"test:integration": "vitest run tests/integration/",
|
|
52
|
+
"test:coverage": "vitest run --coverage",
|
|
53
|
+
"test:watch": "vitest --watch",
|
|
54
|
+
"prepublishOnly": "bun run build",
|
|
55
|
+
"size": "size-limit",
|
|
56
|
+
"attw": "attw --pack ."
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@arethetypeswrong/cli": "^0.17.0",
|
|
60
|
+
"@biomejs/biome": "2.3.11",
|
|
61
|
+
"@size-limit/preset-small-lib": "^11.0.0",
|
|
62
|
+
"@vitest/coverage-v8": "^4.0.0",
|
|
63
|
+
"msw": "^2.7.0",
|
|
64
|
+
"size-limit": "^11.0.0",
|
|
65
|
+
"tsup": "^8.4.0",
|
|
66
|
+
"typescript": "^5.9.3",
|
|
67
|
+
"vitest": "^4.0.16"
|
|
68
|
+
},
|
|
69
|
+
"size-limit": [
|
|
70
|
+
{
|
|
71
|
+
"path": "./dist/index.js",
|
|
72
|
+
"limit": "10 KB"
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|