Логи в файлах: написал своё приложение для просмотра структурированных логов

Не каждому проекту нужно децентрализованное логирование. В моём случае, оказалось проще хранить логи в .json файлах формата Compact Log Event Format (CLEF). Мне нужно было простое и бесплатное решение для просмотра логов.

Логотип разработанного приложения

Логотип разработанного приложения

Готовое решение

На момент написания статьи в свободном доступе было только 1 подходящее приложение для чтения логов в формате CLEF — Compact Log Viewer, доступно в Microsoft Store.

Compact Log Viewer

Compact Log Viewer

Уже неплохо, приложение позволяло смотреть и фильтровать логи. Однако в этом решении было несколько минусов:

  1. Каждому разработчике нужно устанавливать локально через Microsoft Store

  2. За раз можно открыть только 1 файл

  3. Нельзя поставить на тестовый сервер если это линукс

  4. Часть UI занимает бесполезный график

  5. После повторного открытия приложения загруженный ранее файл терялся, его нужно было грузить заново

Через некоторое время я устал от постоянного скачивания логов с сервера на локальную машину для их просмотра. Поэтому решил написать своё собственное приложение.

Требования к разрабатываемому приложению

К разрабатываемое приложению я установил следующие требования:

  1. Отображать список логов

  2. Поддерживать просмотр свойств отдельных логово

  3. Поддерживать фильтрацию по свойствам

  4. Поддерживать загрузку нескольких файлов

  5. Сохранять логи между сессиями

  6. Поддерживать запуск в среде Docker

Поддержка среды Docker позволила сделать приложение «доступным из коробки», включив его в дефолтный docker-compose файл на проектах. Также стала возможна загрузка на сервер.

Процесс разработки

Для разработки проекта был выбор между React и Angular. По личным предпочтениям был выбран React + StyledComponents.

Для UI компонентов выбрал бесплатное решение RsuiteJs.

Flow-приложения:

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

  2. Загрузить логи

  3. Перенаправить на страницу просмотра логов

Реализация загрузки логов

Для форм остановился на связке 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.": "value" }, реализация:

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

Документация CS Logs viewer

Итоги

Разработал на React CS Logs viewer для просмотра структурированных логов, образ которого весит около 55 мб, в то время как рассматриваемый выше аналог занимает 300 мб. Приложение можно деплоить на тестовый сервер или поставлять в docker-compose файле вместе с кодом своего проекта, чтобы можно было смотреть логи на любой системе с установленной Docker-средой.

Буду рад вашим предложениям и замечаниям!

© Habrahabr.ru