Логирование процесса валидации

При работе с формами и сложными схемами валидации ошибки часто возникают не в пользовательских данных, а в самой конфигурации схемы. Логирование позволяет:

  • отслеживать последовательность выполнения проверок;
  • анализировать входящие значения;
  • понимать, какая именно проверка завершилась ошибкой;
  • контролировать преобразования данных (transform);
  • выявлять конфликты между nullable, required, default;
  • диагностировать асинхронные валидаторы;
  • анализировать производительность схем.

В экосистеме React чаще всего используется связка:

  • Yup
  • @hookform/resolvers
  • react-hook-form

yupResolver выступает посредником между схемой Yup и системой обработки формы.


Архитектура процесса валидации

При использовании yupResolver цепочка выглядит следующим образом:

Форма → react-hook-form → yupResolver → Yup Schema → Validation Result

Каждый этап может быть источником ошибок:

Этап Возможные проблемы
react-hook-form неправильная регистрация полей
yupResolver некорректная передача context
Yup schema ошибки в правилах
transform неожиданные преобразования
async validation race condition
UI rendering устаревшие ошибки

Логирование помогает определить проблемный участок.


Базовое логирование yupResolver

Установка зависимостей

npm install yup react-hook-form @hookform/resolvers

Простейшая схема

import * as yup from 'yup';

export const schema = yup.object({
  email: yup
    .string()
    .email('Некорректный email')
    .required('Email обязателен'),

  age: yup
    .number()
    .min(18, 'Минимальный возраст 18')
});

Подключение yupResolver

import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';

const {
  register,
  handleSubmit,
  formState: { errors }
} = useForm({
  resolver: yupResolver(schema)
});

Логирование через validate()

Ручной вызов validate

Перед интеграцией в форму удобно тестировать схему напрямую.

schema.validate(data)
  .then(result => {
    console.log('VALID:', result);
  })
  .catch(error => {
    console.log('ERROR:', error);
  });

Полный объект ValidationError

schema.validate(data)
  .catch(error => {
    console.log(error.name);
    console.log(error.message);
    console.log(error.path);
    console.log(error.errors);
    console.log(error.inner);
  });

Структура ValidationError

ValidationError {
  name: 'ValidationError',
  message: 'Email обязателен',
  path: 'email',
  errors: [
    'Email обязателен'
  ],
  inner: [...]
}

Логирование всех ошибок

По умолчанию Yup возвращает первую найденную ошибку.

abortEarly: false

schema.validate(data, {
  abortEarly: false
})
.catch(error => {
  console.log(error.inner);
});

Анализ inner

error.inner.forEach(item => {
  console.log({
    path: item.path,
    message: item.message,
    value: item.value
  });
});

Вывод:

{
  path: 'email',
  message: 'Email обязателен',
  value: ''
}

Логирование внутри test()

Метод test() — ключевая точка глубокой диагностики.


Базовый test

const schema = yup.object({
  username: yup.string().test(
    'username-check',
    'Некорректное имя',
    function(value) {

      console.log('VALUE:', value);

      return value !== 'admin';
    }
  )
});

Логирование контекста

test(
  'age-check',
  'Ошибка возраста',
  function(value) {

    console.log(this);

    return true;
  }
)

Полезные поля контекста

function(value) {

  console.log(this.path);
  console.log(this.parent);
  console.log(this.options);
  console.log(this.schema);

  return true;
}

this.parent

Позволяет видеть весь объект формы.

password: yup.string(),

confirmPassword: yup.string()
  .test(
    'match',
    'Пароли не совпадают',
    function(value) {

      console.log(this.parent);

      return value === this.parent.password;
    }
  )

Логирование transform()

transform() особенно важен, потому что преобразование происходит до валидации.


Анализ преобразований

const schema = yup.object({
  age: yup.number()
    .transform((value, originalValue) => {

      console.log('TRANSFORM');
      console.log('value:', value);
      console.log('original:', originalValue);

      return Number(originalValue);
    })
});

Проверка NaN

.transform((value, originalValue) => {

  const parsed = Number(originalValue);

  console.log({
    originalValue,
    parsed,
    isNaN: Number.isNaN(parsed)
  });

  return parsed;
})

Логирование cast()

Метод cast() позволяет проверить преобразования без запуска валидации.


Использование cast

const result = schema.cast({
  age: '25'
});

console.log(result);

Диагностика преобразований

const rawData = {
  age: '18',
  isAdmin: 'true'
};

console.log('RAW:', rawData);

