Wykorzystanie języka AWK do analizy logów systemowych Paweł Głaz, Mieczysław Kuczyński Katedra Zastosowań Matematyki, Akademia Rolnicza w Lublinie Wprowadzenie Język programowania AWK charakteryzuje się sekwencyjnym przetwarzaniem plików tekstowych. Jest on prostym językiem o dużych możliwościach umożliwiających analizę tekstów i wydobycie z nich potrzebnych informacji. Dzięki niemu można stworzyć bardzo krótkie lecz funkcjonalne programy, których napisanie za pomocą innych narzędzi zajęłoby znacznie więcej czasu i wysiłku. Zastosowanie języka AWK do analizy logów systemowych jest trafnym przykładem wykorzystania jego możliwości. Pliki logów, jako pliki tekstowe, generowane są w postaci niewygodnej do czytania i przeglądania, a dzięki AWK można wyłowić tylko te wpisy, które interesują użytkownika. W rozważaniach oparto się na implementacji AWK o nazwie GAWK (Gnu AWK). GAWK działa na szerokim spektrum systemów unixowych i wchodzi w skład każdej większej dystrybucji Linuxa. 1. Logi systemowe Logi zawierają pełne dane o zdarzeniach zachodzących w systemie operacyjnym. Informacje takie jak uruchomienie bądź zakończenie programu, zalogowanie lub wylogowanie się użytkownika, próba dostęp do serwera sieciowego czy błąd uruchamianego procesu, zapisywane są w logach. Dzięki temu logi pozwalają na diagnozowanie problemów w systemie. Katalogiem, w którym zapisywane są logi systemowe, jest /var/log, a większość z nich jest zapisywana przez program syslog, którego głównym zastosowaniem jest zautomatyzowanie i zestandaryzowanie procesu obsługi logów. Jego główny moduł, to demon syslogd, który jest najczęściej uruchamiany jako osobny proces podczas inicjacji systemu. Działanie demona syslog definiuje się za pomocą pliku konfiguracyjnego, którym domyślnie jest /etc/syslog.conf. Jest to plik tekstowy z dwoma polami rozdzielonymi znakiem tabulacji, gdzie pierwszym polem jest selektor a drugim działanie -action. Selektor wskazuje program wysyłający komunikat, a składa się on z nazwy programu i poziomu bezpieczeństwa. Nazwy programów muszą pochodzić z krótkiej listy narzędzi zdefiniowanych dla używanego jądra. Poziom bezpieczeństwa równiej bierze się z gotowej listy: alert - wymagające natychmiastowego działania, crit krytyczne, debug uruchomieniowe, emerg - sytuacje zagrożenia, err błędy, info informacyjne, notice - wymagające zwrócenia szczególnej uwagi, warning ostrzeżenia. Można użyć znaku * w znaczeniu wszystkie poziomy lub słowa none w znaczeniu -żaden z poziomów. W jednym wierszu można użyć wielu programów rozdzielając nazwy przecinkami Każda informacja zawarta w pliku /etc/syslog.conf składa się z trzech pól: źródło_komunikatu.rodzaj_komunikatu wyjście np.: authpriv.* /var/log/securr
Jeśli kilka rodzajów komunikatów ma to same wyjście, należy oddzielić je średnikiem, np.: cron.=debug;cron.=info;cron.=notice var/log/cron/info Źródła komunikatów mogące wystąpić w pliku to : auth - dane związane z autoryzacją, authpriv - inne komunikaty związane z autoryzacją, cron - komunikaty crona, daemon - inne demony, ftp - komunikaty z serwera FTP, kern - komunikaty jądra systemu, local0-local7 - komunikaty lokalne lpr - system obsługi drukarki, mail - komunikaty związane z pocztą mark - w regularnych odstępach czasu wysyła datę i czas news - system wiadomości, syslog - komunikaty demona syslog user - procesy użytkowników, uucp - komunikaty protokołu UUCP Logi systemowe są generowane w określonym formacie. Przykładem wiersza logu /var/log/messages może być: Znacznik czasu nazwa komputera program zdarzenie gdzie znacznik czasu jest wyrażony datą w postaci jak poniżej: Jan 30 21:15:00 tezeusz sshd[27469]: Accepted password for ozzy from 195.244.123.15 port 1844 2. Jezyk AWK Nazwa AWK pochodzi od inicjałów jego projektantów: Alfreda V. Aho, Petera J. Weinbergera i Briana W. Kernighana. Pierwotna wersja AWK została napisana w 1977 roku w AT&T Bell Laboratories. W 1985 roku nowa wersja uczyniła ten język programowania potężniejszym, wprowadzając funkcje definiowane przez użytkownika, wiele strumieni wejściowych i obliczanie wyrażeń regularnych. Ta nowa wersja stała się ogólnie dostępna z Unix System V Release 3.1. Wersja w System V Release 4 dodała kilka nowych cech jak również oczyściła zachowanie niektórych elementów języka. Późniejsza specyfikacja AWK w standardzie POSIX (Command Language and Utilities) objaśniła język w oparciu o opinie zarówno projektantów GAWK, jak i projektantów pierwotnego AWK z Bell Labs. Wykonana w ramach GNU implementacja GAWK jest w pełni zgodna w górę z wersją AWK z Systemu V Release 4 oraz ze specyfikacją POSIX. Znaczy to, że wszystkie poprawnie napisane programy w AWK powinny działać z GAWK, dlatego zwykle nie rozróżniamy obu tych implementacji. Narzędzie AWK interpretuje specjalizowany język programowania, umożliwiający obsługę prostych zadań formatowania danych za pomocą zaledwie kilku linii kodu. Stosując omawiany język można: zarządzać małymi, osobistymi bazami danych, generować raporty, sprawdzać poprawność danych, tworzyć indeksy i wykonywać inne zadania przygotowywania dokumentów, a nawet eksperymentować z algorytmami, które później mogą być zaadaptowane do innych języków komputerowych. Istnieje kilka sposobów uruchamiania programu AWK. Jeśli jest on krótki, najłatwiej zawrzeć go w wierszu poleceń uruchamiając w następujący sposób: awk 'program' plik-wej1 plik-wej2... gdzie program składa się z szeregu wzorców i akcji. Gdy program jest długi, zwykle wygodniej jest umieścić go w pliku i polecić uruchomienie: awk -f plik-programu plik-wej1 plik-wej2... Podstawową funkcją AWK jest wyszukiwanie w plikach wierszy (lub innych jednostkach tekstu) zawierających pewien wzorzec. Gdy wiersz pasuje do jednego ze
wzorców, AWK wykonuje na nim zadane akcje kontynuując takie przetwarzanie wierszy wejściowych aż do osiągnięcia końca tekstów. Programy w awk różnią się od programów w większości innych języków, gdyż są sterowane danymi (data-driven): to znaczy, opisujemy dane, z którymi chcemy pracować, a następnie co zrobić po ich znalezieniu. Inne języki przeważnie są proceduralne; musimy opisać, bardzo szczegółowo, każdy krok jaki ma być wykonany przez program. Pracując w językach proceduralnych zwykle dużo trudniej jest precyzyjnie opisać dane, jakie będzie przetwarzał program. Z tego powodu, programy awk są często budująco łatwe zarówno w pisaniu jak i czytaniu. Przy uruchamianiu awk, określa się program awk, mówiący awk, co ma zrobić. Program taki składa się z szeregu reguł. (Może też zawierać definicje funkcji, zaawansowaną cechę, o której na razie nie będziemy mówić. Każda reguła określa jeden wzorzec jaki ma być szukany i jedną akcję jaka zostanie wykonana po znalezieniu tego wzorca. Składniowo, reguła składa się ze wzorca, po którym następuje akcja. Akcja ujęta jest w nawiasy klamrowe, co oddziela ją od wzorca. Reguły są zwykle rozdzielane znakami nowej linii. Stąd też program awk wygląda tak: wzorzec { akcja } wzorzec { akcja } Przy czym wzorzec i akcja są opcjonalne. Jeśli nie zostanie podany wzorzec, {akcja} jest wykonywana na wszystkich wierszach. Jeśli nie podano akcji, wyświetlany jest dopasowany wiersz. Wzorzec może mieć następujące postacie: /wyrażenie regularne/ { akcja } - Akcja jest wykonywana za każdym razem gdy linia pliku wejściowego zawiera ciąg znaków pasujący do wyrażenia regularnego. Najprostszym wyrażeniem regularnym jest ciąg liter, cyfr lub zarówno liter jak cyfr. Wyrażenie relacyjne { akcja } - W wyrażeniach relacyjnych stosuje się operatory relacji. Przykładowo $2 > $1 powoduje wybranie wierszy w których drugie pole jest większe od pierwszego. Porównane mogą być zarówno liczby jak i ciągi znaków a program awk dokona porównania w zależności od typu danych w $1 i $2. <wzorzec1>, <wzorzec2> {akcja} Wzorzec zakresu tworzą dwa wzorce rozdzielone przecinkiem. Dopasowuje zakres kolejnych wierszy wejściowych. Pierwszy wzorzec decyduje o tym, gdzie zaczyna się zakres, a drugi, gdzie się on kończy BEGIN { akcja} Za pomoca wzorca BEGIN można określić akcje wykonywane przed przetworzeniem pierwszego wiersza danych wejściowych (zazwyczaj ustala się w ten sposób zmienne globalne) END { akcja } Za pomocą wzorca END można natomiast określić akcje które będą wykonywane po odczytaniu ostatniego rekordu danych wejściowych. Pusty Wzorzec pusty pasuje do niego każdy rekord wejściowy
Akcje zawierają jedno lub więcej poleceń, funkcji lub przypisań zmiennych, umieszczonych w nowych wierszach lub oddzielonych od siebie średnikami wszystko to jest umieszczone wewnątrz nawiasów klamrowych.. Przykłady prostych akcji: {print $1} drukowanie pierwszego pola każdego wiersza /wzorzec/ {print $1} drukowanie pierwszego pola wierszy zawierających wzorzec W typowym programie awk, całość wejścia czytana jest albo ze standardowego wejścia (domyślnie z klawiatury, ale często potokiem z innego polecenia) albo z plików, których nazwy podano w wierszu poleceń awk. Jeżeli podano pliki wejściowe, to awk czyta je kolejno, odczytując wszystkie dane z jednego przed przejściem do następnego. Wejście czytane jest jednostkami zwanymi rekordami, i przetwarzane przez reguły danego programu po jednym rekordzie naraz. Domyślnie każdy rekord jest jednym wierszem. Każdy rekord jest automatycznie dzielony na kawałki zwane polami. Ułatwia to programom pracę z częściami rekordów. Narzędzie awk dzieli wejście naszego programu awk na rekordy i pola. Rekordy oddzielane są znakiem nazywanym separatorem rekordów (record separator). Domyślnie separatorem rekordów jest znak nowej linii. Z tego powodu domyślnie rekordy są pojedynczymi wierszami. Jako separatora rekordów można użyć innego znaku, przypisując go zmiennej wbudowanej RS. Wartość RS, jak każdej innej zmiennej programu awk, można zmienić za pomocą operatora przypisania =. Nowy znak separatora rekordów powinien być ujęty w znaki cudzysłowu, które sygnalizują stałą łańcuchową. Często odpowiednim do wykonania takiej zmiany miejscem jest początek wykonywania programu, przed przetworzeniem czegokolwiek z wejścia, tak by pierwszy rekord został odczytany za pomocą właściwego separatora. Można to osiągnąć wykorzystując specjalny wzorzec BEGIN. Narzędzie awk zapamiętuje liczbę rekordów, jakie do tej pory przeczytano z bieżącego pliku wejściowego. Wartość ta przechowywana jest we wbudowanej zmiennej o nazwie FNR. Jest ona zerowana przy rozpoczynaniu nowego pliku. Inna zmienna wbudowana, NR, jest całkowitą liczbą rekordów przeczytanych dotąd ze wszystkich plików danych. Zaczyna się od zera, ale nigdy nie jest automatycznie zerowana. Przy odczycie przez awk rekordu wejściowego, jest on automatycznie rozdzielany, parsowany, przez interpreter na kawałki zwane polami (fields). Domyślnie pola rozdzielane są białymi znakami, podobnie jak słowa w wierszu. Biały znak w awk oznacza łańcuch złożony z jednej lub więcej spacji, tabulacji lub znaków nowej linii; (5) inne znaki, jak formfeed (znak wysuwu strony), i tak dalej, uważane za białe znaki przez inne języki nie są białymi znakami dla awk. Celem istnienia pól jest zapewnienie nam wygodniejszego dostępu przy odwoływaniu się do tych fragmentów rekordu. Nie musimy ich używać jeśli chcemy, możemy działać na całym rekordzie ale to dzięki polom proste programy awk są tak efektywne. Odwołując się do pola w programie awk, używamy znaku dolara, `$', po którym występuje numer żądanego pola. Zatem, $1 odnosi się do pierwszego pola, $2 do
drugiego, i tak dalej. Na przykład, załóżmy że mamy następujący wiersz wejścia. Próbując odwołać się do pola za ostatnim polem, jak na przykład $8 gdy rekord ma tylko siedem pól, otrzymujemy łańcuch pusty. Symbol $0, wyglądający jak odwołanie do "zerowego" pola, jest przypadkiem specjalnym: reprezentuje cały rekord wejściowy. $0 wykorzystuje się, gdy nie jesteśmy zainteresowani polami. Wyrażenia są podstawowymi elementami konstrukcyjnymi wzorców i akcji awk. Wyrażenie rozwija się w wartość, którą można wypisać, porównywać, przechować w zmiennej czy przesłać do funkcji. Dodatkowo, wyrażenie, za pomocą operatora przypisania, może przypisać nową wartość zmiennej lub polu. Wyrażenie może służyć jako samodzielny wzorzec lub instrukcja akcji. Większość pozostałych rodzajów instrukcji zawiera jedno lub więcej wyrażeń, określających dane na których mają działać. Jak w innych językach, wyrażenia w awk zawierają zmienne, odwołania do tablic, stałe i wywołania funkcji, jak również ich kombinacje z rozmaitymi operatorami. Użyte po prawej stronie operatorów ~ lub!~ wyrażenie regularne stałe oznacza po prostu wyrażenie regularne jakie ma zostać dopasowane. Funkcje napisowe służą do sprawnego manipulowania napisami, badają lub zmieniają tekst jednego lub więcej łańcuchów. gsub(r,s,t) - Zamienia wyrazenie regularne r na napis s w napisie t. Zwraca liczbe zamian. Wywołana z dwoma parametrami zamienia napis w $0. gensub(r,s,a,t) Uogólniona funkcja gsub. Zwraca zmieniony napis (nie modyfikuje oryginalnego napisu t ). Zamienia wyrażenie regularne r w oparciu o napis s, w napisie t (jezeli nie ma t zamienia w $0). Argument a określa, który z kolei podnapis pasujący do wyrażenia r ma być wymieniony. Jeżeli a jest napisem rozpoczynającym sie od "g" (lub "G") to wymieniane są wszystkie napisy pasujace do r. Funkcja gensub umozliwia wstawienie fragmentów wyrażenia regularnego w napisie s. Jeżeli wyrazenie r podzielimy za pomoca nawiasów, ( i ) na czesci składowe, to te składowe moga później pojawic sie w napisie s. Oznaczamy je jako \n, gdzie n jest cyfra od 1 do 9. Znaczenie tego jest takie, ze napis pasujący do n-tej składowej jest kopiowany, z tekstu t do tekstu zwracanego przez funkcje. W efekcie możliwe są wszelkiego rodzaju zmiany kontekstowe. Symbol \0 oznacza całe wyrażenie regularne r. index(s,t) - Zwraca numer pierwszego znaku napisu t w napisie s. Jeżeli s nie zawiera t zwraca 0. length(s) - Podaje długosc napisu s lub długosc $0 (jezeli nie podano s). match(s,r) - Jezeli s zawiera napis pasujący do r, to zwraca numer pierwszego znaku tego napisu; w przeciwnym razie zwraca 0. Przypisuje wartości zmiennym RSTART i RLENGTH. RSTART jest równe wartosci zwracanej przez funkcje, RLENGTH jest równe długości napisu pasujacego do r. split(s,a,fs) - Z napisu s tworzy tablice napisów a, wykorzystujac do separacji wyrazenie regularne fs. Jezeli split jest wywołane tylko z dwoma parametrami to separatorem jest wartosc zmiennej FS. sprintf(format, lista-wyrazen) - Zwraca napis, sformatowany według specyfikacji format.
sub(r,s,t) - Jak sub, ale zamienia tylko pierwszy napis pasujacy do r na s. substr(s,p,n) - Zwraca napis wyciety z s poczawszy od pozycji p o długosci n znaków (lub do konca s, jezeli argument n jest pominiety). tolower(s) - Zwraca napis, w którym duze litery zostały zamienione na małe. toupper(s) - Zwraca napis, w którym małe litery zostały zamienione na duze. 3. Analiza przykładowego logu przy pomocy języka AWK Pliki logów jako pliki tekstowe są świetnym materiałem do przetworzenia wykorzystując język awk. Dzięki niemu możemy wyłowić z pliku logu, który często ma kilka tysięcy wierszy, interesujące nas informacje. Przykład 1 Wykorzystując wyrażenie regularne chcemy wyświetlić wszystkie wiersze logu z miesiąca stycznia (ang. January). Pamiętając że nazwa miesiąca zapisywana jest w postaci pierwszych trzech liter, wyświetlamy wszystkie wiersze których pierwsze pole ma wartość Jan wykonujemy to poleceniem: awk $1 ~ /Jan/ {print $0} /var/log/messages Na standardowym wyjściu czyli ekranie zostaną wyświetlone wszystkie wiersze z pliku /var/log/messages których pierwsze pole ma wartość Jan Przykład 2 Chcemy za pomocą programu awk wyłowić zdarzenia zaistniałe w określonym dniu. Biorąc pod uwagę format daty w wierszu pliku logu, która jest określona w postaci: miesiąc dzień godz:min:sek wydajemy polecenie: awk $1==Jan && $2==31 {print $0} /var/log/messages 4. Aplikacja własna Program do analizy i wyświetlania logów w systemie Linux. Skrypt napisany w powłoce BASH z wykorzystaniem okienek dialogowych. W zależności od wybranych przez użytkownika pozycji, konstruowane są zapytania w języku AWK, wynik wyświetlony zostaje na ekranie.
Rys.1 Menu główne W menu głównym użytkownik wybiera, który z logów ma zostać przeanalizowany Rys.2 Menu pliku /var/log/messages Po wybraniu logu istnieje możliwość wybrania sposobu wyświetlenia tj. wyświetlenie całego logu, wyświetlenie według wybranego dnia, lub wybranego programu który jest logowany w danym pliku logu.
Rys.3 Wybór daty Okienko wyboru daty wg. Której zostanie wyświetlony wynik zapytania Rys.4 Wyświetlony wynik zapytania
Podsumowanie Literatura [1] Dale Dougherty, Arnold Robbins - sed & awk, Helion 2002 [2] Tomasz Przechlewski - Opis języka AWK, http://gnu.univ.gda.pl/~tomasz/prog/awk/awk-intro/awk-man-b5.pdf [3] Arnold D. Robbins - Efektywne programowanie w AWK : http://ptm.linux.pl/man_html/info_pl_html/gawk/gawk_toc.html [4] Joseph D. Sloan - Narzędzia administrowania siecią, wyd. RM, W-wa 2002.