Enhance logger middleware by integrating Pino for improved logging capabilities. Added dynamic log level configuration based on environment variables and implemented console capture for info, warn, and error logs. Suppressed debug logs when log level is set to info or higher, ensuring cleaner output in production environments.

This commit is contained in:
dal 2025-07-03 12:31:50 -06:00
parent 1b9a10610d
commit 56530248dc
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
2 changed files with 157 additions and 9 deletions

View File

@ -0,0 +1,124 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import type { Mock } from 'vitest';
describe('logger middleware', () => {
let originalEnv: NodeJS.ProcessEnv;
let originalConsole: {
info: typeof console.info;
warn: typeof console.warn;
error: typeof console.error;
debug: typeof console.debug;
};
beforeEach(() => {
// Save original env and console
originalEnv = { ...process.env };
originalConsole = {
info: console.info,
warn: console.warn,
error: console.error,
debug: console.debug,
};
// Clear module cache to reload logger with new env
vi.resetModules();
});
afterEach(() => {
// Restore original env and console
process.env = originalEnv;
console.info = originalConsole.info;
console.warn = originalConsole.warn;
console.error = originalConsole.error;
console.debug = originalConsole.debug;
});
it('should use info level by default when LOG_LEVEL is not set', async () => {
delete process.env.LOG_LEVEL;
const { loggerMiddleware } = await import('./logger');
// Verify logger was created (basic check)
expect(loggerMiddleware).toBeDefined();
});
it('should respect LOG_LEVEL environment variable', async () => {
process.env.LOG_LEVEL = 'warn';
const { loggerMiddleware } = await import('./logger');
expect(loggerMiddleware).toBeDefined();
});
it('should capture console methods when LOG_LEVEL is set', async () => {
process.env.LOG_LEVEL = 'info';
// Create mocks to track console calls
const mockInfo = vi.fn();
const mockWarn = vi.fn();
const mockError = vi.fn();
const mockDebug = vi.fn();
console.info = mockInfo;
console.warn = mockWarn;
console.error = mockError;
console.debug = mockDebug;
// Import logger which should override console methods
await import('./logger');
// Test that console methods were overridden
const testMessage = 'Test message';
console.info(testMessage);
console.warn(testMessage);
console.error(testMessage);
console.debug(testMessage);
// Original mocks should not have been called (they were overridden)
expect(mockInfo).not.toHaveBeenCalled();
expect(mockWarn).not.toHaveBeenCalled();
expect(mockError).not.toHaveBeenCalled();
expect(mockDebug).not.toHaveBeenCalled();
});
it('should not capture console methods when LOG_LEVEL is not set', async () => {
delete process.env.LOG_LEVEL;
// Create mocks
const mockInfo = vi.fn();
console.info = mockInfo;
// Import logger
await import('./logger');
// Test that console.info was NOT overridden
console.info('Test message');
// Original mock should have been called
expect(mockInfo).toHaveBeenCalledWith('Test message');
});
it('should suppress debug logs when LOG_LEVEL is info', async () => {
process.env.LOG_LEVEL = 'info';
await import('./logger');
// console.debug should be a no-op function
const result = console.debug('This should be suppressed');
expect(result).toBeUndefined();
});
it('should handle multiple arguments in console methods', async () => {
process.env.LOG_LEVEL = 'info';
// We can't easily test the actual pino output, but we can verify
// the console methods accept multiple arguments without error
await import('./logger');
expect(() => {
console.info('Message', { data: 'object' }, 123);
console.warn('Warning', 'with', 'multiple', 'args');
console.error('Error', new Error('test'), { context: 'data' });
}).not.toThrow();
});
});

View File

@ -1,24 +1,48 @@
import { pinoLogger } from 'hono-pino'; import { pinoLogger } from 'hono-pino';
import pino from 'pino';
import 'pino-pretty'; import 'pino-pretty';
const isDev = process.env.NODE_ENV !== 'production'; const isDev = process.env.NODE_ENV !== 'production';
const logLevel = process.env.LOG_LEVEL || 'info';
let isPinoPrettyAvailable = true; let isPinoPrettyAvailable = true;
// Create base pino instance for console capture
const baseLogger = pino({
level: logLevel,
transport: isDev && isPinoPrettyAvailable
? {
target: 'pino-pretty',
options: { colorize: true },
}
: undefined,
});
// Simple console capture - only override if LOG_LEVEL is set
if (process.env.LOG_LEVEL) {
const originalConsole = {
info: console.info,
warn: console.warn,
error: console.error,
};
console.info = (...args) => baseLogger.info(args.join(' '));
console.warn = (...args) => baseLogger.warn(args.join(' '));
console.error = (...args) => baseLogger.error(args.join(' '));
// Suppress debug logs when LOG_LEVEL is info or higher
if (logLevel !== 'debug' && logLevel !== 'trace') {
console.debug = () => {};
}
}
// Create logger with fallback for pino-pretty failures // Create logger with fallback for pino-pretty failures
function createLogger() { function createLogger() {
console.log('in logger middleware NODE_ENV', process.env.NODE_ENV);
// Try pino-pretty in development // Try pino-pretty in development
if (isDev && isPinoPrettyAvailable) { if (isDev && isPinoPrettyAvailable) {
try { try {
return pinoLogger({ return pinoLogger({
pino: { pino: baseLogger,
level: 'info',
transport: {
target: 'pino-pretty',
options: { colorize: true },
},
},
}); });
} catch (error) { } catch (error) {
console.error('pino-pretty not available, falling back to JSON logging', error); console.error('pino-pretty not available, falling back to JSON logging', error);
@ -29,7 +53,7 @@ function createLogger() {
// Fallback to simple JSON logging // Fallback to simple JSON logging
return pinoLogger({ return pinoLogger({
pino: { pino: {
level: isDev ? 'info' : 'debug', level: logLevel,
}, },
}); });
} }