Логи в файлах: написал своё приложение для просмотра структурированных логов
Не каждому проекту нужно децентрализованное логирование. В моём случае, оказалось проще хранить логи в .json файлах формата Compact Log Event Format (CLEF). Мне нужно было простое и бесплатное решение для просмотра логов.
Логотип разработанного приложения
Готовое решение
На момент написания статьи в свободном доступе было только 1 подходящее приложение для чтения логов в формате CLEF — Compact Log Viewer, доступно в Microsoft Store.
Compact Log Viewer
Уже неплохо, приложение позволяло смотреть и фильтровать логи. Однако в этом решении было несколько минусов:
Каждому разработчике нужно устанавливать локально через Microsoft Store
За раз можно открыть только 1 файл
Нельзя поставить на тестовый сервер если это линукс
Часть UI занимает бесполезный график
После повторного открытия приложения загруженный ранее файл терялся, его нужно было грузить заново
Через некоторое время я устал от постоянного скачивания логов с сервера на локальную машину для их просмотра. Поэтому решил написать своё собственное приложение.
Требования к разрабатываемому приложению
К разрабатываемое приложению я установил следующие требования:
Отображать список логов
Поддерживать просмотр свойств отдельных логово
Поддерживать фильтрацию по свойствам
Поддерживать загрузку нескольких файлов
Сохранять логи между сессиями
Поддерживать запуск в среде Docker
Поддержка среды Docker позволила сделать приложение «доступным из коробки», включив его в дефолтный docker-compose файл на проектах. Также стала возможна загрузка на сервер.
Процесс разработки
Для разработки проекта был выбор между React и Angular. По личным предпочтениям был выбран React + StyledComponents.
Для UI компонентов выбрал бесплатное решение RsuiteJs.
Flow-приложения:
Перейти на страницу загрузки логов
Загрузить логи
Перенаправить на страницу просмотра логов
Реализация загрузки логов
Для форм остановился на связке Yup с Formik. Создал обёртки над компонентами форм, FileUploader
от RSuite не подходил, используя библиотеку ReactDropzone написал кастомную реализацию.
Страница загрузки файлов
Хранение загруженных логов
Для упрощения проекта отказался от backend и NoSql базы данных. Логи записываю в хранилище браузера используя Localforage. Добавил AppState
и AppStorage
контексты.
//прослойка над хранилищем, контролирует согласованность данных за счет storageVersion
export interface AppStorage {
loadedLogFilesInfo?: FileInfo[];
csLogs?: CsLog[];
storageVersion: number;
}
//Основной контекст приложения
export interface AppState {
loadedLogFilesInfo?: FileInfo[];
csLogs?: CsLog[];
}
export function BaseLayout(): ReactElement {
//кастомная реализация светлой и темной темы
const theme = useTheme();
return (
);
}
Для дальнейшего отображения логов в списке нужно установить что принимать за уникальный Id каждой записи в файле. Рассмотрев представленный ниже пример логов. Видно, что поля id нету. Можно попробовать использовать "@t"
, однако гарантии уникальности время лога не даёт.
{"@t":"2024-03-06T07:43:20.5672449Z","@mt":"Starting up","@l":"Information","EnvironmentName":"Staging"}
{"@t":"2024-03-06T07:43:23.3405140Z","@mt":"Executing ViewResult, running view {ViewName}.","@l":"Warning","@tr":"f3e14fda062a0ae0d641782c77ee0617","@sp":"0aee0c652548be62","ViewName":"Index","EventId":{"Id":1,"Name":"ViewResultExecuting"},"SourceContext":"Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor","ActionId":"9ea02b27-cd15-4432-82b1-7f9768a48e4b","ActionName":"DealerServiceSystem.Web.Controllers.CheckListCategoryController.Index (DealerServiceSystem.Web)","RequestId":"40000bc6-0002-f200-b63f-84710c7967bb","EnvironmentName":"Staging"}
{"@t":"2024-03-06T07:43:23.7431244Z","@mt":"Executed ViewResult - view {ViewName} executed in {ElapsedMilliseconds}ms.","@l":"Debug","@tr":"f3e14fda062a0ae0d641782c77ee0617","@sp":"0aee0c652548be62","ViewName":"Index","ElapsedMilliseconds":404.2372,"EventId":{"Id":4,"Name":"ViewResultExecuted"},"SourceContext":"Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor","ActionId":"9ea02b27-cd15-4432-82b1-7f9768a48e4b","RequestId":"40000bc6-0002-f200-b63f-84710c7967bb","EnvironmentName":"Staging"}
{"@t":"2024-03-06T07:43:23.7466340Z","@mt":"Executed action in {ElapsedMilliseconds}ms","@l":"Warning","@tr":"f3e14fda062a0ae0d641782c77ee0617","@sp":"0aee0c652548be62","ElapsedMilliseconds":536.3229,"EventId":{"Id":105,"Name":"ActionExecuted"},"SourceContext":"Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker","RequestId":"40000bc6-0002-f200-b63f-84710c7967bb","EnvironmentName":"Staging"}
Решил эту проблему добавив «искусственный» id
, сгенерированный через crypto-randomuuid, вышли следующие структуры:
export interface CsLog {
id: string;
data: CsLogData;
}
export interface CsLogData {
[key: string]: any;
'@t': Date;
'@mt': string;
'@l'?: 'Verbose' | 'Debug' | 'Information' | 'Warning' | 'Error' | 'Fatal';
}
Реализация просмотра и фильтрации логов
Реализовал пагинацию в отдельном компоненте, используется следующим образом:
const [paginatedLogs, setPaginatedLogs] = useState(null)
Для фильтра по логам использовал SearchJs, синтаксис простейшего фильтра { "data.
реализация:
searchjs.matchArray(appState.csLogs, JSON.parse(filter))
Валидацию фильтра сделал в лоб, пытаюсь парсить json, ошибка — неверный формат:
export const logsViewPageFormSchema: yup.Schema = yup.object({
filter: yup.string().test('json', 'Filter must have JSON format', (value) => {
if (value === undefined) {
return true;
}
try {
JSON.parse(value);
return true;
} catch (error) {
return false;
}
})
});
В итоге вышла следующая страница:
Страница просмотра логов
Публикация приложения
Чтобы сделать проект общедоступным выложил его код в общедоступный репозиторий на GitHub по этой ссылке.
Настроил workflow на билд Docker Image-а и паблиш в репозитории DockerHub ссылка. Приложение можно запустить используя следующий docker-compose файл, проект будет доступен на http://localhost:8080 порту:
version: "3.8"
services:
client:
image: "migiki/cs-logs-viewer:latest"
container_name: cs-logs-viewer
ports:
- "8080:80"
Также добавил MkDocs документацию к проекту, доступна по ссылке
Документация CS Logs viewer
Итоги
Разработал на React CS Logs viewer для просмотра структурированных логов, образ которого весит около 55 мб, в то время как рассматриваемый выше аналог занимает 300 мб. Приложение можно деплоить на тестовый сервер или поставлять в docker-compose файле вместе с кодом своего проекта, чтобы можно было смотреть логи на любой системе с установленной Docker-средой.
Буду рад вашим предложениям и замечаниям!