Статьи

Ваши журналы ваши данные: logstash +asticsearch

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

Это не имеет большого значения, если у вас запущено и работает одно приложение, но в настоящее время приложения, в частности веб-приложения, работают на сотнях серверов. При таких масштабах выяснить, где проблема становится проблемой. Разве не было бы неплохо иметь какое-то представление, которое объединяет все  журналы  всех наших запущенных приложений в одну панель инструментов, чтобы мы могли видеть целое изображение, построенное из кусочков? Пожалуйста, добро пожаловать:  Logstash , структура  агрегации журналов .

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

Позвольте мне представить пример проекта: отдельное Java-приложение, в котором выполняется многопоточность. Существует протоколирование в файл сконфигурированного с помощью большого  Logback  библиотеки ( SLF4J  может быть использован в качестве замены бесшовной). Файл POM выглядит довольно просто:

    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">
4.0.0

com.example
logstash
0.0.1-SNAPSHOT
jar
UTF-8
1.0.6

ch.qos.logback
logback-classic
${logback.version}
ch.qos.logback
logback-core
${logback.version}
org.apache.maven.plugins
maven-compiler-plugin
3.0

1.7
1.7

И есть только один класс Java под названием  Starter,  который использует   службы Executors для одновременной работы. Несомненно, каждый поток выполняет некоторую регистрацию, и время от времени возникает исключение.


package com.example.logstash;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Starter {
private final static Logger log = LoggerFactory.getLogger( Starter.class );

public static void main( String[] args ) {
final ExecutorService executor = Executors.newCachedThreadPool();
final Collection< Future< Void > > futures = new ArrayList< Future< Void > >();
final Random random = new Random();

for( int i = 0; i < 10; ++i ) {
futures.add(
executor.submit(
new Callable< Void >() {
public Void call() throws Exception {
int sleep = Math.abs( random.nextInt( 10000 ) % 10000 );
log.warn( "Sleeping for " + sleep + "ms" );
Thread.sleep( sleep );
return null;
}
}
)
);
}

for( final Future< Void > future: futures ) {
try {
Void result = future.get( 3, TimeUnit.SECONDS );
log.info( "Result " + result );
} catch (InterruptedException | ExecutionException | TimeoutException ex ) {
log.error( ex.getMessage(), ex );
}
}
}
}

Идея состоит в том, чтобы продемонстрировать не только простые однострочные события регистрации, но и знаменитые трассировки стека Java. Поскольку каждый поток спит в течение произвольного временного интервала, он вызывает исключение TimeoutException каждый раз, когда запрашивается результат вычисления из базового будущего объекта и для его возврата требуется более 3 секунд. Последняя часть —  это  конфигурация Logback ( logback.xml ):

        /tmp/application.log
true

[%level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %logger{36} - %msg%n

И мы  готовы  идти! Обратите внимание, что путь  к файлу  /tmp/application.log соответствует  c: \ tmp \ application.log в Windows. Запуск нашего приложения заполнил бы файл журнала чем-то вроде этого:

[WARN] 2013-02-19 19:26:03.175 [pool-2-thread-1] com.example.logstash.Starter - Sleeping for 2506ms
[WARN] 2013-02-19 19:26:03.175 [pool-2-thread-4] com.example.logstash.Starter - Sleeping for 9147ms
[WARN] 2013-02-19 19:26:03.175 [pool-2-thread-9] com.example.logstash.Starter - Sleeping for 3124ms
[WARN] 2013-02-19 19:26:03.175 [pool-2-thread-3] com.example.logstash.Starter - Sleeping for 6239ms
[WARN] 2013-02-19 19:26:03.175 [pool-2-thread-5] com.example.logstash.Starter - Sleeping for 4534ms
[WARN] 2013-02-19 19:26:03.175 [pool-2-thread-10] com.example.logstash.Starter - Sleeping for 1167ms
[WARN] 2013-02-19 19:26:03.175 [pool-2-thread-7] com.example.logstash.Starter - Sleeping for 7228ms
[WARN] 2013-02-19 19:26:03.175 [pool-2-thread-6] com.example.logstash.Starter - Sleeping for 1587ms
[WARN] 2013-02-19 19:26:03.175 [pool-2-thread-8] com.example.logstash.Starter - Sleeping for 9457ms
[WARN] 2013-02-19 19:26:03.176 [pool-2-thread-2] com.example.logstash.Starter - Sleeping for 1584ms
[INFO] 2013-02-19 19:26:05.687 [main] com.example.logstash.Starter - Result null
[INFO] 2013-02-19 19:26:05.687 [main] com.example.logstash.Starter - Result null
[ERROR] 2013-02-19 19:26:08.695 [main] com.example.logstash.Starter - null
java.util.concurrent.TimeoutException: null
at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:258) ~[na:1.7.0_13]
at java.util.concurrent.FutureTask.get(FutureTask.java:119) ~[na:1.7.0_13]
at com.example.logstash.Starter.main(Starter.java:43) ~[classes/:na]
[ERROR] 2013-02-19 19:26:11.696 [main] com.example.logstash.Starter - null
java.util.concurrent.TimeoutException: null
at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:258) ~[na:1.7.0_13]
at java.util.concurrent.FutureTask.get(FutureTask.java:119) ~[na:1.7.0_13]
at com.example.logstash.Starter.main(Starter.java:43) ~[classes/:na]
[INFO] 2013-02-19 19:26:11.696 [main] com.example.logstash.Starter - Result null
[INFO] 2013-02-19 19:26:11.696 [main] com.example.logstash.Starter - Result null
[INFO] 2013-02-19 19:26:11.697 [main] com.example.logstash.Starter - Result null
[INFO] 2013-02-19 19:26:12.639 [main] com.example.logstash.Starter - Result null
[INFO] 2013-02-19 19:26:12.639 [main] com.example.logstash.Starter - Result null
[INFO] 2013-02-19 19:26:12.639 [main] com.example.logstash.Starter - Result null

