W zeszłym tygodniu zacząłem serię o narzędziach elastic. Na samym początku jedynie opisałem co będę chciał zrobić oraz jakie narzędzia będą nam potrzebne. W tym tygodniu przechodzę już do opisu pierwszego z nich – Logstash.

Z cyklu elastic, do tej pory ukazały się artykuły:

  1. elastic – wstęp
  2. elastic – logstash
  3. elastic – elasticserach
  4. elastic – kibana

Logstash to narzędzie umożliwiające łączenie wielu strumieni wejściowych danych, filtrowanie każdego strumienia wypluwanie z mapowanych lub nie danych do strumieni wyjściowych. Nie ma tam zbytnio magii. To co na dziś będziemy potrzebować to przygotowane środowisko z pierwszej części. Zaś tutaj skoncentrujemy się nad tym jak to wszystko skonfigurować.

To co musimy zrobić to stworzyć plik konfiguracyjny zawierający opis tego co chcemy by logstash robił. Plik zaś to DSL stworzony przez firmę elasitc z wykorzystaniem Treetop. W tym celu wystarczy stworzyć plik logstash.conf i wgrać go do katalogu \bin w folderze logstash. Następnie w dowolnym edytorze plików tekstowych uzpełńmy go o 3 sekcje: input, filter i output.

  • input – definiuje źródła – skąd brać co i jak
  • output – gdzie ma trafić przefiltrowana informacja
  • filter – filtruje informacje i uporządkowuje je zgodnie z “naszym” schematem/założeniem

Pusty plik wygląda tak:

input {

}

filter {

}

output {

}

Teraz zaczniemy go wypełniać. Na początku określmy źródło, w naszym przypadku są to pliki logu IIS.

input

Nasz input może wyglądać tak:

input {
    file {
        type => "IISLog"
        path => ["C:/inetpub/logs/LogFiles/W3SVC*/*.log"]
        start_position => "beginning"
    }
}

O co tu c’mon?

  • file – określa, że źródłem będą pliki
  • type – nadaje nazwę, do której potem możemy się odwoływać
  • path – określa wzorzec który ma wychwycić wszystkie możliwe pliku log w IIS
  • start_position – to skąd ma logstash zacząć czytać pliki – u nas od początku, a to ze względu na to, że pewnie już mamy tam jakieś dane i chcielibyśmy je zaczytać. Jak by nas to nie interesowało i jedynie nowe informacje miałyby wpadać, wystarczy tą opcję pominąć lub ustawić jej wartość domyślną end.

Tych sekcji z plikami możemy mieć więcej – ważne by type było unikatowe.

output

Jeżeli byśmy nie chcieli nic filtrować to możemy stworzyć nasz output:

output {
    stdout { codec => rubydebug }
    elasticsearch {
        hosts => ["localhost:9200"]
    }
}

Gdzie:

  • stdout – to po prostu standardowe wyjście, w tym momencie mówimy, wyświetl nam to co otrzymujesz jako output. W tym wypadku mówimy mu jeszcze by użył biblioteki do wyświetlania tych danych, w tym wypadku jest to awesome print.
  • elasticsearch – tak jak stdout, tylko, że przekaż to co dostajemy do serwerów dostępnych pod kreślonymi adresami.

To co jeszcze możemy zrobić to na przykład w zależności od type zdefiniowanego w input, przekierować na odpowiednie źródła danych:

output {
    if [type] == "IISLog" {
        elasticsearch {
            hosts => ["localhost:9200"]
        }
    }
}

Czyli do elasticsearch zostanie przekazany log tylko i wyłącznie jak type jest równo IISLog. Czyli mając różne źródła, możemy w różne miejsca je wrzucać.

filter

