@andrey4emk/npm-app-back-b24 5.1.0 → 5.2.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/bitrix24/b24/retry.ts +9 -2
- package/logs/logs.ts +58 -1
- package/package.json +1 -1
package/bitrix24/b24/retry.ts
CHANGED
|
@@ -133,8 +133,11 @@ export function retryDelayFor(error: unknown, attempt: number): number {
|
|
|
133
133
|
return isNonJsonResponseError(error) ? NON_JSON_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1) : RETRY_DELAY_MS;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
/** Начало фразы про создающий вызов — по нему уровень отмены повтора отличает её от резервирующей */
|
|
137
|
+
const CREATING_REASON_HEAD = "вызов создаёт сущности (";
|
|
138
|
+
|
|
136
139
|
/** Фраза для лога: повтор отменён, потому что вызов создаёт сущности */
|
|
137
|
-
const creatingReason = (what: string): string =>
|
|
140
|
+
const creatingReason = (what: string): string => `${CREATING_REASON_HEAD}${what})`;
|
|
138
141
|
|
|
139
142
|
/** Фраза для лога: повтор отменён, потому что вызов резервирует данные на портале */
|
|
140
143
|
const reservingReason = (what: string): string => `вызов резервирует данные на портале (${what})`;
|
|
@@ -447,8 +450,12 @@ export async function runWithRetry<T>(fn: () => Promise<T>, label: string, block
|
|
|
447
450
|
// потеряться уже после того, как портал его выполнил. Различить нельзя,
|
|
448
451
|
// поэтому вызовы под ограничением не повторяем — лучше вернуть ошибку,
|
|
449
452
|
// чем создать дубликат или потерять зарезервированный пакет
|
|
453
|
+
// Отмена только резервирующего вызова (event.offline.get) — warn: приложение и так
|
|
454
|
+
// пишет свою строку error о том же сбое, а две строки дали бы два сообщения в
|
|
455
|
+
// Телеграме. Отмена создающего остаётся error: сущность могла не создаться
|
|
450
456
|
if (blockReason && !proven) {
|
|
451
|
-
|
|
457
|
+
const level = blockReason.includes(CREATING_REASON_HEAD) ? "error" : "warn";
|
|
458
|
+
logs.add(`${label}${code}: повтор отменён, ${blockReason} — ${msg}`, level);
|
|
452
459
|
throw error;
|
|
453
460
|
}
|
|
454
461
|
|
package/logs/logs.ts
CHANGED
|
@@ -68,6 +68,26 @@ function describeSendError(error: unknown): string {
|
|
|
68
68
|
return typeof causeLabel === "string" && causeLabel ? `${name} (${causeLabel})` : name;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Признаки разового сетевого сбоя в тексте error-лога: обрыв, таймаут, недоступный хост.
|
|
73
|
+
* Проверяется текст, а не тип ошибки: строки о сбое пишут и сами приложения
|
|
74
|
+
* («Не удалось получить события. read ECONNRESET»), типа ошибки у логгера нет
|
|
75
|
+
*/
|
|
76
|
+
const NETWORK_FAILURE_RE =
|
|
77
|
+
/ECONNRESET|socket hang up|ETIMEDOUT|ECONNREFUSED|EHOSTUNREACH|ENETUNREACH|ENOTFOUND|EAI_AGAIN|UND_ERR_SOCKET|UND_ERR_CONNECT_TIMEOUT|Request timeout exceeded|fetch failed|other side closed|Error connecting to authorization server/i;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Строки с сетевым признаком, которые уходят сразу и по одной: отменённое создание
|
|
81
|
+
* сущности, провал обмена токена, неизвестный статус доставки — их разбирает человек
|
|
82
|
+
*/
|
|
83
|
+
const NETWORK_KEEP_RE = /вызов создаёт сущности|обновление токена не удалось|статус неизвестен/;
|
|
84
|
+
|
|
85
|
+
/** Окно, за которое считаются сетевые сбои, и пауза между сводками, мс */
|
|
86
|
+
const NETWORK_WINDOW_MS = 15 * 60_000;
|
|
87
|
+
|
|
88
|
+
/** Со скольких сетевых сбоев за окно в Телеграм уходит сводка */
|
|
89
|
+
const NETWORK_THRESHOLD = 3;
|
|
90
|
+
|
|
71
91
|
/** Ключ ретранслятора — только видимые символы ASCII, без пробелов и управляющих */
|
|
72
92
|
const RELAY_KEY_RE = /^[\x21-\x7e]+$/;
|
|
73
93
|
|
|
@@ -140,6 +160,12 @@ class LogsAPI {
|
|
|
140
160
|
/** Ключ клиента ретранслятора (`LOG_RELAY_KEY`); `undefined` — канал выключен */
|
|
141
161
|
private readonly relayKey: string | undefined;
|
|
142
162
|
|
|
163
|
+
/** Время сетевых сбоев за последние `NETWORK_WINDOW_MS`, мс */
|
|
164
|
+
private networkFailures: number[] = [];
|
|
165
|
+
|
|
166
|
+
/** Когда ушла последняя сводка сетевых сбоев, мс */
|
|
167
|
+
private lastNetworkSummaryAt = Number.NEGATIVE_INFINITY;
|
|
168
|
+
|
|
143
169
|
constructor() {
|
|
144
170
|
this.initConfig();
|
|
145
171
|
const url = process.env.LOG_RELAY_URL?.trim() || undefined;
|
|
@@ -189,6 +215,7 @@ class LogsAPI {
|
|
|
189
215
|
* независимо от настройки `error.enabled` в `log.json`. Настройка `enabled`
|
|
190
216
|
* управляет только выводом в консоль. Это сделано намеренно, чтобы критические
|
|
191
217
|
* ошибки всегда доходили до команды даже при отключённом консольном логировании.
|
|
218
|
+
* Исключение — разовые сетевые сбои: они уходят сводкой, см. `routeToRelay()`.
|
|
192
219
|
*
|
|
193
220
|
* @param message - Текст сообщения или объект для вывода
|
|
194
221
|
* @param level - Уровень логирования (trace, debug, info, warn, error)
|
|
@@ -204,7 +231,7 @@ class LogsAPI {
|
|
|
204
231
|
// В консоль ниже уходят исходные message и jsonData — маскировка телефонов
|
|
205
232
|
// касается только текста для Телеграма
|
|
206
233
|
if (levelStr === "error") {
|
|
207
|
-
this.
|
|
234
|
+
this.routeToRelay(messageText, jsonData);
|
|
208
235
|
}
|
|
209
236
|
|
|
210
237
|
// Проверяем конфиг, и если логирование отключено, то выходим
|
|
@@ -228,6 +255,36 @@ class LogsAPI {
|
|
|
228
255
|
}
|
|
229
256
|
}
|
|
230
257
|
|
|
258
|
+
/**
|
|
259
|
+
* Решает, уходит ли error-лог в Телеграм сразу.
|
|
260
|
+
*
|
|
261
|
+
* Разовый сетевой сбой проходит сам, а сообщение о нём в Телеграме — шум: 25.10.2026
|
|
262
|
+
* один недоступный сервер авторизации дал 14 сообщений подряд. Поэтому строка
|
|
263
|
+
* с сетевым признаком остаётся только в консоли, а в Телеграм уходит одна сводка,
|
|
264
|
+
* когда за 15 минут набралось 3 сбоя и больше; следующая — не раньше чем через 15 минут.
|
|
265
|
+
* Длинный обрыв так и виден — сводкой раз в 15 минут.
|
|
266
|
+
*/
|
|
267
|
+
private routeToRelay(message: string, jsonData?: unknown): void {
|
|
268
|
+
if (!this.relayUrl || !this.relayKey) return;
|
|
269
|
+
|
|
270
|
+
if (!NETWORK_FAILURE_RE.test(message) || NETWORK_KEEP_RE.test(message)) {
|
|
271
|
+
this.sendToRelay(message, jsonData);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const now = Date.now();
|
|
276
|
+
this.networkFailures = this.networkFailures.filter((at) => now - at < NETWORK_WINDOW_MS);
|
|
277
|
+
this.networkFailures.push(now);
|
|
278
|
+
|
|
279
|
+
if (this.networkFailures.length < NETWORK_THRESHOLD) return;
|
|
280
|
+
if (now - this.lastNetworkSummaryAt < NETWORK_WINDOW_MS) return;
|
|
281
|
+
|
|
282
|
+
this.lastNetworkSummaryAt = now;
|
|
283
|
+
const minutes = NETWORK_WINDOW_MS / 60_000;
|
|
284
|
+
const summary = `Сетевые сбои: ${this.networkFailures.length} за ${minutes} мин (одиночные в Телеграм не отправляются, все строки — в логе контейнера). Последний:\n${message}`;
|
|
285
|
+
this.sendToRelay(summary, jsonData);
|
|
286
|
+
}
|
|
287
|
+
|
|
231
288
|
/**
|
|
232
289
|
* Готовит текст error-лога и отправляет его в ретранслятор (fire-and-forget).
|
|
233
290
|
*
|