@andrey4emk/npm-app-back-b24 3.0.2 → 3.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/logs/logs.ts CHANGED
@@ -19,6 +19,17 @@ interface LogLevelConfig {
19
19
  /** Полная конфигурация логирования */
20
20
  type LogConfig = Record<LogLevel, LogLevelConfig>;
21
21
 
22
+ // ==================== Константы B24 Chat ====================
23
+
24
+ /** ID бота в B24 для отправки error-логов */
25
+ const B24_BOT_ID = 66600;
26
+
27
+ /** CLIENT_ID бота в B24 */
28
+ const B24_CLIENT_ID = "6rf74zyu842h6op1z186b08k7aavqq7z";
29
+
30
+ /** Лимит длины сообщения для B24 чата (безопасный, с запасом на BB-обёртку) */
31
+ const B24_TEXT_LIMIT = 4000;
32
+
22
33
  // ==================== Настройки ====================
23
34
 
24
35
  const configDir: string = process.env.CONFIG_DIR || "../config";
@@ -62,8 +73,20 @@ class LogsAPI {
62
73
 
63
74
  private readonly resetColor = "\x1b[0m";
64
75
 
76
+ /** Webhook URL для отправки error-логов в B24 чат */
77
+ private readonly b24WebhookUrl: string | undefined;
78
+
79
+ /** ID чата B24 для отправки error-логов (формат: chatXXXXXX) */
80
+ private readonly b24ChatId: string | undefined;
81
+
82
+ /** Имя приложения для заголовка сообщения */
83
+ private readonly appName: string;
84
+
65
85
  constructor() {
66
86
  this.initConfig();
87
+ this.b24WebhookUrl = process.env.LOG_B24_WEBHOOK_URL;
88
+ this.b24ChatId = process.env.LOG_B24_CHAT_ID;
89
+ this.appName = process.env.APP_NAME || "Unknown App";
67
90
  }
68
91
 
69
92
  /** Проверяем и заполняем конфиг дефолтными значениями если данных нет */
@@ -77,12 +100,24 @@ class LogsAPI {
77
100
 
78
101
  /**
79
102
  * Добавляет сообщение в лог
103
+ *
104
+ * Важно: при уровне `error` сообщение отправляется в B24 чат (через webhook)
105
+ * независимо от настройки `error.enabled` в `log.json`. Настройка `enabled`
106
+ * управляет только выводом в консоль. Это сделано намеренно, чтобы критические
107
+ * ошибки всегда доходили до команды даже при отключённом консольном логировании.
108
+ *
80
109
  * @param message - Текст сообщения или объект для вывода
81
110
  * @param level - Уровень логирования (trace, debug, info, warn, error)
82
111
  * @param jsonData - Дополнительные данные для вывода (опционально)
83
112
  */
84
113
  add(message: string | object, level?: LogLevel, jsonData?: unknown): void {
85
114
  const levelStr: LogLevel = level || "info";
115
+ const messageText = typeof message === "string" ? message : JSON.stringify(message);
116
+
117
+ // Отправляем error-логи в B24 чат независимо от настроек консольного вывода
118
+ if (levelStr === "error") {
119
+ this.sendToB24Chat(messageText, jsonData);
120
+ }
86
121
 
87
122
  // Проверяем конфиг, и если логирование отключено, то выходим
88
123
  const isLoggingEnabled = confLog.get(`${levelStr}.enabled`) as boolean;
@@ -90,8 +125,6 @@ class LogsAPI {
90
125
  return;
91
126
  }
92
127
 
93
- const messageText = typeof message === "string" ? message : JSON.stringify(message);
94
-
95
128
  // Получаем цвет для уровня
96
129
  const color = (confLog.get(`${levelStr}.color`) as string) || "\x1b[37m";
97
130
 
@@ -106,6 +139,73 @@ class LogsAPI {
106
139
  console.dir(jsonData, { depth: null, colors: true });
107
140
  }
108
141
  }
142
+
143
+ /**
144
+ * Убирает BB-теги для plain text отправки (при обрезке текста)
145
+ */
146
+ private stripBbCodes(text: string): string {
147
+ return text.replace(/\[\/?(b|code)\]/g, "");
148
+ }
149
+
150
+ /**
151
+ * Отправляет error-сообщение в чат B24 через webhook (fire-and-forget)
152
+ * Использует нативный fetch, а не fetchRetry — во избежание циклической зависимости
153
+ * Ошибки не пробрасываются, только console.error
154
+ */
155
+ private sendToB24Chat(message: string, jsonData?: unknown): void {
156
+ if (!this.b24WebhookUrl || !this.b24ChatId) return;
157
+
158
+ // Нормализуем URL — гарантируем trailing slash
159
+ const baseUrl = this.b24WebhookUrl.endsWith("/") ? this.b24WebhookUrl : `${this.b24WebhookUrl}/`;
160
+ const url = `${baseUrl}imbot.message.add.json`;
161
+
162
+ let text = `[b]ERROR[/b] | ${this.appName}\n${message}`;
163
+
164
+ // Добавляем jsonData если есть
165
+ if (jsonData !== undefined) {
166
+ // Безопасная сериализация — логгер не должен бросать исключения
167
+ let jsonStr: string;
168
+ try {
169
+ jsonStr = JSON.stringify(jsonData, null, 2);
170
+ } catch {
171
+ jsonStr = "[Не удалось сериализовать jsonData]";
172
+ }
173
+ text += `\n[code]${jsonStr}[/code]`;
174
+ }
175
+
176
+ // Обрезаем до лимита B24
177
+ let isTruncated = false;
178
+ if (text.length > B24_TEXT_LIMIT) {
179
+ const truncatedSuffix = "\n...(обрезано)";
180
+ text = text.slice(0, B24_TEXT_LIMIT - truncatedSuffix.length) + truncatedSuffix;
181
+ isTruncated = true;
182
+ }
183
+
184
+ // Если текст обрезан — отправляем без BB-тегов, чтобы не разорвать [code]...[/code]
185
+ if (isTruncated) {
186
+ text = this.stripBbCodes(text);
187
+ }
188
+
189
+ fetch(url, {
190
+ method: "POST",
191
+ headers: { "Content-Type": "application/json" },
192
+ body: JSON.stringify({
193
+ BOT_ID: B24_BOT_ID,
194
+ CLIENT_ID: B24_CLIENT_ID,
195
+ DIALOG_ID: this.b24ChatId,
196
+ MESSAGE: text,
197
+ }),
198
+ })
199
+ .then((response) => {
200
+ if (!response.ok) {
201
+ console.error(`Logs B24 Chat: HTTP ${response.status}`);
202
+ }
203
+ })
204
+ .catch((error: unknown) => {
205
+ const errMsg = error instanceof Error ? error.message : String(error);
206
+ console.error(`Logs B24 Chat: ошибка отправки — ${errMsg}`);
207
+ });
208
+ }
109
209
  }
110
210
 
111
211
  export const logs = new LogsAPI();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andrey4emk/npm-app-back-b24",
3
- "version": "3.0.2",
3
+ "version": "3.2.0",
4
4
  "description": "Bitrix24 OAuth helpers for Node.js projects",
5
5
  "main": "index.ts",
6
6
  "type": "module",
@@ -64,7 +64,7 @@ export async function fetchRetry(
64
64
 
65
65
  logs.add(
66
66
  `fetchRetry: попытка ${attempt}/${retries} не удалась (${lastError.message}), повтор через ${delay}мс — ${String(url)}`,
67
- "error"
67
+ "warn"
68
68
  );
69
69
 
70
70
  await new Promise((resolve) => setTimeout(resolve, delay));