Логирование и отладка

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

  • отсутствующими CLDR-данными;
  • некорректной инициализацией локали;
  • конфликтами форматов дат и чисел;
  • ошибками загрузки модулей;
  • неправильной сериализацией сообщений;
  • проблемами при динамическом переключении языка.

Поэтому логирование и отладка становятся обязательной частью архитектуры приложений, использующих Globalize.


Типичные категории ошибок

Ошибки загрузки CLDR

Globalize не содержит встроенных данных локализации. Все данные поставляются отдельно через CLDR JSON-файлы.

Пример ошибки:

Error: E_MISSING_CLDR: Missing required CLDR content `main/en/numbers`

Причина:

Globalize.load({});

или загрузка неполного набора данных.

Корректная инициализация:

const Globalize = require("globalize");
const cldrData = require("cldr-data");

Globalize.load(
    cldrData.entireSupplemental(),
    cldrData.entireMainFor("en")
);

Globalize.locale("en");

Ошибки отсутствующей локали

Если локаль не была зарегистрирована, попытка форматирования вызовет исключение.

Пример:

Globalize.locale("fr");

const formatter = Globalize.numberFormatter();

console.log(formatter(100));

При отсутствии французских CLDR-данных:

E_MISSING_CLDR

Полезно логировать список загруженных локалей:

console.log(Globalize.cldr);

Ошибки форматирования сообщений

Модуль globalize/message использует ICU MessageFormat.

Некорректный шаблон:

"{name"

Приведёт к ошибке парсинга.

Диагностический код:

try {
    const formatter = Globalize.messageFormatter("{name");
} catch (error) {
    console.error("Ошибка MessageFormat:", error.message);
}

Базовые техники логирования

Логирование инициализации

Одна из важнейших практик — фиксировать этапы запуска системы локализации.

Пример:

console.log("Загрузка CLDR...");
Globalize.load(data);

console.log("Установка локали...");
Globalize.locale("ru");

console.log("Создание форматтеров...");

Такой подход позволяет быстро определить точку сбоя.


Логирование используемой локали

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

console.log("Текущая локаль:", Globalize.locale().locale);

Пример вывода:

Текущая локаль: ru

Логирование входных данных

Ошибки форматирования часто возникают из-за неожиданного типа данных.

Пример:

function formatPrice(value) {
    console.log("Тип значения:", typeof value);
    console.log("Значение:", value);

    return Globalize.numberFormatter({
        style: "currency",
        currency: "USD"
    })(value);
}

Использование try/catch

Globalize активно использует исключения. Любые операции форматирования желательно оборачивать в обработчики ошибок.

Форматирование чисел

try {
    const formatter = Globalize.numberFormatter();

    console.log(formatter("abc"));
} catch (error) {
    console.error(error);
}

Форматирование дат

try {
    const formatter = Globalize.dateFormatter({
        datetime: "medium"
    });

    console.log(formatter(new Date()));
} catch (error) {
    console.error("Ошибка даты:", error.message);
}

Работа с сообщениями

try {
    const msg = Globalize.messageFormatter("hello");

    console.log(msg());
} catch (error) {
    console.error("Ошибка сообщений:", error);
}

Отладка CLDR-данных

Проверка загрузки данных

Globalize использует объект CLDR для хранения всех локализационных данных.

Проверка:

const cldr = require("cldrjs");

console.log(cldr._resolved);

Проверка наличия секций

const cldr = Globalize.cldr;

console.log(
    cldr.main("numbers")
);

Если результат:

undefined

значит раздел не был загружен.


Проверка supplemental-данных

Многие ошибки возникают из-за отсутствия supplemental-файлов.

Например:

Globalize.load(
    require("cldr-data/supplemental/likelySubtags.json")
);

Без них могут не работать:

  • plural rules;
  • currency formatting;
  • date formatting;
  • likely subtags;
  • numbering systems.

Отладка модулей форматирования

NumberFormatter

Диагностика числовых настроек:

const formatter = Globalize.numberFormatter({
    minimumFractionDigits: 2,
    maximumFractionDigits: 2
});

console.log(formatter(10));

Результат:

10,00

Если формат неожиданен, следует проверить:

  • текущую локаль;
  • numbering system;
  • символы разделителей;
  • настройки ICU.

DateFormatter

Проблемы с датами часто связаны с часовыми поясами.

const formatter = Globalize.dateFormatter({
    skeleton: "yMMMd"
});

console.log(formatter(new Date()));

Для диагностики полезно логировать исходный объект даты:

console.log(date.toISOString());

RelativeTimeFormatter

const formatter = Globalize.relativeTimeFormatter("day");

console.log(formatter(-1));

Если вывод неверный:

  • проверяется locale;
  • проверяются plural rules;
  • проверяется наличие supplemental/plurals.json.

Использование debug-режимов сборщиков

Webpack

В production-сборке сообщения об ошибках могут минимизироваться.

Для полноценной диагностики:

mode: "development",
devtool: "source-map"

Vite

export default defineConfig({
    build: {
        sourcemap: true
    }
});

Rollup

export default {
    output: {
        sourcemap: true
    }
};

Source maps позволяют получать исходные строки ошибок вместо минифицированного кода.


Отладка динамической загрузки локалей

Проблема асинхронной загрузки

Частая ошибка:

Globalize.locale("de");

const formatter = Globalize.numberFormatter();

до завершения загрузки CLDR.

Корректный подход:

async function initLocale(locale) {
    const data = await loadLocale(locale);

    Globalize.load(data);
    Globalize.locale(locale);

    console.log("Локаль готова:", locale);
}

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

