Тема сегодняшнего поста немного отходит от повседневного программирования и разработки, но, тем не менее, охватывает очень важную тему: файлы журналов нашего приложения. Наши приложения генерируют огромное количество журналов, которые, если все сделано правильно , чрезвычайно полезны для устранения неполадок.
Это не имеет большого значения, если у вас запущено и работает одно приложение, но в настоящее время приложения, в частности веб-приложения, работают на сотнях серверов. При таких масштабах выяснить, где проблема становится проблемой. Разве не было бы неплохо иметь какое-то представление, которое объединяет все журналы всех наших запущенных приложений в одну панель инструментов, чтобы мы могли видеть целое изображение, построенное из кусочков? Пожалуйста, добро пожаловать: 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 с Redis , ZeroMQ , RabbitMQ , … позволяет собирать журналы из десятков различных источников и объединять их в одном месте. Большое спасибо, ребята из Logstash !