software-development
June 19

Пастер

Рассказываю про еще один наш внутренний проект, сильно помогающий в разработке ПО.

Основной функционал

Суть

— Привет!

Тут опять какая-то непонятная ошибка на сервере, поможешь?

— Помогу, скинь лог.

— Вот:

Argument for @NotNull parameter 'document' of com/intellij/codeInsight/daemon/impl/HighlightInfo.fromAnnotation must not be null
java.lang.IllegalArgumentException: Argument for @NotNull parameter 'document' of com/intellij/codeInsight/daemon/impl/HighlightInfo.fromAnnotation must not be null
	at com.intellij.codeInsight.daemon.impl.HighlightInfo.$$reportNull$$0(HighlightInfo.java)
	at com.intellij.codeInsight.daemon.impl.HighlightInfo.fromAnnotation(HighlightInfo.java)
	at com.intellij.codeInsight.daemon.impl.AnnotatorRunner.lambda$runAnnotator$2(AnnotatorRunner.java:144)
	at com.intellij.codeInsight.daemon.impl.AnnotationSessionImpl.computeWithSession(AnnotationSessionImpl.java:87)
	at com.intellij.codeInsight.daemon.impl.AnnotatorRunner.runAnnotator(AnnotatorRunner.java:122)
	at com.intellij.codeInsight.daemon.impl.AnnotatorRunner.lambda$runAnnotatorsAsync$0(AnnotatorRunner.java:70)
	at com.intellij.openapi.application.impl.AnyThreadWriteThreadingSupport.tryRunReadAction$lambda$11(AnyThreadWriteThreadingSupport.kt:522)
	at com.intellij.openapi.application.impl.AnyThreadWriteThreadingSupport.runWithTemporaryThreadLocal(AnyThreadWriteThreadingSupport.kt:204)
	at com.intellij.openapi.application.impl.AnyThreadWriteThreadingSupport.tryRunReadAction(AnyThreadWriteThreadingSupport.kt:522)
	at com.intellij.openapi.application.impl.ApplicationImpl.tryRunReadAction(ApplicationImpl.java:1064)
	at com.intellij.openapi.progress.impl.CoreProgressManager.registerIndicatorAndRun(CoreProgressManager.java:755)
	at com.intellij.openapi.progress.impl.CoreProgressManager.computeUnderProgress(CoreProgressManager.java:711)
	at com.intellij.openapi.progress.impl.CoreProgressManager.executeProcessUnderProgress(CoreProgressManager.java:679)
	at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:77)
	at com.intellij.concurrency.ApplierCompleter.wrapInReadActionAndIndicator(ApplierCompleter.java:164)
	at com.intellij.concurrency.ApplierCompleter.lambda$wrapAndRun$1(ApplierCompleter.java:145)
	at com.intellij.openapi.application.impl.ApplicationImpl.executeByImpatientReader(ApplicationImpl.java:197)
	at com.intellij.concurrency.ApplierCompleter.wrapAndRun(ApplierCompleter.java:145)
	at com.intellij.concurrency.ApplierCompleter.exec(ApplierCompleter.java:113)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:507)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1491)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:2073)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2035)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:187)
	Suppressed: com.intellij.util.ExceptionUtilRt$RethrownStack: Rethrown at
		at com.intellij.util.ExceptionUtilRt.addRethrownStackAsSuppressed(ExceptionUtilRt.java:41)
		at com.intellij.util.ExceptionUtilRt.rethrowUnchecked(ExceptionUtilRt.java:17)
		at com.intellij.util.ExceptionUtil.rethrowUnchecked(ExceptionUtil.java:131)
		at com.intellij.util.ExceptionUtil.rethrow(ExceptionUtil.java:146)
		at com.intellij.concurrency.ApplierCompleter.execAll(ApplierCompleter.java:178)
		... 21 more
	Suppressed: com.intellij.util.ExceptionUtilRt$RethrownStack: Rethrown at
		at com.intellij.util.ExceptionUtilRt.addRethrownStackAsSuppressed(ExceptionUtilRt.java:41)
		at com.intellij.util.ExceptionUtilRt.rethrowUnchecked(ExceptionUtilRt.java:17)