Теперь давайте посмотрим, что  Logstash  может сделать для нас. Из раздела загрузки мы получаем единственный файл JAR:  logstash-1.1.9-monolithic.jar . Это все, что нам нужно на данный момент. К сожалению, из-  за этой ошибки в Windows  мы должны где-  то развернуть  logstash-1.1.9-monolithic.jar , например, в  папку logstash-1.1.9-monolithic  .  Logstash  имеет только три концепции:  входыфильтры  и  выходы . Те  , которые  очень хорошо объяснено в  документации . В нашем случае вход представляет собой файл журнала приложения,  c: \ tmp \ application.log . Но каков будет выход? ElasticSearch, кажется, является отличным кандидатом для этого: давайте внесем в журнал наши  логи в  любое время. Давайте скачаем и запустим его:

elasticsearch.bat -Des.index.store.type=memory -Des.network.host=localhost

Теперь мы  это  готовы к интеграции  Logstash ,  который должен хвост наш лог — файл и кормить его непосредственно  ElasticSearch . Следующая конфигурация делает именно это ( logstash.conf ):

input {
file {
add_field => [ "host", "my-dev-host" ]
path => "c:\tmp\application.log"
type => "app"
format => "plain"
}
}

output {
elasticsearch_http {
host => "localhost"
port => 9200
type => "app"
flush_size => 10
}
}

filter {
multiline {
type => "app"
pattern => "^[^\[]"
what => "previous"
}
}

На первый взгляд это может показаться не очень ясным, но позвольте мне объяснить, что к чему. Таким образом, вводом является c: \ tmp \ application.log , который представляет собой простой текстовый файл ( format => «plain» ). Типа => «приложение»  служит простым маркером , так что различные типы входов могут быть направлены на выходы через фильтры с тем же типом. Add_field => [ «хозяин», «мой-DEV-хост»]  позволяет вводить дополнительные произвольные данные во входящий поток, Fе имя хоста.

Вывод довольно ясный:  ElasticSearch  over HTTP, порт 9200 (настройки по умолчанию). Фильтрам нужно немного магии, все из-за следов стека Java. Многострочный  фильтр будет клеить трассировки стека в лог заявление он принадлежит к так она будет храниться в виде одной (большой) многострочный. Давайте запустим  Logstash :

java -cp logstash-1.1.9-monolithic logstash.runner agent -f logstash.conf

Большой! Теперь, когда мы запускаем наше приложение,  Logstash  будет наблюдать файл журнала, фильтровать его свойства и отправлять напрямую в  ElasticSearch . Круто, но как мы можем выполнить поиск или хотя бы посмотреть, какие у нас есть данные? Хотя  ElasticSearch  имеет великолепный REST API, мы можем использовать другой отличный проект,  Kibana , интерфейс веб-интерфейса для  ElasticSearch . Установка очень проста и без проблем. После нескольких необходимых шагов мы запускаем Kibana  :

ruby kibana.rb

По умолчанию  Kibana  предоставляет веб-интерфейс, доступный через порт 5601, давайте нацелим на него наш браузер, http: // localhost: 5601 /,  и мы должны увидеть что-то подобное (пожалуйста, нажмите на изображение, чтобы увеличить его):

Все наши   операторы журналов, дополненные именем хоста  ,  просто есть. Исключения (со следами стеки)  будут  связаны с соответствующим заявлением журнала. Уровни журнала, отметки времени, все показывается. Полнотекстовый поиск доступен из коробки, благодаря  ElasticSearch .

Это все круто, но наше приложение очень просто. Будет ли этот подход работать при развертывании с несколькими серверами / несколькими приложениями? Я уверен, что это будет работать просто отлично. Интеграция Logstash  с  RedisZeroMQ , RabbitMQ , … позволяет  собирать журналы  из десятков различных источников и объединять их в одном месте. Большое спасибо,   ребята из Logstash !