В этой статье я покажу несколько работающих решений задачи передачи и анализа логов из 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?
Возможно кому-то покажется странным сама идея
Хотя Azure поддерживает Java давно, особенно на уровне PaaS, предоставляя SDK для Java,
Почему log4j?
Для java есть несколько подсистем для логирования. Мы будем использовать именно log4j потому что
- Он очень популярный
- Его возможностей по конфигурированию достаточно для интеграции с теми ресурсами Azure, которые нам будут необходимы
- Всё конфигурирование можно осуществить через внешний конфигурационный файл
- Конфигурационный файл с новыми настройками можно указать и для уже собранного приложения (-Dlog4j.configurationFile=log4j2.xml), таким образом все сценарии в статье могут быть реализованы вообще без изменения кода приложения.
На чём я тестировал
В качестве тествого приложения я брал самый обычный springboot starter app с модулем spring-boot-starter-log4j2 но последней версии 2.0.0.M5, т.к. для одного из сценариев нужна будет последняя версия log4j.
<?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&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"
{
"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"
}
{
"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:
{
"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"
}
]
}
{
"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...)
В результате мы получаем простой доступ к данным лога
В заключение
Надеюсь, эта статья окажется кому-нибудь полезна.
Мне она уже принесла большую пользу, т.к. только с помощью реализации практических кейсов можно реально понять возможности и зрелость той или иной технологии.
Если вдруг я какие-нибудь сценарии упустил — напишите пожалуйста об этом в комментах.
Автор: Потураев Андрей