Не каждому проекту нужно децентрализованное логирование. В моём случае, оказалось проще хранить логи в .json файлах формата Compact Log Event Format (CLEF). Мне нужно было простое и бесплатное решение для просмотра логов.
Готовое решение
На момент написания статьи в свободном доступе было только 1 подходящее приложение для чтения логов в формате CLEF — Compact Log Viewer, доступно в Microsoft Store.
Уже неплохо, приложение позволяло смотреть и фильтровать логи. Однако в этом решении было несколько минусов:
-
Каждому разработчике нужно устанавливать локально через 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 ( <ErrorBoundary FallbackComponent={ErrorFallback}> <AppStorageContextProvider> <AppStateContextProvider> <RSuiteProvider theme={theme}> <AppLayout /> </RSuiteProvider> </AppStateContextProvider> </AppStorageContextProvider> </ErrorBoundary> ); }
Для дальнейшего отображения логов в списке нужно установить что принимать за уникальный 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<CsLog[] | null>(null) <StyledPagination values={filteredLogs} onChange={setPaginatedLogs} />
Для фильтра по логам использовал SearchJs, синтаксис простейшего фильтра { "data.<property_name>": "value" }, реализация:
searchjs.matchArray(appState.csLogs, JSON.parse(filter))
Валидацию фильтра сделал в лоб, пытаюсь парсить json, ошибка — неверный формат:
export const logsViewPageFormSchema: yup.Schema<LogsViewPageFormData> = 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 документацию к проекту, доступна по ссылке
Итоги
Разработал на React CS Logs viewer для просмотра структурированных логов, образ которого весит около 55мб, в то время как рассматриваемый выше аналог занимает 300мб. Приложение можно деплоить на тестовый сервер или поставлять в docker-compose файле вместе с кодом своего проекта, чтобы можно было смотреть логи на любой системе с установленной Docker-средой.
Буду рад вашим предложениям и замечаниям!
ссылка на оригинал статьи https://habr.com/ru/articles/805949/
Добавить комментарий