To jest najbardziej zaawansowana sekcja i pewnie tutaj będziemy spędzać najwięcej czasu z plikami logów. Na przykład to co możemy zrobić z plikami logów IIS to nie przekazywać linijek rozpoczynających się od komentarza (#):

filter {
    # ignore log comments
    if [message] =~ "^#" {
        drop {}
    }
}

Z tego fragmentu możemy się zorientować, że:

  • message – to jest nasza linijka tekstu.
  • drop – nie przekazuj dalej
  • # – komentarze
  • regex do porównywania

Tutaj w filter możemy naprawdę dużo rzeczy zrobić, w naszym przypadku, chcielibyśmy w jakiś usystematyzowany sposób przechwycić linijkę treści i zamienić ją na dane które da się analizować. Na przykład, czy response code był 500 czy 200? Kiedy to się wydarzyło? Jakiej strony dotyczył request? To są wszystkie te same pola co wymieniłem w poprzedniej części, tylko że teraz chcemy by nasz logstash wiedział, że się znajdują i nadał im bardziej przyjazne nazwy. W tym celu korzystamy z rozszrzenia gork (jest dostępne domyślnie).

gork

Sposób wykorzystania gork jest prosty, wystarczy:

gork {
    match => ["message", "WYRAŻENIE_GORK"]
}

Dosłownie to mówi, z dopasuj mi linijkę logu do wyrażenia, jak się nie uda, olej, nie przekazuj dalej wiadomości. Dlatego też jak wspominałem, bardzo ważne będzie to by mieć poprawne kolumny – takie jak ja, albo trzeba będzie samemu poprawić gork.

Wyrażenie porównywania jest dość proste, długie, ale proste, dla naszego przykładu wygląda ono tak:

%{TIMESTAMP_ISO8601:log_timestamp} %{IP:serverip} %{NOTSPACE:method} %{URIPATH:page} %{NOTSPACE:querystring} %{NUMBER:port} %{NOTSPACE:username} %{IP:clientip} %{NOTSPACE:useragent} %{NOTSPACE:referer} %{NUMBER:status} %{NUMBER:substatus} %{NUMBER:scstatus} %{NUMBER:timetaken}

Jak można zaobserwować występuje wzorzec: %{CAPITAL_LETTERS:small_letters} gdzie CAPITAL_LETTERS oznacza typ danego elementu (liczba, ciąg znaków bez spacji, IP itp.), zaś to co mamy po : to opcjonalna nazwa pola.  Nasz wzorzec możemy zweryfikować wchodząc na stronę GrokDebug i wklejając wyrażenie gork do Pattern, zaś do Input wklejamy linijkę z naszego pliku logu IIS. Zaznaczmy jeszcze dwie opcje (by wizualnie było ładniej):

  • Named Captures Only – czasami niektóre typy tworzą dodatkowe pola – na przykład TIMESTAMP stworzy nam YEAR, MONTH etc. Jeżeli tego nie chcemy to zaznaczamy Named Captures Only
  • Singles – powoduje, że każda nazwana wartość będzie traktowana jako wartość pojedyncza a nie tablica

Wynik naszego gork powinien wyglądać tak:

{
  "log_timestamp": [
    "2016-09-28 12:32:57"
  ],
  "serverip": [
    "::1"
  ],
  "method": [
    "GET"
  ],
  "page": [
    "/"
  ],
  "querystring": [
    "-"
  ],
  "port": [
    "80"
  ],
  "username": [
    "-"
  ],
  "clientip": [
    "::1"
  ],
  "useragent": [
    "Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/54.0.2840.34+Safari/537.36"
  ],
  "referer": [
    "-"
  ],
  "status": [
    "200"
  ],
  "substatus": [
    "0"
  ],
  "scstatus": [
    "0"
  ],
  "timetaken": [
    "804"
  ]
}

Jeżeli wynik się nie pokazuje, to znaczy, że mamy zły gork. Mamy dwa wyjścia z tej sytuacji: albo zmieniamy gork, albo ustawienia IIS tak by dopasowały do listy pól z poprzedniej części.

Mając gotowego gorka, moglibyśmy zakończyć zabawę z konfiguracją logstasha. Jednak, by mieć coś więcej z tych logów, dodajmy kolejne trzy filtry.

useragent

Jedno z pól gorkowych wykorzystuje następujący wzorzec: %{NOTSPACE:useragent} i odpowiada on wychwyceniu ciągu znaków:

Mozilla/5.0+(Windows+NT+10.0;+Win64;+x64)+AppleWebKit/537.36+(KHTML,+like+Gecko)+Chrome/54.0.2840.34+Safari/537.36

Ten ciąg może wytrawnemu oko coś powie. Mi osobiście za dużo nie mówi. Dlatego też możemy to trochę upiększyć i wyciągnąć z tego informację na temat przeglądarki. By to zrobić wykorzystujemy plugin useragent (domyślnie jest on dostępny):

useragent {
    source=> "useragent"
    prefix=> "browser_"
}

Gdzie:

  • source – to pole zawierające user agent.
  • prefix – to przedrostek jaki zostanie dodany do wyciągniętej przeglądarki. Dzięki czemu będziemy mieli wszystko ładnie zgrupowane pod jedną nazwą.

date

Plugin jest odpowiedzialny za parsowanie daty i ustawianie tego jako timestamp danego zdarzenia (danego logu). W przeciwnym wypadku, logstash ustawi to za nas – i nie koniecznie ten czas będzie tym który by nas interesował (na przykład czas przeczytania pliku a nie zapisu zdarzenia).

Plugin date jest banalnie prosty i jedynie co musimy podać to:

date {
    match => [ "log_timestamp", "YYYY-MM-dd HH:mm:ss" ]
    timezone => "Europe/Warsaw"
}

To co robimy, to dopasowujemy wartośc pola log_timestamp do wzorca po prawej stronie, i jak pasuje, to ustawiamy to jako timestamp w określonej strefie czasowej. Lista wszystkich możliwych wartości strefy czasowej znajduje się tutaj.

mutate

Skoro wykorzystujemy log_timestamp jako timestamp naszego zdarzenia, to pole to, nie jest nam już do niczego potrzebne i nie musimy go dublować w wyniku końcowym. Wystarczy więc, że dodamy:

mutate {
    remove_field => [ "log_timestamp"]
}

By usunąć pole z wynikowego loga.

Podsumowanie logstash.config

Skończyliśmy konfigurację logstash. Teraz będziemy mogli zająć się uruchomieniem logstash, a następnie konfiguracją i uruchomienim elasticsearch.

Dla pewności nasz plik logstash.config powinien wyglądać następująco:

input {
	file {
		type => "IISLog"
		path => ["C:/inetpub/logs/LogFiles/W3SVC*/*.log"]
		start_position => "beginning"
	}
}

filter {

	# ignore log comments
	if [message] =~ "^#" {
		drop {}
	}

    grok {
        match => ["message", "%{TIMESTAMP_ISO8601:log_timestamp} %{IP:serverip} %{NOTSPACE:method} %{URIPATH:page} %{NOTSPACE:querystring} %{NUMBER:port} %{NOTSPACE:username} %{IP:clientip} %{NOTSPACE:useragent} %{NOTSPACE:referer} %{NUMBER:status} %{NUMBER:substatus} %{NUMBER:scstatus} %{NUMBER:timetaken}"]
    }

    # https://www.elastic.co/guide/en/logstash/current/plugins-filters-date.html
    date {
        match => [ "log_timestamp", "YYYY-MM-dd HH:mm:ss" ]
        timezone => "Etc/Warsaw"
    }
    
    # https://www.elastic.co/guide/en/logstash/current/plugins-filters-useragent.html
    useragent {
        source => "useragent"
        prefix => "browser_"
    }
    
    mutate {
        remove_field => [ "log_timestamp"]
    }
}

# output logs to console and to elasticsearch
output {

	stdout { codec => rubydebug }
	
	if [type] == "IISLog" {
		elasticsearch {
			hosts => ["localhost:9200"]
		}
	}
}

Uruchomienie logstash

By logstash nam zbierało informację wrzucało dane do elasticsearch, musimy logstash odpalić:

logstash.bat -f logstash.conf

Jeżeli dostaniemy jakikolwiek błąd uruchomienia, to trzeba się upewnić czy jest to błąd Javy czy błąd ruby. Jeżeli Javy to pewnie mamy coś nie tak ze ścieżkami. Jeżeli ruby… to powiem tak, ja musiałem wejść do plików które on tam wyświetlał by dowiedzieć się dlaczego to nie działa i to dalej nie było takie proste.

Najlepiej więc by błędu nie było :) a jak jest. To cóż. Dajcie znać w komentarzu a pomogę.

Podsumowanie

Przeszliśmy przez prostą konfigurację logstasha i wytłumaczyliśmy sobie do czego służy input, output i filter. Oraz z jakich filtrów możemy skorzystać by zrobić/napisać taki plik samemu. To wbrew pozorom nie jest takie trudne.

Za tydzień zajmiemy się elasticsearch.

7 KOMENTARZE

  1. Hej
    Ciekawe narzędzie i fajny opis, natomiast z niecierpliwością czekam na elasticsearch. ;)

    P.s
    Coś się zduplikowało w podsumowaniu :)

  2. Czytam, czytam, akurat się tym zajmuję, więc tekst trafiony w dychę… mała uwaga spell-ologiczna ;-) nie gork tylko grok?
    Pozdrawiam,
    Sławek

Comments are closed.