В библиотеке Class-validator процесс валидации не сопровождается
встроенной системой логирования в привычном смысле. Результатом работы
функций validate и validateOrReject выступает
структура ошибок, которая содержит всю необходимую информацию для
построения детализированных логов. Поэтому логирование реализуется
поверх выходных данных валидатора и представляет собой отдельный слой
инфраструктуры.
Основная цель детального логирования — восстановление полного контекста валидации: какое поле проверялось, какое значение поступило, какое ограничение нарушено и на каком уровне вложенности находится ошибка.
Ключевые источники информации для логирования:
ValidationErrorproperty, value,
constraintschildrenКаждая ошибка валидации представлена объектом следующей структуры:
property — имя поля, в котором возникла ошибкаvalue — фактическое значение, которое не прошло
проверкуconstraints — набор нарушенных правил в формате ключ →
сообщениеchildren — вложенные ошибки для объектов и
массивовtarget — исходный объект (используется ограниченно,
может быть исключён в логах)contexts — дополнительные данные (если заданы кастомные
валидаторы)Особенность структуры заключается в рекурсивности: сложные DTO с вложенными объектами формируют дерево ошибок, которое требует обхода.
Функция validate возвращает массив ошибок. Отсутствие
ошибок означает успешную валидацию.
Типовой подход к фиксации результата:
import { validate } from "class-validator";
async function logValidation(dto) {
const errors = await validate(dto);
if (errors.length > 0) {
console.log("Validation failed");
console.log(JSON.stringify(errors, null, 2));
} else {
console.log("Validation successful");
}
}
Такой формат подходит только для первичной диагностики, так как структура содержит избыточные данные и плохо читается при большом количестве вложенных объектов.
Для построения полноценного логирования требуется обход дерева
ValidationError. Каждый узел может содержать собственные
ошибки и вложенные структуры.
Базовый рекурсивный обход:
function flattenValidationErrors(errors, parentPath = "") {
const result = [];
for (const error of errors) {
const currentPath = parentPath
? `${parentPath}.${error.property}`
: error.property;
if (error.constraints) {
result.push({
field: currentPath,
messages: Object.values(error.constraints),
value: error.value,
});
}
if (error.children && error.children.length > 0) {
result.push(
...flattenValidationErrors(error.children, currentPath)
);
}
}
return result;
}
Результат такой обработки:
Функция validateOrReject выбрасывает исключение при
наличии ошибок, что удобно для централизованного логирования в
обработчиках ошибок.
import { validateOrReject } from "class-validator";
async function process(dto) {
try {
await validateOrReject(dto);
} catch (errors) {
const formatted = flattenValidationErrors(errors);
console.error("Validation error:");
console.error(JSON.stringify(formatted, null, 2));
}
}
Такой подход используется в архитектурах, где исключения являются основным механизмом управления потоком выполнения.
Сырые объекты ошибок редко используются напрямую. Обычно применяется структурирование:
Рекомендуемый формат записи:
Пример преобразования:
function formatLogEntry(error) {
return {
field: error.field,
message: error.messages.join("; "),
value: error.value,
level: error.field.split(".").length,
};
}
Такой формат облегчает интеграцию с системами логирования и аналитики.
Для сложных систем важно сохранять контекст выполнения:
Пример обогащённого логирования:
function logWithContext(errors, context) {
const flat = flattenValidationErrors(errors);
console.log(JSON.stringify({
context,
errors: flat,
timestamp: new Date().toISOString(),
}, null, 2));
}
Контекст позволяет сопоставлять ошибки с конкретными операциями системы.
При большом объёме данных логирование требует разделения на уровни:
debug — полная структура ValidationErrorinfo — краткий список полей с ошибкамиwarn — бизнес-значимые ошибкиerror — критические сбои валидации входных данныхФильтрация часто строится на количестве ошибок или типе DTO.
Class-validator не привязан к конкретной системе логирования, поэтому часто используется интеграция с универсальными библиотеками:
pinowinstonПример с pino:
import pino from "pino";
const logger = pino();
function logValidationErrors(errors) {
const flat = flattenValidationErrors(errors);
logger.error({
type: "validation_error",
errors: flat,
});
}
Такая модель обеспечивает:
Вложенные DTO формируют дерево глубиной более одного уровня. Без рекурсивной обработки теряется информация о пути к ошибке.
Пример структуры:
user
address
Ошибка на уровне zipCode должна логироваться как:
user.address.zipCode
Именно формирование полного пути является ключевым аспектом детального логирования.
При реализации встречаются характерные ошибки:
target приводит к утечке чувствительных
данныхКорректная архитектура предполагает отделение слоя валидации от слоя наблюдаемости и приведение ошибок к единому формату до записи в лог-систему