Pull to refresh

Стриминг и анализ логов Java приложений в MS Azure с использованием Log4j и Stream Analytics

Reading time 10 min
Views 3.7K

В этой статье я покажу несколько работающих решений задачи передачи и анализа логов из Java приложений в MS Azure. Мы рассмотрим решения как для windows, так и для linux виртуальных машин, находящихся как в облаке, так и on-premise. В качестве подсистемы логирования для Java будем использовать log4j2.


Для анализа логов будем использовать Azure Stream Analytics.



Чтобы понять о чём вообще идёт речь в статье — желательно обладать базовыми знаниями по log4j2 и некоторым ресурсам Azure, а именно stream analytics, event hub, blob storage.
Если у вас есть желание их (знания) освежить — вот ссылки
Apache Log4j 2
Azure Stream Analytics Documentation
Azure Event Hubs
Azure Storage


Почему java?


Возможно кому-то покажется странным сама идея хостинга java приложений в Azure VM, но вот по данным из отчета по анализу рынка IaaS 20011-2026 в Германии от Colorbridge Gmbh, Azure IaaS используют лишь чуть меньше, чем AWS. Поэтому, если оставить предрассудки, такая постановка вопроса окажется вполне разумной, а кому-то — даже злободневной.


Хотя Azure поддерживает Java давно, особенно на уровне PaaS, предоставляя SDK для Java, хостинг Java приложений в Web App а также популярное в java и opensource мире ПО по схеме SaaS. Но вот на уровне IaaS (в общем-то абстрагированном от самого ПО), специфика работы с Java не сильно освещена. А она есть, как минимум в области логирования. Попытаемся это исправить.


Почему log4j?


Для java есть несколько подсистем для логирования. Мы будем использовать именно log4j потому что


  • Он очень популярный
  • Его возможностей по конфигурированию достаточно для интеграции с теми ресурсами Azure, которые нам будут необходимы
  • Всё конфигурирование можно осуществить через внешний конфигурационный файл
  • Конфигурационный файл с новыми настройками можно указать и для уже собранного приложения (-Dlog4j.configurationFile=log4j2.xml), таким образом все сценарии в статье могут быть реализованы вообще без изменения кода приложения.

На чём я тестировал


В качестве тествого приложения я брал самый обычный springboot starter app с модулем spring-boot-starter-log4j2 но последней версии 2.0.0.M5, т.к. для одного из сценариев нужна будет последняя версия log4j.


pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>log4j2-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>log4j2-demo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.M5</version>
        <relativePath/>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <repositories>
        <repository>
            <id>sboot</id>
            <name>your custom repo</name>
             <url>https://repo.spring.io/libs-milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.0.0.M5</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <version>2.0.0.M5</version>
        </dependency>
        <!-- Exclude Spring Boot's Default Logging -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- Add Log4j2 Dependency -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
            <version>2.0.0.M5</version>
        </dependency>
    </dependencies>

    <pluginRepositories>
        <pluginRepository>
            <id>sbootplug</id>
            <url>https://repo.spring.io/libs-milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.0.0.M5</version>
            </plugin>
        </plugins>
    </build>
</project>

Для тестирования возможности добавить функционал по стримингу логов для уже готового приложения я ставил эксперименты над java minecraft сервером :)


Что дальше?


А дальше будет несколько сценариев с указанием способа их реализации и присущими им ограничениям.


Смысл сценариев — организация пайплайна по


  • сбору логов от java приложения
  • передаче их в какое-нибудь хранилище в Azure (мы будем использовать или Blob Storage или EventHub)
  • передаче их в Stream Analytics и первичный анализ (в основном я буду показывать как добраться из Stream Analytics до данных, переданных в log4j)
    а вот шаг потребления данных (создания всяких дашбордов и прочее) — не будет освещён в данной статье.

Сценарий 1, универсальный



Особенности: Windows или Linux OS, VM в Azure или On-premise
Ограничения: нужна <определённая> версия log4j2