const parsed = schema.cast(rawData);

console.log('PARSED:', parsed);

Логирование через resolver wrapper

Один из лучших способов диагностики — создание собственного wrapper вокруг yupResolver.


Базовый wrapper

import { yupResolver } from '@hookform/resolvers/yup';

const loggingResolver = (schema) => {

  const resolver = yupResolver(schema);

  return async (data, context, options) => {

    console.log('INPUT DATA:', data);

    const result = await resolver(
      data,
      context,
      options
    );

    console.log('RESOLVER RESULT:', result);

    return result;
  };
};

Подключение

useForm({
  resolver: loggingResolver(schema)
});

Логирование ошибок react-hook-form

Отслеживание formState.errors

const {
  formState: { errors }
} = useForm({
  resolver: yupResolver(schema)
});

useEffect(() => {
  console.log(errors);
}, [errors]);

Структура ошибок

{
  email: {
    type: 'required',
    message: 'Email обязателен'
  }
}

Глубокое логирование resolver

Расширенный resolver

const loggingResolver = (schema) => {

  const resolver = yupResolver(schema);

  return async (data, context, options) => {

    console.group('VALIDATION START');

    console.log('DAT A:', data);
    console.log('CONTEXT:', context);
    console.log('OPTIONS:', options);

    const start = performance.now();

    try {

      const result = await resolver(
        data,
        context,
        options
      );

      console.log('RESULT:', result);

      return result;

    } catch(error) {

      console.error('VALIDATION ERROR:', error);

      throw error;

    } finally {

      const end = performance.now();

      console.log('TIME:', end - start);

      console.groupEnd();
    }
  };
};

Логирование async validation

Асинхронные проверки — частый источник нестабильного поведения.


Асинхронный test

email: yup.string().test(
  'email-exists',
  'Email уже используется',
  async function(value) {

    console.log('CHECK EMAIL:', value);

    const response = await fetch('/api/check');

    const result = await response.json();

    console.log(result);

    return !result.exists;
  }
)

Логирование времени запроса

async function(value) {

  const start = Date.now();

  const response = await fetch('/api/check');

  const end = Date.now();

  console.log('REQUEST TIME:', end - start);

  return true;
}

Отладка race condition

При быстром вводе могут завершаться устаревшие запросы.


Логирование последовательности

let requestId = 0;

test(
  'async-check',
  'Ошибка',
  async function(value) {

    requestId++;

    const currentId = requestId;

    console.log('START:', currentId);

    await fakeRequest();

    console.log('END:', currentId);

    return true;
  }
)

Логирование conditional validation

when()

const schema = yup.object({

  type: yup.string(),

  taxNumber: yup.string()
    .when('type', {

      is: 'company',

      then: (schema) => {

        console.log('COMPANY VALIDATION');

        return schema.required();
      },

      otherwise: (schema) => {

        console.log('PERSON VALIDATION');

        return schema;
      }
    })
});

Логирование lazy()

Динамическая схема

const dynamicSchema = yup.lazy((value) => {

  console.log('LAZY VALUE:', value);

  if (typeof value === 'string') {
    return yup.string();
  }

  if (typeof value === 'number') {
    return yup.number();
  }

  return yup.mixed();
});

Логирование context

context позволяет передавать внешние данные в схему.


Передача context

useForm({
  resolver: yupResolver(schema),
  context: {
    role: 'admin'
  }
});

Чтение context

test(
  'role-check',
  'Недостаточно прав',
  function(value) {

    console.log(this.options.context);

    return true;
  }
)

Логирование nested schema

Вложенные объекты

const schema = yup.object({

  profile: yup.object({

    firstName: yup.string(),

    contacts: yup.object({

      phone: yup.string()
    })
  })
});

Анализ путей

.catch(error => {

  error.inner.forEach(item => {

    console.log(item.path);
  });
});

Вывод:

profile.contacts.phone

Логирование массива

Array validation

const schema = yup.object({

  tags: yup.array()
    .of(
      yup.string().required()
    )
});

Ошибки массива

.catch(error => {

  error.inner.forEach(item => {

    console.log(item.path);
  });
});

Вывод:

tags[0]
tags[1]

Интеграция с logger-системами

Создание универсального logger

const logger = {

  info(message, payload) {
    console.log(message, payload);
  },

  error(message, payload) {
    console.error(message, payload);
  }
};

Использование в test

test(
  'custom-check',
  'Ошибка',
  function(value) {

    logger.info('VALIDATION', {
      path: this.path,
      value
    });

    return true;
  }
)

