@mhmdhammoud/meritt-utils 1.6.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/elastic-transport.js +129 -67
- package/package.json +1 -1
- package/src/lib/elastic-transport.ts +142 -84
|
@@ -33,42 +33,141 @@ function getIndexName(index, time) {
|
|
|
33
33
|
}
|
|
34
34
|
return index.replace('%{DATE}', time.substring(0, 10));
|
|
35
35
|
}
|
|
36
|
-
function
|
|
36
|
+
function createBulkSender(opts, client, splitter) {
|
|
37
37
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
38
38
|
const esVersion = Number((_b = (_a = opts.esVersion) !== null && _a !== void 0 ? _a : opts['es-version']) !== null && _b !== void 0 ? _b : 7);
|
|
39
39
|
const index = (_c = opts.index) !== null && _c !== void 0 ? _c : 'pino';
|
|
40
40
|
const buildIndexName = typeof index === 'function' ? index : null;
|
|
41
41
|
const opType = esVersion >= 7 ? undefined : undefined;
|
|
42
|
+
const flushBytes = (_e = (_d = opts.flushBytes) !== null && _d !== void 0 ? _d : opts['flush-bytes']) !== null && _e !== void 0 ? _e : 1000;
|
|
43
|
+
const flushInterval = (_g = (_f = opts.flushInterval) !== null && _f !== void 0 ? _f : opts['flush-interval']) !== null && _g !== void 0 ? _g : 3000;
|
|
44
|
+
let buffer = [];
|
|
45
|
+
let bufferedBytes = 0;
|
|
46
|
+
let timer;
|
|
47
|
+
let isFlushing = false;
|
|
48
|
+
let flushAgain = false;
|
|
42
49
|
const indexName = (time = new Date().toISOString()) => buildIndexName ? buildIndexName(time) : getIndexName(index, time);
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
const clearFlushTimer = () => {
|
|
51
|
+
if (timer) {
|
|
52
|
+
clearTimeout(timer);
|
|
53
|
+
timer = undefined;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const scheduleFlush = () => {
|
|
57
|
+
var _a;
|
|
58
|
+
if (timer || buffer.length === 0) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
timer = setTimeout(() => {
|
|
62
|
+
timer = undefined;
|
|
63
|
+
void flush();
|
|
64
|
+
}, flushInterval);
|
|
65
|
+
(_a = timer.unref) === null || _a === void 0 ? void 0 : _a.call(timer);
|
|
66
|
+
};
|
|
67
|
+
const buildOperation = (doc) => {
|
|
68
|
+
var _a, _b;
|
|
69
|
+
try {
|
|
50
70
|
const d = doc;
|
|
51
71
|
const date = (_b = (_a = d.time) !== null && _a !== void 0 ? _a : d['@timestamp']) !== null && _b !== void 0 ? _b : new Date().toISOString();
|
|
52
72
|
if (opType === 'create') {
|
|
53
73
|
d['@timestamp'] = date;
|
|
54
74
|
}
|
|
55
|
-
return
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
75
|
+
return [
|
|
76
|
+
{
|
|
77
|
+
index: {
|
|
78
|
+
_index: indexName(date),
|
|
79
|
+
op_type: opType,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
doc,
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
catch (_c) {
|
|
86
|
+
return [
|
|
87
|
+
{
|
|
88
|
+
index: {
|
|
89
|
+
_index: indexName(),
|
|
90
|
+
op_type: opType,
|
|
91
|
+
},
|
|
59
92
|
},
|
|
60
|
-
|
|
93
|
+
doc,
|
|
94
|
+
];
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const emitDroppedDocument = (doc, cause) => {
|
|
98
|
+
const error = new Error('Dropped document');
|
|
99
|
+
error.document = doc;
|
|
100
|
+
error.cause = cause;
|
|
101
|
+
splitter.emit('insertError', error);
|
|
102
|
+
};
|
|
103
|
+
const flush = async () => {
|
|
104
|
+
var _a, _b;
|
|
105
|
+
if (isFlushing) {
|
|
106
|
+
flushAgain = true;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
clearFlushTimer();
|
|
110
|
+
if (buffer.length === 0) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
isFlushing = true;
|
|
114
|
+
const batch = buffer;
|
|
115
|
+
buffer = [];
|
|
116
|
+
bufferedBytes = 0;
|
|
117
|
+
try {
|
|
118
|
+
const operations = batch.flatMap(buildOperation);
|
|
119
|
+
const response = await client.bulk({
|
|
120
|
+
operations,
|
|
121
|
+
refresh: false,
|
|
122
|
+
timeout: opts.requestTimeout ? `${opts.requestTimeout}ms` : undefined,
|
|
123
|
+
});
|
|
124
|
+
const body = response;
|
|
125
|
+
if (body.errors && Array.isArray(body.items)) {
|
|
126
|
+
body.items.forEach((item, index) => {
|
|
127
|
+
const result = Object.values(item)[0];
|
|
128
|
+
if (result === null || result === void 0 ? void 0 : result.error) {
|
|
129
|
+
emitDroppedDocument(batch[index], result.error);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
splitter.emit('insert', {
|
|
134
|
+
successful: batch.length,
|
|
135
|
+
failed: body.errors ? ((_b = (_a = body.items) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) : 0,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
splitter.emit('error', err);
|
|
140
|
+
// Drop the failed batch instead of wedging the stream. The next log line
|
|
141
|
+
// creates a fresh bulk request and can recover without a process restart.
|
|
142
|
+
batch.forEach((doc) => emitDroppedDocument(doc, err));
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
isFlushing = false;
|
|
146
|
+
if (flushAgain || buffer.length > 0) {
|
|
147
|
+
flushAgain = false;
|
|
148
|
+
scheduleFlush();
|
|
149
|
+
if (bufferedBytes >= flushBytes) {
|
|
150
|
+
void flush();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
add(doc) {
|
|
157
|
+
buffer.push(doc);
|
|
158
|
+
bufferedBytes += Buffer.byteLength(JSON.stringify(doc));
|
|
159
|
+
if (bufferedBytes >= flushBytes) {
|
|
160
|
+
void flush();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
scheduleFlush();
|
|
61
164
|
},
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
165
|
+
flush,
|
|
166
|
+
async close() {
|
|
167
|
+
clearFlushTimer();
|
|
168
|
+
await flush();
|
|
66
169
|
},
|
|
67
|
-
}
|
|
68
|
-
bulkInsert.then((stats) => splitter.emit('insert', stats), (err) => {
|
|
69
|
-
splitter.emit('error', err);
|
|
70
|
-
onFatalError(err);
|
|
71
|
-
});
|
|
170
|
+
};
|
|
72
171
|
}
|
|
73
172
|
const createElasticTransport = (opts = {}) => {
|
|
74
173
|
const splitter = split(function (line) {
|
|
@@ -119,56 +218,19 @@ const createElasticTransport = (opts = {}) => {
|
|
|
119
218
|
clientOpts.ConnectionPool = opts.ConnectionPool;
|
|
120
219
|
}
|
|
121
220
|
const client = new elasticsearch_1.Client(clientOpts);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const pool = client.connectionPool;
|
|
221
|
+
const bulkSender = createBulkSender(opts, client, splitter);
|
|
222
|
+
splitter.on('data', (doc) => {
|
|
223
|
+
bulkSender.add(doc);
|
|
224
|
+
});
|
|
225
|
+
splitter.on('finish', () => {
|
|
226
|
+
void bulkSender.close();
|
|
227
|
+
});
|
|
130
228
|
const splitterWithDestroy = splitter;
|
|
131
229
|
const originalDestroy = splitterWithDestroy.destroy.bind(splitterWithDestroy);
|
|
132
|
-
const startBulkHandler = () => {
|
|
133
|
-
if (isTransportClosed || isBulkHandlerActive) {
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
isBulkHandlerActive = true;
|
|
137
|
-
initializeBulkHandler(opts, client, splitter, () => {
|
|
138
|
-
isBulkHandlerActive = false;
|
|
139
|
-
scheduleBulkHandlerRestart();
|
|
140
|
-
});
|
|
141
|
-
};
|
|
142
|
-
const scheduleBulkHandlerRestart = () => {
|
|
143
|
-
var _a, _b, _c;
|
|
144
|
-
if (isTransportClosed || isRestartScheduled) {
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
isRestartScheduled = true;
|
|
148
|
-
if (typeof pool.resurrect === 'function') {
|
|
149
|
-
pool.resurrect({ name: 'elasticsearch-js' });
|
|
150
|
-
}
|
|
151
|
-
const retryDelayMs = Math.min(Number((_b = (_a = opts.flushInterval) !== null && _a !== void 0 ? _a : opts['flush-interval']) !== null && _b !== void 0 ? _b : 3000), 5000);
|
|
152
|
-
const timer = setTimeout(() => {
|
|
153
|
-
isRestartScheduled = false;
|
|
154
|
-
startBulkHandler();
|
|
155
|
-
}, retryDelayMs);
|
|
156
|
-
(_c = timer.unref) === null || _c === void 0 ? void 0 : _c.call(timer);
|
|
157
|
-
};
|
|
158
230
|
splitterWithDestroy.destroy = function (err) {
|
|
159
|
-
|
|
160
|
-
scheduleBulkHandlerRestart();
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
isTransportClosed = true;
|
|
231
|
+
void bulkSender.close();
|
|
164
232
|
originalDestroy(err);
|
|
165
233
|
};
|
|
166
|
-
client.diagnostic.on('resurrect', () => {
|
|
167
|
-
if (!isBulkHandlerActive) {
|
|
168
|
-
scheduleBulkHandlerRestart();
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
startBulkHandler();
|
|
172
234
|
return splitter;
|
|
173
235
|
};
|
|
174
236
|
exports.createElasticTransport = createElasticTransport;
|
package/package.json
CHANGED
|
@@ -17,7 +17,6 @@ const split = require('split2') as (
|
|
|
17
17
|
fn: (line: string) => unknown,
|
|
18
18
|
opts?: { autoDestroy?: boolean }
|
|
19
19
|
) => NodeJS.ReadWriteStream
|
|
20
|
-
import { Readable } from 'stream'
|
|
21
20
|
import { Client } from '@elastic/elasticsearch'
|
|
22
21
|
import type { ClientOptions } from '@elastic/elasticsearch'
|
|
23
22
|
|
|
@@ -73,54 +72,161 @@ function getIndexName(
|
|
|
73
72
|
return index.replace('%{DATE}', time.substring(0, 10))
|
|
74
73
|
}
|
|
75
74
|
|
|
76
|
-
function
|
|
75
|
+
function createBulkSender(
|
|
77
76
|
opts: ElasticTransportOptions,
|
|
78
77
|
client: Client,
|
|
79
|
-
splitter: NodeJS.ReadWriteStream
|
|
80
|
-
|
|
81
|
-
|
|
78
|
+
splitter: NodeJS.ReadWriteStream
|
|
79
|
+
): {
|
|
80
|
+
add: (doc: unknown) => void
|
|
81
|
+
flush: () => Promise<void>
|
|
82
|
+
close: () => Promise<void>
|
|
83
|
+
} {
|
|
82
84
|
const esVersion = Number(opts.esVersion ?? opts['es-version'] ?? 7)
|
|
83
85
|
const index = opts.index ?? 'pino'
|
|
84
86
|
const buildIndexName = typeof index === 'function' ? index : null
|
|
85
87
|
const opType = esVersion >= 7 ? undefined : undefined
|
|
88
|
+
const flushBytes = opts.flushBytes ?? opts['flush-bytes'] ?? 1000
|
|
89
|
+
const flushInterval = opts.flushInterval ?? opts['flush-interval'] ?? 3000
|
|
90
|
+
|
|
91
|
+
let buffer: unknown[] = []
|
|
92
|
+
let bufferedBytes = 0
|
|
93
|
+
let timer: NodeJS.Timeout | undefined
|
|
94
|
+
let isFlushing = false
|
|
95
|
+
let flushAgain = false
|
|
86
96
|
|
|
87
97
|
const indexName = (time = new Date().toISOString()) =>
|
|
88
98
|
buildIndexName ? buildIndexName(time) : getIndexName(index as string, time)
|
|
89
99
|
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
const clearFlushTimer = () => {
|
|
101
|
+
if (timer) {
|
|
102
|
+
clearTimeout(timer)
|
|
103
|
+
timer = undefined
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const scheduleFlush = () => {
|
|
108
|
+
if (timer || buffer.length === 0) {
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
timer = setTimeout(() => {
|
|
112
|
+
timer = undefined
|
|
113
|
+
void flush()
|
|
114
|
+
}, flushInterval)
|
|
115
|
+
timer.unref?.()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const buildOperation = (doc: unknown): [Record<string, unknown>, unknown] => {
|
|
119
|
+
try {
|
|
96
120
|
const d = doc as LogDocument
|
|
97
121
|
const date = d.time ?? d['@timestamp'] ?? new Date().toISOString()
|
|
98
122
|
if (opType === 'create') {
|
|
99
123
|
d['@timestamp'] = date
|
|
100
124
|
}
|
|
101
|
-
return
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
125
|
+
return [
|
|
126
|
+
{
|
|
127
|
+
index: {
|
|
128
|
+
_index: indexName(date),
|
|
129
|
+
op_type: opType,
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
doc,
|
|
133
|
+
]
|
|
134
|
+
} catch {
|
|
135
|
+
return [
|
|
136
|
+
{
|
|
137
|
+
index: {
|
|
138
|
+
_index: indexName(),
|
|
139
|
+
op_type: opType,
|
|
140
|
+
},
|
|
105
141
|
},
|
|
142
|
+
doc,
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const emitDroppedDocument = (doc: unknown, cause?: unknown) => {
|
|
148
|
+
const error = new Error('Dropped document') as Error & {
|
|
149
|
+
document: unknown
|
|
150
|
+
cause?: unknown
|
|
151
|
+
}
|
|
152
|
+
error.document = doc
|
|
153
|
+
error.cause = cause
|
|
154
|
+
splitter.emit('insertError', error)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const flush = async (): Promise<void> => {
|
|
158
|
+
if (isFlushing) {
|
|
159
|
+
flushAgain = true
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
clearFlushTimer()
|
|
163
|
+
if (buffer.length === 0) {
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
isFlushing = true
|
|
168
|
+
const batch = buffer
|
|
169
|
+
buffer = []
|
|
170
|
+
bufferedBytes = 0
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const operations = batch.flatMap(buildOperation)
|
|
174
|
+
const response = await client.bulk({
|
|
175
|
+
operations,
|
|
176
|
+
refresh: false,
|
|
177
|
+
timeout: opts.requestTimeout ? `${opts.requestTimeout}ms` : undefined,
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
const body = response as {
|
|
181
|
+
errors?: boolean
|
|
182
|
+
items?: Array<Record<string, { error?: unknown }>>
|
|
106
183
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
184
|
+
if (body.errors && Array.isArray(body.items)) {
|
|
185
|
+
body.items.forEach((item, index) => {
|
|
186
|
+
const result = Object.values(item)[0]
|
|
187
|
+
if (result?.error) {
|
|
188
|
+
emitDroppedDocument(batch[index], result.error)
|
|
189
|
+
}
|
|
190
|
+
})
|
|
111
191
|
}
|
|
112
|
-
error.document = doc
|
|
113
|
-
splitter.emit('insertError', error)
|
|
114
|
-
},
|
|
115
|
-
})
|
|
116
192
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
193
|
+
splitter.emit('insert', {
|
|
194
|
+
successful: batch.length,
|
|
195
|
+
failed: body.errors ? (body.items?.length ?? 0) : 0,
|
|
196
|
+
})
|
|
197
|
+
} catch (err) {
|
|
120
198
|
splitter.emit('error', err)
|
|
121
|
-
|
|
199
|
+
// Drop the failed batch instead of wedging the stream. The next log line
|
|
200
|
+
// creates a fresh bulk request and can recover without a process restart.
|
|
201
|
+
batch.forEach((doc) => emitDroppedDocument(doc, err))
|
|
202
|
+
} finally {
|
|
203
|
+
isFlushing = false
|
|
204
|
+
if (flushAgain || buffer.length > 0) {
|
|
205
|
+
flushAgain = false
|
|
206
|
+
scheduleFlush()
|
|
207
|
+
if (bufferedBytes >= flushBytes) {
|
|
208
|
+
void flush()
|
|
209
|
+
}
|
|
210
|
+
}
|
|
122
211
|
}
|
|
123
|
-
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
add(doc: unknown) {
|
|
216
|
+
buffer.push(doc)
|
|
217
|
+
bufferedBytes += Buffer.byteLength(JSON.stringify(doc))
|
|
218
|
+
if (bufferedBytes >= flushBytes) {
|
|
219
|
+
void flush()
|
|
220
|
+
return
|
|
221
|
+
}
|
|
222
|
+
scheduleFlush()
|
|
223
|
+
},
|
|
224
|
+
flush,
|
|
225
|
+
async close() {
|
|
226
|
+
clearFlushTimer()
|
|
227
|
+
await flush()
|
|
228
|
+
},
|
|
229
|
+
}
|
|
124
230
|
}
|
|
125
231
|
|
|
126
232
|
export const createElasticTransport = (
|
|
@@ -179,71 +285,23 @@ export const createElasticTransport = (
|
|
|
179
285
|
}
|
|
180
286
|
|
|
181
287
|
const client = new Client(clientOpts)
|
|
288
|
+
const bulkSender = createBulkSender(opts, client, splitter)
|
|
182
289
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
let isTransportClosed = false
|
|
290
|
+
splitter.on('data', (doc) => {
|
|
291
|
+
bulkSender.add(doc)
|
|
292
|
+
})
|
|
293
|
+
splitter.on('finish', () => {
|
|
294
|
+
void bulkSender.close()
|
|
295
|
+
})
|
|
190
296
|
|
|
191
|
-
const pool = client.connectionPool as {
|
|
192
|
-
resurrect?: (opts: { name: string }) => void
|
|
193
|
-
}
|
|
194
297
|
const splitterWithDestroy = splitter as NodeJS.ReadWriteStream & {
|
|
195
298
|
destroy: (err?: Error) => void
|
|
196
299
|
}
|
|
197
300
|
const originalDestroy = splitterWithDestroy.destroy.bind(splitterWithDestroy)
|
|
198
|
-
|
|
199
|
-
const startBulkHandler = () => {
|
|
200
|
-
if (isTransportClosed || isBulkHandlerActive) {
|
|
201
|
-
return
|
|
202
|
-
}
|
|
203
|
-
isBulkHandlerActive = true
|
|
204
|
-
initializeBulkHandler(opts, client, splitter, () => {
|
|
205
|
-
isBulkHandlerActive = false
|
|
206
|
-
scheduleBulkHandlerRestart()
|
|
207
|
-
})
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const scheduleBulkHandlerRestart = () => {
|
|
211
|
-
if (isTransportClosed || isRestartScheduled) {
|
|
212
|
-
return
|
|
213
|
-
}
|
|
214
|
-
isRestartScheduled = true
|
|
215
|
-
|
|
216
|
-
if (typeof pool.resurrect === 'function') {
|
|
217
|
-
pool.resurrect({ name: 'elasticsearch-js' })
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const retryDelayMs = Math.min(
|
|
221
|
-
Number(opts.flushInterval ?? opts['flush-interval'] ?? 3000),
|
|
222
|
-
5000
|
|
223
|
-
)
|
|
224
|
-
const timer = setTimeout(() => {
|
|
225
|
-
isRestartScheduled = false
|
|
226
|
-
startBulkHandler()
|
|
227
|
-
}, retryDelayMs)
|
|
228
|
-
timer.unref?.()
|
|
229
|
-
}
|
|
230
|
-
|
|
231
301
|
splitterWithDestroy.destroy = function (err?: Error) {
|
|
232
|
-
|
|
233
|
-
scheduleBulkHandlerRestart()
|
|
234
|
-
return
|
|
235
|
-
}
|
|
236
|
-
isTransportClosed = true
|
|
302
|
+
void bulkSender.close()
|
|
237
303
|
originalDestroy(err)
|
|
238
304
|
}
|
|
239
305
|
|
|
240
|
-
client.diagnostic.on('resurrect', () => {
|
|
241
|
-
if (!isBulkHandlerActive) {
|
|
242
|
-
scheduleBulkHandlerRestart()
|
|
243
|
-
}
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
startBulkHandler()
|
|
247
|
-
|
|
248
306
|
return splitter
|
|
249
307
|
}
|