Алгоритм работы:


  • Логи с помощью HTTP appender'а пушатся напрямую в Azure в EventHub
  • EventHub мониторит джоба Stream Analytics и при появлении сообщений начинает их разбирать и анализировать в соответствии с заданным запросом

Детали реализации:


Настройка Log4j
В конфиге log4j2 должен быть определён HTTP appender


<Http name="Http" url='https://<event hubs namespace>.servicebus.windows.net/<event hub>/messages?timeout=60&amp;api-version=2014-01'>
    <Property name="Authorization" value="SharedAccessSignature sr=xxxsig=yyyse=zzzskn=<event hub poicy>" />
    <Property name="Content-Type" value="application/atom+xml;type=entry;charset=utf-8" />
    <Property name="Host" value="<event hubs namespace>.servicebus.windows.net" />
    <JsonLayout properties="true"/>
</Http>

Чуть подробнее про Authorization хедер.
Для того, чтобы работать с REST API EventHub требуется авторизация по т.н. SaS токену. Это по сути, хеш урла ресурса и времени жизни токена.


Для формирования sas токена кроме event hub namespace и event hub также потребуется знать имя и ключ policy event hub'а с правами на отсылку сообщений. Вся информация есть на portal.azure.com.


Майкрософт предоставляет примеры кода для генерации Authorization хедера на различных языках, в т.ч. на Java.


Я же пользуюсь вот этим html снипетом, который нашёл в интернетах и слегка доработал — он генерирует Authorization хедер для EventHub с временем жизни равным году.


Настройка Stream Analytics
В Stream Analytics джобе настраивается input с EventHub с параметрами
Event serialization format = JSON, Encoding = UTF-8, Event compression type = None
При этом доступ к данным, которые пушит http appender log4j выполняется просто, непосредственно через select * from (и так будет не всегда)



Чтобы хоть чуть-чуть показать мощь Stream Analytics посмотрите пожалуйста на вот такой запрос
WITH errors as (
    SELECT
        *
    FROM
        javahub
    WHERE level='FATAL' OR level='ERROR'
),
activity as (
    SELECT
       System.TimeStamp AS WindowEnd, level, COUNT(*)
    FROM
        javahub
    GROUP BY TumblingWindow( second , 10 ), level
)

select * into pbierrors from errors;
select * into pbiactivity from activity;

Этим запросом мы


  • Пересылаем в pbierrors оутпут все сообщения лога с уровнем FATAL или ERROR
  • Каждые 10 секунд пересылаем в pbiactivity оутпут количество сообщений, поступивших за эти 10 секунд, сгруппированных по уровню лога

Пара кликов мышкой в power bi и мы можем мониторить не только ошибки приложения, но и следить за общей активностью.


Сценарий 2, только Windows, бюджетный



Особенности: не нужен EventHub. Меньший объём трафика (логи архивируются перед передачей), не надо заморачиваться с SaS токенами.
Ограничения: VM только в Azure. Логи поступают с небольшой задержкой.


Алгоритм работы


  • Логи с помощью RollingRandomAccessFile appender'а пишутся в файл
  • По достижении определённых условий (триггеры log4j) логи архивируются в отдельную папку
  • Папку мониторит Azure Monitoring & Diagnostics Extension для VM и при появлении нового файла с архивом перекладывает его в Blob storage в Azure
  • Blob storage мониторит джоба Stream Analytics и при появлении нового файла с архивом начинает его разбирать и анализировать в соответствии с заданным запросом

Детали реализации


Настройка Log4j
Обязательно надо обратить внимание, что


  • Архивирование должно производится НЕ в ту папку, в которую пишутся текущие логи
  • Каждый файл должен иметь хедер — список имён полей
  • Если есть желание в папке, где архивируются логи, огранизовать иерархию директорий — в качестве имён директорий можно использовать (из динамики) только дату (yyyy-MM-dd) (разделители могут быть произвольными) и\или час (HH)
    Пример определения "правильного" appender'а log4j
    <RollingRandomAccessFile name='File' fileName="latest.log" filePattern="logs\%d{yyyy-MM-dd}\%d{HH}\%d{HH-mm-ss}-%i.log.gz">
            <PatternLayout pattern='%d{yyyy-MM-dd HH:mm:ss};%level;%msg%n'>
                <header>TS;LEVEL;MESSAGE%n</header>
            </PatternLayout>
            <Policies>
                <CronTriggeringPolicy schedule='10 * * * * ?' evaluateOnStartup='true'/>
                <SizeBasedTriggeringPolicy size='10 MB'/>
            </Policies>
            <DefaultRolloverStrategy max='100'/>
        </RollingRandomAccessFile>

