@yixinshuke/node-log 0.0.1 → 0.0.2

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/AGENTS.md CHANGED
@@ -163,6 +163,8 @@ async function writeAll(fh: FileHandle, chunk: string, timeoutMs: number): Promi
163
163
 
164
164
  ## 4. 日志格式(JSONL 单行,强制字段)
165
165
 
166
+ 宿主可用 `blankLineCategories` 指定需要空行分隔的类别:每条 JSON 记录后追加 `\n\n`,其余类别仍为 `\n`。分隔符与记录作为整体参与批次字节计数、切块和失败回填;按行读取时跳过空行。类别含义由宿主定义,组件不硬编码业务类别。
167
+
166
168
  ```json
167
169
  {"timestamp":"2026-09-29T10:00:00.123+08:00","host":"pos-node-03","pid":1234,"level":"info","merchantId":1,"storeId":1,"system":"支付系统","bizKey":"202609290001","message":"订单创建成功"}
168
170
  ```
@@ -446,4 +448,21 @@ src/
446
448
  - 任何错误在被捕获、包装、向上抛出或返回给调用方时,**必须携带具体、可定位的信息**:实际出错的操作、关键参数、原始错误(message / 错误码 / SQL / HTTP 状态码等至少其一);
447
449
  - 日志与返回信息要让排查者「一眼看出是哪一步、什么数据、什么条件下出的错」,而不是把所有失败都归一成一句空话;
448
450
  - 允许降级 / 重试 / 返回默认值等容错处理,但**容错的同时必须把具体原因记下来**(日志告警或在错误信息里附带原始错误),不得静默吞掉真实错误、只留一句「请稍后重试」;
449
- - 若需对外部用户隐藏内部细节,应「对外给友好文案 + 对内保留完整错误链」,两者不可只取前者而丢弃后者。
451
+ - 若需对外部用户隐藏内部细节,应「对外给友好文案 + 对内保留完整错误链」,两者不可只取前者而丢弃后者。
452
+
453
+ ---
454
+
455
+ ## 三个环境(全工作区标准)
456
+
457
+ 权威出处:`../docs/0业务逻辑/003-开发测试生产环境.md`(改动环境口径先改那份,再同步本节)。
458
+
459
+ | 环境 | 地址 |
460
+ | --- | --- |
461
+ | 开发 | `127.0.0.1:3101 ~ 3105` |
462
+ | 测试 | `1dl-test` / `2sh-test` / `3pay-test` / `4hy-test` / `5pos-test`.yixinshuke.com |
463
+ | 生产·线路1 | `1dl` / `2sh` / `3pay` / `4hy` / `5pos`.yixinshuke.com |
464
+ | 生产·线路2 | 上列域名各加 `2`(`1dl2` / `2sh2` / `3pay2` / `4hy2` / `5pos2`.yixinshuke.com) |
465
+
466
+ - 服务器端:`.env` 取值 `dev`(默认) / `test` / `prod`;设置优先级 CLI `--env` > `.env` 的 `NODE_ENV` > 操作系统 `NODE_ENV`;启动时在控制台打印当前环境。
467
+ - 网页端:URL query `?env=dev/test/prod`,不带参数 = 生产;登录卡片下方与主界面右上角可选线路——开发环境显示「开发 线路1 线路2」,测试环境显示「测试 线路1 线路2」,生产环境显示「线路1 线路2」;线路1/线路2 始终是生产环境的线路地址。
468
+ - 桌面端:默认生产模式;`pos.exe --env=dev/test/prod`,或 EXE 同目录 `.env` 的 `env=dev/test/prod`;优先级 启动参数 > `.env` > 默认生产模式;线路选择 UI 与网页端相同。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yixinshuke/node-log",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Node.js 日志组件 - 环形内存缓冲 + 节流追加写到文件(一单据一文件),写失败绝不阻断业务",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -30,7 +30,7 @@
30
30
  "license": "MIT",
31
31
  "devDependencies": {
32
32
  "@types/node": "^22.0.0",
33
- "@yixinshuke/node-lock": "file:../node-lock",
33
+ "@yixinshuke/node-lock": "^0.2.0",
34
34
  "typescript": "^5.5.0",
35
35
  "vitest": "^2.0.0",
36
36
  "tsx": "^4.7.0"
package/src/config.ts CHANGED
@@ -20,6 +20,8 @@ export interface LoggerConfig {
20
20
  baseDir: string;
21
21
  /** 最低记录级别,默认 info */
22
22
  minLevel?: LogLevel;
23
+ /** 指定类别的每条日志后增加一个空行;类别由宿主定义,默认不增加 */
24
+ blankLineCategories?: readonly string[];
23
25
  /** 环形缓冲容量(条),满则覆盖最老,默认 10000 */
24
26
  maxBufferSize?: number;
25
27
  /** 节流刷盘间隔(毫秒),默认 3000 */
@@ -59,6 +61,7 @@ export interface LoggerConfig {
59
61
  export const DEFAULT_CONFIG: Required<LoggerConfig> = {
60
62
  baseDir: './logs',
61
63
  minLevel: 'info',
64
+ blankLineCategories: [],
62
65
  maxBufferSize: 10000,
63
66
  flushIntervalMs: 3000,
64
67
  flushJitterMs: 500,
package/src/logger.ts CHANGED
@@ -272,24 +272,25 @@ export class GlobalLogger {
272
272
  this.retryAfter.set(rel, Date.now() + delay);
273
273
  }
274
274
 
275
- /** 按 maxFlushBytes 切块(不切断行) */
275
+ /** 按 maxFlushBytes 切块(不切断记录及其空行分隔符) */
276
276
  private buildChunks(entries: LogEntry[]): Chunk[] {
277
277
  const limit = Math.max(1, Math.floor(this.config.maxFlushBytes));
278
278
  const chunks: Chunk[] = [];
279
279
  let lines: string[] = [];
280
280
  let size = 0;
281
281
  for (const entry of entries) {
282
- const line = this.formatEntry(entry);
283
- const lineSize = Buffer.byteLength(line, 'utf8') + 1;
282
+ const separator = this.config.blankLineCategories.includes(entry.context.category ?? '') ? '\n\n' : '\n';
283
+ const line = this.formatEntry(entry) + separator;
284
+ const lineSize = Buffer.byteLength(line, 'utf8');
284
285
  if (lines.length > 0 && size + lineSize > limit) {
285
- chunks.push({ text: lines.join('\n') + '\n', count: lines.length });
286
+ chunks.push({ text: lines.join(''), count: lines.length });
286
287
  lines = [];
287
288
  size = 0;
288
289
  }
289
290
  lines.push(line);
290
291
  size += lineSize;
291
292
  }
292
- if (lines.length > 0) chunks.push({ text: lines.join('\n') + '\n', count: lines.length });
293
+ if (lines.length > 0) chunks.push({ text: lines.join(''), count: lines.length });
293
294
  return chunks;
294
295
  }
295
296