@duckbug/js 0.1.3 → 1.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/README.md +365 -73
- package/dist/cjs/DuckBug/DuckBugHelper.cjs +136 -0
- package/dist/cjs/DuckBug/DuckBugProvider.cjs +82 -13
- package/dist/cjs/DuckBug/DuckBugService.cjs +158 -17
- package/dist/cjs/DuckBug/Pond.cjs +44 -0
- package/dist/cjs/DuckBug/ensureEventId.cjs +47 -0
- package/dist/cjs/DuckBug/finalizeIngestEvent.cjs +54 -0
- package/dist/cjs/DuckBug/index.cjs +18 -2
- package/dist/cjs/DuckBug/parseDuckBugDsn.cjs +82 -0
- package/dist/cjs/DuckBug/sanitizeIngestPayload.cjs +67 -0
- package/dist/cjs/DuckBug/stripIngestSections.cjs +43 -0
- package/dist/cjs/DuckBug/transportTypes.cjs +18 -0
- package/dist/cjs/SDK/DuckSDK.cjs +129 -12
- package/dist/cjs/SDK/index.cjs +5 -2
- package/dist/cjs/contract/index.cjs +18 -0
- package/dist/cjs/contract/ingestEvents.cjs +18 -0
- package/dist/cjs/index.cjs +24 -6
- package/dist/cjs/integrations/node.cjs +54 -0
- package/dist/cjs/sdkIdentity.cjs +47 -0
- package/dist/esm/DuckBug/DuckBugHelper.js +99 -0
- package/dist/esm/DuckBug/DuckBugProvider.js +82 -13
- package/dist/esm/DuckBug/DuckBugService.js +156 -15
- package/dist/esm/DuckBug/Pond.js +10 -0
- package/dist/esm/DuckBug/ensureEventId.js +13 -0
- package/dist/esm/DuckBug/finalizeIngestEvent.js +20 -0
- package/dist/esm/DuckBug/index.js +5 -1
- package/dist/esm/DuckBug/parseDuckBugDsn.js +36 -0
- package/dist/esm/DuckBug/sanitizeIngestPayload.js +33 -0
- package/dist/esm/DuckBug/stripIngestSections.js +9 -0
- package/dist/esm/DuckBug/transportTypes.js +0 -0
- package/dist/esm/SDK/DuckSDK.js +125 -11
- package/dist/esm/SDK/index.js +2 -2
- package/dist/esm/contract/index.js +0 -0
- package/dist/esm/contract/ingestEvents.js +0 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/integrations/node.js +20 -0
- package/dist/esm/sdkIdentity.js +7 -0
- package/dist/types/DuckBug/DuckBugConfig.d.ts +28 -0
- package/dist/types/DuckBug/DuckBugHelper.d.ts +22 -0
- package/dist/types/DuckBug/DuckBugProvider.d.ts +12 -1
- package/dist/types/DuckBug/DuckBugService.d.ts +30 -8
- package/dist/types/DuckBug/Log.d.ts +1 -7
- package/dist/types/DuckBug/Pond.d.ts +9 -0
- package/dist/types/DuckBug/ensureEventId.d.ts +4 -0
- package/dist/types/DuckBug/finalizeIngestEvent.d.ts +11 -0
- package/dist/types/DuckBug/index.d.ts +7 -1
- package/dist/types/DuckBug/parseDuckBugDsn.d.ts +17 -0
- package/dist/types/DuckBug/sanitizeIngestPayload.d.ts +4 -0
- package/dist/types/DuckBug/stripIngestSections.d.ts +4 -0
- package/dist/types/DuckBug/transportTypes.d.ts +7 -0
- package/dist/types/SDK/DuckSDK.d.ts +30 -3
- package/dist/types/SDK/Provider.d.ts +11 -3
- package/dist/types/SDK/index.d.ts +3 -2
- package/dist/types/contract/index.d.ts +1 -0
- package/dist/types/contract/ingestEvents.d.ts +58 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/integrations/node.d.ts +12 -0
- package/dist/types/sdkIdentity.d.ts +7 -0
- package/package.json +23 -7
- package/schemas/error-event.schema.json +147 -0
- package/schemas/log-event.schema.json +126 -0
package/README.md
CHANGED
|
@@ -31,76 +31,251 @@ pnpm add @duckbug/js
|
|
|
31
31
|
|
|
32
32
|
### Basic Usage
|
|
33
33
|
|
|
34
|
+
DSN must follow the ingest URL shape from the DuckBug SDK spec (`duckbug-sdk-spec`): `{origin}/ingest/{projectId}:{publicKey}` or `{origin}/api/ingest/{projectId}:{publicKey}` (for example on `duckbug.io`).
|
|
35
|
+
|
|
34
36
|
```typescript
|
|
35
|
-
import {
|
|
37
|
+
import { Duck, DuckBugProvider, Pond } from '@duckbug/js';
|
|
38
|
+
|
|
39
|
+
const dsn = 'https://api.duckbug.io/ingest/your-project-id:your-public-key';
|
|
40
|
+
const { extraSensitiveKeys } = Pond.ripple(['custom_secret']);
|
|
41
|
+
|
|
42
|
+
const duck = new Duck(
|
|
43
|
+
[new DuckBugProvider({ dsn, extraSensitiveKeys })],
|
|
44
|
+
{
|
|
45
|
+
logReports: {
|
|
46
|
+
log: false,
|
|
47
|
+
warn: true,
|
|
48
|
+
error: true,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Branded + idiomatic error capture
|
|
54
|
+
const err = new Error('Something failed');
|
|
55
|
+
duck.quack('checkout_failed', err);
|
|
56
|
+
duck.captureException(err, 'checkout_failed');
|
|
57
|
+
|
|
58
|
+
// Logging (levels normalized to DEBUG, INFO, WARN, ERROR, FATAL)
|
|
59
|
+
duck.warn('Slow query', { ms: 1200 });
|
|
60
|
+
```
|
|
36
61
|
|
|
37
|
-
|
|
38
|
-
const providers = [
|
|
39
|
-
new DuckBugProvider({
|
|
40
|
-
dsn: 'your-duckbug-dsn-here'
|
|
41
|
-
})
|
|
42
|
-
];
|
|
62
|
+
`DuckSDK` remains available as an alias of the same runtime; prefer `Duck` for new code.
|
|
43
63
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
64
|
+
Before process exit, await `duck.flush()` so queued HTTP work finishes (for example at the end of an `async main()`).
|
|
65
|
+
|
|
66
|
+
### Reserved `_duck` on log payloads
|
|
67
|
+
|
|
68
|
+
The second argument to `log` / `debug` / `warn` / `error` / `fatal` may be a plain object with a reserved **`_duck`** field for technical metadata. **`_duck` is not sent inside `context`** on the wire.
|
|
69
|
+
|
|
70
|
+
- **`_duck.dTags`** — sets top-level `dTags` on the ingest event (same as in a raw JSON body).
|
|
71
|
+
- **`_duck.scope`** — one-shot `Partial<IngestSharedMetadata>` merged into **this** log only (after global `setScope`, before fixed fields). Use e.g. `platform: "ios"` to override the default `"node"`. Optional **`_duck.scope.context`** is used as the event `context` only when there are no other keys besides `_duck`.
|
|
72
|
+
|
|
73
|
+
Any other keys on the object become **`context`**, which matches typical ingest JSON (`dTags` at the root, domain fields under `context`).
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
duck.warn("DUCKBUG_DTAGS_SMOKE_TEST", {
|
|
77
|
+
source: "initDuckBugDeviceContext",
|
|
78
|
+
platform: "ios",
|
|
79
|
+
_duck: { dTags: ["smoke-test", "dtags"] },
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Full usage example
|
|
84
|
+
|
|
85
|
+
End-to-end pattern for a Node/Bun service: DSN from env, scope (release, user, fingerprint), privacy pipeline, `beforeSend`, batched transport with retries, transport errors, console forwarding, global error handlers, structured logs, manual errors, and clean shutdown.
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import {
|
|
89
|
+
Duck,
|
|
90
|
+
DuckBugProvider,
|
|
91
|
+
Pond,
|
|
92
|
+
registerNodeGlobalErrorHandlers,
|
|
93
|
+
} from "@duckbug/js";
|
|
94
|
+
|
|
95
|
+
const dsn = process.env.DUCKBUG_DSN;
|
|
96
|
+
if (!dsn) {
|
|
97
|
+
throw new Error("Set DUCKBUG_DSN to your ingest URL");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const { extraSensitiveKeys } = Pond.ripple([
|
|
101
|
+
"custom_secret",
|
|
102
|
+
"internalToken",
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
// Transport + ingest errors. Privacy (strip / sanitize / eventId / beforeSend) is applied in `Duck` when you use the core client.
|
|
106
|
+
const duckBug = new DuckBugProvider({
|
|
107
|
+
dsn,
|
|
108
|
+
transport: {
|
|
109
|
+
maxBatchSize: 25,
|
|
110
|
+
maxRetries: 2,
|
|
111
|
+
retryDelayMs: 200,
|
|
112
|
+
},
|
|
113
|
+
onTransportError: (info) => {
|
|
114
|
+
console.error("[duckbug transport]", info.message, info.kind, info.itemCount);
|
|
115
|
+
},
|
|
51
116
|
});
|
|
52
117
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
118
|
+
const duck = new Duck(
|
|
119
|
+
[duckBug],
|
|
120
|
+
{
|
|
121
|
+
logReports: {
|
|
122
|
+
log: true,
|
|
123
|
+
warn: true,
|
|
124
|
+
error: true,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
extraSensitiveKeys,
|
|
129
|
+
// Omit whole sections before sanitize (see StrippableIngestSection in types)
|
|
130
|
+
stripSections: ["cookies", "headers"],
|
|
131
|
+
beforeSend: async (arg) => {
|
|
132
|
+
// Drop PII-heavy events in dev, or tweak payload
|
|
133
|
+
if (process.env.NODE_ENV === "test") {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
if (arg.kind === "log" && arg.event.message.includes("healthcheck")) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
return arg.event;
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
// Applied to every subsequent log / error (merge into event)
|
|
145
|
+
duck.setScope({
|
|
146
|
+
release: "my-app@1.4.2",
|
|
147
|
+
environment: process.env.NODE_ENV ?? "development",
|
|
148
|
+
service: "checkout-api",
|
|
149
|
+
fingerprint: "checkout-api-default",
|
|
150
|
+
// Prefer nesting session-like data under context; top-level `session` may be rejected by ingest.
|
|
151
|
+
context: { deployment: "eu-west" },
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const unregisterGlobals = registerNodeGlobalErrorHandlers({
|
|
155
|
+
duck,
|
|
156
|
+
rejectionTag: "unhandledRejection",
|
|
157
|
+
exceptionTag: "uncaughtException",
|
|
158
|
+
});
|
|
59
159
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
testError.stack =
|
|
63
|
-
"Error: Integration test error\n at integration.test.ts:1:1";
|
|
160
|
+
async function main() {
|
|
161
|
+
duck.debug("boot", { pid: process.pid });
|
|
64
162
|
|
|
65
|
-
|
|
66
|
-
|
|
163
|
+
try {
|
|
164
|
+
await doCheckout();
|
|
165
|
+
} catch (e) {
|
|
166
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
167
|
+
duck.quack("checkout_failed", err);
|
|
168
|
+
duck.captureException(err, "checkout_failed");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
duck.warn("slow_query", { table: "orders", ms: 850 });
|
|
172
|
+
duck.error("inventory_low", { sku: "SKU-12", qty: 2 });
|
|
173
|
+
duck.fatal("migration_required", { from: "v10", to: "v11" });
|
|
174
|
+
|
|
175
|
+
await duck.flush();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function doCheckout() {
|
|
179
|
+
// ...
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
main()
|
|
183
|
+
.catch((e) => {
|
|
184
|
+
console.error(e);
|
|
185
|
+
process.exitCode = 1;
|
|
186
|
+
})
|
|
187
|
+
.finally(async () => {
|
|
188
|
+
unregisterGlobals();
|
|
189
|
+
await duck.flush();
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Direct provider (no `Duck`)** — same privacy defaults via `finalizeIngestEvent` inside the provider; you call `sendLog` / `sendError` with `DuckBugLogEvent` / `DuckBugErrorEvent` shapes:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
import { DuckBugProvider, logLevel } from "@duckbug/js";
|
|
197
|
+
|
|
198
|
+
const provider = new DuckBugProvider({ dsn });
|
|
199
|
+
provider.sendLog({
|
|
200
|
+
time: Date.now(),
|
|
201
|
+
level: logLevel.INFO,
|
|
202
|
+
message: "Job finished",
|
|
203
|
+
platform: "node",
|
|
204
|
+
dTags: ["worker", "nightly"],
|
|
205
|
+
});
|
|
206
|
+
await provider.flush();
|
|
67
207
|
```
|
|
68
208
|
|
|
69
209
|
## API Reference
|
|
70
210
|
|
|
71
|
-
### DuckSDK
|
|
211
|
+
### Duck / DuckSDK
|
|
72
212
|
|
|
73
|
-
The main SDK class
|
|
213
|
+
The main SDK class fans out canonical log and error events to all registered providers.
|
|
74
214
|
|
|
75
215
|
#### Constructor
|
|
76
216
|
|
|
77
217
|
```typescript
|
|
78
|
-
new
|
|
218
|
+
new Duck(
|
|
219
|
+
providers: Provider[],
|
|
220
|
+
logProviderConfig?: LogProviderConfig,
|
|
221
|
+
options?: DuckSDKOptions,
|
|
222
|
+
)
|
|
79
223
|
```
|
|
80
224
|
|
|
81
225
|
- `providers`: Array of provider instances
|
|
82
|
-
- `
|
|
226
|
+
- `logProviderConfig`: Optional configuration for console interception (`LogProvider`)
|
|
227
|
+
- `options`: Optional `beforeSend`, `stripSections`, `extraSensitiveKeys` (strip → sanitize → `eventId` → `beforeSend` → providers; matches `duckbug-sdk-spec`)
|
|
83
228
|
|
|
84
229
|
#### Methods
|
|
85
230
|
|
|
86
|
-
- `log(tag
|
|
87
|
-
- `
|
|
88
|
-
- `
|
|
89
|
-
- `
|
|
90
|
-
- `
|
|
91
|
-
|
|
231
|
+
- `log` / `debug` / `warn` / `error` / `fatal(tag, payload?)`: structured logs
|
|
232
|
+
- `quack(tag, error)`: branded manual error capture; tag is sent as `dTags`, message comes from `error.message`
|
|
233
|
+
- `captureException(error, tag?)`: idiomatic alias for `quack` (default tag `error`)
|
|
234
|
+
- `setScope(partial)`: merge shared metadata into subsequent events
|
|
235
|
+
- `flush()`: await transport drains on providers that implement `flush` (for example `DuckBugProvider`)
|
|
236
|
+
|
|
237
|
+
Each captured log/error gets a UUID `eventId` when omitted (idempotency / retries).
|
|
92
238
|
|
|
93
239
|
### DuckBugProvider
|
|
94
240
|
|
|
95
|
-
|
|
241
|
+
First-party provider: posts JSON to single-event ingest by default, or to `/logs/batch` and `/errors/batch` when `transport.maxBatchSize > 1` (body is a JSON array, as required by the DuckBug API).
|
|
96
242
|
|
|
97
243
|
#### Constructor
|
|
98
244
|
|
|
99
245
|
```typescript
|
|
100
|
-
new DuckBugProvider(
|
|
246
|
+
new DuckBugProvider({
|
|
247
|
+
dsn: string,
|
|
248
|
+
extraSensitiveKeys?: string[],
|
|
249
|
+
stripSections?: StrippableIngestSection[],
|
|
250
|
+
beforeSend?: (arg) => event | null | undefined | Promise<...>,
|
|
251
|
+
transport?: {
|
|
252
|
+
maxBatchSize?: number; // default 1 — one POST per event
|
|
253
|
+
maxRetries?: number;
|
|
254
|
+
retryDelayMs?: number;
|
|
255
|
+
fetchImpl?: typeof fetch;
|
|
256
|
+
},
|
|
257
|
+
onTransportError?: (info: TransportFailureInfo) => void,
|
|
258
|
+
})
|
|
259
|
+
// or
|
|
260
|
+
DuckBugProvider.fromDSN(dsn)
|
|
101
261
|
```
|
|
102
262
|
|
|
103
|
-
- `config.dsn`:
|
|
263
|
+
- `config.dsn`: full ingest URL, e.g. `https://api.duckbug.io/ingest/myProject:myKey`
|
|
264
|
+
- `flush()`: returns a `Promise` that resolves when queued requests for this provider have been sent
|
|
265
|
+
|
|
266
|
+
### Privacy, batching, and Node hooks
|
|
267
|
+
|
|
268
|
+
- **Strip sections**: omit whole request fields (`headers`, `cookies`, `session`, …) before sanitize via `stripSections` on `DuckBugProvider` or `DuckSDK` options.
|
|
269
|
+
- **`beforeSend`**: on `Duck` / `DuckSDK` for all providers; on `DuckBugProvider` when using the provider without the core client. Return `null` to drop an event.
|
|
270
|
+
- **Node global errors** (optional, no core framework deps):
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
import { Duck, DuckBugProvider, registerNodeGlobalErrorHandlers } from '@duckbug/js';
|
|
274
|
+
|
|
275
|
+
const duck = new Duck([DuckBugProvider.fromDSN(dsn)]);
|
|
276
|
+
const unregister = registerNodeGlobalErrorHandlers({ duck });
|
|
277
|
+
// ... on shutdown: unregister();
|
|
278
|
+
```
|
|
104
279
|
|
|
105
280
|
### Log Provider Configuration
|
|
106
281
|
|
|
@@ -116,62 +291,54 @@ type LogProviderConfig = {
|
|
|
116
291
|
|
|
117
292
|
## Custom Providers
|
|
118
293
|
|
|
119
|
-
|
|
294
|
+
Implement `Provider`: handle canonical `DuckBugLogEvent` / `DuckBugErrorEvent` from `sendLog` / `sendError` (optional second argument `SendEventMeta` when events are already finalized in `DuckSDK`), and optional console-style methods for `LogProvider` hooks.
|
|
120
295
|
|
|
121
296
|
```typescript
|
|
122
|
-
import {
|
|
297
|
+
import type {
|
|
298
|
+
DuckBugErrorEvent,
|
|
299
|
+
DuckBugLogEvent,
|
|
300
|
+
Provider,
|
|
301
|
+
} from '@duckbug/js';
|
|
123
302
|
|
|
124
303
|
class TelegramProvider implements Provider {
|
|
125
304
|
constructor(private botToken: string, private chatId: string) {}
|
|
126
305
|
|
|
127
|
-
|
|
128
|
-
this.sendToTelegram('📝',
|
|
306
|
+
sendLog(event: DuckBugLogEvent): void {
|
|
307
|
+
this.sendToTelegram('📝', `${event.level} ${event.message}`);
|
|
129
308
|
}
|
|
130
309
|
|
|
131
|
-
|
|
132
|
-
this.sendToTelegram('
|
|
310
|
+
sendError(event: DuckBugErrorEvent): void {
|
|
311
|
+
this.sendToTelegram('💀', event.message);
|
|
133
312
|
}
|
|
134
313
|
|
|
135
|
-
|
|
136
|
-
this.sendToTelegram('
|
|
314
|
+
log(...args: unknown[]): void {
|
|
315
|
+
this.sendToTelegram('📝', String(args[0]));
|
|
137
316
|
}
|
|
138
317
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
INFO: '📝',
|
|
142
|
-
DEBUG: '🦆',
|
|
143
|
-
WARN: '⚠️',
|
|
144
|
-
ERROR: '🚨',
|
|
145
|
-
FATAL: '💀',
|
|
146
|
-
};
|
|
147
|
-
this.sendToTelegram(emojiMap[level], [tag, payload]);
|
|
318
|
+
warn(...args: unknown[]): void {
|
|
319
|
+
this.sendToTelegram('⚠️', String(args[0]));
|
|
148
320
|
}
|
|
149
321
|
|
|
150
|
-
|
|
151
|
-
this.sendToTelegram('
|
|
322
|
+
error(...args: unknown[]): void {
|
|
323
|
+
this.sendToTelegram('🚨', String(args[0]));
|
|
152
324
|
}
|
|
153
325
|
|
|
154
|
-
private sendToTelegram(emoji: string,
|
|
155
|
-
const message = `${emoji} ${
|
|
156
|
-
// Implementation to send message to Telegram
|
|
326
|
+
private sendToTelegram(emoji: string, text: string) {
|
|
327
|
+
const message = `${emoji} ${text}`;
|
|
157
328
|
fetch(`https://api.telegram.org/bot${this.botToken}/sendMessage`, {
|
|
158
329
|
method: 'POST',
|
|
159
330
|
headers: { 'Content-Type': 'application/json' },
|
|
160
|
-
body: JSON.stringify({
|
|
161
|
-
chat_id: this.chatId,
|
|
162
|
-
text: message
|
|
163
|
-
})
|
|
331
|
+
body: JSON.stringify({ chat_id: this.chatId, text: message }),
|
|
164
332
|
});
|
|
165
333
|
}
|
|
166
334
|
}
|
|
167
335
|
|
|
168
|
-
// Usage
|
|
169
336
|
const providers = [
|
|
170
|
-
|
|
171
|
-
new TelegramProvider('your-bot-token', 'your-chat-id')
|
|
337
|
+
DuckBugProvider.fromDSN('https://api.duckbug.io/ingest/project:key'),
|
|
338
|
+
new TelegramProvider('your-bot-token', 'your-chat-id'),
|
|
172
339
|
];
|
|
173
340
|
|
|
174
|
-
const duck = new
|
|
341
|
+
const duck = new Duck(providers);
|
|
175
342
|
```
|
|
176
343
|
|
|
177
344
|
## Development
|
|
@@ -181,7 +348,7 @@ const duck = new DuckSDK(providers);
|
|
|
181
348
|
Install dependencies:
|
|
182
349
|
|
|
183
350
|
```bash
|
|
184
|
-
|
|
351
|
+
bun install
|
|
185
352
|
```
|
|
186
353
|
|
|
187
354
|
### Build
|
|
@@ -189,7 +356,7 @@ yarn install
|
|
|
189
356
|
Build the library:
|
|
190
357
|
|
|
191
358
|
```bash
|
|
192
|
-
|
|
359
|
+
bun run build
|
|
193
360
|
```
|
|
194
361
|
|
|
195
362
|
### Linting
|
|
@@ -197,15 +364,140 @@ yarn build
|
|
|
197
364
|
Run linting:
|
|
198
365
|
|
|
199
366
|
```bash
|
|
200
|
-
|
|
367
|
+
bun run lint
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Commit Messages
|
|
371
|
+
|
|
372
|
+
Этот проект использует [Conventional Commits](https://www.conventionalcommits.org/) для стандартизации сообщений коммитов. Все коммиты должны соответствовать следующему формату:
|
|
373
|
+
|
|
374
|
+
```
|
|
375
|
+
<type>(<scope>): <subject>
|
|
376
|
+
|
|
377
|
+
<body>
|
|
378
|
+
|
|
379
|
+
<footer>
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
#### Типы коммитов (обязательные)
|
|
383
|
+
|
|
384
|
+
- `feat`: Новая функциональность
|
|
385
|
+
- `fix`: Исправление бага
|
|
386
|
+
- `docs`: Изменения в документации
|
|
387
|
+
- `style`: Форматирование кода (не влияет на выполнение кода)
|
|
388
|
+
- `refactor`: Рефакторинг кода
|
|
389
|
+
- `perf`: Улучшение производительности
|
|
390
|
+
- `test`: Добавление или изменение тестов
|
|
391
|
+
- `build`: Изменения в системе сборки или внешних зависимостях
|
|
392
|
+
- `ci`: Изменения в CI конфигурации
|
|
393
|
+
- `chore`: Обновление задач сборки, настроек и т.д.
|
|
394
|
+
- `revert`: Откат предыдущего коммита
|
|
395
|
+
|
|
396
|
+
#### Примеры корректных коммитов
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
feat: добавить поддержку логирования ошибок
|
|
400
|
+
fix: исправить утечку памяти в DuckBugProvider
|
|
401
|
+
docs: обновить README с примерами использования
|
|
402
|
+
test: добавить тесты для DuckSDK
|
|
403
|
+
refactor: улучшить структуру классов провайдеров
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
#### Проверка коммитов
|
|
407
|
+
|
|
408
|
+
Автоматическая проверка формата коммитов выполняется через git hook. При создании коммита с неправильным форматом вы получите подробное сообщение об ошибке с описанием проблемы и примерами правильного формата.
|
|
409
|
+
|
|
410
|
+
**Примеры ошибок:**
|
|
411
|
+
|
|
412
|
+
❌ Если забыли указать тип:
|
|
413
|
+
```
|
|
414
|
+
❌ Тип коммита обязателен!
|
|
415
|
+
📝 Формат коммита: <type>: <описание>
|
|
416
|
+
💡 Примеры:
|
|
417
|
+
feat: добавить новую функцию
|
|
418
|
+
fix: исправить обработку ошибок
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
❌ Если использовали неправильный тип:
|
|
201
422
|
```
|
|
423
|
+
❌ Неверный тип коммита!
|
|
424
|
+
✅ Используйте один из допустимых типов:
|
|
425
|
+
- feat: новая функциональность
|
|
426
|
+
- fix: исправление бага
|
|
427
|
+
...
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Для ручной проверки сообщения коммита:
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
bun run commitlint -- --from HEAD~1 --to HEAD
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### Автоматические релизы
|
|
437
|
+
|
|
438
|
+
Этот проект использует [semantic-release](https://github.com/semantic-release/semantic-release) для автоматического управления версиями и релизами.
|
|
439
|
+
|
|
440
|
+
#### Как это работает:
|
|
441
|
+
|
|
442
|
+
- **Версионирование**: Версия автоматически обновляется на основе типов коммитов:
|
|
443
|
+
- `feat:` → минорное обновление (1.0.0 → 1.1.0)
|
|
444
|
+
- `fix:` → патч (1.0.0 → 1.0.1)
|
|
445
|
+
- `BREAKING CHANGE` или `feat!:` → мажорное обновление (1.0.0 → 2.0.0)
|
|
446
|
+
- `chore:`, `docs:`, `style:` и другие → без релиза
|
|
447
|
+
|
|
448
|
+
- **Автоматические действия при пуше в `main`**:
|
|
449
|
+
1. Анализ коммитов с последнего релиза
|
|
450
|
+
2. Определение новой версии
|
|
451
|
+
3. Генерация CHANGELOG.md
|
|
452
|
+
4. Обновление версии в package.json
|
|
453
|
+
5. Создание git тега
|
|
454
|
+
6. Публикация в npm
|
|
455
|
+
7. Создание GitHub Release с заметками
|
|
456
|
+
|
|
457
|
+
#### Настройка:
|
|
458
|
+
|
|
459
|
+
1. **Создайте NPM токен** (только для публикации):
|
|
460
|
+
- Перейдите на https://www.npmjs.com/settings/YOUR_USERNAME/tokens
|
|
461
|
+
- Создайте токен с правами `Automation`
|
|
462
|
+
- Добавьте его в GitHub Secrets как `NPM_TOKEN`
|
|
463
|
+
|
|
464
|
+
2. **GitHub Actions**:
|
|
465
|
+
- Workflow `release.yaml` автоматически запускается при пуше в `main` или `beta`
|
|
466
|
+
- Использует `GITHUB_TOKEN` (автоматически предоставляется GitHub Actions)
|
|
467
|
+
- Использует `NPM_TOKEN` из секретов для публикации в npm
|
|
468
|
+
|
|
469
|
+
#### Примеры коммитов для релизов:
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
# Патч релиз (1.0.0 → 1.0.1)
|
|
473
|
+
fix: исправить обработку ошибок в DuckBugProvider
|
|
474
|
+
|
|
475
|
+
# Минорный релиз (1.0.0 → 1.1.0)
|
|
476
|
+
feat: добавить поддержку фильтрации логов
|
|
477
|
+
|
|
478
|
+
# Мажорный релиз (1.0.0 → 2.0.0)
|
|
479
|
+
feat!: изменить API провайдеров
|
|
480
|
+
|
|
481
|
+
# или
|
|
482
|
+
|
|
483
|
+
feat: добавить новую функцию
|
|
484
|
+
|
|
485
|
+
BREAKING CHANGE: изменена структура конфигурации DuckBugProvider
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
**Примечание**: Коммиты без типа или с типом `chore`, `docs`, `style` не создают новый релиз, но могут быть включены в CHANGELOG.
|
|
202
489
|
|
|
203
490
|
## TypeScript Support
|
|
204
491
|
|
|
205
492
|
This package includes TypeScript definitions. All exports are fully typed:
|
|
206
493
|
|
|
207
494
|
```typescript
|
|
208
|
-
import type {
|
|
495
|
+
import type {
|
|
496
|
+
Provider,
|
|
497
|
+
DuckBugConfig,
|
|
498
|
+
DuckBugLogEvent,
|
|
499
|
+
LogLevel,
|
|
500
|
+
} from "@duckbug/js";
|
|
209
501
|
```
|
|
210
502
|
|
|
211
503
|
## Browser Compatibility
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.d = (exports1, definition)=>{
|
|
5
|
+
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: definition[key]
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
})();
|
|
11
|
+
(()=>{
|
|
12
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
13
|
+
})();
|
|
14
|
+
(()=>{
|
|
15
|
+
__webpack_require__.r = (exports1)=>{
|
|
16
|
+
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
17
|
+
value: 'Module'
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
20
|
+
value: true
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
})();
|
|
24
|
+
var __webpack_exports__ = {};
|
|
25
|
+
__webpack_require__.r(__webpack_exports__);
|
|
26
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
+
processError: ()=>processError,
|
|
28
|
+
parseError: ()=>parseError
|
|
29
|
+
});
|
|
30
|
+
const external_sdkIdentity_cjs_namespaceObject = require("../sdkIdentity.cjs");
|
|
31
|
+
function tryJsonObjectFromMessage(message) {
|
|
32
|
+
const trimmed = message.trim();
|
|
33
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return null;
|
|
34
|
+
try {
|
|
35
|
+
const v = JSON.parse(message);
|
|
36
|
+
if (null !== v && "object" == typeof v) return v;
|
|
37
|
+
} catch {}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
function parseStacktrace(stack) {
|
|
41
|
+
let file = "unknown";
|
|
42
|
+
let line = 0;
|
|
43
|
+
let stacktrace;
|
|
44
|
+
if (stack) {
|
|
45
|
+
const stackLines = stack.split("\n");
|
|
46
|
+
let firstStackLineWithFile = null;
|
|
47
|
+
for(let i = 1; i < stackLines.length; i++){
|
|
48
|
+
const lineStr = stackLines[i];
|
|
49
|
+
if (-1 !== lineStr.indexOf("at ") && (-1 !== lineStr.indexOf(":") || -1 !== lineStr.indexOf("("))) {
|
|
50
|
+
firstStackLineWithFile = lineStr;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (firstStackLineWithFile) {
|
|
55
|
+
const match = firstStackLineWithFile.match(/\(([^)]+):(\d+):(\d+)\)/) || firstStackLineWithFile.match(/at\s+.*?\(([^)]+):(\d+):(\d+)\)/) || firstStackLineWithFile.match(/([^:()\s]+):(\d+):(\d+)/);
|
|
56
|
+
if (match?.[1]) {
|
|
57
|
+
file = match[1].replace(/^file:\/\//, "").replace(/^\/+/, "");
|
|
58
|
+
line = parseInt(match[2] || "0", 10);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
stacktrace = {
|
|
62
|
+
raw: stack,
|
|
63
|
+
frames: stackLines.filter((lineStr)=>lineStr.trim()).map((lineStr, index)=>({
|
|
64
|
+
index,
|
|
65
|
+
content: lineStr.trim()
|
|
66
|
+
}))
|
|
67
|
+
};
|
|
68
|
+
} else {
|
|
69
|
+
stacktrace = {
|
|
70
|
+
raw: "",
|
|
71
|
+
frames: []
|
|
72
|
+
};
|
|
73
|
+
file = "unknown";
|
|
74
|
+
line = 0;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
file,
|
|
78
|
+
line,
|
|
79
|
+
stacktrace
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function parseContext(contextStr) {
|
|
83
|
+
if (!contextStr) return null;
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(contextStr);
|
|
86
|
+
} catch {
|
|
87
|
+
return {
|
|
88
|
+
message: contextStr
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function parseError(error) {
|
|
93
|
+
const { file, line, stacktrace } = parseStacktrace(error.stack);
|
|
94
|
+
const context = parseContext(error.message);
|
|
95
|
+
return {
|
|
96
|
+
file,
|
|
97
|
+
line,
|
|
98
|
+
stacktrace,
|
|
99
|
+
context
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function processError(error, tag, time) {
|
|
103
|
+
const parsed = parseError(error);
|
|
104
|
+
const message = "string" == typeof error.message && error.message.length > 0 ? error.message : "Error";
|
|
105
|
+
const event = {
|
|
106
|
+
time,
|
|
107
|
+
message,
|
|
108
|
+
stacktrace: parsed.stacktrace,
|
|
109
|
+
stacktraceAsString: error.stack ?? "",
|
|
110
|
+
file: parsed.file,
|
|
111
|
+
line: parsed.line,
|
|
112
|
+
exception: {
|
|
113
|
+
type: error.name,
|
|
114
|
+
message: error.message
|
|
115
|
+
},
|
|
116
|
+
platform: "node",
|
|
117
|
+
sdk: {
|
|
118
|
+
...external_sdkIdentity_cjs_namespaceObject.SDK_IDENTITY
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
if (tag.length > 0) event.dTags = [
|
|
122
|
+
tag
|
|
123
|
+
];
|
|
124
|
+
const fromJson = tryJsonObjectFromMessage(error.message);
|
|
125
|
+
if (fromJson) event.extra = fromJson;
|
|
126
|
+
return event;
|
|
127
|
+
}
|
|
128
|
+
exports.parseError = __webpack_exports__.parseError;
|
|
129
|
+
exports.processError = __webpack_exports__.processError;
|
|
130
|
+
for(var __webpack_i__ in __webpack_exports__)if (-1 === [
|
|
131
|
+
"parseError",
|
|
132
|
+
"processError"
|
|
133
|
+
].indexOf(__webpack_i__)) exports[__webpack_i__] = __webpack_exports__[__webpack_i__];
|
|
134
|
+
Object.defineProperty(exports, '__esModule', {
|
|
135
|
+
value: true
|
|
136
|
+
});
|