mirror of
https://github.com/Silentely/eSIM-Tools.git
synced 2026-09-03 06:24:20 +08:00
- 修改 `server-logger.js` 核心逻辑,将日志输出从 `JSON.stringify` 改为模板字符串拼接,格式变更为 `[LEVEL] [function] [requestId] message | key=value` - 同步更新 `bff-proxy.js` 和 `markdown-negotiation.js` 中的 Deno 环境内联日志函数,保持 Edge Function 与 Node.js 环境日志格式一致 - 调整 context 序列化方式,使用 `key=value` 空格分隔,无 context 时不输出分隔符 `|`,并移除 `timestamp` 字段 - 更新 `server-logger.test.js` 测试用例,断言日志输出为字符串匹配而非 JSON 解析,并增加单行输出及无 context 时不包含分隔符的验证
188 lines
7.1 KiB
JavaScript
188 lines
7.1 KiB
JavaScript
/**
|
||
* server-logger 模块单元测试
|
||
* 覆盖日志输出格式、日志级别控制、工厂函数等核心功能
|
||
*
|
||
* 输出格式: [LEVEL] [function] [requestId] message | key=value key=value
|
||
*/
|
||
|
||
'use strict';
|
||
|
||
const { createLogger, parseLogLevel, LOG_LEVELS } = require('../../netlify/functions/_shared/server-logger');
|
||
|
||
describe('server-logger', () => {
|
||
let consoleSpy;
|
||
|
||
beforeEach(() => {
|
||
consoleSpy = {
|
||
log: jest.spyOn(console, 'log').mockImplementation(),
|
||
warn: jest.spyOn(console, 'warn').mockImplementation(),
|
||
error: jest.spyOn(console, 'error').mockImplementation(),
|
||
};
|
||
});
|
||
|
||
afterEach(() => {
|
||
Object.values(consoleSpy).forEach(spy => spy.mockRestore());
|
||
});
|
||
|
||
describe('createLogger', () => {
|
||
it('应该返回具有 info/warn/error/debug 方法的 logger 实例', () => {
|
||
const logger = createLogger('test-fn', 'req-1');
|
||
expect(typeof logger.info).toBe('function');
|
||
expect(typeof logger.warn).toBe('function');
|
||
expect(typeof logger.error).toBe('function');
|
||
expect(typeof logger.debug).toBe('function');
|
||
});
|
||
|
||
it('应该输出人类可读格式的日志', () => {
|
||
const logger = createLogger('test-fn', 'req-1');
|
||
logger.info('test message');
|
||
expect(consoleSpy.log).toHaveBeenCalledTimes(1);
|
||
const output = consoleSpy.log.mock.calls[0][0];
|
||
expect(output).toBe('[INFO] [test-fn] [req-1] test message');
|
||
});
|
||
|
||
it('应该支持附加 context 字段(key=value 格式)', () => {
|
||
const logger = createLogger('test-fn', 'req-1');
|
||
logger.info('with context', { status: 200, duration: 42 });
|
||
const output = consoleSpy.log.mock.calls[0][0];
|
||
expect(output).toContain('[INFO] [test-fn] [req-1] with context');
|
||
expect(output).toContain('status=200');
|
||
expect(output).toContain('duration=42');
|
||
expect(output).toContain(' | ');
|
||
});
|
||
|
||
it('无 context 时不应包含分隔符 |', () => {
|
||
const logger = createLogger('test-fn', 'req-1');
|
||
logger.info('no context');
|
||
const output = consoleSpy.log.mock.calls[0][0];
|
||
expect(output).not.toContain(' | ');
|
||
});
|
||
|
||
it('context 为 null 或 undefined 时不应报错', () => {
|
||
const logger = createLogger('test-fn', 'req-1');
|
||
expect(() => logger.info('msg', null)).not.toThrow();
|
||
expect(() => logger.info('msg', undefined)).not.toThrow();
|
||
expect(() => logger.warn('msg', null)).not.toThrow();
|
||
expect(() => logger.error('msg', null)).not.toThrow();
|
||
const output = consoleSpy.log.mock.calls[0][0];
|
||
expect(output).toContain('[INFO] [test-fn] [req-1] msg');
|
||
});
|
||
|
||
it('warn 应该输出到 console.warn', () => {
|
||
const logger = createLogger('test-fn', 'req-1');
|
||
logger.warn('warning msg');
|
||
expect(consoleSpy.warn).toHaveBeenCalledTimes(1);
|
||
const output = consoleSpy.warn.mock.calls[0][0];
|
||
expect(output).toContain('[WARN]');
|
||
expect(output).toContain('warning msg');
|
||
});
|
||
|
||
it('error 应该输出到 console.error', () => {
|
||
const logger = createLogger('test-fn', 'req-1');
|
||
logger.error('error msg', { stack: 'fake stack' });
|
||
expect(consoleSpy.error).toHaveBeenCalledTimes(1);
|
||
const output = consoleSpy.error.mock.calls[0][0];
|
||
expect(output).toContain('[ERROR]');
|
||
expect(output).toContain('error msg');
|
||
expect(output).toContain('stack=fake stack');
|
||
});
|
||
|
||
it('DEBUG 日志在非 DEBUG 环境下应该被抑制', () => {
|
||
const originalLevel = process.env.LOG_LEVEL;
|
||
delete process.env.LOG_LEVEL;
|
||
const logger = createLogger('test-fn', 'req-1');
|
||
logger.debug('debug msg');
|
||
expect(consoleSpy.log).not.toHaveBeenCalled();
|
||
if (originalLevel) process.env.LOG_LEVEL = originalLevel;
|
||
});
|
||
|
||
it('DEBUG 日志在 LOG_LEVEL=DEBUG 时应该输出', () => {
|
||
const originalLevel = process.env.LOG_LEVEL;
|
||
process.env.LOG_LEVEL = 'DEBUG';
|
||
const logger = createLogger('test-fn', 'req-1');
|
||
logger.debug('debug msg');
|
||
const output = consoleSpy.log.mock.calls[0][0];
|
||
expect(output).toContain('[DEBUG]');
|
||
expect(output).toContain('debug msg');
|
||
if (originalLevel) process.env.LOG_LEVEL = originalLevel;
|
||
else delete process.env.LOG_LEVEL;
|
||
});
|
||
|
||
it('LOG_LEVEL=ERROR 时应该抑制 INFO 和 WARN', () => {
|
||
const originalLevel = process.env.LOG_LEVEL;
|
||
process.env.LOG_LEVEL = 'ERROR';
|
||
const logger = createLogger('test-fn', 'req-1');
|
||
logger.info('should not appear');
|
||
logger.warn('should not appear');
|
||
expect(consoleSpy.log).not.toHaveBeenCalled();
|
||
expect(consoleSpy.warn).not.toHaveBeenCalled();
|
||
// ERROR 仍然输出
|
||
logger.error('should appear');
|
||
expect(consoleSpy.error).toHaveBeenCalledTimes(1);
|
||
if (originalLevel) process.env.LOG_LEVEL = originalLevel;
|
||
else delete process.env.LOG_LEVEL;
|
||
});
|
||
|
||
it('LOG_LEVEL=WARN 时应该抑制 INFO 和 DEBUG', () => {
|
||
const originalLevel = process.env.LOG_LEVEL;
|
||
process.env.LOG_LEVEL = 'WARN';
|
||
const logger = createLogger('test-fn', 'req-1');
|
||
logger.info('should not appear');
|
||
logger.debug('should not appear');
|
||
expect(consoleSpy.log).not.toHaveBeenCalled();
|
||
// WARN 和 ERROR 仍然输出
|
||
logger.warn('should appear');
|
||
logger.error('should appear');
|
||
expect(consoleSpy.warn).toHaveBeenCalledTimes(1);
|
||
expect(consoleSpy.error).toHaveBeenCalledTimes(1);
|
||
if (originalLevel) process.env.LOG_LEVEL = originalLevel;
|
||
else delete process.env.LOG_LEVEL;
|
||
});
|
||
|
||
it('多个 logger 实例应该互不干扰', () => {
|
||
const logger1 = createLogger('fn-a', 'req-1');
|
||
const logger2 = createLogger('fn-b', 'req-2');
|
||
logger1.info('from a');
|
||
logger2.info('from b');
|
||
const out1 = consoleSpy.log.mock.calls[0][0];
|
||
const out2 = consoleSpy.log.mock.calls[1][0];
|
||
expect(out1).toContain('[fn-a]');
|
||
expect(out1).toContain('[req-1]');
|
||
expect(out2).toContain('[fn-b]');
|
||
expect(out2).toContain('[req-2]');
|
||
});
|
||
|
||
it('日志格式应该方便用户复制(一行一条)', () => {
|
||
const logger = createLogger('giffgaff-graphql', 'abc-123');
|
||
logger.info('calling_upstream', { operationName: 'getESims', isSwap: false });
|
||
const output = consoleSpy.log.mock.calls[0][0];
|
||
// 确认是单行、无换行符、包含所有关键信息
|
||
expect(output).not.toContain('\n');
|
||
expect(output).toBe('[INFO] [giffgaff-graphql] [abc-123] calling_upstream | operationName=getESims isSwap=false');
|
||
});
|
||
});
|
||
|
||
describe('parseLogLevel', () => {
|
||
it('应该解析有效日志级别', () => {
|
||
expect(parseLogLevel('DEBUG')).toBe(0);
|
||
expect(parseLogLevel('INFO')).toBe(1);
|
||
expect(parseLogLevel('WARN')).toBe(2);
|
||
expect(parseLogLevel('ERROR')).toBe(3);
|
||
});
|
||
|
||
it('未知级别应该默认为 INFO', () => {
|
||
expect(parseLogLevel('UNKNOWN')).toBe(1);
|
||
});
|
||
|
||
it('undefined 应该默认为 INFO', () => {
|
||
expect(parseLogLevel(undefined)).toBe(1);
|
||
});
|
||
});
|
||
|
||
describe('LOG_LEVELS', () => {
|
||
it('应该包含四个标准级别', () => {
|
||
expect(LOG_LEVELS).toEqual({ DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 });
|
||
});
|
||
});
|
||
});
|