console.log("CLDR загружен:", !!Globalize.cldr);

Интеграция с системами логирования

Winston

Интеграция с Winston:

const winston = require("winston");

const logger = winston.createLogger({
    transports: [
        new winston.transports.Console()
    ]
});

try {
    Globalize.locale("ru");
} catch (error) {
    logger.error(error.message);
}

Pino

Пример с Pino:

const pino = require("pino");
const logger = pino();

logger.info("Локаль установлена");

Browser Console API

В браузере полезно использовать разные уровни логирования.

console.info("Информация");
console.warn("Предупреждение");
console.error("Ошибка");
console.debug("Отладка");

Глобальный перехват ошибок

Node.js

process.on("uncaughtException", (error) => {
    console.error("Глобальная ошибка:", error);
});

Browser

window.oner ror = function(message, source, line) {
    console.error(message);
};

Анализ стеков ошибок

Типичный стек:

E_MISSING_CLDR: Missing required CLDR content
    at validate (...)
    at numberFormatter (...)

Ключевые элементы:

  • тип ошибки;
  • вызывающий formatter;
  • отсутствующий CLDR-путь;
  • стек вызовов.

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

Source maps особенно важны при production-debugging.

Пример ошибки без source maps:

app.min.js:1

С source maps:

src/i18n/globalize.js:42

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

Избыточное создание formatter-объектов

Плохая практика:

function render(value) {
    return Globalize.numberFormatter()(value);
}

Formatter создаётся каждый вызов.

Корректнее:

const formatter = Globalize.numberFormatter();

function render(value) {
    return formatter(value);
}

Логирование времени выполнения

console.time("format");

formatter(100000);

console.timeEnd("format");

Отладка plural rules

Пример:

const formatter = Globalize.pluralGenerator();

console.log(formatter(1));
console.log(formatter(2));
console.log(formatter(5));

Результаты помогают проверить корректность pluralization.


Проверка ICU MessageFormat

Диагностика параметров

const formatter = Globalize.messageFormatter(
    "Привет, {name}"
);

console.log(
    formatter({
        name: "Alex"
    })
);

Проверка отсутствующих параметров

console.log(
    formatter({})
);

Может привести к:

undefined

или ошибкам шаблона.


Логирование переключения языка

function changeLocale(locale) {
    console.log("Смена локали:", locale);

    Globalize.locale(locale);

    console.log(
        "Активная локаль:",
        Globalize.locale().locale
    );
}

Отладка в React-приложениях

При использовании React проблемы часто возникают из-за повторных рендеров.

Пример диагностики

useEffect(() => {
    console.log("Locale changed:", locale);
}, [locale]);

Проверка formatter recreation

const formatter = useMemo(() => {
    return Globalize.numberFormatter();
}, [locale]);

Без useMemo formatter может создаваться слишком часто.


Отладка на сервере

SSR и locale leakage

В server-side rendering нельзя хранить locale глобально.

Плохой пример:

Globalize.locale(req.locale);

При параллельных запросах возможны конфликты.

Безопаснее:

const instance = new Globalize(req.locale);

Тестирование локализации

Snapshot-тесты

Пример с Jest:

test("currency formatting", () => {
    const formatter = Globalize.currencyFormatter("USD");

    expect(formatter(10))
        .toMatchSnapshot();
});

Проверка нескольких локалей

const locales = ["en", "ru", "fr"];

locales.forEach(locale => {
    Globalize.locale(locale);

    console.log(
        locale,
        Globalize.formatNumber(1000)
    );
});

Диагностика memory leaks

Проблема кэширования formatter-объектов

Если formatter создаётся для тысяч локалей:

cache[locale] = Globalize(locale).numberFormatter();

кэш может бесконтрольно расти.

Диагностика:

console.log(
    Object.keys(cache).length
);

Отладка lazy loading

Контроль загрузки чанков

import("./locale/ru.json")
    .then(data => {
        console.log("Locale loaded");
    })
    .catch(error => {
        console.error(error);
    });

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

Хорошая практика:

logger.error({
    locale: "ru",
    module: "currency",
    message: error.message
});

Вместо:

console.log(error);

Структурированные логи проще анализировать в:

  • ELK Stack;
  • Grafana;
  • Datadog;
  • Sentry.

Интеграция с Sentry

Пример с Sentry:

Sentry.captureException(error, {
    extra: {
        locale: Globalize.locale().locale
    }
});

Наиболее полезные точки логирования

Рекомендуется логировать:

  • загрузку CLDR;
  • смену локали;
  • ошибки formatter;
  • ошибки MessageFormat;
  • загрузку translation-файлов;
  • fallback locale;
  • время инициализации;
  • lazy loading;
  • missing translations;
  • plural rules;
  • currency formatting.

Частые анти-паттерны

Подавление ошибок

Плохо:

try {
    formatter(value);
} catch {}

Ошибка теряется полностью.


Избыточное логирование

Плохо:

console.log("render");

внутри каждого рендера React-компонента.


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

Плохо:

console.error(error);

Лучше:

console.error({
    locale: "ru",
    currency: "KZT",
    error: error.message
});

Практика централизованной диагностики

Полезно создать единый слой работы с Globalize.

class I18nService {
    setLocale(locale) {
        console.log("Switch locale:", locale);

        Globalize.locale(locale);
    }

    formatNumber(value) {
        try {
            return Globalize.formatNumber(value);
        } catch (error) {
            console.error(error);

            return value;
        }
    }
}

Такой подход упрощает:

  • аудит ошибок;
  • централизованное логирование;
  • диагностику production-проблем;
  • интеграцию с monitoring-системами;
  • тестирование локализации.