Настройка Azure Monitoring & Diagnostics Extension для VM


  • сформировать два конфига (примеры ниже)
  • с помощью Azure CLI 2 выполнить

az vm extension set --name IaaSDiagnostics \
                    --publisher "Microsoft.Azure.Diagnostics" \
                    --resource-group <group name> \
                    --vm-name <vm name> \
                    --protected-settings "privateSettings.json" \
                    --settings "publicSettings.json" \
                    --version "1.11.1.0"

publicSettings.json
{
    "WadCfg": {
        "DiagnosticMonitorConfiguration": {
            "overallQuotaInMB": 10000,
            "DiagnosticInfrastructureLogs": {
                "scheduledTransferLogLevelFilter": "Error"
            },
            "Directories": {
                "scheduledTransferPeriod": "PT1M",
                "DataSources": [
                    {
                        "containerName": "<blob container name in your storage account>",
                        "Absolute": {
                            "path": "C:\\<folder>\\<to monitor>",
                            "expandEnvironment": false
                        }
                    }
                ]
            }
        }
    },
    "StorageAccount": "<your storage account name>",
    "StorageType": "Table"
}

privateSettings.json
{
    "storageAccountName": "<your storage account name>",
    "storageAccountKey": "<storage account access key (use portal to obtain it)>"
}

Настройка Stream Analytics
В качестве input используется Blob storage с параметрами:
PathPattern — путь до файлов с архивированными логами в Blob storage. Если вы делали иерархию директорий (как в примере выше) — то тут она также должна быть учтена.


Пример: WAD/be7f1c92-2841-4ea1-b9d8-ec83c211b8ea/IaaS/_minesrv/{date}/{time}/
DateFormat должен быть задан в соответствии с форматом паттерна %d в log4j
Event serialization format = CSV, Delimeter = semicolon, Encoding= UTF-8, Event compression type = GZIP


Доступ к данным в запросах Steam Analytics также непосредственный.
select * from вернёт таблицу с полями TS, LEVEL, MESSAGE (в соответствии с хедером, определённым в log4j)

И ещё один запрос посложнее
WITH 
SessionInfo AS (  
    SELECT
        TS, 'START' as EVENT, SUBSTRING(MESSAGE, 0, REGEXMATCH(MESSAGE, '[ ]*joined the game')) as PLAYER
    FROM
        logslob
    TIMESTAMP BY TS
    WHERE REGEXMATCH(MESSAGE, 'joined the game') > 0
UNION
    SELECT
        TS, 'END' as EVENT, SUBSTRING(MESSAGE, 0, REGEXMATCH(MESSAGE, '[ ]*left the game')) as PLAYER
    FROM
        logslob
    TIMESTAMP BY TS
    WHERE REGEXMATCH(MESSAGE, 'left the game') > 0
),
RawLogs AS (
    SELECT
        TS, LEVEL, MESSAGE
    FROM
        logslob
    TIMESTAMP BY TS
)

SELECT * INTO sbq from SessionInfo;
SELECT * INTO pbi from RawLogs;

Тут мы пересылаем в оутпут RawLogs все логи, а вот в SessionInfo отдельно записи о старте и остановке сессии с указанием имени игрока — для последующих уведомлений


Сценарий 3, только Linux



Особенности: минимальная настройка Log4j (и минимальные требования к версии log4j) — просто запись в файл. Обрабатываются все новые записи в файле (без задержки)
Ограничения: VM только в Azure, запись в файл только в json, более сложный доступ к данным из stream analytics job


