@richardmcquiston01/error-log-adapter 0.8.0 → 0.9.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 +42 -21
- package/dist/index.cjs +289 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +129 -1
- package/dist/index.d.ts +129 -1
- package/dist/index.mjs +288 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -127,6 +127,15 @@ declare class WebhookErrorAdapter<TCode extends string = string> implements Erro
|
|
|
127
127
|
/** @throws {RangeError} if `url` is not a valid `https://` URL (see `allowInsecure`) or a timeout, retry or signing option is invalid. */
|
|
128
128
|
constructor(url: string, options?: WebhookErrorOptions);
|
|
129
129
|
log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
|
|
130
|
+
/**
|
|
131
|
+
* Sends several entries in one request, as a JSON array of the same records
|
|
132
|
+
* `log` sends one at a time (each with an `id`). `BatchingErrorAdapter` uses
|
|
133
|
+
* this when it wraps a webhook, so the receiver must accept an array. The
|
|
134
|
+
* whole request succeeds or fails together.
|
|
135
|
+
*/
|
|
136
|
+
logBatch(entries: ErrorLogEntry<TCode>[]): Promise<ErrorLogResult>;
|
|
137
|
+
/** Sends one prepared body, trying again as `retries` allows. */
|
|
138
|
+
private deliver;
|
|
130
139
|
/** Milliseconds to wait before retry number `retryNumber` (1 for the first). */
|
|
131
140
|
private retryWaitMs;
|
|
132
141
|
private send;
|
|
@@ -1175,4 +1184,123 @@ declare class CloudWatchLogsErrorAdapter<TCode extends string = string> implemen
|
|
|
1175
1184
|
private fail;
|
|
1176
1185
|
}
|
|
1177
1186
|
|
|
1178
|
-
|
|
1187
|
+
/**
|
|
1188
|
+
* An adapter that can send several entries in one go, such as
|
|
1189
|
+
* `WebhookErrorAdapter` (one request carrying a JSON array).
|
|
1190
|
+
* `BatchingErrorAdapter` uses it when it is there. The whole batch succeeds or
|
|
1191
|
+
* fails together.
|
|
1192
|
+
*/
|
|
1193
|
+
interface BatchCapableAdapter<TCode extends string = string> extends ErrorLogAdapter<TCode> {
|
|
1194
|
+
logBatch(entries: ErrorLogEntry<TCode>[]): Promise<ErrorLogResult>;
|
|
1195
|
+
}
|
|
1196
|
+
interface BatchingErrorOptions<TCode extends string = string> {
|
|
1197
|
+
/** Deliver once this many entries are waiting. Defaults to 20. */
|
|
1198
|
+
batchSize?: number;
|
|
1199
|
+
/**
|
|
1200
|
+
* Also deliver once the oldest waiting entry has waited this long (ms).
|
|
1201
|
+
* Defaults to 5000. Pass `Infinity` to deliver only when `batchSize` is
|
|
1202
|
+
* reached or `flush()` is called. The timer does not keep the process alive,
|
|
1203
|
+
* so call `flush()` before exiting.
|
|
1204
|
+
*/
|
|
1205
|
+
maxWaitMs?: number;
|
|
1206
|
+
/**
|
|
1207
|
+
* How many times to try an entry again after a failed delivery before giving
|
|
1208
|
+
* up on it. Defaults to 3 (four attempts in all).
|
|
1209
|
+
*/
|
|
1210
|
+
retries?: number;
|
|
1211
|
+
/** Wait before the first retry, in milliseconds; it doubles after that. Defaults to 500. */
|
|
1212
|
+
retryDelayMs?: number;
|
|
1213
|
+
/** Longest wait between attempts, in milliseconds. Defaults to 30000. */
|
|
1214
|
+
maxRetryDelayMs?: number;
|
|
1215
|
+
/**
|
|
1216
|
+
* Most entries kept while the sink is slow or down. Past this, the oldest
|
|
1217
|
+
* are dropped. Defaults to 1000. Must be at least `batchSize`.
|
|
1218
|
+
*/
|
|
1219
|
+
maxBuffered?: number;
|
|
1220
|
+
/**
|
|
1221
|
+
* Use the wrapped adapter's `logBatch` when it has one. Defaults to `true`.
|
|
1222
|
+
* Pass `false` to deliver one entry at a time even then (a wrapped
|
|
1223
|
+
* `WebhookErrorAdapter` then sends one request per entry, not an array).
|
|
1224
|
+
*/
|
|
1225
|
+
useBatchMethod?: boolean;
|
|
1226
|
+
/**
|
|
1227
|
+
* Called when entries are given up on, either because they failed every
|
|
1228
|
+
* attempt or because the buffer was full. Errors it throws are ignored.
|
|
1229
|
+
*/
|
|
1230
|
+
onDrop?: (entries: ErrorLogEntry<TCode>[], reason: string) => void;
|
|
1231
|
+
/** Text printed before failure messages. Defaults to `[error-log-adapter]`. */
|
|
1232
|
+
prefix?: string;
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Puts a queue in front of any other adapter. `log()` returns `ok: true` as
|
|
1236
|
+
* soon as the entry is queued, so a slow or failing sink never holds up the
|
|
1237
|
+
* application; entries are then delivered in order, in batches, and a failed
|
|
1238
|
+
* delivery is tried again with exponential backoff before the entry is given up
|
|
1239
|
+
* on (see `onDrop`).
|
|
1240
|
+
*
|
|
1241
|
+
* Delivery uses the wrapped adapter's `logBatch` when it has one (the webhook
|
|
1242
|
+
* sends one request for the whole batch); otherwise the entries go one at a
|
|
1243
|
+
* time through `log`, stopping at the first failure so a sink that is down is
|
|
1244
|
+
* not hammered. Because of that, `ok: true` from `log()` means "queued", not
|
|
1245
|
+
* "stored": call `flush()` before the process exits, and watch `onDrop` or the
|
|
1246
|
+
* console for entries that could not be delivered. The queue is in memory, so
|
|
1247
|
+
* entries still waiting when the process dies are lost; put a durable adapter
|
|
1248
|
+
* beside it in a `CompositeErrorAdapter` when that matters.
|
|
1249
|
+
*
|
|
1250
|
+
* ```ts
|
|
1251
|
+
* const errorLog = new BatchingErrorAdapter(
|
|
1252
|
+
* new WebhookErrorAdapter('https://example.com/errors'),
|
|
1253
|
+
* { batchSize: 50, maxWaitMs: 2000 }
|
|
1254
|
+
* );
|
|
1255
|
+
* process.on('beforeExit', () => void errorLog.flush());
|
|
1256
|
+
* ```
|
|
1257
|
+
*
|
|
1258
|
+
* Do not also turn on the wrapped adapter's own retries (for example the
|
|
1259
|
+
* webhook's `retries`): the two multiply.
|
|
1260
|
+
*/
|
|
1261
|
+
declare class BatchingErrorAdapter<TCode extends string = string> implements ErrorLogAdapter<TCode> {
|
|
1262
|
+
private readonly inner;
|
|
1263
|
+
private readonly batchSize;
|
|
1264
|
+
private readonly maxWaitMs;
|
|
1265
|
+
private readonly retries;
|
|
1266
|
+
private readonly retryDelayMs;
|
|
1267
|
+
private readonly maxRetryDelayMs;
|
|
1268
|
+
private readonly maxBuffered;
|
|
1269
|
+
private readonly useBatchMethod;
|
|
1270
|
+
private readonly onDrop;
|
|
1271
|
+
private readonly logPrefix;
|
|
1272
|
+
private queue;
|
|
1273
|
+
private waitTimer;
|
|
1274
|
+
private backoffTimer;
|
|
1275
|
+
private draining;
|
|
1276
|
+
private flushers;
|
|
1277
|
+
private droppedSinceFlush;
|
|
1278
|
+
private lastDropReason;
|
|
1279
|
+
/** @throws {RangeError} if an option is out of range. */
|
|
1280
|
+
constructor(inner: ErrorLogAdapter<TCode>, options?: BatchingErrorOptions<TCode>);
|
|
1281
|
+
/** Queues the entry. Resolves ok straight away; delivery happens in the background. */
|
|
1282
|
+
log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
|
|
1283
|
+
/**
|
|
1284
|
+
* Delivers everything queued, retrying as configured, and resolves once the
|
|
1285
|
+
* queue is empty. Resolves `ok: false` if any entries were given up on since
|
|
1286
|
+
* the last flush. With retries on and the sink down this can take a while
|
|
1287
|
+
* (the waits add up to well over a minute with the defaults).
|
|
1288
|
+
*/
|
|
1289
|
+
flush(): Promise<ErrorLogResult>;
|
|
1290
|
+
private scheduleDrain;
|
|
1291
|
+
private startDraining;
|
|
1292
|
+
/** Delivers batches until the queue is empty, waiting out a backoff after each failure. */
|
|
1293
|
+
private drain;
|
|
1294
|
+
/**
|
|
1295
|
+
* Tries to deliver `batch` (the front of the queue). Delivered entries leave
|
|
1296
|
+
* the queue; an entry that failed has its failure count raised and stays.
|
|
1297
|
+
* Resolves to the failure message, or undefined when everything went through.
|
|
1298
|
+
*/
|
|
1299
|
+
private deliver;
|
|
1300
|
+
/** The wrapped adapter should not throw, but a bug in one must not stop the queue. */
|
|
1301
|
+
private safely;
|
|
1302
|
+
private backoff;
|
|
1303
|
+
private drop;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
export { type BatchCapableAdapter, BatchingErrorAdapter, type BatchingErrorOptions, type CloudWatchBatchOptions, type CloudWatchLogEvent, type CloudWatchLogsClientLike, CloudWatchLogsErrorAdapter, type CloudWatchLogsErrorOptions, type CloudWatchPutLogEventsInput, type CollectionLike, CompositeErrorAdapter, type CompositeErrorOptions, ConsoleErrorAdapter, type ConsoleErrorOptions, type EmitterLike, type ErrorLogAdapter, type ErrorLogEntry, type ErrorLogResult, EventEmitterErrorAdapter, type EventEmitterErrorOptions, type EventEmitterErrorRecord, type FileSystemLike, type FirebaseErrorOptions, type FirebaseErrorRecord, type FirestoreCollectionLike, FirestoreErrorAdapter, FlatFileErrorAdapter, type FlatFileErrorOptions, type IdbDatabaseLike, type IdbObjectStoreLike, type IdbRequestLike, type IdbTransactionLike, type IdbUpgradeDatabaseLike, IndexedDbErrorAdapter, type IndexedDbErrorOptions, type IndexedDbErrorRecord, JsonFileErrorAdapter, type JsonFileErrorOptions, type JsonFileErrorRecord, type LockManagerLike, MongoDbErrorAdapter, type MongoDbErrorOptions, MySqlErrorAdapter, PostgresErrorAdapter, type ReadWriteFileSystemLike, RealtimeDatabaseErrorAdapter, type RealtimeDatabaseRefLike, type RedisClientLike, RedisErrorAdapter, type RedisErrorOptions, type S3BatchOptions, type S3ClientLike, S3ErrorAdapter, type S3ErrorOptions, type S3PutObjectInput, type SerializedErrorLogEntry, type SqlClientLike, type SqlDialect, type SqlErrorOptions, type SqliteClientLike, SqliteErrorAdapter, type SqliteErrorOptions, type StorageLike, type SupabaseClientLike, SupabaseErrorAdapter, type SupabaseErrorLike, type SupabaseErrorOptions, type SupabaseErrorRow, type SupabaseInsertResult, type SupabaseSetupOptions, WebSocketErrorAdapter, type WebSocketErrorOptions, type WebSocketLike, WebStorageErrorAdapter, type WebStorageErrorOptions, type WebStorageErrorRecord, WebhookErrorAdapter, type WebhookErrorOptions, buildCreateErrorLogTableSql, buildErrorLogTableDdl, buildMySqlWidenColumnsSql, buildSupabaseSetupSql, createErrorLogStore, formatErrorLogLine, serializeCause, serializeEntry };
|
package/dist/index.d.ts
CHANGED
|
@@ -127,6 +127,15 @@ declare class WebhookErrorAdapter<TCode extends string = string> implements Erro
|
|
|
127
127
|
/** @throws {RangeError} if `url` is not a valid `https://` URL (see `allowInsecure`) or a timeout, retry or signing option is invalid. */
|
|
128
128
|
constructor(url: string, options?: WebhookErrorOptions);
|
|
129
129
|
log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
|
|
130
|
+
/**
|
|
131
|
+
* Sends several entries in one request, as a JSON array of the same records
|
|
132
|
+
* `log` sends one at a time (each with an `id`). `BatchingErrorAdapter` uses
|
|
133
|
+
* this when it wraps a webhook, so the receiver must accept an array. The
|
|
134
|
+
* whole request succeeds or fails together.
|
|
135
|
+
*/
|
|
136
|
+
logBatch(entries: ErrorLogEntry<TCode>[]): Promise<ErrorLogResult>;
|
|
137
|
+
/** Sends one prepared body, trying again as `retries` allows. */
|
|
138
|
+
private deliver;
|
|
130
139
|
/** Milliseconds to wait before retry number `retryNumber` (1 for the first). */
|
|
131
140
|
private retryWaitMs;
|
|
132
141
|
private send;
|
|
@@ -1175,4 +1184,123 @@ declare class CloudWatchLogsErrorAdapter<TCode extends string = string> implemen
|
|
|
1175
1184
|
private fail;
|
|
1176
1185
|
}
|
|
1177
1186
|
|
|
1178
|
-
|
|
1187
|
+
/**
|
|
1188
|
+
* An adapter that can send several entries in one go, such as
|
|
1189
|
+
* `WebhookErrorAdapter` (one request carrying a JSON array).
|
|
1190
|
+
* `BatchingErrorAdapter` uses it when it is there. The whole batch succeeds or
|
|
1191
|
+
* fails together.
|
|
1192
|
+
*/
|
|
1193
|
+
interface BatchCapableAdapter<TCode extends string = string> extends ErrorLogAdapter<TCode> {
|
|
1194
|
+
logBatch(entries: ErrorLogEntry<TCode>[]): Promise<ErrorLogResult>;
|
|
1195
|
+
}
|
|
1196
|
+
interface BatchingErrorOptions<TCode extends string = string> {
|
|
1197
|
+
/** Deliver once this many entries are waiting. Defaults to 20. */
|
|
1198
|
+
batchSize?: number;
|
|
1199
|
+
/**
|
|
1200
|
+
* Also deliver once the oldest waiting entry has waited this long (ms).
|
|
1201
|
+
* Defaults to 5000. Pass `Infinity` to deliver only when `batchSize` is
|
|
1202
|
+
* reached or `flush()` is called. The timer does not keep the process alive,
|
|
1203
|
+
* so call `flush()` before exiting.
|
|
1204
|
+
*/
|
|
1205
|
+
maxWaitMs?: number;
|
|
1206
|
+
/**
|
|
1207
|
+
* How many times to try an entry again after a failed delivery before giving
|
|
1208
|
+
* up on it. Defaults to 3 (four attempts in all).
|
|
1209
|
+
*/
|
|
1210
|
+
retries?: number;
|
|
1211
|
+
/** Wait before the first retry, in milliseconds; it doubles after that. Defaults to 500. */
|
|
1212
|
+
retryDelayMs?: number;
|
|
1213
|
+
/** Longest wait between attempts, in milliseconds. Defaults to 30000. */
|
|
1214
|
+
maxRetryDelayMs?: number;
|
|
1215
|
+
/**
|
|
1216
|
+
* Most entries kept while the sink is slow or down. Past this, the oldest
|
|
1217
|
+
* are dropped. Defaults to 1000. Must be at least `batchSize`.
|
|
1218
|
+
*/
|
|
1219
|
+
maxBuffered?: number;
|
|
1220
|
+
/**
|
|
1221
|
+
* Use the wrapped adapter's `logBatch` when it has one. Defaults to `true`.
|
|
1222
|
+
* Pass `false` to deliver one entry at a time even then (a wrapped
|
|
1223
|
+
* `WebhookErrorAdapter` then sends one request per entry, not an array).
|
|
1224
|
+
*/
|
|
1225
|
+
useBatchMethod?: boolean;
|
|
1226
|
+
/**
|
|
1227
|
+
* Called when entries are given up on, either because they failed every
|
|
1228
|
+
* attempt or because the buffer was full. Errors it throws are ignored.
|
|
1229
|
+
*/
|
|
1230
|
+
onDrop?: (entries: ErrorLogEntry<TCode>[], reason: string) => void;
|
|
1231
|
+
/** Text printed before failure messages. Defaults to `[error-log-adapter]`. */
|
|
1232
|
+
prefix?: string;
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Puts a queue in front of any other adapter. `log()` returns `ok: true` as
|
|
1236
|
+
* soon as the entry is queued, so a slow or failing sink never holds up the
|
|
1237
|
+
* application; entries are then delivered in order, in batches, and a failed
|
|
1238
|
+
* delivery is tried again with exponential backoff before the entry is given up
|
|
1239
|
+
* on (see `onDrop`).
|
|
1240
|
+
*
|
|
1241
|
+
* Delivery uses the wrapped adapter's `logBatch` when it has one (the webhook
|
|
1242
|
+
* sends one request for the whole batch); otherwise the entries go one at a
|
|
1243
|
+
* time through `log`, stopping at the first failure so a sink that is down is
|
|
1244
|
+
* not hammered. Because of that, `ok: true` from `log()` means "queued", not
|
|
1245
|
+
* "stored": call `flush()` before the process exits, and watch `onDrop` or the
|
|
1246
|
+
* console for entries that could not be delivered. The queue is in memory, so
|
|
1247
|
+
* entries still waiting when the process dies are lost; put a durable adapter
|
|
1248
|
+
* beside it in a `CompositeErrorAdapter` when that matters.
|
|
1249
|
+
*
|
|
1250
|
+
* ```ts
|
|
1251
|
+
* const errorLog = new BatchingErrorAdapter(
|
|
1252
|
+
* new WebhookErrorAdapter('https://example.com/errors'),
|
|
1253
|
+
* { batchSize: 50, maxWaitMs: 2000 }
|
|
1254
|
+
* );
|
|
1255
|
+
* process.on('beforeExit', () => void errorLog.flush());
|
|
1256
|
+
* ```
|
|
1257
|
+
*
|
|
1258
|
+
* Do not also turn on the wrapped adapter's own retries (for example the
|
|
1259
|
+
* webhook's `retries`): the two multiply.
|
|
1260
|
+
*/
|
|
1261
|
+
declare class BatchingErrorAdapter<TCode extends string = string> implements ErrorLogAdapter<TCode> {
|
|
1262
|
+
private readonly inner;
|
|
1263
|
+
private readonly batchSize;
|
|
1264
|
+
private readonly maxWaitMs;
|
|
1265
|
+
private readonly retries;
|
|
1266
|
+
private readonly retryDelayMs;
|
|
1267
|
+
private readonly maxRetryDelayMs;
|
|
1268
|
+
private readonly maxBuffered;
|
|
1269
|
+
private readonly useBatchMethod;
|
|
1270
|
+
private readonly onDrop;
|
|
1271
|
+
private readonly logPrefix;
|
|
1272
|
+
private queue;
|
|
1273
|
+
private waitTimer;
|
|
1274
|
+
private backoffTimer;
|
|
1275
|
+
private draining;
|
|
1276
|
+
private flushers;
|
|
1277
|
+
private droppedSinceFlush;
|
|
1278
|
+
private lastDropReason;
|
|
1279
|
+
/** @throws {RangeError} if an option is out of range. */
|
|
1280
|
+
constructor(inner: ErrorLogAdapter<TCode>, options?: BatchingErrorOptions<TCode>);
|
|
1281
|
+
/** Queues the entry. Resolves ok straight away; delivery happens in the background. */
|
|
1282
|
+
log(entry: ErrorLogEntry<TCode>): Promise<ErrorLogResult>;
|
|
1283
|
+
/**
|
|
1284
|
+
* Delivers everything queued, retrying as configured, and resolves once the
|
|
1285
|
+
* queue is empty. Resolves `ok: false` if any entries were given up on since
|
|
1286
|
+
* the last flush. With retries on and the sink down this can take a while
|
|
1287
|
+
* (the waits add up to well over a minute with the defaults).
|
|
1288
|
+
*/
|
|
1289
|
+
flush(): Promise<ErrorLogResult>;
|
|
1290
|
+
private scheduleDrain;
|
|
1291
|
+
private startDraining;
|
|
1292
|
+
/** Delivers batches until the queue is empty, waiting out a backoff after each failure. */
|
|
1293
|
+
private drain;
|
|
1294
|
+
/**
|
|
1295
|
+
* Tries to deliver `batch` (the front of the queue). Delivered entries leave
|
|
1296
|
+
* the queue; an entry that failed has its failure count raised and stays.
|
|
1297
|
+
* Resolves to the failure message, or undefined when everything went through.
|
|
1298
|
+
*/
|
|
1299
|
+
private deliver;
|
|
1300
|
+
/** The wrapped adapter should not throw, but a bug in one must not stop the queue. */
|
|
1301
|
+
private safely;
|
|
1302
|
+
private backoff;
|
|
1303
|
+
private drop;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
export { type BatchCapableAdapter, BatchingErrorAdapter, type BatchingErrorOptions, type CloudWatchBatchOptions, type CloudWatchLogEvent, type CloudWatchLogsClientLike, CloudWatchLogsErrorAdapter, type CloudWatchLogsErrorOptions, type CloudWatchPutLogEventsInput, type CollectionLike, CompositeErrorAdapter, type CompositeErrorOptions, ConsoleErrorAdapter, type ConsoleErrorOptions, type EmitterLike, type ErrorLogAdapter, type ErrorLogEntry, type ErrorLogResult, EventEmitterErrorAdapter, type EventEmitterErrorOptions, type EventEmitterErrorRecord, type FileSystemLike, type FirebaseErrorOptions, type FirebaseErrorRecord, type FirestoreCollectionLike, FirestoreErrorAdapter, FlatFileErrorAdapter, type FlatFileErrorOptions, type IdbDatabaseLike, type IdbObjectStoreLike, type IdbRequestLike, type IdbTransactionLike, type IdbUpgradeDatabaseLike, IndexedDbErrorAdapter, type IndexedDbErrorOptions, type IndexedDbErrorRecord, JsonFileErrorAdapter, type JsonFileErrorOptions, type JsonFileErrorRecord, type LockManagerLike, MongoDbErrorAdapter, type MongoDbErrorOptions, MySqlErrorAdapter, PostgresErrorAdapter, type ReadWriteFileSystemLike, RealtimeDatabaseErrorAdapter, type RealtimeDatabaseRefLike, type RedisClientLike, RedisErrorAdapter, type RedisErrorOptions, type S3BatchOptions, type S3ClientLike, S3ErrorAdapter, type S3ErrorOptions, type S3PutObjectInput, type SerializedErrorLogEntry, type SqlClientLike, type SqlDialect, type SqlErrorOptions, type SqliteClientLike, SqliteErrorAdapter, type SqliteErrorOptions, type StorageLike, type SupabaseClientLike, SupabaseErrorAdapter, type SupabaseErrorLike, type SupabaseErrorOptions, type SupabaseErrorRow, type SupabaseInsertResult, type SupabaseSetupOptions, WebSocketErrorAdapter, type WebSocketErrorOptions, type WebSocketLike, WebStorageErrorAdapter, type WebStorageErrorOptions, type WebStorageErrorRecord, WebhookErrorAdapter, type WebhookErrorOptions, buildCreateErrorLogTableSql, buildErrorLogTableDdl, buildMySqlWidenColumnsSql, buildSupabaseSetupSql, createErrorLogStore, formatErrorLogLine, serializeCause, serializeEntry };
|
package/dist/index.mjs
CHANGED
|
@@ -140,6 +140,12 @@ var ConsoleErrorAdapter = class {
|
|
|
140
140
|
}
|
|
141
141
|
};
|
|
142
142
|
|
|
143
|
+
// src/backoff.ts
|
|
144
|
+
function backoffDelayMs(retryNumber, baseMs, maxMs) {
|
|
145
|
+
const ceiling = Math.min(maxMs, baseMs * 2 ** (retryNumber - 1));
|
|
146
|
+
return ceiling / 2 + Math.random() * ceiling / 2;
|
|
147
|
+
}
|
|
148
|
+
|
|
143
149
|
// src/uuid.ts
|
|
144
150
|
function generateId() {
|
|
145
151
|
const cryptoApi = globalThis.crypto;
|
|
@@ -351,6 +357,36 @@ var WebhookErrorAdapter = class {
|
|
|
351
357
|
`could not prepare the entry: ${this.redact(describeError(error))}`
|
|
352
358
|
);
|
|
353
359
|
}
|
|
360
|
+
return this.deliver(body);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Sends several entries in one request, as a JSON array of the same records
|
|
364
|
+
* `log` sends one at a time (each with an `id`). `BatchingErrorAdapter` uses
|
|
365
|
+
* this when it wraps a webhook, so the receiver must accept an array. The
|
|
366
|
+
* whole request succeeds or fails together.
|
|
367
|
+
*/
|
|
368
|
+
async logBatch(entries) {
|
|
369
|
+
if (entries.length === 0) {
|
|
370
|
+
return { ok: true };
|
|
371
|
+
}
|
|
372
|
+
let body;
|
|
373
|
+
try {
|
|
374
|
+
body = JSON.stringify(
|
|
375
|
+
entries.map((entry) => ({
|
|
376
|
+
id: resolveEntryId(entry),
|
|
377
|
+
...serializeEntry(entry)
|
|
378
|
+
}))
|
|
379
|
+
);
|
|
380
|
+
} catch (error) {
|
|
381
|
+
console.error(`${this.prefix} WebhookErrorAdapter:`, error);
|
|
382
|
+
return this.fail(
|
|
383
|
+
`could not prepare the entries: ${this.redact(describeError(error))}`
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
return this.deliver(body);
|
|
387
|
+
}
|
|
388
|
+
/** Sends one prepared body, trying again as `retries` allows. */
|
|
389
|
+
async deliver(body) {
|
|
354
390
|
let attempt = await this.send(body);
|
|
355
391
|
let attempts = 1;
|
|
356
392
|
while (!attempt.ok && attempt.retryable && attempts <= this.retries) {
|
|
@@ -370,11 +406,7 @@ var WebhookErrorAdapter = class {
|
|
|
370
406
|
if (retryAfterMs !== void 0) {
|
|
371
407
|
return Math.min(retryAfterMs, this.maxRetryDelayMs);
|
|
372
408
|
}
|
|
373
|
-
|
|
374
|
-
this.maxRetryDelayMs,
|
|
375
|
-
this.retryDelayMs * 2 ** (retryNumber - 1)
|
|
376
|
-
);
|
|
377
|
-
return ceiling / 2 + Math.random() * ceiling / 2;
|
|
409
|
+
return backoffDelayMs(retryNumber, this.retryDelayMs, this.maxRetryDelayMs);
|
|
378
410
|
}
|
|
379
411
|
async send(body) {
|
|
380
412
|
const controller = new AbortController();
|
|
@@ -2094,7 +2126,258 @@ function describeRejection(output, eventCount) {
|
|
|
2094
2126
|
}
|
|
2095
2127
|
return reasons.length === 0 ? void 0 : `CloudWatch Logs accepted the request but rejected events: ${reasons.join("; ")}`;
|
|
2096
2128
|
}
|
|
2129
|
+
|
|
2130
|
+
// src/adapters/batching-error-adapter.ts
|
|
2131
|
+
var DEFAULT_BATCH_SIZE = 20;
|
|
2132
|
+
var DEFAULT_MAX_WAIT_MS = 5e3;
|
|
2133
|
+
var DEFAULT_RETRIES = 3;
|
|
2134
|
+
var DEFAULT_RETRY_DELAY_MS2 = 500;
|
|
2135
|
+
var DEFAULT_MAX_RETRY_DELAY_MS2 = 3e4;
|
|
2136
|
+
var DEFAULT_MAX_BUFFERED = 1e3;
|
|
2137
|
+
function hasLogBatch(adapter) {
|
|
2138
|
+
return typeof adapter.logBatch === "function";
|
|
2139
|
+
}
|
|
2140
|
+
var BatchingErrorAdapter = class {
|
|
2141
|
+
/** @throws {RangeError} if an option is out of range. */
|
|
2142
|
+
constructor(inner, options = {}) {
|
|
2143
|
+
this.inner = inner;
|
|
2144
|
+
this.queue = [];
|
|
2145
|
+
this.waitTimer = null;
|
|
2146
|
+
this.backoffTimer = null;
|
|
2147
|
+
this.draining = null;
|
|
2148
|
+
this.flushers = 0;
|
|
2149
|
+
this.droppedSinceFlush = 0;
|
|
2150
|
+
this.lastDropReason = "";
|
|
2151
|
+
this.batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
2152
|
+
this.maxWaitMs = options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;
|
|
2153
|
+
this.retries = options.retries ?? DEFAULT_RETRIES;
|
|
2154
|
+
this.retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS2;
|
|
2155
|
+
this.maxRetryDelayMs = options.maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS2;
|
|
2156
|
+
this.maxBuffered = options.maxBuffered ?? DEFAULT_MAX_BUFFERED;
|
|
2157
|
+
this.useBatchMethod = options.useBatchMethod ?? true;
|
|
2158
|
+
this.onDrop = options.onDrop;
|
|
2159
|
+
this.logPrefix = options.prefix ?? DEFAULT_PREFIX;
|
|
2160
|
+
if (!Number.isInteger(this.batchSize) || this.batchSize < 1) {
|
|
2161
|
+
throw new RangeError(
|
|
2162
|
+
`BatchingErrorAdapter: batchSize must be a positive integer, received ${String(options.batchSize)}.`
|
|
2163
|
+
);
|
|
2164
|
+
}
|
|
2165
|
+
if (Number.isNaN(this.maxWaitMs) || this.maxWaitMs <= 0) {
|
|
2166
|
+
throw new RangeError(
|
|
2167
|
+
`BatchingErrorAdapter: maxWaitMs must be a positive number or Infinity, received ${String(options.maxWaitMs)}.`
|
|
2168
|
+
);
|
|
2169
|
+
}
|
|
2170
|
+
if (!Number.isInteger(this.retries) || this.retries < 0) {
|
|
2171
|
+
throw new RangeError(
|
|
2172
|
+
`BatchingErrorAdapter: retries must be a whole number of 0 or more, received ${String(options.retries)}.`
|
|
2173
|
+
);
|
|
2174
|
+
}
|
|
2175
|
+
if (!Number.isFinite(this.retryDelayMs) || this.retryDelayMs <= 0) {
|
|
2176
|
+
throw new RangeError(
|
|
2177
|
+
`BatchingErrorAdapter: retryDelayMs must be a positive number, received ${String(options.retryDelayMs)}.`
|
|
2178
|
+
);
|
|
2179
|
+
}
|
|
2180
|
+
if (!Number.isFinite(this.maxRetryDelayMs) || this.maxRetryDelayMs < this.retryDelayMs) {
|
|
2181
|
+
throw new RangeError(
|
|
2182
|
+
`BatchingErrorAdapter: maxRetryDelayMs must be a number no smaller than retryDelayMs (${this.retryDelayMs}), received ${String(options.maxRetryDelayMs)}.`
|
|
2183
|
+
);
|
|
2184
|
+
}
|
|
2185
|
+
if (!Number.isInteger(this.maxBuffered) || this.maxBuffered < this.batchSize) {
|
|
2186
|
+
throw new RangeError(
|
|
2187
|
+
`BatchingErrorAdapter: maxBuffered must be a whole number no smaller than batchSize (${this.batchSize}), received ${String(options.maxBuffered)}.`
|
|
2188
|
+
);
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
/** Queues the entry. Resolves ok straight away; delivery happens in the background. */
|
|
2192
|
+
log(entry) {
|
|
2193
|
+
this.queue.push({ entry, failures: 0 });
|
|
2194
|
+
if (this.queue.length > this.maxBuffered) {
|
|
2195
|
+
const overflow = this.queue.splice(
|
|
2196
|
+
0,
|
|
2197
|
+
this.queue.length - this.maxBuffered
|
|
2198
|
+
);
|
|
2199
|
+
this.drop(
|
|
2200
|
+
overflow,
|
|
2201
|
+
`the buffer of ${this.maxBuffered} entries was full because the wrapped adapter keeps failing`
|
|
2202
|
+
);
|
|
2203
|
+
}
|
|
2204
|
+
if (this.queue.length >= this.batchSize) {
|
|
2205
|
+
this.startDraining();
|
|
2206
|
+
} else {
|
|
2207
|
+
this.scheduleDrain();
|
|
2208
|
+
}
|
|
2209
|
+
return Promise.resolve({ ok: true });
|
|
2210
|
+
}
|
|
2211
|
+
/**
|
|
2212
|
+
* Delivers everything queued, retrying as configured, and resolves once the
|
|
2213
|
+
* queue is empty. Resolves `ok: false` if any entries were given up on since
|
|
2214
|
+
* the last flush. With retries on and the sink down this can take a while
|
|
2215
|
+
* (the waits add up to well over a minute with the defaults).
|
|
2216
|
+
*/
|
|
2217
|
+
async flush() {
|
|
2218
|
+
this.flushers += 1;
|
|
2219
|
+
this.backoffTimer?.ref?.();
|
|
2220
|
+
try {
|
|
2221
|
+
this.startDraining();
|
|
2222
|
+
while (this.draining !== null) {
|
|
2223
|
+
await this.draining;
|
|
2224
|
+
}
|
|
2225
|
+
} finally {
|
|
2226
|
+
this.flushers -= 1;
|
|
2227
|
+
}
|
|
2228
|
+
if (this.droppedSinceFlush > 0) {
|
|
2229
|
+
const count = this.droppedSinceFlush;
|
|
2230
|
+
const reason = this.lastDropReason;
|
|
2231
|
+
this.droppedSinceFlush = 0;
|
|
2232
|
+
this.lastDropReason = "";
|
|
2233
|
+
return {
|
|
2234
|
+
ok: false,
|
|
2235
|
+
error: `BatchingErrorAdapter gave up on ${count} entr${count === 1 ? "y" : "ies"}: ${reason}`
|
|
2236
|
+
};
|
|
2237
|
+
}
|
|
2238
|
+
return { ok: true };
|
|
2239
|
+
}
|
|
2240
|
+
scheduleDrain() {
|
|
2241
|
+
if (this.waitTimer !== null || this.maxWaitMs === Infinity) {
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
2244
|
+
const timer = setTimeout(() => {
|
|
2245
|
+
this.waitTimer = null;
|
|
2246
|
+
this.startDraining();
|
|
2247
|
+
}, this.maxWaitMs);
|
|
2248
|
+
timer.unref?.();
|
|
2249
|
+
this.waitTimer = timer;
|
|
2250
|
+
}
|
|
2251
|
+
startDraining() {
|
|
2252
|
+
if (this.waitTimer !== null) {
|
|
2253
|
+
clearTimeout(this.waitTimer);
|
|
2254
|
+
this.waitTimer = null;
|
|
2255
|
+
}
|
|
2256
|
+
if (this.draining !== null || this.queue.length === 0) {
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
this.draining = this.drain().catch((error) => {
|
|
2260
|
+
console.error(`${this.logPrefix} BatchingErrorAdapter:`, error);
|
|
2261
|
+
}).finally(() => {
|
|
2262
|
+
this.draining = null;
|
|
2263
|
+
if (this.queue.length > 0) {
|
|
2264
|
+
this.scheduleDrain();
|
|
2265
|
+
}
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
2268
|
+
/** Delivers batches until the queue is empty, waiting out a backoff after each failure. */
|
|
2269
|
+
async drain() {
|
|
2270
|
+
let failedRounds = 0;
|
|
2271
|
+
while (this.queue.length > 0) {
|
|
2272
|
+
const batch = this.queue.slice(0, this.batchSize);
|
|
2273
|
+
const failure = await this.deliver(batch);
|
|
2274
|
+
if (failure === void 0) {
|
|
2275
|
+
failedRounds = 0;
|
|
2276
|
+
continue;
|
|
2277
|
+
}
|
|
2278
|
+
failedRounds += 1;
|
|
2279
|
+
const exhausted = this.queue.filter(
|
|
2280
|
+
(item) => item.failures > this.retries
|
|
2281
|
+
);
|
|
2282
|
+
if (exhausted.length > 0) {
|
|
2283
|
+
this.queue = this.queue.filter(
|
|
2284
|
+
(item) => item.failures <= this.retries
|
|
2285
|
+
);
|
|
2286
|
+
this.drop(
|
|
2287
|
+
exhausted,
|
|
2288
|
+
`delivery failed ${this.retries + 1} time${this.retries === 0 ? "" : "s"}; last error: ${failure}`
|
|
2289
|
+
);
|
|
2290
|
+
}
|
|
2291
|
+
if (this.queue.length > 0) {
|
|
2292
|
+
await this.backoff(failedRounds);
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
/**
|
|
2297
|
+
* Tries to deliver `batch` (the front of the queue). Delivered entries leave
|
|
2298
|
+
* the queue; an entry that failed has its failure count raised and stays.
|
|
2299
|
+
* Resolves to the failure message, or undefined when everything went through.
|
|
2300
|
+
*/
|
|
2301
|
+
async deliver(batch) {
|
|
2302
|
+
const remove = (delivered) => {
|
|
2303
|
+
const gone = new Set(delivered);
|
|
2304
|
+
this.queue = this.queue.filter(
|
|
2305
|
+
(item) => !gone.has(item)
|
|
2306
|
+
);
|
|
2307
|
+
};
|
|
2308
|
+
if (this.useBatchMethod && hasLogBatch(this.inner)) {
|
|
2309
|
+
const result = await this.safely(
|
|
2310
|
+
() => this.inner.logBatch(
|
|
2311
|
+
batch.map(
|
|
2312
|
+
(item) => item.entry
|
|
2313
|
+
)
|
|
2314
|
+
)
|
|
2315
|
+
);
|
|
2316
|
+
if (result.ok) {
|
|
2317
|
+
remove(batch);
|
|
2318
|
+
return void 0;
|
|
2319
|
+
}
|
|
2320
|
+
for (const item of batch) {
|
|
2321
|
+
item.failures += 1;
|
|
2322
|
+
}
|
|
2323
|
+
return result.error ?? "the wrapped adapter reported a failure";
|
|
2324
|
+
}
|
|
2325
|
+
for (const item of batch) {
|
|
2326
|
+
const result = await this.safely(
|
|
2327
|
+
() => this.inner.log(item.entry)
|
|
2328
|
+
);
|
|
2329
|
+
if (result.ok) {
|
|
2330
|
+
remove([item]);
|
|
2331
|
+
continue;
|
|
2332
|
+
}
|
|
2333
|
+
item.failures += 1;
|
|
2334
|
+
return result.error ?? "the wrapped adapter reported a failure";
|
|
2335
|
+
}
|
|
2336
|
+
return void 0;
|
|
2337
|
+
}
|
|
2338
|
+
/** The wrapped adapter should not throw, but a bug in one must not stop the queue. */
|
|
2339
|
+
async safely(call) {
|
|
2340
|
+
try {
|
|
2341
|
+
return await call();
|
|
2342
|
+
} catch (error) {
|
|
2343
|
+
return { ok: false, error: describeError(error) };
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
backoff(failedRounds) {
|
|
2347
|
+
return new Promise((resolve) => {
|
|
2348
|
+
const timer = setTimeout(
|
|
2349
|
+
() => {
|
|
2350
|
+
this.backoffTimer = null;
|
|
2351
|
+
resolve();
|
|
2352
|
+
},
|
|
2353
|
+
backoffDelayMs(failedRounds, this.retryDelayMs, this.maxRetryDelayMs)
|
|
2354
|
+
);
|
|
2355
|
+
if (this.flushers === 0) {
|
|
2356
|
+
timer.unref?.();
|
|
2357
|
+
}
|
|
2358
|
+
this.backoffTimer = timer;
|
|
2359
|
+
});
|
|
2360
|
+
}
|
|
2361
|
+
drop(items, reason) {
|
|
2362
|
+
if (items.length === 0) {
|
|
2363
|
+
return;
|
|
2364
|
+
}
|
|
2365
|
+
this.droppedSinceFlush += items.length;
|
|
2366
|
+
this.lastDropReason = reason;
|
|
2367
|
+
console.error(
|
|
2368
|
+
`${this.logPrefix} BatchingErrorAdapter: gave up on ${items.length} entr${items.length === 1 ? "y" : "ies"}: ${reason}`
|
|
2369
|
+
);
|
|
2370
|
+
try {
|
|
2371
|
+
this.onDrop?.(
|
|
2372
|
+
items.map((item) => item.entry),
|
|
2373
|
+
reason
|
|
2374
|
+
);
|
|
2375
|
+
} catch {
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
};
|
|
2097
2379
|
export {
|
|
2380
|
+
BatchingErrorAdapter,
|
|
2098
2381
|
CloudWatchLogsErrorAdapter,
|
|
2099
2382
|
CompositeErrorAdapter,
|
|
2100
2383
|
ConsoleErrorAdapter,
|