Дальше вам надо выделить место ошибки и объяснить происходящее вопрошающему, заодно описав шаги по устранению — все это называется «гадание по серверным логам», практикуемое лучшими представителями инженерной касты.

Другой пример:

— Привет, есть проблема с кодом, который прислал Чандракант.

— Не можем разобраться без поллитры, а еще не пятница.

— Поможешь?

— Ок, показывай.

— Вот:

public ServletMapping getServletMapping(String pathSpec)
    {
        if (pathSpec == null)
            return null;

        ServletMapping mapping = null;
        for (int i = 0; i < _servletMappings.size() && mapping == null; i++)
        {
            ServletMapping m = _servletMappings.get(i);
            if (m.getPathSpecs() != null)
            {
                for (String p : m.getPathSpecs())
                {
                    if (pathSpec.equals(p))
                    {
                        mapping = m;
                        break;
                    }
                }
            }
        }
        return mapping;
    }

Таких переписок может быть десяток в день, а результат изысканий надо как-то сохранять, поскольку и трейс с ошибкой и решение могут понадобиться в дальнейшем.

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

Для того чтобы иметь возможность быстро разбираться с такими задачами и появился наш «Пастер» — своеобразное чистилище для багов.

История

Проект «Paster» появился на свет в далеком 2009 м году и с тех пор неоднократно переделывался, адаптируясь под реалии проекта на котором происходило его использование.

Так «Пастер» выглядел в 2013 м году на проекте IQCard:

2013й год, Пастер используется для сбора багов в большом финтех-проекте

В этой версии был адаптер для сбора ошибок через электронную почту и авторизация по OpenID/OAuth.

Следующий проект — уже государственный, поэтому существенная часть данных представляла собой огромные XML со сложной структурой:

2014й год, Пастер в работе, собирает баги госпроекта

На какое-то время проект был заморожен и не использовался например в Сингапуре, но позже был обновлен и вернулся в строй.

Наше время

Пастер на данный момент активно используется в нашей собственной работе для коммерческой разработки, исходный код открыт с 2015 года, разработка проекта ведется на Github: https://github.com/alex0x08/paster

Все найденные проблемы стоит публиковать в Issues, пулл-реквесты принимаются, но стоит помнить что Пастер в первую очередь рабочий инструмент а не комбайн, поэтому должен оставаться максимально простым и компактным.

Фичи

За 15 лет использования в Пастере добавлялись и удалялись самые разнообразные опции:

авторизация с помощью OpenID/OAuth, iframe-виджет и плагин для Jira, адаптер для сбора сообщений из почты, адаптер для ESB и еще куча всего.

Что-то оказалось невостребованным, что-то — слишком сложным для длительного сопровождения.

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

Комментирование

Главное, ради чего создавался Пастер — возможность построчного комментирования больших блоков текста.

Так это выглядит в действии:

Для отображения строк и подсветки синтаксиса используется сильно переделанная версия замечательной библиотеки Syntax Highlighter, разработка которой к сожалению была остановлена в 2016м году.

Благодаря этой библиотеке и дополнительным оптимизациям Пастер умеет раскрашивать без особых тормозов примерно 100Кб данных со сложным синтаксисом: XML, JSON и так далее.

Рисование

Помимо комментирования, Пастер позволяет своеобразное рисование поверх текста — для возможности подчеркнуть, зачеркнуть или еще как-то отметить нужные блоки.

В действии:

То самое «ткнуть носом» в источник проблемы в программно-графическом исполнении.

Превью

При работе с записями из интерфейса, Пастер умеет генерировать «превью» текущего состояния:

Добавленные комментарии и раскраска будет видна в этом превью.

Поиск

Разумеется для проекта, ориентированного на работу с текстом наличие полнотекстового поиска является обязательным.

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