Production-логирование

В production нельзя бесконтрольно использовать console.log.


Условное логирование

const isDev = process.env.NODE_ENV === 'development';

if (isDev) {
  console.log(data);
}

Безопасный helper

export const debugValidation = (...args) => {

  if (
    process.env.NODE_ENV === 'development'
  ) {
    console.log(...args);
  }
};

Логирование производительности

Большие схемы могут замедлять интерфейс.


Замер времени

const start = performance.now();

await schema.validate(data);

const end = performance.now();

console.log(`Validation: ${end - start}ms`);

Анализ тяжёлых полей

test(
  'heavy-check',
  'Ошибка',
  async function(value) {

    const start = performance.now();

    await heavyOperation();

    const end = performance.now();

    console.log(this.path, end - start);

    return true;
  }
)

Debug middleware для Yup

Создание middleware

export const withValidationDebug = (
  schema
) => {

  return {

    async validate(data, options) {

      console.group('YUP DEBUG');

      console.log('INPUT:', data);

      try {

        const result = await schema.validate(
          data,
          options
        );

        console.log('SUCCESS:', result);

        return result;

      } catch(error) {

        console.error('FAILED:', error);

        throw error;

      } finally {

        console.groupEnd();
      }
    }
  };
};

Типичные проблемы, выявляемые логированием

undefined вместо null

console.log(value === undefined);
console.log(value === null);

Пустая строка в number()

.transform((value, originalValue) => {

  console.log(typeof originalValue);

  return originalValue === ''
    ? undefined
    : value;
})

Неверный path

console.log(error.path);

Неожиданный cast

console.log(schema.cast(data));

Централизованная система debug

Полноценный debug helper

export const createValidationDebugger = (
  schema
) => {

  const resolver = yupResolver(schema);

  return async (
    data,
    context,
    options
  ) => {

    const start = performance.now();

    console.group('VALIDATION');

    console.log('DAT A:', data);
    console.log('CONTEXT:', context);

    try {

      const result = await resolver(
        data,
        context,
        options
      );

      console.log('VALUES:', result.values);
      console.log('ERRORS:', result.errors);

      return result;

    } catch(error) {

      console.error(error);

      throw error;

    } finally {

      const end = performance.now();

      console.log(
        'DURATION:',
        `${end - start}ms`
      );

      console.groupEnd();
    }
  };
};

Практика структурированного логирования

Плохой вариант

console.log(value);

Хороший вариант

console.log({
  type: 'validation',
  field: this.path,
  value,
  timestamp: Date.now()
});

Логирование уровней ошибок

Разделение severity

logger.info('Validation started');

logger.warn('Suspicious value');

logger.error('Validation failed');

Отладка схем через describe()

Анализ структуры схемы

console.log(schema.describe());

Что возвращает describe()

{
  type: 'object',
  fields: {
    email: {
      type: 'string',
      tests: [...]
    }
  }
}

Комбинирование describe и логирования

const description = schema.describe();

console.log(
  JSON.stringify(description, null, 2)
);

Практика изоляции ошибок

Проверка отдельного поля

await schema.fields.email.validate(
  'wrong-email'
);

Локальное логирование

schema.fields.email
  .validate(value)
  .catch(console.error);

Инструменты browser debugging

console.group

console.group('EMAIL VALIDATION');

console.log(value);

console.groupEnd();

console.table

console.table(error.inner);

console.trace

console.trace('Validation stack');

Паттерн audit logging

Создание audit trail

const audit = [];

test(
  'audit-check',
  'Ошибка',
  function(value) {

    audit.push({
      field: this.path,
      value,
      time: Date.now()
    });

    return true;
  }
)

Debugging complex forms

Логирование изменённых полей

const watched = watch();

useEffect(() => {
  console.log(watched);
}, [watched]);

Практика минимального шума

Избыточное логирование усложняет диагностику.

Оптимальная стратегия:

  • логировать только проблемные поля;
  • группировать сообщения;
  • использовать timestamps;
  • отключать debug в production;
  • избегать дублирования сообщений;
  • хранить structured logs;
  • разделять validation и network logs.

Практика production-ready диагностики

Качественная система логирования Yup должна:

  • поддерживать уровни логирования;
  • измерять время выполнения;
  • отслеживать async validation;
  • сохранять path ошибок;
  • логировать context;
  • анализировать transform;
  • поддерживать nested schema;
  • позволять централизованное отключение debug;
  • иметь единый формат сообщений;
  • интегрироваться с monitoring-системами.