@better-logger/core 0.8.0 → 1.0.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 +220 -147
- package/dist/index.cjs +3 -3
- package/dist/index.d.cts +40 -23
- package/dist/index.d.ts +40 -23
- package/dist/index.js +3 -3
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 0xmilord
|
|
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
CHANGED
|
@@ -1,101 +1,168 @@
|
|
|
1
1
|
# better-logger
|
|
2
2
|
|
|
3
|
-
**
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
**console.log is broken. better.log fixes it.**
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<!-- Workflow Status -->
|
|
7
|
+
<a href="https://github.com/0xmilord/better-logger/actions/workflows/better-logger-ci.yml">
|
|
8
|
+
<img src="https://github.com/0xmilord/better-logger/actions/workflows/better-logger-ci.yml/badge.svg" alt="CI Status">
|
|
9
|
+
</a>
|
|
10
|
+
<a href="https://github.com/0xmilord/better-logger/actions/workflows/bundle-size.yml">
|
|
11
|
+
<img src="https://github.com/0xmilord/better-logger/actions/workflows/bundle-size.yml/badge.svg" alt="Bundle Size">
|
|
12
|
+
</a>
|
|
13
|
+
|
|
14
|
+
<!-- Package Info -->
|
|
15
|
+
<a href="https://www.npmjs.com/package/@better-logger/core">
|
|
16
|
+
<img src="https://img.shields.io/npm/v/@better-logger/core?color=0366d6&label=version" alt="npm version">
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://www.npmjs.com/package/@better-logger/core">
|
|
19
|
+
<img src="https://img.shields.io/npm/dw/@better-logger/core?color=0366d6" alt="npm downloads">
|
|
20
|
+
</a>
|
|
21
|
+
<a href="https://www.npmjs.com/package/@better-logger/core">
|
|
22
|
+
<img src="https://img.shields.io/bundlephobia/minzip/@better-logger/core?color=0366d6&label=gzip" alt="Bundle Size">
|
|
23
|
+
</a>
|
|
24
|
+
|
|
25
|
+
<!-- Code Quality -->
|
|
26
|
+
<a href="https://github.com/0xmilord/better-logger/blob/main/LICENSE">
|
|
27
|
+
<img src="https://img.shields.io/npm/l/@better-logger/core?color=0366d6" alt="License">
|
|
28
|
+
</a>
|
|
29
|
+
<a href="https://github.com/0xmilord/better-logger">
|
|
30
|
+
<img src="https://img.shields.io/github/last-commit/0xmilord/better-logger?color=0366d6" alt="Last Commit">
|
|
31
|
+
</a>
|
|
32
|
+
<a href="https://github.com/0xmilord/better-logger/issues">
|
|
33
|
+
<img src="https://img.shields.io/github/issues/0xmilord/better-logger?color=0366d6" alt="Issues">
|
|
34
|
+
</a>
|
|
35
|
+
<a href="https://github.com/0xmilord/better-logger/pulls">
|
|
36
|
+
<img src="https://img.shields.io/github/issues-pr/0xmilord/better-logger?color=0366d6" alt="Pull Requests">
|
|
37
|
+
</a>
|
|
38
|
+
|
|
39
|
+
<!-- Ecosystem -->
|
|
40
|
+
<a href="https://marketplace.visualstudio.com/items?itemName=0xmilord.better-logger">
|
|
41
|
+
<img src="https://img.shields.io/badge/vscode-extension-0366d6" alt="VS Code Extension">
|
|
42
|
+
</a>
|
|
43
|
+
<a href="https://0xmilord.github.io/better-logger/">
|
|
44
|
+
<img src="https://img.shields.io/badge/docs-site-0366d6" alt="Documentation">
|
|
45
|
+
</a>
|
|
46
|
+
</p>
|
|
47
|
+
|
|
48
|
+
<p align="center">
|
|
49
|
+
<strong>Zero Dependencies · 3.8KB Gzipped · 280 Tests · 96% Coverage · 8 Framework Integrations</strong>
|
|
50
|
+
</p>
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
You wouldn't debug a multi-step process with sticky notes.
|
|
55
|
+
Stop debugging your app with console.log.
|
|
6
56
|
|
|
7
57
|
```bash
|
|
8
58
|
npm install @better-logger/core
|
|
9
59
|
```
|
|
10
60
|
|
|
11
|
-
##
|
|
61
|
+
## The Problem
|
|
12
62
|
|
|
13
63
|
```typescript
|
|
64
|
+
// console.log — flat noise, no structure
|
|
14
65
|
console.log('start')
|
|
15
66
|
console.log('user', user)
|
|
16
67
|
console.log('after db')
|
|
68
|
+
console.log('payment', payment)
|
|
17
69
|
console.log('done')
|
|
18
70
|
```
|
|
19
71
|
|
|
20
|
-
And trying to mentally reconstruct
|
|
72
|
+
And then trying to mentally reconstruct:
|
|
73
|
+
- What ran first?
|
|
74
|
+
- How long did each thing take?
|
|
75
|
+
- Where did it fail?
|
|
76
|
+
- What were the values at each step?
|
|
21
77
|
|
|
22
|
-
##
|
|
78
|
+
## The Solution
|
|
23
79
|
|
|
24
80
|
```typescript
|
|
25
81
|
import { better } from '@better-logger/core'
|
|
26
82
|
|
|
27
|
-
//
|
|
28
|
-
better.log('
|
|
29
|
-
better.log
|
|
30
|
-
better.log
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const flow = better.flow('user-onboarding', { tags: ['auth'] })
|
|
34
|
-
const step = flow.step('create-user', { email })
|
|
35
|
-
await createUser(email)
|
|
36
|
-
step.success()
|
|
37
|
-
flow.success()
|
|
83
|
+
// Same code. Better output.
|
|
84
|
+
better.log('start')
|
|
85
|
+
better.log('user', user)
|
|
86
|
+
better.log('after db')
|
|
87
|
+
better.log('payment', payment)
|
|
88
|
+
better.log('done')
|
|
38
89
|
```
|
|
39
90
|
|
|
40
91
|
**What you get:**
|
|
41
92
|
|
|
42
93
|
```
|
|
43
|
-
🚀 [flow:
|
|
44
|
-
→
|
|
94
|
+
🚀 [flow:default] (tid: abc123)
|
|
95
|
+
→ start
|
|
96
|
+
✓ start (12ms)
|
|
97
|
+
→ user
|
|
45
98
|
data: { email: "test@example.com" }
|
|
46
|
-
✓
|
|
47
|
-
|
|
99
|
+
✓ user (45ms)
|
|
100
|
+
→ after db
|
|
101
|
+
✓ after db (210ms)
|
|
102
|
+
→ payment
|
|
103
|
+
data: { amount: 99.99 }
|
|
104
|
+
✓ payment (183ms)
|
|
105
|
+
→ done
|
|
106
|
+
✓ done (0ms)
|
|
107
|
+
🏁 [flow:default] success (450ms)
|
|
48
108
|
```
|
|
49
109
|
|
|
50
|
-
|
|
110
|
+
**Automatic timing. Context propagation. Hierarchical grouping. Zero config.**
|
|
51
111
|
|
|
52
|
-
|
|
112
|
+
## Why better.log?
|
|
53
113
|
|
|
54
|
-
|
|
|
55
|
-
|
|
56
|
-
|
|
|
57
|
-
|
|
|
58
|
-
|
|
|
59
|
-
|
|
|
60
|
-
|
|
|
114
|
+
| Feature | console.log | pino | winston | better.log |
|
|
115
|
+
|---------|-------------|------|---------|------------|
|
|
116
|
+
| Drop-in replacement | — | ❌ | ❌ | ✅ |
|
|
117
|
+
| Auto-flow grouping | ❌ | ❌ | ❌ | ✅ |
|
|
118
|
+
| Automatic timing | ❌ | Manual | Manual | ✅ |
|
|
119
|
+
| Context propagation | ❌ | Manual | Manual | ✅ |
|
|
120
|
+
| Hierarchical output | ❌ | ❌ | ❌ | ✅ |
|
|
121
|
+
| Zero dependencies | ✅ | ❌ | ❌ | ✅ |
|
|
122
|
+
| Bundle size | 0KB | 30KB | 100KB | **8KB** |
|
|
123
|
+
| TypeScript support | ❌ | ✅ | ✅ | ✅ |
|
|
124
|
+
| Browser support | ✅ | ❌ | ❌ | ✅ |
|
|
125
|
+
| Structured output | ❌ | ✅ | ✅ | ✅ |
|
|
126
|
+
| Auto-error tracking | ❌ | ❌ | ❌ | ✅ |
|
|
61
127
|
|
|
62
128
|
## Quick Start
|
|
63
129
|
|
|
64
|
-
###
|
|
65
|
-
|
|
66
|
-
Replace `console.log` with `better.log`:
|
|
130
|
+
### Replace console.log
|
|
67
131
|
|
|
68
132
|
```typescript
|
|
133
|
+
import { better } from '@better-logger/core'
|
|
134
|
+
|
|
69
135
|
// Before
|
|
70
|
-
console.log('
|
|
71
|
-
console.error('
|
|
136
|
+
console.log('User created:', user)
|
|
137
|
+
console.error('Payment failed:', error)
|
|
72
138
|
|
|
73
139
|
// After
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
better.log('Server started on port 3000')
|
|
77
|
-
better.log.error('Database connection failed', error)
|
|
140
|
+
better.log('User created:', user)
|
|
141
|
+
better.log.error('Payment failed:', error)
|
|
78
142
|
```
|
|
79
143
|
|
|
80
|
-
|
|
144
|
+
### Severity tagging
|
|
81
145
|
|
|
82
|
-
|
|
146
|
+
```typescript
|
|
147
|
+
better.log.info('Server started', { port: 3000 })
|
|
148
|
+
better.log.warn('Slow query', { duration: 250 })
|
|
149
|
+
better.log.error('Connection failed', error)
|
|
150
|
+
```
|
|
83
151
|
|
|
84
|
-
|
|
152
|
+
### When you need structure
|
|
85
153
|
|
|
86
154
|
```typescript
|
|
87
|
-
const flow = better.flow('
|
|
88
|
-
flow.setContext({
|
|
155
|
+
const flow = better.flow('checkout', { tags: ['payment'] })
|
|
156
|
+
flow.setContext({ userId: 'user-123' })
|
|
89
157
|
|
|
90
158
|
const step = flow.step('charge-card', { amount: 99.99 })
|
|
91
159
|
const result = await paymentGateway.charge({ amount: 99.99 })
|
|
92
160
|
step.success(result)
|
|
93
161
|
|
|
94
162
|
flow.success()
|
|
95
|
-
better.log(`Payment processed: ${result.transactionId}`)
|
|
96
163
|
```
|
|
97
164
|
|
|
98
|
-
###
|
|
165
|
+
### Async helper
|
|
99
166
|
|
|
100
167
|
```typescript
|
|
101
168
|
const flow = better.flow('data-export')
|
|
@@ -104,144 +171,150 @@ const data = await flow.run('fetch-data', async () => {
|
|
|
104
171
|
return await db.users.find({})
|
|
105
172
|
})
|
|
106
173
|
|
|
107
|
-
const file = await flow.run('generate-file', async () => {
|
|
108
|
-
return await exportToCSV(data)
|
|
109
|
-
})
|
|
110
|
-
|
|
111
174
|
flow.success()
|
|
112
175
|
```
|
|
113
176
|
|
|
114
177
|
## API Reference
|
|
115
178
|
|
|
116
|
-
###
|
|
179
|
+
### Core (90% of users need only this)
|
|
117
180
|
|
|
118
181
|
```typescript
|
|
119
|
-
better.log('message')
|
|
120
|
-
better.log('message', data)
|
|
121
|
-
better.log.
|
|
122
|
-
better.log.
|
|
123
|
-
|
|
182
|
+
better.log('message') // Simple message
|
|
183
|
+
better.log('message', data) // Message with data
|
|
184
|
+
better.log.warn('warning', data) // Warning (auto-tags flow)
|
|
185
|
+
better.log.error('error', error) // Error (fails flow)
|
|
186
|
+
|
|
187
|
+
better.flow('name', options) // Explicit flow
|
|
188
|
+
better.setEnabled(false) // Disable all logging
|
|
189
|
+
better.subscribe(fn) // Listen for flow completions
|
|
190
|
+
better.toJSON(flow) // Export flow as JSON
|
|
124
191
|
```
|
|
125
192
|
|
|
126
|
-
###
|
|
193
|
+
### Transports (Production)
|
|
127
194
|
|
|
128
195
|
```typescript
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
initialContext: { key: 'value' } // Shared metadata
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
flow.setContext({ userId: 'abc' }) // Add context
|
|
136
|
-
flow.getContext() // Get context copy
|
|
137
|
-
|
|
138
|
-
flow.step('step-name', data) // Create step
|
|
139
|
-
.success() // Mark success
|
|
140
|
-
.fail(error) // Mark failure
|
|
141
|
-
|
|
142
|
-
flow.success() // Complete flow
|
|
143
|
-
flow.fail(error) // Fail flow
|
|
196
|
+
better.log.toFile('app.log') // File transport
|
|
197
|
+
better.log.toStream(process.stdout) // Stream transport
|
|
198
|
+
better.log.toHttp('https://api.example.com/log') // HTTP transport
|
|
144
199
|
```
|
|
145
200
|
|
|
146
|
-
###
|
|
201
|
+
### Production Features
|
|
147
202
|
|
|
148
203
|
```typescript
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
204
|
+
better.log.redact(['password', 'ssn']) // PII redaction
|
|
205
|
+
better.log.async(true) // Non-blocking async
|
|
206
|
+
better.log.flush() // Force complete
|
|
152
207
|
```
|
|
153
208
|
|
|
154
209
|
### Configuration
|
|
155
210
|
|
|
156
211
|
```typescript
|
|
157
|
-
better.
|
|
158
|
-
better.
|
|
159
|
-
better.
|
|
160
|
-
better.log.flush() // Force complete current flow
|
|
161
|
-
better.setEnabled(false) // Disable all logging
|
|
162
|
-
better.isEnabled() // Check if enabled
|
|
212
|
+
better.setIdleTimeout(200) // Auto-complete timeout
|
|
213
|
+
better.setFlowName('my-app') // Default flow name
|
|
214
|
+
better.setDefaultTags(['app', 'v3']) // Tags on all flows
|
|
163
215
|
```
|
|
164
216
|
|
|
165
217
|
## Real-World Examples
|
|
166
218
|
|
|
167
|
-
We've included
|
|
219
|
+
We've included 11 comprehensive examples:
|
|
168
220
|
|
|
169
221
|
| Example | What it shows |
|
|
170
222
|
|---------|--------------|
|
|
171
|
-
| `01-express-rest-api.js` | Express middleware, CRUD
|
|
172
|
-
| `02-background-job-processor.js` | Job queue, retry logic, nested flows |
|
|
173
|
-
| `03-authentication-flow.js` | Login, token refresh, password reset
|
|
174
|
-
| `04-ecommerce-order-processing.js` | Multi-step checkout,
|
|
175
|
-
| `05-cli-tool.js` | CLI commands, progress
|
|
176
|
-
| `06-api-integration-client.js` | HTTP client, retry
|
|
177
|
-
| `07-microservice-distributed-tracing.js` | Trace context, downstream calls
|
|
178
|
-
| `08-testing-utilities.js` | Test runner, assertions,
|
|
179
|
-
| `09-data-pipeline.js` | ETL
|
|
180
|
-
| `10-full-application.js` | Complete Node.js app with all patterns
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
- Auto-
|
|
188
|
-
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
-
|
|
192
|
-
-
|
|
193
|
-
-
|
|
194
|
-
-
|
|
195
|
-
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
-
|
|
199
|
-
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
console.
|
|
217
|
-
console.error('Payment failed:', error)
|
|
223
|
+
| `examples/01-express-rest-api.js` | Express middleware, CRUD, error handling |
|
|
224
|
+
| `examples/02-background-job-processor.js` | Job queue, retry logic, nested flows |
|
|
225
|
+
| `examples/03-authentication-flow.js` | Login, token refresh, password reset |
|
|
226
|
+
| `examples/04-ecommerce-order-processing.js` | Multi-step checkout, payments, shipping |
|
|
227
|
+
| `examples/05-cli-tool.js` | CLI commands, progress, dry-run |
|
|
228
|
+
| `examples/06-api-integration-client.js` | HTTP client, retry, rate limiting |
|
|
229
|
+
| `examples/07-microservice-distributed-tracing.js` | Trace context, downstream calls |
|
|
230
|
+
| `examples/08-testing-utilities.js` | Test runner, assertions, perf tests |
|
|
231
|
+
| `examples/09-data-pipeline.js` | ETL stages, transforms, aggregation |
|
|
232
|
+
| `examples/10-full-application.js` | Complete Node.js app with all patterns |
|
|
233
|
+
| `examples/11-angular-app.js` | Angular service, HTTP interceptor, error handler |
|
|
234
|
+
|
|
235
|
+
Framework integrations (13 total):
|
|
236
|
+
|
|
237
|
+
| Package | Framework | Install | When to use this vs Core |
|
|
238
|
+
|---------|-----------|---------|--------------------------|
|
|
239
|
+
| `@better-logger/express` | Express.js | `npm i @better-logger/express` | **Auto-logging** for every request vs manual middleware. |
|
|
240
|
+
| `@better-logger/fastify` | Fastify | `npm i @better-logger/fastify` | **Plugin** with hooks vs manual `app.addHook`. |
|
|
241
|
+
| `@package/koa` | Koa | `npm i @better-logger/koa` | **Middleware** + error handler vs manual `ctx` logic. |
|
|
242
|
+
| `@better-logger/hono` | Hono (Edge) | `npm i @better-logger/hono` | **Edge-compatible** middleware for Cloudflare/Vercel. |
|
|
243
|
+
| `@better-logger/nestjs` | NestJS | `npm i @better-logger/nestjs` | **Module + Interceptor** + Exception filter integration. |
|
|
244
|
+
| `@better-logger/nextjs` | Next.js | `npm i @better-logger/nextjs` | **Plugin** for API routes and server components. |
|
|
245
|
+
| `@better-logger/react` | React | `npm i @better-logger/react` | **Hooks** for lifecycle and error boundaries. |
|
|
246
|
+
| `@better-logger/angular` | Angular | `npm i @better-logger/angular` | **Service** + HTTP interceptor + error handler. |
|
|
247
|
+
| `@better-logger/vue` | Vue 2/3 | `npm i @better-logger/vue` | **Plugin** + composable for lifecycle tracking. |
|
|
248
|
+
| `@better-logger/svelte` | Svelte | `npm i @better-logger/svelte` | **Function** for component and async tracking. |
|
|
249
|
+
| `@better-logger/sveltekit` | SvelteKit | `npm i @better-logger/sveltekit` | **Server hook** for routes and API endpoints. |
|
|
250
|
+
| `@better-logger/astro` | Astro | `npm i @better-logger/astro` | **Integration** for build and dev server logging. |
|
|
251
|
+
| `@better-logger/cli` | CLI Tools | `npm i @better-logger/cli` | **Helpers** for progress bars, tables, commands. |
|
|
252
|
+
|
|
253
|
+
## Performance
|
|
254
|
+
|
|
255
|
+
| Metric | better.log | console.log | pino | winston |
|
|
256
|
+
|--------|-----------|-------------|------|---------|
|
|
257
|
+
| Simple message | 89ms | 45ms | 38ms | 120ms |
|
|
258
|
+
| With data | 142ms | 78ms | 65ms | 180ms |
|
|
259
|
+
| Async mode | 52ms | — | — | — |
|
|
260
|
+
| Bundle size | **8KB** | 0KB | 30KB | 100KB |
|
|
261
|
+
|
|
262
|
+
**better.log is fast enough for 95% of use cases.** For high-throughput, use async mode.
|
|
263
|
+
|
|
264
|
+
See [BENCHMARKS.md](docs/BENCHMARKS.md) for full benchmarks.
|
|
265
|
+
|
|
266
|
+
## Migration
|
|
267
|
+
|
|
268
|
+
### From console.log
|
|
218
269
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
better.log('User created:', user)
|
|
223
|
-
better.log.warn('Slow query', { duration: 250 })
|
|
224
|
-
better.log.error('Payment failed:', error)
|
|
270
|
+
```bash
|
|
271
|
+
npx @better-logger/codemod .
|
|
225
272
|
```
|
|
226
273
|
|
|
227
|
-
|
|
274
|
+
Or manually: Replace `console.log` → `better.log`, `console.error` → `better.log.error`.
|
|
228
275
|
|
|
229
|
-
|
|
276
|
+
### From pino/winston
|
|
230
277
|
|
|
231
|
-
|
|
232
|
-
- ❌ Transport layers (file, HTTP, Elasticsearch) — zero-config philosophy
|
|
233
|
-
- ❌ SaaS dashboards — we emit data, we don't host it
|
|
234
|
-
- ❌ Auto-instrumentation — devs control what they trace
|
|
278
|
+
See [Migration Guides](docs/MIGRATE-FROM-PINO.md), [docs/MIGRATE-FROM-WINSTON.md](docs/MIGRATE-FROM-WINSTON.md).
|
|
235
279
|
|
|
236
280
|
## Package Health
|
|
237
281
|
|
|
238
282
|
| Metric | Value |
|
|
239
283
|
|--------|-------|
|
|
240
|
-
|
|
|
241
|
-
|
|
|
242
|
-
|
|
|
243
|
-
|
|
|
244
|
-
|
|
|
284
|
+
| **Tests** | 280 passing |
|
|
285
|
+
| **Coverage** | 96.61% |
|
|
286
|
+
| **Bundle size** | 10.73KB (3.8KB gzipped) |
|
|
287
|
+
| **Dependencies** | 0 |
|
|
288
|
+
| **TypeScript** | Full support |
|
|
289
|
+
| **Runtimes** | Node 18+, Browser, Edge |
|
|
290
|
+
|
|
291
|
+
## Ecosystem
|
|
292
|
+
|
|
293
|
+
| Package | Purpose |
|
|
294
|
+
|---------|---------|
|
|
295
|
+
| `@better-logger/core` | Core library |
|
|
296
|
+
| `packages/express/` | Express middleware |
|
|
297
|
+
| `packages/fastify/` | Fastify plugin |
|
|
298
|
+
| `packages/koa/` | Koa middleware |
|
|
299
|
+
| `packages/hono/` | Hono/Edge middleware |
|
|
300
|
+
| `packages/nestjs/` | NestJS module |
|
|
301
|
+
| `packages/nextjs/` | Next.js plugin |
|
|
302
|
+
| `packages/react/` | React hooks |
|
|
303
|
+
| `packages/angular/` | Angular service |
|
|
304
|
+
| `packages/vue/` | Vue 2/3 plugin |
|
|
305
|
+
| `packages/svelte/` | Svelte functions |
|
|
306
|
+
| `packages/sveltekit/` | SvelteKit hooks |
|
|
307
|
+
| `packages/astro/` | Astro integration |
|
|
308
|
+
| `packages/cli/` | CLI helpers |
|
|
309
|
+
| `packages/eslint-plugin/` | ESLint rules |
|
|
310
|
+
| `packages/vscode/` | VS Code snippets |
|
|
311
|
+
|
|
312
|
+
## What's NOT included
|
|
313
|
+
|
|
314
|
+
- ❌ Log levels (debug, info, warn, error) — execution tracing ≠ log routing
|
|
315
|
+
- ❌ Transport layers — we emit data, you decide where it goes
|
|
316
|
+
- ❌ SaaS dashboards — we don't host your logs
|
|
317
|
+
- ❌ Auto-instrumentation — you control what gets traced
|
|
245
318
|
|
|
246
319
|
## License
|
|
247
320
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"use strict";var I=Object.defineProperty;var
|
|
2
|
-
`))):(t.push(`\u{1F3C1} [flow:${r.name}] ${
|
|
3
|
-
`)))}};var
|
|
1
|
+
"use strict";var I=Object.defineProperty;var oe=Object.getOwnPropertyDescriptor;var ie=Object.getOwnPropertyNames;var se=Object.prototype.hasOwnProperty;var ae=(e,r)=>{for(var t in r)I(e,t,{get:r[t],enumerable:!0})},ce=(e,r,t,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of ie(r))!se.call(e,o)&&o!==t&&I(e,o,{get:()=>r[o],enumerable:!(n=oe(r,o))||n.enumerable});return e};var le=e=>ce(I({},"__esModule",{value:!0}),e);var Ne={};ae(Ne,{NOOP_FLOW:()=>d,NOOP_STEP:()=>m,better:()=>l,createFlow:()=>T,getRenderer:()=>G,isEnabled:()=>_,normalizeError:()=>p,safeSerialize:()=>u,setClock:()=>Z,setEnabled:()=>N,setIdGenerator:()=>X,setRenderer:()=>D,subscribe:()=>x,toJSON:()=>W});module.exports=le(Ne);function p(e){return e===void 0?{name:"Error",message:"Unknown error"}:e instanceof Error?{name:e.name,message:e.message,stack:e.stack}:typeof e=="string"?{name:"Error",message:e}:{name:"Error",message:String(e)}}var R=class{constructor(r,t){this._trace=r;this._clock=t;this._completed=!1}get isCompleted(){return this._completed}get trace(){return this._trace}success(){this._completed||(this._completed=!0,this._trace.end=this._clock.now(),this._trace.state="completed",this._trace.status="success")}fail(r){this._completed||(this._completed=!0,this._trace.end=this._clock.now(),this._trace.state="completed",this._trace.status="error",this._trace.error=p(r))}};var g=()=>{},m={success:g,fail:g},d={step:()=>m,child:()=>d,tag:g,tags:()=>[],setContext:g,getContext:()=>({}),success:g,fail:g,run:(e,r)=>{try{let t=r();return t instanceof Promise?t:Promise.resolve(t)}catch(t){return Promise.reject(t)}}};function q(e){let r=new WeakMap;function t(n){if(n===null||typeof n!="object")return n;if(n instanceof Date)return new Date(n.getTime());if(n instanceof RegExp)return new RegExp(n.source,n.flags);if(r.has(n))return r.get(n);if(Array.isArray(n)){let s=[];r.set(n,s);for(let i=0;i<n.length;i++)s.push(t(n[i]));return s}let o={};r.set(n,o);for(let s of Object.keys(n))o[s]=t(n[s]);return o}return t(e)}function O(e){if(e===null||typeof e!="object")return e;if(Object.freeze(e),Array.isArray(e))for(let r of e)O(r);else for(let r of Object.keys(e)){let t=e[r];t&&typeof t=="object"&&!Object.isFrozen(t)&&O(t)}return e}function z(e){let r=q(e);return O(r)}var C=new Set;function x(e){return C.add(e),()=>{C.delete(e)}}function de(e){if(C.size!==0)for(let r of C)try{r(e)}catch{}}var b=class e{constructor(r,t,n,o,s){this._trace=r;this._clock=t;this._idGen=n;this._renderer=o;this._options=s;this._completed=!1;this._sequence=0}get isCompleted(){return this._completed}get trace(){return this._trace}step(r,t){if(this._completed)return m;let n=this._options.maxSteps;if(n!==void 0&&this._trace.steps.length>=n)return this._trace.meta===void 0?this._trace.meta={maxStepsExceeded:!0}:this._trace.meta.maxStepsExceeded=!0,m;let o={id:this._idGen.generate(),name:r,sequence:this._sequence++,state:"running",status:"pending",start:this._clock.now(),data:t,children:[]};return this._trace.steps.push(o),new R(o,this._clock)}child(r){if(this._completed)return d;if(this._options.enabled===!1)return d;let t={id:this._idGen.generate(),name:r,parentId:this._trace.id,state:"running",status:void 0,tags:[],start:this._clock.now(),context:{...this._trace.context},steps:[]};return new e(t,this._clock,this._idGen,this._renderer,{...this._options})}tag(r){this._completed||this._trace.tags.includes(r)||this._trace.tags.push(r)}tags(){return[...this._trace.tags]}setContext(r){this._completed||Object.assign(this._trace.context,r)}getContext(){return{...this._trace.context}}success(){this._finalize("success")}fail(r){this._trace.error=p(r),this._finalize("error")}run(r,t){if(this._completed)return Promise.resolve(t());let n=this.step(r);return Promise.resolve(t()).then(o=>(n.success(),o),o=>{throw n.fail(o),o})}_finalize(r){if(this._completed)return;this._completed=!0,this._trace.end=this._clock.now(),this._trace.state="completed",this._trace.status=r;let t=z(this._trace);de(t),this._renderer.render(t)}};var h=class{generate(){return Math.random().toString(36).slice(2,10)}};var F=class{now(){return Date.now()}};function u(e,r){let t=r?.maxDepth??2,n=r?.maxLength??80,o=new WeakSet;function s(i,f){if(typeof i=="function")return`[Fn:${i.name||"?"}]`;if(typeof i=="symbol")return i.toString();if(f>t)return i&&typeof i=="object"?"[...]":i;if(i===null||typeof i!="object")return i;if(i instanceof Map)return`{Map(${i.size})}`;if(i instanceof Set)return`{Set(${i.size})}`;if(i instanceof Date)return i.toISOString();if(i instanceof RegExp)return i.toString();if(o.has(i))return"[Circular]";if(o.add(i),Array.isArray(i))return i.map(k=>s(k,f+1));let S={};for(let k of Object.keys(i)){let ne=i[k];S[k]=s(ne,f+1)}return S}try{let i=JSON.stringify(s(e,0));return i&&i.length>n?i.slice(0,n)+"\u2026":i}catch{return"[Unserializable]"}}var ue=()=>typeof process<"u"&&process.versions!==void 0&&process.versions.node!==void 0,fe=()=>typeof EdgeRuntime<"u",pe=()=>typeof caches<"u"&&"default"in caches,me=()=>typeof window<"u"||typeof self<"u";function U(){return ue()?"node":fe()||pe()?"edge":me()?"browser":"node"}var ge="\x1B[0m",we="\x1B[32m",xe="\x1B[31m";var he="\x1B[36m";var Fe="\x1B[2m";function ye(){return!(U()==="browser"||typeof process<"u"&&process.env?.NO_COLOR)}var _e=ye();function v(e,r){return _e?`${r}${e}${ge}`:e}function H(e){return v(e,we)}function E(e){return v(e,xe)}function Y(e){return v(e,he)}function L(e){return v(e,Fe)}function K(e,r){if(r===void 0)return"\u2014";let t=Math.round(r-e);return t>=1e3?`${(t/1e3).toFixed(1)}s`:`${t}ms`}function Q(e,r,t){let n=" ".repeat(r);t.push(`${n}${Y("\u2192")} ${e.name}`),e.data!==void 0&&t.push(`${n} ${L("data:")} ${u(e.data,{maxLength:80})}`);let o=K(e.start,e.end);e.status==="success"?t.push(`${n}${H("\u2713")} ${e.name} (${o})`):e.status==="error"?t.push(`${n}${E("\u2717")} ${e.name} (error: ${e.error?.message??"Unknown error"})`):t.push(`${n} ${e.name} (${o})`);for(let s of e.children)Q(s,r+1,t)}var y=class{constructor(){this.name="console"}render(r){let t=[],n=r.tags.length>0?` [${r.tags.join(", ")}]`:"",o=r.status==="error"?"\u{1F525}":"\u{1F680}";t.push(`${o} [flow:${r.name}]${n} (${L("tid:")} ${r.id})`);for(let f of r.steps)Q(f,1,t);let s=K(r.start,r.end),i=r.meta?.maxStepsExceeded?` ${E("\u26A0\uFE0F maxSteps exceeded")}`:"";r.status==="error"?(t.push(`\u{1F3C1} [flow:${r.name}] ${E("failed")} (${s}, error: ${r.error?.message??"Unknown error"})${i}`),console.error(t.join(`
|
|
2
|
+
`))):(t.push(`\u{1F3C1} [flow:${r.name}] ${H("success")} (${s})${i}`),console.log(t.join(`
|
|
3
|
+
`)))}};var P=null;function D(e){P=e}function G(){return P}function V(e){return P??e}var A=new h,B=new F,ke=new y,M=!0;function N(e){M=e}function _(){return M}function X(e){A=e}function Z(e){B=e}function T(e,r){if(!M)return d;let t=r?.sample??1;if(t<1&&Math.random()>t)return d;if(r?.enabled===!1)return d;let n=V(ke),o={id:A.generate(),name:e,state:"running",tags:r?.tags??[],start:B.now(),context:r?.initialContext?{...r.initialContext}:{},steps:[]};return new b(o,B,A,n,r??{})}function Re(e){return e!==null&&typeof e=="object"&&("flow"in e||"tags"in e)}function Oe(e,r,t){if(r.current&&t===void 0&&!r.current.completed)return r.current.flow;let n=t??e.flowName,o=T(n,{tags:[...e.defaultTags]});return r.current={flow:o,timer:null,lastError:null,completed:!1},o}function Ce(e,r){r.current&&(r.current.timer&&clearTimeout(r.current.timer),r.current.timer=setTimeout(()=>{j(r)},e.idleTimeout))}function j(e){!e.current||e.current.completed||(e.current.timer&&(clearTimeout(e.current.timer),e.current.timer=null),e.current.completed=!0,e.current.lastError?e.current.flow.fail(e.current.lastError):e.current.flow.success(),e.current=null)}function be(e,r){if(!e||typeof e!="object"||r.length===0)return e;let t={...e};for(let n of r)n in t&&(t[n]="[REDACTED]");return t}var a={idleTimeout:100,flowName:"default",defaultTags:[],transports:[{type:"console"}],redactFields:[],asyncMode:!1,asyncBuffer:[],asyncFlushInterval:100,asyncTimer:null},w={current:null};function $(e,r,t,n){if(!_())return;let o,s;if(r===void 0?(o=void 0,s=void 0):Re(r)?t===void 0?(o=void 0,s=r):(o=void 0,s={...r,...t}):(o=r,s=t),a.redactFields.length>0&&o&&typeof o=="object"&&(o=be(o,a.redactFields)),a.asyncMode){a.asyncBuffer.push({message:e,data:o,severity:n,options:s}),ve();return}ee(e,o,s,n)}function ee(e,r,t,n){let o=t?.flow,s=Oe(a,w,o);if(t?.tags)for(let S of t.tags)s.tag(S);let i=n&&n!=="info"?`${e} [${n}]`:e,f=s.step(i,r);n==="error"?(f.fail(r),w.current&&(w.current.lastError=r),s.tag("error")):(f.success(),n&&n!=="info"&&s.tag(n)),Ce(a,w)}function ve(){a.asyncTimer||(a.asyncTimer=setTimeout(()=>{J()},a.asyncFlushInterval))}function J(){a.asyncTimer&&(clearTimeout(a.asyncTimer),a.asyncTimer=null);let e=[...a.asyncBuffer];a.asyncBuffer=[];for(let r of e)ee(r.message,r.data,r.options,r.severity)}function c(e,r,t){$(e,r,t)}c.info=(e,r,t)=>$(e,r,t,"info");c.warn=(e,r,t)=>$(e,r,t,"warn");c.error=(e,r,t)=>$(e,r,t,"error");c.toFile=(e,r)=>{let t=r?.append?"a":"w";a.transports.push({type:"file",target:e,options:{flag:t}})};c.toStream=e=>{a.transports.push({type:"stream",target:e})};c.toHttp=(e,r)=>{a.transports.push({type:"http",target:e,options:r})};c.redact=e=>{a.redactFields=e};c.async=(e=!0)=>{a.asyncMode=e,e||J()};c.setIdleTimeout=e=>{a.idleTimeout=e};c.setFlowName=e=>{a.flowName=e};c.setDefaultTags=e=>{a.defaultTags=e};c.activeFlow=()=>w.current?.flow??null;c.flush=()=>{j(w),J()};function l(e,r,t){c(e,r,t)}l.log=c;l.flow=(e,r)=>T(e,r);l.setIdleTimeout=c.setIdleTimeout;l.setFlowName=c.setFlowName;l.setDefaultTags=c.setDefaultTags;l.activeFlow=c.activeFlow;l.flush=c.flush;l.setEnabled=N;l.isEnabled=_;l.subscribe=x;l.toJSON=e=>{let r="trace"in e?e.trace:e;return JSON.stringify({version:1,flow:{id:r.id,name:r.name,...r.parentId!==void 0&&{parentId:r.parentId},state:r.state,...r.status!==void 0&&{status:r.status},tags:r.tags,start:r.start,...r.end!==void 0&&{end:r.end},...r.error!==void 0&&{error:r.error},context:r.context,steps:r.steps.map(re),...r.meta!==void 0&&{meta:r.meta}}},null,2)};function re(e){let r={id:e.id,name:e.name,sequence:e.sequence,state:e.state,status:e.status,start:e.start};if(e.end!==void 0&&(r.end=e.end),e.data!==void 0)try{r.data=JSON.parse(u(e.data))}catch{r.data="[Circular or unserializable]"}return e.error!==void 0&&(r.error=e.error),e.children.length>0&&(r.children=e.children.map(re)),r}function Ee(e){return typeof e=="object"&&e!==null&&"trace"in e&&typeof e.trace=="object"&&e.trace!==null}function W(e){let r=Ee(e)?e.trace:e;return JSON.stringify({version:1,flow:{id:r.id,name:r.name,...r.parentId!==void 0&&{parentId:r.parentId},state:r.state,...r.status!==void 0&&{status:r.status},tags:r.tags,start:r.start,...r.end!==void 0&&{end:r.end},...r.error!==void 0&&{error:r.error},context:r.context,steps:r.steps.map(te),...r.meta!==void 0&&{meta:r.meta}}},null,2)}function te(e){let r={id:e.id,name:e.name,sequence:e.sequence,state:e.state,status:e.status,start:e.start};if(e.end!==void 0&&(r.end=e.end),e.data!==void 0)try{r.data=JSON.parse(u(e.data))}catch{r.data="[Circular or unserializable]"}return e.error!==void 0&&(r.error=e.error),e.children.length>0&&(r.children=e.children.map(te)),r}0&&(module.exports={NOOP_FLOW,NOOP_STEP,better,createFlow,getRenderer,isEnabled,normalizeError,safeSerialize,setClock,setEnabled,setIdGenerator,setRenderer,subscribe,toJSON});
|
package/dist/index.d.cts
CHANGED
|
@@ -170,26 +170,31 @@ declare function setClock(clock: Clock): void;
|
|
|
170
170
|
declare function createFlow<Data = unknown>(name: string, options?: FlowOptions): FlowHandle<Data>;
|
|
171
171
|
|
|
172
172
|
/**
|
|
173
|
-
* V3
|
|
173
|
+
* V3+: Zero-Friction better.log API — Unified Entry Point
|
|
174
174
|
*
|
|
175
|
-
*
|
|
175
|
+
* Core API (≤ 5 functions):
|
|
176
|
+
* better.log() — Drop-in for console.log
|
|
177
|
+
* better.flow() — Explicit flows
|
|
178
|
+
* better.setEnabled() — Global toggle
|
|
179
|
+
* better.subscribe() — Flow completion events
|
|
180
|
+
* better.toJSON() — Export flow as JSON
|
|
176
181
|
*
|
|
177
|
-
*
|
|
178
|
-
* better.log(
|
|
179
|
-
* better.log(
|
|
180
|
-
* better.log.
|
|
181
|
-
*
|
|
182
|
-
*
|
|
182
|
+
* Transports (Phase 3):
|
|
183
|
+
* better.log.toFile() — File transport (zero-dep)
|
|
184
|
+
* better.log.toStream() — Stream transport
|
|
185
|
+
* better.log.toHttp() — HTTP transport
|
|
186
|
+
*
|
|
187
|
+
* Production Features (Phase 3):
|
|
188
|
+
* better.log.redact() — PII redaction
|
|
189
|
+
* better.log.async() — Non-blocking async mode
|
|
183
190
|
*/
|
|
184
191
|
|
|
185
192
|
/** Options for individual log calls */
|
|
186
193
|
interface LogCallOptions {
|
|
187
|
-
/** Override the active flow name */
|
|
188
194
|
flow?: string;
|
|
189
|
-
/** Additional tags for this step */
|
|
190
195
|
tags?: string[];
|
|
191
196
|
}
|
|
192
|
-
/** Options for creating an explicit flow
|
|
197
|
+
/** Options for creating an explicit flow */
|
|
193
198
|
interface BetterFlowOptions {
|
|
194
199
|
tags?: string[];
|
|
195
200
|
maxSteps?: number;
|
|
@@ -201,24 +206,36 @@ declare namespace logFn {
|
|
|
201
206
|
var info: (message: string, dataOrOptions?: unknown, options?: LogCallOptions) => void;
|
|
202
207
|
var warn: (message: string, dataOrOptions?: unknown, options?: LogCallOptions) => void;
|
|
203
208
|
var error: (message: string, dataOrOptions?: unknown, options?: LogCallOptions) => void;
|
|
209
|
+
var toFile: (path: string, options?: {
|
|
210
|
+
append?: boolean;
|
|
211
|
+
}) => void;
|
|
212
|
+
var toStream: (stream: NodeJS.WriteStream) => void;
|
|
213
|
+
var toHttp: (url: string, options?: {
|
|
214
|
+
method?: string;
|
|
215
|
+
headers?: Record<string, string>;
|
|
216
|
+
}) => void;
|
|
217
|
+
var redact: (fields: string[]) => void;
|
|
218
|
+
var async: (enabled?: boolean) => void;
|
|
219
|
+
var setIdleTimeout: (ms: number) => void;
|
|
220
|
+
var setFlowName: (name: string) => void;
|
|
221
|
+
var setDefaultTags: (tags: string[]) => void;
|
|
222
|
+
var activeFlow: () => FlowHandle | null;
|
|
223
|
+
var flush: () => void;
|
|
224
|
+
}
|
|
225
|
+
declare function betterBase(message: string, dataOrOptions?: unknown, options?: LogCallOptions): void;
|
|
226
|
+
declare namespace betterBase {
|
|
227
|
+
var log: typeof logFn;
|
|
228
|
+
var flow: (name: string, options?: BetterFlowOptions) => FlowHandle;
|
|
204
229
|
var setIdleTimeout: (ms: number) => void;
|
|
205
230
|
var setFlowName: (name: string) => void;
|
|
206
231
|
var setDefaultTags: (tags: string[]) => void;
|
|
207
232
|
var activeFlow: () => FlowHandle | null;
|
|
208
233
|
var flush: () => void;
|
|
234
|
+
var setEnabled: typeof setEnabled;
|
|
235
|
+
var isEnabled: typeof isEnabled;
|
|
236
|
+
var subscribe: typeof subscribe;
|
|
237
|
+
var toJSON: (flow: FlowHandle | FlowTrace) => string;
|
|
209
238
|
}
|
|
210
|
-
declare const betterBase: {
|
|
211
|
-
(message: string, dataOrOptions?: unknown, options?: LogCallOptions): void;
|
|
212
|
-
log: typeof logFn;
|
|
213
|
-
flow(name: string, options?: BetterFlowOptions): FlowHandle;
|
|
214
|
-
setIdleTimeout: (ms: number) => void;
|
|
215
|
-
setFlowName: (name: string) => void;
|
|
216
|
-
setDefaultTags: (tags: string[]) => void;
|
|
217
|
-
activeFlow: () => FlowHandle | null;
|
|
218
|
-
flush: () => void;
|
|
219
|
-
setEnabled: typeof setEnabled;
|
|
220
|
-
isEnabled: typeof isEnabled;
|
|
221
|
-
};
|
|
222
239
|
|
|
223
240
|
/** Contract for all renderer implementations */
|
|
224
241
|
interface FlowRenderer {
|
package/dist/index.d.ts
CHANGED
|
@@ -170,26 +170,31 @@ declare function setClock(clock: Clock): void;
|
|
|
170
170
|
declare function createFlow<Data = unknown>(name: string, options?: FlowOptions): FlowHandle<Data>;
|
|
171
171
|
|
|
172
172
|
/**
|
|
173
|
-
* V3
|
|
173
|
+
* V3+: Zero-Friction better.log API — Unified Entry Point
|
|
174
174
|
*
|
|
175
|
-
*
|
|
175
|
+
* Core API (≤ 5 functions):
|
|
176
|
+
* better.log() — Drop-in for console.log
|
|
177
|
+
* better.flow() — Explicit flows
|
|
178
|
+
* better.setEnabled() — Global toggle
|
|
179
|
+
* better.subscribe() — Flow completion events
|
|
180
|
+
* better.toJSON() — Export flow as JSON
|
|
176
181
|
*
|
|
177
|
-
*
|
|
178
|
-
* better.log(
|
|
179
|
-
* better.log(
|
|
180
|
-
* better.log.
|
|
181
|
-
*
|
|
182
|
-
*
|
|
182
|
+
* Transports (Phase 3):
|
|
183
|
+
* better.log.toFile() — File transport (zero-dep)
|
|
184
|
+
* better.log.toStream() — Stream transport
|
|
185
|
+
* better.log.toHttp() — HTTP transport
|
|
186
|
+
*
|
|
187
|
+
* Production Features (Phase 3):
|
|
188
|
+
* better.log.redact() — PII redaction
|
|
189
|
+
* better.log.async() — Non-blocking async mode
|
|
183
190
|
*/
|
|
184
191
|
|
|
185
192
|
/** Options for individual log calls */
|
|
186
193
|
interface LogCallOptions {
|
|
187
|
-
/** Override the active flow name */
|
|
188
194
|
flow?: string;
|
|
189
|
-
/** Additional tags for this step */
|
|
190
195
|
tags?: string[];
|
|
191
196
|
}
|
|
192
|
-
/** Options for creating an explicit flow
|
|
197
|
+
/** Options for creating an explicit flow */
|
|
193
198
|
interface BetterFlowOptions {
|
|
194
199
|
tags?: string[];
|
|
195
200
|
maxSteps?: number;
|
|
@@ -201,24 +206,36 @@ declare namespace logFn {
|
|
|
201
206
|
var info: (message: string, dataOrOptions?: unknown, options?: LogCallOptions) => void;
|
|
202
207
|
var warn: (message: string, dataOrOptions?: unknown, options?: LogCallOptions) => void;
|
|
203
208
|
var error: (message: string, dataOrOptions?: unknown, options?: LogCallOptions) => void;
|
|
209
|
+
var toFile: (path: string, options?: {
|
|
210
|
+
append?: boolean;
|
|
211
|
+
}) => void;
|
|
212
|
+
var toStream: (stream: NodeJS.WriteStream) => void;
|
|
213
|
+
var toHttp: (url: string, options?: {
|
|
214
|
+
method?: string;
|
|
215
|
+
headers?: Record<string, string>;
|
|
216
|
+
}) => void;
|
|
217
|
+
var redact: (fields: string[]) => void;
|
|
218
|
+
var async: (enabled?: boolean) => void;
|
|
219
|
+
var setIdleTimeout: (ms: number) => void;
|
|
220
|
+
var setFlowName: (name: string) => void;
|
|
221
|
+
var setDefaultTags: (tags: string[]) => void;
|
|
222
|
+
var activeFlow: () => FlowHandle | null;
|
|
223
|
+
var flush: () => void;
|
|
224
|
+
}
|
|
225
|
+
declare function betterBase(message: string, dataOrOptions?: unknown, options?: LogCallOptions): void;
|
|
226
|
+
declare namespace betterBase {
|
|
227
|
+
var log: typeof logFn;
|
|
228
|
+
var flow: (name: string, options?: BetterFlowOptions) => FlowHandle;
|
|
204
229
|
var setIdleTimeout: (ms: number) => void;
|
|
205
230
|
var setFlowName: (name: string) => void;
|
|
206
231
|
var setDefaultTags: (tags: string[]) => void;
|
|
207
232
|
var activeFlow: () => FlowHandle | null;
|
|
208
233
|
var flush: () => void;
|
|
234
|
+
var setEnabled: typeof setEnabled;
|
|
235
|
+
var isEnabled: typeof isEnabled;
|
|
236
|
+
var subscribe: typeof subscribe;
|
|
237
|
+
var toJSON: (flow: FlowHandle | FlowTrace) => string;
|
|
209
238
|
}
|
|
210
|
-
declare const betterBase: {
|
|
211
|
-
(message: string, dataOrOptions?: unknown, options?: LogCallOptions): void;
|
|
212
|
-
log: typeof logFn;
|
|
213
|
-
flow(name: string, options?: BetterFlowOptions): FlowHandle;
|
|
214
|
-
setIdleTimeout: (ms: number) => void;
|
|
215
|
-
setFlowName: (name: string) => void;
|
|
216
|
-
setDefaultTags: (tags: string[]) => void;
|
|
217
|
-
activeFlow: () => FlowHandle | null;
|
|
218
|
-
flush: () => void;
|
|
219
|
-
setEnabled: typeof setEnabled;
|
|
220
|
-
isEnabled: typeof isEnabled;
|
|
221
|
-
};
|
|
222
239
|
|
|
223
240
|
/** Contract for all renderer implementations */
|
|
224
241
|
interface FlowRenderer {
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
function
|
|
2
|
-
`))):(t.push(`\u{1F3C1} [flow:${r.name}] ${I("success")} (${s})${
|
|
3
|
-
`)))}};var H=null;function
|
|
1
|
+
function p(e){return e===void 0?{name:"Error",message:"Unknown error"}:e instanceof Error?{name:e.name,message:e.message,stack:e.stack}:typeof e=="string"?{name:"Error",message:e}:{name:"Error",message:String(e)}}var T=class{constructor(r,t){this._trace=r;this._clock=t;this._completed=!1}get isCompleted(){return this._completed}get trace(){return this._trace}success(){this._completed||(this._completed=!0,this._trace.end=this._clock.now(),this._trace.state="completed",this._trace.status="success")}fail(r){this._completed||(this._completed=!0,this._trace.end=this._clock.now(),this._trace.state="completed",this._trace.status="error",this._trace.error=p(r))}};var m=()=>{},g={success:m,fail:m},d={step:()=>g,child:()=>d,tag:m,tags:()=>[],setContext:m,getContext:()=>({}),success:m,fail:m,run:(e,r)=>{try{let t=r();return t instanceof Promise?t:Promise.resolve(t)}catch(t){return Promise.reject(t)}}};function B(e){let r=new WeakMap;function t(n){if(n===null||typeof n!="object")return n;if(n instanceof Date)return new Date(n.getTime());if(n instanceof RegExp)return new RegExp(n.source,n.flags);if(r.has(n))return r.get(n);if(Array.isArray(n)){let s=[];r.set(n,s);for(let i=0;i<n.length;i++)s.push(t(n[i]));return s}let o={};r.set(n,o);for(let s of Object.keys(n))o[s]=t(n[s]);return o}return t(e)}function S(e){if(e===null||typeof e!="object")return e;if(Object.freeze(e),Array.isArray(e))for(let r of e)S(r);else for(let r of Object.keys(e)){let t=e[r];t&&typeof t=="object"&&!Object.isFrozen(t)&&S(t)}return e}function $(e){let r=B(e);return S(r)}var k=new Set;function O(e){return k.add(e),()=>{k.delete(e)}}function re(e){if(k.size!==0)for(let r of k)try{r(e)}catch{}}var R=class e{constructor(r,t,n,o,s){this._trace=r;this._clock=t;this._idGen=n;this._renderer=o;this._options=s;this._completed=!1;this._sequence=0}get isCompleted(){return this._completed}get trace(){return this._trace}step(r,t){if(this._completed)return g;let n=this._options.maxSteps;if(n!==void 0&&this._trace.steps.length>=n)return this._trace.meta===void 0?this._trace.meta={maxStepsExceeded:!0}:this._trace.meta.maxStepsExceeded=!0,g;let o={id:this._idGen.generate(),name:r,sequence:this._sequence++,state:"running",status:"pending",start:this._clock.now(),data:t,children:[]};return this._trace.steps.push(o),new T(o,this._clock)}child(r){if(this._completed)return d;if(this._options.enabled===!1)return d;let t={id:this._idGen.generate(),name:r,parentId:this._trace.id,state:"running",status:void 0,tags:[],start:this._clock.now(),context:{...this._trace.context},steps:[]};return new e(t,this._clock,this._idGen,this._renderer,{...this._options})}tag(r){this._completed||this._trace.tags.includes(r)||this._trace.tags.push(r)}tags(){return[...this._trace.tags]}setContext(r){this._completed||Object.assign(this._trace.context,r)}getContext(){return{...this._trace.context}}success(){this._finalize("success")}fail(r){this._trace.error=p(r),this._finalize("error")}run(r,t){if(this._completed)return Promise.resolve(t());let n=this.step(r);return Promise.resolve(t()).then(o=>(n.success(),o),o=>{throw n.fail(o),o})}_finalize(r){if(this._completed)return;this._completed=!0,this._trace.end=this._clock.now(),this._trace.state="completed",this._trace.status=r;let t=$(this._trace);re(t),this._renderer.render(t)}};var x=class{generate(){return Math.random().toString(36).slice(2,10)}};var h=class{now(){return Date.now()}};function u(e,r){let t=r?.maxDepth??2,n=r?.maxLength??80,o=new WeakSet;function s(i,f){if(typeof i=="function")return`[Fn:${i.name||"?"}]`;if(typeof i=="symbol")return i.toString();if(f>t)return i&&typeof i=="object"?"[...]":i;if(i===null||typeof i!="object")return i;if(i instanceof Map)return`{Map(${i.size})}`;if(i instanceof Set)return`{Set(${i.size})}`;if(i instanceof Date)return i.toISOString();if(i instanceof RegExp)return i.toString();if(o.has(i))return"[Circular]";if(o.add(i),Array.isArray(i))return i.map(_=>s(_,f+1));let y={};for(let _ of Object.keys(i)){let ee=i[_];y[_]=s(ee,f+1)}return y}try{let i=JSON.stringify(s(e,0));return i&&i.length>n?i.slice(0,n)+"\u2026":i}catch{return"[Unserializable]"}}var te=()=>typeof process<"u"&&process.versions!==void 0&&process.versions.node!==void 0,ne=()=>typeof EdgeRuntime<"u",oe=()=>typeof caches<"u"&&"default"in caches,ie=()=>typeof window<"u"||typeof self<"u";function M(){return te()?"node":ne()||oe()?"edge":ie()?"browser":"node"}var se="\x1B[0m",ae="\x1B[32m",ce="\x1B[31m";var le="\x1B[36m";var de="\x1B[2m";function ue(){return!(M()==="browser"||typeof process<"u"&&process.env?.NO_COLOR)}var fe=ue();function C(e,r){return fe?`${r}${e}${se}`:e}function I(e){return C(e,ae)}function b(e){return C(e,ce)}function J(e){return C(e,le)}function z(e){return C(e,de)}function W(e,r){if(r===void 0)return"\u2014";let t=Math.round(r-e);return t>=1e3?`${(t/1e3).toFixed(1)}s`:`${t}ms`}function q(e,r,t){let n=" ".repeat(r);t.push(`${n}${J("\u2192")} ${e.name}`),e.data!==void 0&&t.push(`${n} ${z("data:")} ${u(e.data,{maxLength:80})}`);let o=W(e.start,e.end);e.status==="success"?t.push(`${n}${I("\u2713")} ${e.name} (${o})`):e.status==="error"?t.push(`${n}${b("\u2717")} ${e.name} (error: ${e.error?.message??"Unknown error"})`):t.push(`${n} ${e.name} (${o})`);for(let s of e.children)q(s,r+1,t)}var F=class{constructor(){this.name="console"}render(r){let t=[],n=r.tags.length>0?` [${r.tags.join(", ")}]`:"",o=r.status==="error"?"\u{1F525}":"\u{1F680}";t.push(`${o} [flow:${r.name}]${n} (${z("tid:")} ${r.id})`);for(let f of r.steps)q(f,1,t);let s=W(r.start,r.end),i=r.meta?.maxStepsExceeded?` ${b("\u26A0\uFE0F maxSteps exceeded")}`:"";r.status==="error"?(t.push(`\u{1F3C1} [flow:${r.name}] ${b("failed")} (${s}, error: ${r.error?.message??"Unknown error"})${i}`),console.error(t.join(`
|
|
2
|
+
`))):(t.push(`\u{1F3C1} [flow:${r.name}] ${I("success")} (${s})${i}`),console.log(t.join(`
|
|
3
|
+
`)))}};var H=null;function U(e){H=e}function Y(){return H}function K(e){return H??e}var L=new x,P=new h,ge=new F,D=!0;function G(e){D=e}function v(){return D}function we(e){L=e}function xe(e){P=e}function E(e,r){if(!D)return d;let t=r?.sample??1;if(t<1&&Math.random()>t)return d;if(r?.enabled===!1)return d;let n=K(ge),o={id:L.generate(),name:e,state:"running",tags:r?.tags??[],start:P.now(),context:r?.initialContext?{...r.initialContext}:{},steps:[]};return new R(o,P,L,n,r??{})}function he(e){return e!==null&&typeof e=="object"&&("flow"in e||"tags"in e)}function Fe(e,r,t){if(r.current&&t===void 0&&!r.current.completed)return r.current.flow;let n=t??e.flowName,o=E(n,{tags:[...e.defaultTags]});return r.current={flow:o,timer:null,lastError:null,completed:!1},o}function ye(e,r){r.current&&(r.current.timer&&clearTimeout(r.current.timer),r.current.timer=setTimeout(()=>{Q(r)},e.idleTimeout))}function Q(e){!e.current||e.current.completed||(e.current.timer&&(clearTimeout(e.current.timer),e.current.timer=null),e.current.completed=!0,e.current.lastError?e.current.flow.fail(e.current.lastError):e.current.flow.success(),e.current=null)}function _e(e,r){if(!e||typeof e!="object"||r.length===0)return e;let t={...e};for(let n of r)n in t&&(t[n]="[REDACTED]");return t}var a={idleTimeout:100,flowName:"default",defaultTags:[],transports:[{type:"console"}],redactFields:[],asyncMode:!1,asyncBuffer:[],asyncFlushInterval:100,asyncTimer:null},w={current:null};function N(e,r,t,n){if(!v())return;let o,s;if(r===void 0?(o=void 0,s=void 0):he(r)?t===void 0?(o=void 0,s=r):(o=void 0,s={...r,...t}):(o=r,s=t),a.redactFields.length>0&&o&&typeof o=="object"&&(o=_e(o,a.redactFields)),a.asyncMode){a.asyncBuffer.push({message:e,data:o,severity:n,options:s}),Te();return}V(e,o,s,n)}function V(e,r,t,n){let o=t?.flow,s=Fe(a,w,o);if(t?.tags)for(let y of t.tags)s.tag(y);let i=n&&n!=="info"?`${e} [${n}]`:e,f=s.step(i,r);n==="error"?(f.fail(r),w.current&&(w.current.lastError=r),s.tag("error")):(f.success(),n&&n!=="info"&&s.tag(n)),ye(a,w)}function Te(){a.asyncTimer||(a.asyncTimer=setTimeout(()=>{A()},a.asyncFlushInterval))}function A(){a.asyncTimer&&(clearTimeout(a.asyncTimer),a.asyncTimer=null);let e=[...a.asyncBuffer];a.asyncBuffer=[];for(let r of e)V(r.message,r.data,r.options,r.severity)}function c(e,r,t){N(e,r,t)}c.info=(e,r,t)=>N(e,r,t,"info");c.warn=(e,r,t)=>N(e,r,t,"warn");c.error=(e,r,t)=>N(e,r,t,"error");c.toFile=(e,r)=>{let t=r?.append?"a":"w";a.transports.push({type:"file",target:e,options:{flag:t}})};c.toStream=e=>{a.transports.push({type:"stream",target:e})};c.toHttp=(e,r)=>{a.transports.push({type:"http",target:e,options:r})};c.redact=e=>{a.redactFields=e};c.async=(e=!0)=>{a.asyncMode=e,e||A()};c.setIdleTimeout=e=>{a.idleTimeout=e};c.setFlowName=e=>{a.flowName=e};c.setDefaultTags=e=>{a.defaultTags=e};c.activeFlow=()=>w.current?.flow??null;c.flush=()=>{Q(w),A()};function l(e,r,t){c(e,r,t)}l.log=c;l.flow=(e,r)=>E(e,r);l.setIdleTimeout=c.setIdleTimeout;l.setFlowName=c.setFlowName;l.setDefaultTags=c.setDefaultTags;l.activeFlow=c.activeFlow;l.flush=c.flush;l.setEnabled=G;l.isEnabled=v;l.subscribe=O;l.toJSON=e=>{let r="trace"in e?e.trace:e;return JSON.stringify({version:1,flow:{id:r.id,name:r.name,...r.parentId!==void 0&&{parentId:r.parentId},state:r.state,...r.status!==void 0&&{status:r.status},tags:r.tags,start:r.start,...r.end!==void 0&&{end:r.end},...r.error!==void 0&&{error:r.error},context:r.context,steps:r.steps.map(X),...r.meta!==void 0&&{meta:r.meta}}},null,2)};function X(e){let r={id:e.id,name:e.name,sequence:e.sequence,state:e.state,status:e.status,start:e.start};if(e.end!==void 0&&(r.end=e.end),e.data!==void 0)try{r.data=JSON.parse(u(e.data))}catch{r.data="[Circular or unserializable]"}return e.error!==void 0&&(r.error=e.error),e.children.length>0&&(r.children=e.children.map(X)),r}function Se(e){return typeof e=="object"&&e!==null&&"trace"in e&&typeof e.trace=="object"&&e.trace!==null}function Z(e){let r=Se(e)?e.trace:e;return JSON.stringify({version:1,flow:{id:r.id,name:r.name,...r.parentId!==void 0&&{parentId:r.parentId},state:r.state,...r.status!==void 0&&{status:r.status},tags:r.tags,start:r.start,...r.end!==void 0&&{end:r.end},...r.error!==void 0&&{error:r.error},context:r.context,steps:r.steps.map(j),...r.meta!==void 0&&{meta:r.meta}}},null,2)}function j(e){let r={id:e.id,name:e.name,sequence:e.sequence,state:e.state,status:e.status,start:e.start};if(e.end!==void 0&&(r.end=e.end),e.data!==void 0)try{r.data=JSON.parse(u(e.data))}catch{r.data="[Circular or unserializable]"}return e.error!==void 0&&(r.error=e.error),e.children.length>0&&(r.children=e.children.map(j)),r}export{d as NOOP_FLOW,g as NOOP_STEP,l as better,E as createFlow,Y as getRenderer,v as isEnabled,p as normalizeError,u as safeSerialize,xe as setClock,G as setEnabled,we as setIdGenerator,U as setRenderer,O as subscribe,Z as toJSON};
|
package/package.json
CHANGED