Поиск возможен как по тексту, так и по атрибутам: теги, тип, приоритет.

Для текста работает подсветка результатов.

Представление данных

С помощью черной магии и колдовства особо глубоких знаний Spring, мы реализовали представление всех отдаваемых данных в трех разных форматах: XML, JSON и plain text.

Так это выглядит для одной записи:

Для списка доступны форматы RSS и Atom:

И поиск и пагинация точно также отдаются в разных форматах — благодаря магии Spring.

Так что весь Пастер представляет собой одно сплошное API, готовое к интеграции с другими системами.

Технические особенности

Пастер это веб-приложение, которое изначально (с 2009) года было написано и разрабатывалось целиком на Scala, при этом использовались стандартные для корпоративной Java технологии и фреймворки вроде Spring и Hibernate.

Интерфейс построен на классическом JSP, с использованием Apache Tiles (наш форк), на стороне браузера ныне используется Bootstrap и чистый современный JavaScript без фреймворков. В старых версиях использовался Mootools.

На среднем слое используется Spring с XML-конфигурацией.

Для полнотекстового поиска используется Hibernate Search (в прошлом Compass Search)

Современная версия Пастера использует Jakarta API 10, для запуска используется специальное приложение со встроенным Jetty 12.

Но при желании возможно развертывание Пастера например в Apache Tomcat.

Установка

Все что нужно это скачать архив с релизной сборкой Пастера и распаковать.

В архиве находятся скрипты запуска и само приложение:

Пастер работает на любой современной версии Java 17+, от любых вендоров.

Можно использовать как OpenJDK так и официальные или альтернативные сборки Java — все будет работать.

Инсталлятор

С 2023 года у Пастера появился полноценный веб-инсталлятор, который запускается сразу после первого запуска приложения — по аналогии с Atlassian Jira.

С помощью шагов инсталлятора можно бысто и просто провести начальную настройку Пастера.

Для начала выбирается язык системы:

У Пастера есть автоматическое определение языка:

Если страница инсталлятора была открыта из браузера с русской локалью — все надписи сразу будут на русском.

Если выбрать другую локаль - следующие шаги будут на выбранном языке.

Чекбокс «allow locale switch» отвечает за возможность переключения локали во время работы.

Если он отмечен галочкой - справа в верхнем углу появится выпадающий список с доступными локалями для переключения:

Следующим шагом настраивается подключение к базе данных:

По-умолчанию Пастер использует встроенную СУБД H2, которой вполне хватает для работы в небольшой команде.

Технически возможно использовать для Пастера любую СУБД из поддерживаемых Hibernate, но поскольку мы сами используем лишь три:

H2, Postgres и MySQL 

в инсталляторе представлены только они.

Если вам необходимо использовать альтернативные СУБД — выберите во время инсталляции H2 и сделайте перенастройку через конфигурационный файл (см. ниже)

Также на данном шаге можно проверить подключение к базе:

На следующем шаге осуществляется настройка режима работы, дополнительные права и набор пользователей:

Поскольку Пастер расчитан на работу в небольших командах технических специалистов — у него не бывает большого количества пользователей и логика управления сильно упрощена:

все пользователи хранятся в одном CSV-файле, который считывается при запуске приложения.

Во время установки записи формируются и сохраняются в домашнем каталоге Пастера.

Режим работы

У Пастера есть два режима работы авторизации:

  • публичный — все записи доступны к просмотру без авторизации, добавление новых и комментирование настраивается опциями;
  • приватный — для работы сразу требуется авторизация.

В первом случае

Последним шагом установки является перезагрузка приложения, для возможности использования новых настроек:

После перезагрузки произойдет переход на стартовую страницу Пастера, если был выбран «приватный» режим работы — появится окно авторизации:

Основной экран после авторизации:

Эпилог

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

Если у вас есть коварный план по покорению мировых рынков с собственным программным продуктом — Парстер это живая иллюстрация того как ваш продукт может выглядеть.