Алгоритм работы:


  • Логи с помощью File appender'а пишутся в файл
  • Файл мониторит Azure Linux Diagnostics Extension для VM и при появлении новых записей в файле пушит их в EventHub
  • EventHub мониторит джоба Stream Analytics и при появлении сообщений начинает их разбирать и анализировать в соответствии с заданным запросом

Детали реализации


Настройка Log4j
Логи должны писаться в формате json и обязательно — каждый объект на одну строчку файла с логами
К счастью, с помощью log4j это можно настроить просто


<File name="FileLog" fileName="app.log">  
    <JsonLayout properties="true" compact="true" eventEol="true"/>
</File>

Настройка Azure Linux Diagnostics Extension для VM


  • сформировать два конфига (примеры ниже)
  • с помощью Azure CLI 2 выполнить

az vm extension set --name LinuxDiagnostic \
                    --publisher "Microsoft.Azure.Diagnostics" \
                    --resource-group <group name>\
                    --vm-name <vm name>\
                    --protected-settings "linux_privateSettings.json" \
                    --settings "linux_publicSettings.json" \
                    --version "3.0.109"

  • в конфигах мы прописываем storage account, хотя он не используется для передачи логов. Он нужен только для диагностических записей самого Linux Diagnostics Extension
  • в конфигах мы прописываем sas токен для sotage account (а не просто ключ, как в случае с Diagnostics Extension для Windows), к счатью сгенерировать его можно через портал
  • в конфигах мы прописываем sas токен для event hub — он почти не отличается по формату от того, что мы генерировали для первого сценария (и генерируется тем же кодом или сниппетом)

linux_publicSettings.json
linux_publicSettings.json:
  {
    "StorageAccount": "<your storage account>",
    "sampleRateInSeconds": 15,
    "ladCfg": {
        "diagnosticMonitorConfiguration": {
          "metrics": {
            "metricAggregation": [
              {
                "scheduledTransferPeriod": "PT1H"
              },
              {
                "scheduledTransferPeriod": "PT1M"
              }
            ],
            "resourceId": "/subscriptions/<subscription id>/resourceGroups/<resource group>/providers/Microsoft.Compute/virtualMachines/<vm name>"
          }
        }
      },
    "fileLogs": [
      {
        "file": "/<path>/<to>/<log file>",
        "sinks": "LinuxEH"
      }
    ]
  }

linux_privateSettings.json
{
    "storageAccountName" : "<your storage account>",
    "storageAccountSasToken": "<sas token for storage account - generate it on the portal>",
    "sinksConfig": {
      "sink": [
        {
          "name": "LinuxEH",
          "type": "EventHub",
          "sasURL": "https://<event hub namespace>.servicebus.windows.net/<event hub>?sr=xxxxxx&sig=yyyy&se=zzzz&skn=<policy name>"
        }
      ]
    }
}

Настройка Stream Analytics
В Stream Analytics джобе настраивается input с EventHub с параметрами
Event serialization format = JSON, Encoding = UTF-8, Event compression type = None
При этом доступ к данным, которые логируются, не такой простой. Если мы просто выполним select * from мы увидим примерно вот такую картину


т.е. нужны нам данные скрыты где-то в проперти json объекта, хранимого в поле PROPERTIES
Но и эту проблему в stream analytics можно решить красиво (да, мне очень нравится эта штука :)), например вот таким запросом


with events as (
 select UDF.to_json(properties.MSG) as obj
 from ehtest
)

select obj.* from events

где UDF.to_json — это написанная нами функция по конвертации строки в JSON объект (да, там ещё и функции можно писать, на javascript...)



В результате мы получаем простой доступ к данным лога



В заключение


Надеюсь, эта статья окажется кому-нибудь полезна.
Мне она уже принесла большую пользу, т.к. только с помощью реализации практических кейсов можно реально понять возможности и зрелость той или иной технологии.
Если вдруг я какие-нибудь сценарии упустил — напишите пожалуйста об этом в комментах.

Tags:
Hubs:
+3
Comments 3
Comments Comments 3

Articles