Blog (76)
Komentarze (5.6k)
Recenzje (0)
@nintyfanlibsell i integracja z libgreattao

libsell i integracja z libgreattao

Libsell to biblioteka logów. Nazwa pochodzi od Simple Extensible Log Library,. a to dlatego, że aplikacja może ją rozszerzyć. Możliwość rozszerzenia została wprowadzona, jako iż użytkownik może zdefiniować coś, co nazwałem potokami przetwarzania. W takim potoku mogą wystąpić komendy zdefiniowane przez aplikację, ale o możliwości zdefiniowania reguł(potoków) przez użytkownika, jak i możliwości rozszerzenia przez aplikację, nie teraz. Opiszę też to, jak korzystać z interfejsów oferowanych przez Libgreattao dla logów. Jestem autorem libsell, jak i libgreattao. Oba rozwiązania jeszcze nie są w wersji stabilnej.

Wstęp

Wiele bardziej zaawansowanych aplikacji posiada własny systemów logów. Na marginesie,Libgreattao także posiadał własny system logów. Problemem jest to, że taki system logów jest niepraktyczny dla użytkownika, gdyż skąd użytkownik ma wiedzieć np., gdzie aplikacja zapisuje logi. Jest znowu praktyczny dla programisty. Postanowiłem znaleźć złoty środek, oferując możliwość określenia potoków, co jest przydatne zarówno dla programisty i użytkownika. Wprowadziłem także prosty, sieciowy protokół do przesyłania logów do specjalnej aplikacji, uruchomionej na prawach użytkownika, która ma wyświetlać logi w oknie. Jeżeli środowisko użytkownika będzie odpowiednio skonfigurowane, to aplikacja napisana w libsell, może wysyłać logi do tego serwera, zamiast chamsko wypychać je na terminala. Libsell posiada także możliwość przesyłania logów do syslog, jednak syslog jest dla administratorów, a nie dla użytkowników i programistów.

Libsell pozwala na definiowanie wielu kontekstów, w ramach których istnieją potoki, przez co możemy bardzo łatwo sterować tym, jak dany komunikat zostanie przefiltrowany, przetworzony, i gdzie trafi.

Co do libgreattao, to korzysta ono z dwóch kontekstów, a oba mogą być określone przez zmienną środowiskową lub parametr przekazany z wiersza poleceń.

Pierwszy program w libsell

Najprostszy program w libsell wygląda tak:

[code=C] #include <sell.h>

int main(int argc, char **argv) { void *log_context = sell_initialize("domyślny kontekst", 0);

sell_add_default_rules(log_context);

sell_put_to_log(log_context, "main", "uruchamianie programu", "Inicjowanie generatora liczb pseudolosowych", 0); // Coś robimy sell_put_to_log(log_context, "main", "uruchamianie programu", "Uruchamianie trybu graficznego", 0); // Coś robimy while (cosik) { //nasz program }

sell_put_to_log(log_context, "main", "Wyłączanie programu", "Zwalnianie pamięci", 0); } [/code]

Powyższy przykład się nie skompiluje ze względu na brak zmiennej cosik, jednak omówię ogólna koncepcję. Pierwsze, co robimy, to tworzymy kontekst za pomocą sell_initialize. Przekazujemy jej nazwę kontekstu, która będzie m.in przesłana jako nazwa programu do serwera logów. Drugi parametr to flagi, lecz obecnie nie ma żadnych flag zdefiniowanych. Kolejnym etapem jest wywołanie sell_add_default_rules, która dodaje standardowe potoki przetwarzania do naszego kontekstu, jednak nie w przypadku, gdy istnieje zmienna środowiskowa SELL_RULES, bo w takim wypadku dodawane są potoki zdefiniowane w tej zmiennej. O definiowaniu potoków przez użytkownika napiszę nieco dalej. Domyślny zestaw potoków zawiera tylko jeden potok z jednym poleceniem. Te polecenie nakazuje wypisanie komunikatu na standardowe wyjście błędów. Teraz najważniejsze - sell_put_to_log. Jest to makro, które pobiera nazwę pliku, nazwę funkcji, numer linii, i wraz z podanymi parametrami, przekazuje to wszystko do funkcji sell_put_to_log_custom. Możemy także wywołać sell_put_to_,log_custom nie przez makro set_put_to_log, lecz raczej nie będzie takiej potrzeby. Jakie są parametry tego makra? Pierwszym jest kontekst, następnym nazwa modułu aplikacji, kolejnym wykonywane zadanie lub powód dodania wpisu do logów, następnym komunikat, a ostatnim flagi. Domyślnie nie ma żadnych flag, lecz aplikacja może zdefiniować swoje. Co do modułu, to może to być nazwa katalogu z plikiem źródłowym lub nazwa klasy związanej z komunikatem.

