ТЗ: поддержка FON в LogIt++
1. Цель
Добавить в библиотеку log-it-cpp поддержку структурированных логов в формате FON — Fast Object Notation.
Реализовать:
низкоуровневый сериализатор FON без внешних зависимостей;
форматер FonLogFormatter;
файловый backend FonFileLogger;
расширение .fon;
публичные include-файлы и макросы;
тесты;
пример использования;
документацию.
Репозиторий:
https://github.com/NewYaroslav/log-it-cpp
Целевой стандарт:
Не использовать:
2. Исходная архитектура
В библиотеке уже существует интерфейс:
class ILogFormatter {
public:
virtual ~ILogFormatter() = default;
virtual void set_timestamp_offset(int64_t offset_ms) = 0;
virtual std::string format(const LogRecord& record) const = 0;
};
SimpleLogFormatter уже умеет формировать обычный текст и JSON вручную.
LogRecord содержит уровень, timestamp, файл, строку, функцию, формат, имена аргументов, аргументы, ID потока и индекс backend'а.
FileLogger получает уже отформатированную строку:
void log(const LogRecord& record, const std::string& message) override;
и добавляет перевод строки при записи.
Сейчас расширение .log жёстко прописано в логике открытия, ротации и поиска файлов. Например, ротация явно использует base + ".log".
3. Общая архитектура решения
Добавить следующие сущности:
include/logit_cpp/logit/formatter/FonEncoder.hpp
include/logit_cpp/logit/formatter/FonLogFormatter.hpp
include/logit_cpp/logit/loggers/FonFileLogger.hpp
Назначение:
FonEncoder
↓
FonLogFormatter
↓
FonFileLogger
FonEncoder отвечает только за синтаксис FON.
FonLogFormatter преобразует LogRecord в одну FON-запись.
FonFileLogger является удобной специализацией файлового backend'а с расширением .fon.
Не дублировать в FonFileLogger всю реализацию FileLogger.
4. Формат выходной записи
Одна запись должна занимать ровно одну строку:
timestamp_ms=l:1782131537125,level=e:2,level_name=s:"info",message=s:"Order opened",file=s:"trade.cpp",line=i:142,function=s:"open_order",thread_id=s:"1234",logger_index=i:0
Форматер не добавляет \n.
Перевод строки добавляет backend.
Порядок полей должен быть детерминированным.
4.1. Обязательные поля
timestamp_ms
level
level_name
message
file
line
function
thread_id
logger_index
Рекомендуемое соответствие типов:
Поле | FON
-- | --
timestamp_ms | l — int64
level | e — uint8
level_name | s
message | s
file | s
line | i — int32
function | s
thread_id | s
logger_index | i
4.2. Дополнительные поля
Добавлять, если они включены конфигурацией:
format
arg_names
args
flags
context
Пример:
format=s:"Order {} opened",
arg_names=s:"order_id",
args=s:["12345"],
flags=o:{print_mode=b:0,fmt_mode=b:1,raw_mode=b:0}
На первом этапе args_array сериализовать как массив строк:
args=s:["123","BUY","1.07235"]
Причина: текущий JSON-форматер также вызывает to_string() для каждого аргумента и теряет исходный тип.
Не пытаться в рамках этой задачи перерабатывать VariableValue и вводить полноценное типизированное структурированное логирование.
5. FonEncoder
5.1. Назначение
Добавить небольшой dependency-free класс или набор функций для формирования FON.
Предпочтительный интерфейс:
namespace logit {
namespace fon {
class Encoder {
public:
Encoder();
void reserve(std::size_t size);
void add_string(const std::string& key, const std::string& value);
void add_bool(const std::string& key, bool value);
void add_uint8(const std::string& key, uint8_t value);
void add_int16(const std::string& key, int16_t value);
void add_int32(const std::string& key, int32_t value);
void add_uint32(const std::string& key, uint32_t value);
void add_int64(const std::string& key, int64_t value);
void add_uint64(const std::string& key, uint64_t value);
void add_float(const std::string& key, float value);
void add_double(const std::string& key, double value);
void add_string_array(
const std::string& key,
const std::vector<std::string>& values);
void add_object(
const std::string& key,
const std::string& encoded_object);
const std::string& str() const noexcept;
std::string take();
private:
void append_separator();
std::string m_output;
bool m_first;
};
std::string escape_string(const std::string& value);
} // namespace fon
} // namespace logit
Допускается другое внутреннее устройство, если публичный API остаётся простым.
5.2. Синтаксис типов
Поддержать:
e — uint8
t — int16
i — int32
u — uint32
l — int64
g — uint64
f — float
d — double
s — string
b — bool
o — object
Бинарный тип r и Z85 в рамках этой задачи не нужны.
5.3. Булевы значения
Записывать:
Не использовать:
если официальные реализации ожидают числовую форму.
5.4. Строки
Строка должна заключаться в двойные кавычки:
Обязательно экранировать:
Ключи в текущей реализации являются константами библиотеки, поэтому экранирование ключей можно не реализовывать.
Перед реализацией проверить парсер FON.rust или тесты официальных репозиториев и точно определить поведение для управляющих байтов 0x00–0x1F, не имеющих стандартного escape-представления.
Не вводить неподдерживаемый синтаксис вроде:
без подтверждения, что официальные парсеры его принимают.
Если FON не определяет представление такого символа:
5.5. Числа с плавающей точкой
Требования:
использовать классическую locale;
десятичный разделитель всегда . независимо от системной locale;
не выводить nan, inf, -inf как обычные FON-числа без проверки совместимости;
использовать достаточную точность:
std::numeric_limits<float>::max_digits10
std::numeric_limits<double>::max_digits10
Для C++11 проверить доступность max_digits10. При необходимости использовать:
Политика для NaN и infinity:
строковое значение либо пустое поле
Предпочтительно сериализовать как строку:
value=s:"nan"
value=s:"+inf"
value=s:"-inf"
Но только через отдельный метод или явно документированную ветку.
6. FonLogFormatter
6.1. Интерфейс
class FonLogFormatter : public ILogFormatter {
public:
struct Config {
bool include_level_name = true;
bool include_source = true;
bool include_function = true;
bool include_thread_id = true;
bool include_logger_index = true;
bool include_format = false;
bool include_arg_names = false;
bool include_args = false;
bool include_flags = false;
bool include_context = true;
};
FonLogFormatter();
explicit FonLogFormatter(const Config& config);
void set_timestamp_offset(int64_t offset_ms) override;
std::string format(const LogRecord& record) const override;
private:
Config m_config;
std::atomic<int64_t> m_offset_ms;
};
6.2. Timestamp
Основное поле:
timestamp_ms=l:<record.timestamp_ms + offset>
Стоит сохранить семантику set_timestamp_offset(), совместимую с SimpleLogFormatter.
Не добавлять одновременно локальное время и UTC-время без отдельной настройки.
6.3. Формирование message
Нужно получить итоговое пользовательское сообщение, эквивалентное %v в обычном форматере.
Не записывать в message только record.format, поскольку это может быть шаблон:
LOGIT_INFO("Order", order_id, "opened");
Использовать существующий механизм библиотеки для формирования значения %v.
Предпочтительный вариант:
SimpleLogFormatter m_message_formatter{"%v"};
или более низкоуровневый существующий utility, если он позволяет получить итоговое сообщение без повторной компиляции паттерна.
SimpleLogFormatter не должен создаваться при каждом вызове format().
6.4. Уровень логирования
Добавить функцию преобразования уровня в стабильное имя:
trace
debug
info
warning
error
fatal
Использовать существующее преобразование библиотеки, если оно уже есть.
Не создавать вторую таблицу имён уровней, если аналогичная таблица уже используется PatternCompiler.
6.5. Контекст
При LOGIT_WITH_CONTEXT и наличии snapshot добавить:
context=o:{mdc=o:{request_id=s:"req-42"},ndc=s:["checkout","payment"]}
Сначала изучить фактическую структуру LogContextSnapshot.
Если структура контекста не позволяет безопасно и просто получить MDC/NDC, контекст можно исключить из первой версии, но это должно быть отражено в документации и тестах.
При выключенном LOGIT_WITH_CONTEXT не должно оставаться ссылок на отсутствующие типы и поля.
6.6. Потокобезопасность
format() должен быть const и безопасным для параллельного вызова.
Запрещено:
изменять shared buffer;
использовать общий изменяемый std::ostringstream;
менять глобальную locale;
хранить временный результат в mutable member.
7. Изменение FileLogger
7.1. Добавить расширение в конфигурацию
В FileLogger::Config добавить:
std::string file_extension = ".log";
Инварианты:
"log" → ".log"
"fon" → ".fon"
Рекомендуемый private helper:
static std::string normalize_file_extension(
const std::string& extension);
7.2. Удалить жёстко прописанный .log
Заменить все выражения:
на:
или helper:
make_current_filename(base)
Обновить:
Не делать простую глобальную замену без проверки логики.
7.3. Обратная совместимость
При стандартной конфигурации поведение обязано остаться прежним:
logs/2026-06-22.log
logs/2026-06-22.001.log
Все существующие тесты FileLogger должны проходить без изменений либо с минимальными изменениями, не меняющими ожидаемое поведение.
8. FonFileLogger
8.1. Назначение
FonFileLogger — удобная специализация FileLogger, которая по умолчанию использует:
directory = "logs"
file_extension = ".fon"
Не копировать реализацию ротации, компрессии и очередей.
Предпочтительно наследование:
class FonFileLogger : public FileLogger {
public:
using Config = FileLogger::Config;
FonFileLogger()
: FileLogger(make_default_config()) {}
explicit FonFileLogger(const Config& config)
: FileLogger(normalize_config(config)) {}
private:
static Config make_default_config();
static Config normalize_config(Config config);
};
При передаче пользовательского Config принудительно устанавливать:
config.file_extension = ".fon";
Альтернативный вариант — собственный FonFileLogger::Config, содержащий FileLogger::Config file.
Выбрать вариант, лучше соответствующий текущему стилю библиотеки.
8.2. Расширение
Файлы должны называться:
2026-06-22.fon
2026-06-22.001.fon
2026-06-22_143522.fon
2026-06-22_143522.1.fon
Сжатые:
2026-06-22.001.fon.gz
2026-06-22.001.fon.zst
8.3. Связь backend'а и форматера
Backend не должен самостоятельно повторно сериализовывать обычные записи.
Нормальная схема:
LOGIT_ADD_LOGGER(
logit::FonFileLogger,
(backend_config),
logit::FonLogFormatter,
(formatter_config)
);
Добавить convenience macro, который регистрирует правильную пару.
9. Raw mode
Проверить поведение raw_mode.
Проблема: raw-запись может обходить форматер и попасть в .fon как произвольный текст, сделав файл невалидным.
Выбрать и реализовать одну политику:
Предпочтительная политика
Для FonFileLogger запретить bypass форматера:
timestamp_ms=l:...,level=e:...,message=s:"raw text",raw=b:1
Чтобы не дублировать код, использовать общий FonEncoder.
Если архитектура маршрутизации не позволяет backend'у отличить уже сериализованное сообщение от raw-сообщения, задокументировать ограничение и добавить тест, фиксирующий текущее поведение.
Не оставлять этот случай нерассмотренным.
10. Публичные include-файлы
Обновить:
include/logit_cpp/logit/formatter.hpp
include/logit_cpp/logit/loggers.hpp
include/logit_cpp/logit.hpp
Добавить соответствующие includes в правильном порядке с соблюдением NHR.
Пользователь должен иметь возможность написать:
и использовать:
logit::FonLogFormatter
logit::FonFileLogger
без дополнительных include.
11. Макросы
Изучить существующую систему макросов регистрации backend'ов.
Добавить макросы в том же стиле именования.
Минимально:
LOGIT_ADD_FON_FILE_LOGGER_DEFAULT()
LOGIT_ADD_FON_FILE_LOGGER(backend_config, formatter_config)
Возможный интерфейс:
#define LOGIT_ADD_FON_FILE_LOGGER_DEFAULT()
LOGIT_ADD_LOGGER(
logit::FonFileLogger, (),
logit::FonLogFormatter, ())
И вариант с конфигурацией:
#define LOGIT_ADD_FON_FILE_LOGGER(backend_args, formatter_args)
LOGIT_ADD_LOGGER(
logit::FonFileLogger, backend_args,
logit::FonLogFormatter, formatter_args)
Не добавлять макросы вслепую. Сначала изучить существующие naming conventions, short aliases и правила аргументов LOGIT_ADD_LOGGER.
12. Пример
Добавить:
examples/example_logit_fon.cpp
Пример должен:
создать FonFileLogger;
использовать FonLogFormatter;
записать несколько уровней;
записать строку с кавычками, обратным слешем и переводом строки;
использовать аргументы;
вызвать LOGIT_WAIT().
Пример:
#include <logit.hpp>
int main() {
logit::FileLogger::Config backend_config;
backend_config.directory = "logs/fon";
backend_config.async = true;
backend_config.max_file_size_bytes = 1024 * 1024;
logit::FonLogFormatter::Config formatter_config;
formatter_config.include_args = true;
formatter_config.include_flags = true;
LOGIT_ADD_LOGGER(
logit::FonFileLogger,
(backend_config),
logit::FonLogFormatter,
(formatter_config)
);
const int order_id = 12345;
const double price = 1.07235;
LOGIT_INFO("Application started");
LOGIT_INFO("Order opened", order_id, price);
LOGIT_WARN("Quoted value: \"test\"\nnext line");
LOGIT_WAIT();
}
Ожидаемый файл:
13. Тесты
13.1. FonEncoder
Добавить unit-тесты:
tests/fon_encoder_test.cpp
Проверить:
Примеры:
EXPECT_EQ(
encode_string("message", "Hello"),
"message=s:"Hello""
);
EXPECT_EQ(
encode_string("message", "a"b\c\n"),
"message=s:"a\"b\\c\n""
);
13.2. FonLogFormatter
Добавить:
tests/fon_log_formatter_test.cpp
Проверить:
обязательные поля;
стабильный порядок;
корректные типы;
итоговое значение message;
включение и исключение optional fields;
timestamp offset;
пустые строки;
Unicode UTF-8;
многопоточный вызов format().
13.3. FileLogger extension
Добавить:
tests/file_logger_extension_test.cpp
Проверить:
Проверить:
13.4. FonFileLogger
Добавить:
tests/fon_file_logger_test.cpp
Проверить:
создание .fon;
несколько записей — несколько строк;
каждая запись находится на одной физической строке;
embedded \n записывается как два символа \n;
ротацию;
async wait();
raw mode согласно выбранной политике.
13.5. Проверка совместимости
Желательно добавить небольшой interoperability-тест:
сгенерировать строку C++-форматером;
передать её официальному FON-парсеру в CI либо fixture-тесте;
убедиться, что запись разбирается.
Не делать Rust обязательной зависимостью основной сборки.
Допустимо хранить заранее проверенные fixtures:
14. Документация
Добавить:
Описать:
Обновить:
README.md
README-RU.md
docs/mainpage.dox
guides/orientation.md
Не позиционировать FON как стандартный или широко распространённый формат.
Указать, что это опциональный машинно-читаемый формат.
15. Требования к коду
Соблюдать стиль проекта:
/// \brief ...
/// \param ...
/// \return ...
комментарии в коде на английском;
include guards в существующем стиле;
namespace logit;
без std::variant;
без std::filesystem в C++11-ветке;
без изменения публичного поведения существующих backend'ов;
без дополнительного worker thread в форматере;
без глобального состояния.
16. Производительность
Форматирование выполняется на горячем пути, поэтому:
предварительно вызывать reserve();
избегать создания std::ostringstream на каждое маленькое поле;
целые числа можно сначала реализовать через std::to_string;
не использовать std::regex;
не парсить полученную строку повторно;
не выполнять валидацию всей FON-записи после формирования;
не создавать SimpleLogFormatter("%v") на каждый вызов.
Ориентировочно:
fon::Encoder encoder;
encoder.reserve(record.format.size() + record.file.size() + 256);
Оптимизация не должна ухудшать корректность.
17. Критерии приёмки
Задача считается выполненной, когда:
FonLogFormatter формирует валидную FON-запись.
Каждая запись занимает одну строку.
FonFileLogger создаёт файлы .fon.
Ротация правильно сохраняет .fon.
Сжатые файлы имеют форму .fon.gz или .fon.zst.
Старый FileLogger продолжает создавать .log.
Все старые тесты проходят.
Все новые тесты проходят на:
Windows;
Linux;
macOS;
C++11;
C++17.
Emscripten продолжает компилироваться с корректной заглушкой.
Нет зависимости от Rust, Go или стороннего FON SDK.
Публичный пример собирается.
README содержит рабочий usage example.
Строки, содержащие \n, не разбивают FON-запись на несколько строк.
Поведение raw mode явно определено и протестировано.
18. Что не входит в задачу
Не реализовывать:
парсер FON;
чтение .fon обратно в LogRecord;
Z85;
бинарные поля;
schema registry;
сетевой FON backend;
типизированную переработку VariableValue;
преобразование старых .log файлов;
индексацию FON-файлов;
отдельный viewer.
Это можно сделать отдельными задачами.
Моя рекомендация по реализации
Самая важная часть здесь не FonLogFormatter, а обобщение расширения в FileLogger. Сейчас .log зашито во множество мест, поэтому создание отдельной копии FonFileLogger быстро приведёт к расхождению двух реализаций.
Правильная схема:
FileLogger
├── file_extension = ".log"
└── вся общая ротация и компрессия
FonFileLogger : FileLogger
└── file_extension = ".fon"
FonLogFormatter
└── LogRecord → FON line
Также я бы оставил FonEncoder внутренним или полупубличным utility. Полный FON SDK библиотеке логирования не нужен: здесь требуется только односторонняя сериализация фиксированной структуры.
ТЗ: поддержка FON в LogIt++
1. Цель
Добавить в библиотеку
log-it-cppподдержку структурированных логов в формате FON — Fast Object Notation.Реализовать:
низкоуровневый сериализатор FON без внешних зависимостей;
форматер
FonLogFormatter;файловый backend
FonFileLogger;расширение
.fon;публичные include-файлы и макросы;
тесты;
пример использования;
документацию.
Репозиторий:
Целевой стандарт:
Не использовать:
Rust;
fon.hизFON.go;внешние FON-библиотеки;
JSON-библиотеки;
C++17-only API в общем коде;
исключения в обычном пути форматирования, если можно обойтись без них.
2. Исходная архитектура
В библиотеке уже существует интерфейс:
SimpleLogFormatterуже умеет формировать обычный текст и JSON вручную.LogRecordсодержит уровень, timestamp, файл, строку, функцию, формат, имена аргументов, аргументы, ID потока и индекс backend'а.FileLoggerполучает уже отформатированную строку:и добавляет перевод строки при записи.
Сейчас расширение
.logжёстко прописано в логике открытия, ротации и поиска файлов. Например, ротация явно используетbase + ".log".3. Общая архитектура решения
Добавить следующие сущности:
Назначение:
FonEncoderотвечает только за синтаксис FON.FonLogFormatterпреобразуетLogRecordв одну FON-запись.FonFileLoggerявляется удобной специализацией файлового backend'а с расширением.fon.Не дублировать в
FonFileLoggerвсю реализациюFileLogger.4. Формат выходной записи
Одна запись должна занимать ровно одну строку:
Форматер не добавляет
\n.Перевод строки добавляет backend.
Порядок полей должен быть детерминированным.
4.1. Обязательные поля
Рекомендуемое соответствие типов:
Поле | FON -- | -- timestamp_ms | l — int64 level | e — uint8 level_name | s message | s file | s line | i — int32 function | s thread_id | s logger_index | i4.2. Дополнительные поля
Добавлять, если они включены конфигурацией:
Пример:
На первом этапе
args_arrayсериализовать как массив строк:Причина: текущий JSON-форматер также вызывает
to_string()для каждого аргумента и теряет исходный тип.Не пытаться в рамках этой задачи перерабатывать
VariableValueи вводить полноценное типизированное структурированное логирование.5.
FonEncoder5.1. Назначение
Добавить небольшой dependency-free класс или набор функций для формирования FON.
Предпочтительный интерфейс:
Допускается другое внутреннее устройство, если публичный API остаётся простым.
5.2. Синтаксис типов
Поддержать:
Бинарный тип
rи Z85 в рамках этой задачи не нужны.5.3. Булевы значения
Записывать:
Не использовать:
если официальные реализации ожидают числовую форму.
5.4. Строки
Строка должна заключаться в двойные кавычки:
Обязательно экранировать:
Ключи в текущей реализации являются константами библиотеки, поэтому экранирование ключей можно не реализовывать.
Перед реализацией проверить парсер
FON.rustили тесты официальных репозиториев и точно определить поведение для управляющих байтов0x00–0x1F, не имеющих стандартного escape-представления.Не вводить неподдерживаемый синтаксис вроде:
без подтверждения, что официальные парсеры его принимают.
Если FON не определяет представление такого символа:
заменить его на
?;либо применить документированную безопасную замену;
добавить тест.
5.5. Числа с плавающей точкой
Требования:
использовать классическую locale;
десятичный разделитель всегда
.независимо от системной locale;не выводить
nan,inf,-infкак обычные FON-числа без проверки совместимости;использовать достаточную точность:
Для C++11 проверить доступность
max_digits10. При необходимости использовать:Политика для
NaNи infinity:Предпочтительно сериализовать как строку:
Но только через отдельный метод или явно документированную ветку.
6.
FonLogFormatter6.1. Интерфейс
6.2. Timestamp
Основное поле:
Стоит сохранить семантику
set_timestamp_offset(), совместимую сSimpleLogFormatter.Не добавлять одновременно локальное время и UTC-время без отдельной настройки.
6.3. Формирование
messageНужно получить итоговое пользовательское сообщение, эквивалентное
%vв обычном форматере.Не записывать в
messageтолькоrecord.format, поскольку это может быть шаблон:Использовать существующий механизм библиотеки для формирования значения
%v.Предпочтительный вариант:
или более низкоуровневый существующий utility, если он позволяет получить итоговое сообщение без повторной компиляции паттерна.
SimpleLogFormatterне должен создаваться при каждом вызовеformat().6.4. Уровень логирования
Добавить функцию преобразования уровня в стабильное имя:
Использовать существующее преобразование библиотеки, если оно уже есть.
Не создавать вторую таблицу имён уровней, если аналогичная таблица уже используется
PatternCompiler.6.5. Контекст
При
LOGIT_WITH_CONTEXTи наличии snapshot добавить:Сначала изучить фактическую структуру
LogContextSnapshot.Если структура контекста не позволяет безопасно и просто получить MDC/NDC, контекст можно исключить из первой версии, но это должно быть отражено в документации и тестах.
При выключенном
LOGIT_WITH_CONTEXTне должно оставаться ссылок на отсутствующие типы и поля.6.6. Потокобезопасность
format()должен бытьconstи безопасным для параллельного вызова.Запрещено:
изменять shared buffer;
использовать общий изменяемый
std::ostringstream;менять глобальную locale;
хранить временный результат в mutable member.
7. Изменение
FileLogger7.1. Добавить расширение в конфигурацию
В
FileLogger::Configдобавить:Инварианты:
пустое значение заменяется на
.log;значение без точки нормализуется:
значение с точкой не меняется:
Рекомендуемый private helper:
7.2. Удалить жёстко прописанный
.logЗаменить все выражения:
на:
или helper:
Обновить:
открытие текущего файла;
ротацию;
sequence naming;
timestamp naming;
проверку текущего файла;
retention;
перечисление файлов;
фильтрацию сжатых файлов;
Emscripten-заглушку;
все C++11 и C++17 ветки;
Windows и POSIX ветки.
Не делать простую глобальную замену без проверки логики.
7.3. Обратная совместимость
При стандартной конфигурации поведение обязано остаться прежним:
Все существующие тесты
FileLoggerдолжны проходить без изменений либо с минимальными изменениями, не меняющими ожидаемое поведение.8.
FonFileLogger8.1. Назначение
FonFileLogger— удобная специализацияFileLogger, которая по умолчанию использует:Не копировать реализацию ротации, компрессии и очередей.
Предпочтительно наследование:
При передаче пользовательского
Configпринудительно устанавливать:Альтернативный вариант — собственный
FonFileLogger::Config, содержащийFileLogger::Config file.Выбрать вариант, лучше соответствующий текущему стилю библиотеки.
8.2. Расширение
Файлы должны называться:
Сжатые:
8.3. Связь backend'а и форматера
Backend не должен самостоятельно повторно сериализовывать обычные записи.
Нормальная схема:
Добавить convenience macro, который регистрирует правильную пару.
9. Raw mode
Проверить поведение
raw_mode.Проблема: raw-запись может обходить форматер и попасть в
.fonкак произвольный текст, сделав файл невалидным.Выбрать и реализовать одну политику:
Предпочтительная политика
Для
FonFileLoggerзапретить bypass форматера:обычный
raw_modeне должен создавать невалидную строку;raw-текст нужно записать как корректную FON-запись:
Чтобы не дублировать код, использовать общий
FonEncoder.Если архитектура маршрутизации не позволяет backend'у отличить уже сериализованное сообщение от raw-сообщения, задокументировать ограничение и добавить тест, фиксирующий текущее поведение.
Не оставлять этот случай нерассмотренным.
10. Публичные include-файлы
Обновить:
Добавить соответствующие includes в правильном порядке с соблюдением NHR.
Пользователь должен иметь возможность написать:
и использовать:
без дополнительных include.
11. Макросы
Изучить существующую систему макросов регистрации backend'ов.
Добавить макросы в том же стиле именования.
Минимально:
Возможный интерфейс:
И вариант с конфигурацией:
Не добавлять макросы вслепую. Сначала изучить существующие naming conventions, short aliases и правила аргументов
LOGIT_ADD_LOGGER.12. Пример
Добавить:
Пример должен:
создать
FonFileLogger;использовать
FonLogFormatter;записать несколько уровней;
записать строку с кавычками, обратным слешем и переводом строки;
использовать аргументы;
вызвать
LOGIT_WAIT().Пример:
Ожидаемый файл:
13. Тесты
13.1.
FonEncoderДобавить unit-тесты:
Проверить:
пустую запись;
одно поле;
несколько полей;
отсутствие лишней запятой;
все целочисленные типы;
float;double;bool;строки;
экранирование;
массив строк;
вложенный объект;
системную locale с запятой;
NaN;infinity.
Примеры:
13.2.
FonLogFormatterДобавить:
Проверить:
обязательные поля;
стабильный порядок;
корректные типы;
итоговое значение
message;включение и исключение optional fields;
timestamp offset;
пустые строки;
Unicode UTF-8;
многопоточный вызов
format().13.3.
FileLoggerextensionДобавить:
Проверить:
Проверить:
текущий файл;
sequence rotation;
timestamp rotation;
retention;
compression naming;
list/read file API;
C++11 code path там, где это возможно.
13.4.
FonFileLoggerДобавить:
Проверить:
создание
.fon;несколько записей — несколько строк;
каждая запись находится на одной физической строке;
embedded
\nзаписывается как два символа\n;ротацию;
async
wait();raw mode согласно выбранной политике.
13.5. Проверка совместимости
Желательно добавить небольшой interoperability-тест:
сгенерировать строку C++-форматером;
передать её официальному FON-парсеру в CI либо fixture-тесте;
убедиться, что запись разбирается.
Не делать Rust обязательной зависимостью основной сборки.
Допустимо хранить заранее проверенные fixtures:
14. Документация
Добавить:
Описать:
что такое FON;
почему реализация dependency-free;
формат одной записи;
список полей;
типы;
конфигурацию;
ограничения;
raw mode;
отличие от JSON;
пример;
совместимость с официальными реализациями.
Обновить:
Не позиционировать FON как стандартный или широко распространённый формат.
Указать, что это опциональный машинно-читаемый формат.
15. Требования к коду
Соблюдать стиль проекта:
C++11;
snake_caseдля методов и переменных, если это соответствует соседнему коду;Doxygen:
комментарии в коде на английском;
include guards в существующем стиле;
namespace
logit;без
std::variant;без
std::filesystemв C++11-ветке;без изменения публичного поведения существующих backend'ов;
без дополнительного worker thread в форматере;
без глобального состояния.
16. Производительность
Форматирование выполняется на горячем пути, поэтому:
предварительно вызывать
reserve();избегать создания
std::ostringstreamна каждое маленькое поле;целые числа можно сначала реализовать через
std::to_string;не использовать
std::regex;не парсить полученную строку повторно;
не выполнять валидацию всей FON-записи после формирования;
не создавать
SimpleLogFormatter("%v")на каждый вызов.Ориентировочно:
Оптимизация не должна ухудшать корректность.
17. Критерии приёмки
Задача считается выполненной, когда:
FonLogFormatterформирует валидную FON-запись.Каждая запись занимает одну строку.
FonFileLoggerсоздаёт файлы.fon.Ротация правильно сохраняет
.fon.Сжатые файлы имеют форму
.fon.gzили.fon.zst.Старый
FileLoggerпродолжает создавать.log.Все старые тесты проходят.
Все новые тесты проходят на:
Windows;
Linux;
macOS;
C++11;
C++17.
Emscripten продолжает компилироваться с корректной заглушкой.
Нет зависимости от Rust, Go или стороннего FON SDK.
Публичный пример собирается.
README содержит рабочий usage example.
Строки, содержащие
\n, не разбивают FON-запись на несколько строк.Поведение raw mode явно определено и протестировано.
18. Что не входит в задачу
Не реализовывать:
парсер FON;
чтение
.fonобратно вLogRecord;Z85;
бинарные поля;
schema registry;
сетевой FON backend;
типизированную переработку
VariableValue;преобразование старых
.logфайлов;индексацию FON-файлов;
отдельный viewer.
Это можно сделать отдельными задачами.
Моя рекомендация по реализации
Самая важная часть здесь не
FonLogFormatter, а обобщение расширения вFileLogger. Сейчас.logзашито во множество мест, поэтому создание отдельной копииFonFileLoggerбыстро приведёт к расхождению двух реализаций.Правильная схема:
Также я бы оставил
FonEncoderвнутренним или полупубличным utility. Полный FON SDK библиотеке логирования не нужен: здесь требуется только односторонняя сериализация фиксированной структуры.