← Назад к вопросам
Напишите middleware для логирования запросов Express
1.2 Junior🔥 201 комментариев
#Node.js и JavaScript#Фреймворки и библиотеки
Условие
Напишите middleware для Express.js, который логирует информацию о каждом запросе:
- HTTP метод
- URL
- Время выполнения запроса в миллисекундах
function requestLogger() {
// Ваш код
}
// Пример использования:
const express = require("express");
const app = express();
app.use(requestLogger());
app.get("/", (req, res) => {
res.send("Hello");
});
// Ожидаемый вывод в консоль:
// GET / - 5ms
Что проверяется
- Понимание middleware в Express.js
- Работа с объектами request/response
- Измерение времени выполнения
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение
Базовая реализация
import { Request, Response, NextFunction } from 'express';
function requestLogger() {
return (req: Request, res: Response, next: NextFunction) => {
const startTime = Date.now();
// Отслеживаем момент отправки ответа
res.on('finish', () => {
const duration = Date.now() - startTime;
console.log(`${req.method} ${req.url} - ${duration}ms`);
});
next();
};
}
Как это работает
Принцип работы middleware:
- Фиксируем время начала —
startTime = Date.now() - Слушаем событие 'finish' — это событие срабатывает когда ответ полностью отправлен
- Вычисляем время —
duration = Date.now() - startTime - Логируем информацию — выводим метод, URL и время
- Передаём управление —
next()вызывает следующий middleware
Порядок выполнения:
Запрос GET /api/users
├─ requestLogger middleware
│ ├─ startTime = 1000
│ └─ next() → следующий middleware
├─ app.get('/api/users') handler
│ └─ res.send(...) → отправка ответа
└─ res.on('finish') → логирование
├─ duration = 1005 - 1000 = 5ms
└─ console.log('GET /api/users - 5ms')
Пример использования
import express from 'express';
const app = express();
app.use(requestLogger());
app.get('/', (req, res) => {
res.send('Hello');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
// При запросе GET /:
// GET / - 5ms
Расширенная версия с цветным выводом
interface ColorMap {
[key: string]: string;
}
function requestLogger() {
const colors: ColorMap = {
GET: '\x1b[36m', // Cyan
POST: '\x1b[32m', // Green
PUT: '\x1b[33m', // Yellow
DELETE: '\x1b[31m', // Red
PATCH: '\x1b[35m', // Magenta
reset: '\x1b[0m' // Reset
};
return (req: Request, res: Response, next: NextFunction) => {
const startTime = Date.now();
const method = req.method;
const color = colors[method] || colors.reset;
res.on('finish', () => {
const duration = Date.now() - startTime;
const statusColor = res.statusCode >= 400 ? '\x1b[31m' : '\x1b[32m';
const status = res.statusCode;
console.log(
`${color}${method}${colors.reset} ${req.url} - ${statusColor}${status}${colors.reset} ${duration}ms`
);
});
next();
};
}
Версия с дополнительной информацией
function requestLogger() {
return (req: Request, res: Response, next: NextFunction) => {
const startTime = performance.now();
const { ip, method, url, hostname } = req;
res.on('finish', () => {
const duration = (performance.now() - startTime).toFixed(2);
const { statusCode } = res;
const contentLength = res.get('content-length') || 0;
const log = {
timestamp: new Date().toISOString(),
method,
url,
statusCode,
duration: `${duration}ms`,
contentLength,
ip,
hostname,
userAgent: req.get('user-agent')
};
console.log(JSON.stringify(log, null, 2));
});
next();
};
}
Версия с error handling
function requestLogger() {
return (req: Request, res: Response, next: NextFunction) => {
const startTime = Date.now();
// Слушаем и 'finish' и 'close' события
const onResponse = () => {
const duration = Date.now() - startTime;
console.log(`${req.method} ${req.url} - ${duration}ms`);
cleanup();
};
const cleanup = () => {
res.removeListener('finish', onResponse);
res.removeListener('close', onResponse);
};
res.on('finish', onResponse);
res.on('close', onResponse); // Если соединение закроется раньше
next();
};
}
Версия с разными уровнями логирования
enum LogLevel {
INFO = 'INFO',
WARN = 'WARN',
ERROR = 'ERROR'
}
function requestLogger(level: LogLevel = LogLevel.INFO) {
return (req: Request, res: Response, next: NextFunction) => {
const startTime = Date.now();
res.on('finish', () => {
const duration = Date.now() - startTime;
const log = `[${level}] ${req.method} ${req.url} - ${duration}ms (${res.statusCode})`;
if (level === LogLevel.ERROR && res.statusCode >= 500) {
console.error(log);
} else if (level === LogLevel.WARN && res.statusCode >= 400) {
console.warn(log);
} else {
console.log(log);
}
});
next();
};
}
Полный пример с несколькими middleware
import express, { Request, Response } from 'express';
const app = express();
function requestLogger() {
return (req: Request, res: Response, next: NextFunction) => {
const startTime = Date.now();
const method = req.method;
const url = req.url;
res.on('finish', () => {
const duration = Date.now() - startTime;
console.log(`${method} ${url} - ${duration}ms`);
});
next();
};
}
// Middleware цепь
app.use(requestLogger());
app.use(express.json());
app.use(express.static('public'));
app.get('/api/users', (req: Request, res: Response) => {
res.json({ users: [] });
});
app.post('/api/users', (req: Request, res: Response) => {
res.status(201).json({ id: 1 });
});
app.listen(3000, () => {
console.log('Server running');
});
// Вывод:
// GET /api/users - 2ms
// POST /api/users - 3ms
Тестирование
import { describe, it, expect, vi } from 'vitest';
import express, { Request, Response } from 'express';
describe('requestLogger middleware', () => {
it('должен логировать запросы', (done) => {
const app = express();
const consoleSpy = vi.spyOn(console, 'log');
app.use(requestLogger());
app.get('/test', (req, res) => {
res.send('ok');
});
const req = new RequestMock();
const res = new ResponseMock();
middleware(req, res, () => {
res.emit('finish');
expect(consoleSpy).toHaveBeenCalled();
const log = consoleSpy.mock.calls[0][0];
expect(log).toMatch(/GET \/test - \d+ms/);
done();
});
});
it('должен измерять время выполнения', (done) => {
const consoleSpy = vi.spyOn(console, 'log');
const middleware = requestLogger();
const req = { method: 'GET', url: '/api' } as Request;
const res = {
on: vi.fn((event: string, callback: () => void) => {
if (event === 'finish') {
setTimeout(callback, 10);
}
})
} as any;
const next = vi.fn();
middleware(req, res, next);
setTimeout(() => {
expect(consoleSpy).toHaveBeenCalled();
const log = consoleSpy.mock.calls[0][0];
expect(log).toMatch(/\d+ms/);
done();
}, 20);
});
});
Важные замечания
1. Event 'finish' vs 'close':
- 'finish' — ответ полностью отправлен клиенту
- 'close' — соединение закрыто (может произойти раньше finish)
res.on('finish', () => {
// Ответ отправлен
});
res.on('close', () => {
// Соединение закрыто
});
2. Date.now() vs performance.now():
- Date.now() — абсолютное время, подходит для логирования
- performance.now() — высокая точность, подходит для профилирования
3. Порядок middleware:
Между requestLogger и обработчиком маршрута может быть много middleware.
4. Cleanup в обработчиках:
Важно удалять слушатели событий, чтобы избежать утечек памяти.