Niestandardowe zachowanie

Libsell pozwala aplikacji także na określenie niestandardowego zachowania. Aplikacja powinna definiowac zachowanie(potoki) libsell tylko w przypadku, gdy jest to konieczne. Oto przykład kodu:

[code=C] #include <sell.h>

int main(int argc, char **argv) { void *rule; void *log_context = sell_initialize("Kontekst odpluskiwania", 0); rule = sell_prepend_rule(log_context); sell_append_command(rule, "FORMAT=Komunikat z pliku %file i linii %line o treści %message"); sell_append_command(rule, "write_output_to_stderr);

sell_put_to_log(log_context, "Główny moduł programu", "Raport stanu", "Zwalnianie pamięci", 0); } [/code]

Co powyżej robimy? Definiujemy jedną regułę(potok) dla kontekstu log_context, a następnie dodajemy dwa polecenia. Pierwsze zmienia treść tego, co powinno być wypisane, a drugie nakazuje wypisanie tego na standardowe wyjście błędów.

Teraz przedstawię opis najważniejszych poleceń:

  1. FORMAT=wartość - Zmienia format komunikatu.
  2. write_output_to_stderr - Wypisuje komunikat na standardowe wyjście błędów.
  3. write_output_to_stdout - Wypisuje komunikat na standardowe wyjście
  4. write_output_to_selected_file - Wypisuje do pliku określonego przez ostatnią z komend OUTPUT_FILE_APPEND=nazwa_pliku lub OUTPUT_FILE_RESET=nazwa_pliku w bieżącym potoku
  5. send_data_to_log_server - Przesyła dane do serwera logów wskazanego przez ostatnie polecenie OUTPUT_SOCKET=host:port w bieżącym potoku
  6. exit - kończy przetwarzanie potoków
  7. drop - usuwa bieżący potok

Możliwymi podstawieniami w FORMAT= są: %reason, %module, %file, %line, %message. Różnica między OUTPUT_FILE_APPEND i OUTPUT_FILE_RESET jest taka, ze ta pierwsza dodaje wpis na koniec pliku, a ta druga wymazuje zawartość pliku, przed dodaniem wpisu. Odnosi się to tylko, gdy przekażemy im nazwę pliku, bo gdy przekażemy zmienną plikową, to zwiększany jest tylko licznik referencji tej zmiennej. O typach zmiennych i zmiennych napisze później.

Zachowanie definiowane przez użytkownika

Użytkownik może zdefiniować zachowanie aplikacji przez zmienną SELL_RULES. Są dwa warunki: aplikacja wywoła sell_add_default_rules, a potem nie będzie nic majstrować przy kontekście, na którym to wywołała. Postanowiłem nie wymyślać koła na nowo i upodobnić sposób zapisywania reguł do reguł udev-a. W tej zmiennej poszczególne reguły powinny być oddzielone znakiem nowej linii lub średnikiem, a poszczególne polecenia przecinkiem. Występują tutaj trzy typy poleceń: porównanie, przypisanie i polecenie zwykłe. Porównanie to polecenia, które zawiera w sobie wystąpienie znaku równości, a po pierwszym wystąpieniu znaku równości znak równości lub znak r. Pierwszy typ porównania(==) porównuje ciągi znaków dokładnie, a drugi przyrównuje pierwszy operand do wyrażenia regularnego w drugim operandzie. Jeżeli porównanie nie jest prawdzie, to kończy się wykonanie bieżącego potoku i następuje przejście do kolejnego(jeśli istnieje). Przypisanie składa się ze znaku równości, przy czym po pierwszym znaku równości nie może wystąpić kolejny znak równości lub znak r. Polecenie zwykłe nie zawiera znaków równości. By zakomunikować, że przecinek nie pełni roli separatora poleceń, to należy go poprzedzić znakiem znakiem backslash.

Do czego mogą nam posłużyć własne filtry? Powiedzmy, że komunikaty z modułu inputs powinny trafić do inputs.txt, a z modułu graphics powinny trafić do graphics.txt. Możemy też komunikaty z pierwszego programu przefiltrować na podstawie powodu ich podawania, np. gdy chcemy wyświetlić tylko komunikaty dotyczące wyłączania programu. Jednak najpierw muszę wspomnieć o zmiennych.

Zmienne

Libsell obsługuje zmienne kontekstu, jak również zmienne potoku(reguły). Zmienne kontekstu będą nazywane zmiennymi globalnymi, a zmienne reguły zmiennymi lokalnymi. Zmienną globalną definiuje się w ten sposób:


GLOBAL[nazwa_zmiennej]=wartość

Zmienną lokalną definiuje się w ten sposób:


LOCAL[nazwa_zmiennej]=wartość

Wszystkie zmienne definiowane w ten sposób są zmiennymi tekstowymi, poza przypadkiem, gdy zamiast wartości użyjemy podstawienia zmiennej. W takim wypadku zmienna będzie mieć typ zmiennej, którą podstawiliśmy. Podstawienie zmiennej wygląda w taki sposób:


${nazwa_zmiennej}

Możemy poprzedzić znak dolara backslashem(w przypadku zmiennej SELL_RULES, to dwa znaki backslasha), by uprzedzić, że nie używamy podstawienia zmiennej.

Są dwa typy zmiennych: tekstowy i plikowy. Polecenia OUTPUT_FILE_APPEND, OUTPUT_FILE_RESET tworzą zmienną lokalną "OUTPUT FILE" o typie plikowym. Polecenie OUTPUT_SOCKET tworzy zmienną lokalną "OUTPUT SOCKET" o typie plikowym.

Dodatkowo zmiennymi lokalnymi są log_line, log_message, log_reason, log_file, log_module. Inną, specyficzną zmienną jest tablica flag. Tablicę flag można używać tylko do sprawdzania, czy flaga jest ustawiona, czy nie jest ustawiona. Oto przykład:


FLAG[IMPORTANT]==TRUE,write_outout_to_stdout

Przykłady zbiorów reguł


OUTPUT_SOCKET=localhost:1024,GLOBAL[OS]=${OUTPUT_SOCKET},drop
OUTPUT_SOCKET=${OS},send_data_to_log_server

Powyższy zbiór reguł otwiera połączenie do serwera logów nasłuchującym na naszej maszynie, a porcie 1024. To czyni tylko przy pierwszym komunikacie, gdyż na końcu tej reguły jest polecenie drop. Kolejna reguła jest już wykonywana dla każdego komunikatu, i powoduje ona wysłanie danych do serwera logów.


OUTPUT_FILE_RESET=graphics.txt,GLOBAL[GRF]=${OUTPUT FILE},OUTPUT_FILE_RESET=input.txt,GLOBAL[ING]=${OUTPUT FILE},drop
${log_module}==Graphics,OUTPUT_FILE_APPEND=${GRF},write_output_to_selected_file
${log_module}==Input,OUTPUT_FILE_APPEND=${INF},write_output_to_selected_file

Powyższy przykład powoduje rozdzielenie logów w zależności od modułu do plików graphics.txt i input.txt.

Rozszerzanie

Tutaj opiszę czynności, które można wykonać jedynie przed dodaniem reguły do kontekstu. Są to dodawanie własnej flagi i dodawanie własnego polecenia.

Dodawanie własnej flagi

Dodawanie własnej flagi wykonuje się funkcją sell_create_custom_flag. Należy jej przekazać kontekst i ciąg znaków reprezentujący nazwę tworzonej flagi dla podanego kontekstu. Funkcja ta zwraca wartość typu integer, która może być podana w sell_put_log jako ostatni parametr, by uaktywnić flagę. Przykładowe wywołanie:


flag2 = sell_create_custom_flag(log_context, "DIALOG");

Taka flaga może być użyta, by wraz z własnym poleceniem wyświetlającym okno dialogowe, pozwolić użytkownikowi zadecydować, czy wysłanie komunikatu wraz z utworzoną flagą, powinno powodować utworzenie okna dialogowego. Innym przykładem flagi może być flaga o nazwie IMPORTANT, którą użytkownik może żądać przekazać do oddzielnego pliku.

Własne polecenia

Nowe polecenie tworzy się funkcją sell_add_new_command_handler. Jako pierwszy argument trzeba mu przekazać kontekst, jako drugi ciąg znaków reprezentujący nazwę nowotworzonego polecenia, a jako ostatni wskaźnik na funkcję obsługi. Sygnatura funkcji obsługi wygląda następująco:


(void *context_, const char **function_, const char **module_, const char **reason_, 
                              const char **message_,const char **file_, int *line_,
                              const char **output
 ): void

Nie trudno się domyślić, co jej jest przekazywane. Zastosowanie własnego polecenia ma sens tylko w przypadku, gdy pozwolimy użytkownikowi definiować potok. Przykładem zastosowania własnego polecenia jest polecenie wyświetlające okno.

Integracja z libgreattao

Libgreattao korzysta z sell wewnętrznie, jak i również udostępnia specjalne API logów dla aplikacji. Tworzy ono dwa konteksty - jeden do wpisów generowanych przez libgreattao, a drugi dla wpisów tworzonych przez aplikację. Wszystkie podane sposoby służą do przekazania wartości dla sell, więc w ramach tych wartości przekazujemy potoki przetwarzania(reguły).

Podawanie konfiguracji aplikacji

By podać konfigurację aplikacji, to można to zrobić na trzy sposoby:

  1. Poprzez przełącznik --tao-justone-app-sell-config
  2. Poprzez zmienną środowiskową TAO_APP_SELL_CONFIG
  3. Poprzez zmienną środowiskową SELL_RULES

Te wartości mają priorytet zgodny z kolejnością zaprezentowaną, czyli jeżeli podamy -‑tao-justone-app-sell-config, to dwie kolejne wartości nie będą rozpatrywane.

Podawanie konfiguracji libgreattao

Można podać konfigurację dla kontekstu libgreattao na dwa sposoby:

  1. Poprzez przełącznik --tao-justone-debug-sell-config
  2. Przez zmienną środowiskową TAO_DEBUG_SELL_CONFIG

Jak poprzednio, wartości będą rozpatrywane w podanej kolejności. Jest tylko mały niuans. Podczas przetwarzania parametrów wiersza poleceń, będzie brana pod uwagę wartość zmiennej środowiskowej o wspomnianej nazwie. Jest to spowodowane tym, że na tym etapie wartość po wspomnianym przełączniku nie jest znana, więc nie możemy użyć konfiguracji wygenerowanej na podstawie tej wartości.

Dodatkowe informacje

Libgreattao używało, i używa nadal, przełącznika -‑tao-debug-level, który przyjmuje wartości od 0 do 7. Ten przełącznik nadal jest istotny, bo wartość debug level odcina komunikaty wyższego poziomu. Inną istotną informacją jest to, że libgreattao definiuje dla kontekstu debugowania flagi DEBUG_LEVEL_1, DEBUG_LEVEL_2, ..., DEBUG_LEVE_7. Pozwala to na przykład na dodatkowe filtrowanie komunikatów. Zresztą, to od teraz posiadamy informację o funkcji, w której został wygenerowany komunikat przetworzenia logów, itd.

Co do kontekstu aplikacji, to libgreattao definiuje dwa polecenie. Są nimi: send_to_libnotify i show_message_window. Pierwsze jeszcze nie jest obsługiwane, a drugie powoduje wyświetlenie okna komunikatu. Są też dwie flagi - WAIT_TO_RESPONSE i USER_MUST_SEE. W domyślnej konfiguracji, pierwsza powoduje wyświetlenie okna komunikatu, a druga obecnie niczego nie robi.

API libgreattao do obsługi logów

Libgreattao daje programowi tylko możliwość przekazania wpisu logów. Nie można tworzyć własnych flag, jak i nie można tworzyć własnych poleceń. Funkcja do wysyłania wpisu, to tao_log. Jest ona identyczna z sell_put_to_log, z wyjątkiem tego, że jej parametry są przesunięte w lewo, więc pomijamy pierwszy. Analogicznie, istnieje funkcja tao_log_custom. Do tao_log można przekazać następujące flagi: TAO_LOG_WAIT_FOR_RESPONSE i TAO_LOG_USER_MUST_SEE.

To już wszystko na dzisiaj!

Wybrane dla Ciebie
Komentarze (4)