Jaki jest właściwy sposób obsługi wyników debugowania w Javie?

32

Ponieważ moje obecne projekty Java stają się coraz większe, odczuwam również rosnącą potrzebę wstawiania wyników debugowania w kilku punktach mojego kodu.

Aby odpowiednio włączyć lub wyłączyć tę funkcję, w zależności od otwarcia lub zamknięcia sesji testowych, zwykle umieszczam private static final boolean DEBUG = falsena początku zajęć, które sprawdzają moje testy, i trywialnie używam go w ten sposób (na przykład):

public MyClass {
  private static final boolean DEBUG = false;

  ... some code ...

  public void myMethod(String s) {
    if (DEBUG) {
      System.out.println(s);
    }
  }
}

i tym podobne.

Ale to mnie nie rozśmiesza, bo oczywiście działa, ale może być zbyt wiele klas, aby ustawić DEBUG na prawdę, jeśli nie gapisz się tylko na kilka z nich.

I odwrotnie, ja (jak - myślę - wielu innych) nie chciałbym przełączać całej aplikacji w tryb debugowania, ponieważ ilość wysyłanego tekstu może być przytłaczająca.

Czy jest więc właściwy sposób na architektoniczną obsługę takiej sytuacji, czy najbardziej poprawnym jest użycie członka klasy DEBUG?

Federico Zancan
źródło
14
w Javie poprawnym sposobem NIE jest używanie kodu homebrew do logowania. Wybierz ustalone ramy, nie wymyślaj koła na nowo
gnat
Używam boolean DEBUG w niektórych moich bardziej skomplikowanych klasach z tego samego powodu, co powiedziałeś. Zwykle nie chcę debugować całej aplikacji, tylko klasa sprawia mi problemy. Nawyk pochodzi z moich dni COBOL, w których instrukcje DISPLAY były jedyną dostępną formą debugowania.
Gilbert Le Blanc
1
Polecam również poleganie w większym stopniu na debugerze, jeśli to możliwe, a nie zaśmiecanie kodu instrukcjami debugowania.
Andrew T Finnell,
1
Czy ćwiczysz programowanie oparte na testach (TDD) z testami jednostkowymi? Gdy zacząłem to robić, zauważyłem znaczną redukcję „kodu debugowania”.
JW01

Odpowiedzi:

52

Chcesz spojrzeć na strukturę rejestrowania, a może na strukturę fasady rejestrowania.

Istnieje wiele ram rejestrowania, często z nakładającymi się funkcjami, do tego stopnia, że ​​z czasem wielu ewoluowało, aby polegać na wspólnym interfejsie API, lub zaczęło być używanych przez strukturę elewacji, aby wyodrębnić ich użycie i umożliwić ich zamianę na miejscu Jeśli potrzebne.

Ramy

Niektóre ramy rejestrowania

Niektóre fasady z logowaniem

Stosowanie

Podstawowy przykład

Większość z tych frameworków pozwoli ci napisać coś w formie (tutaj używając slf4j-apii logback-core):

package chapters.introduction;

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

// copied from: http://www.slf4j.org/manual.html
public class HelloWorld {

  public static void main(String[] args) {
    final Logger logger = LoggerFactory.getLogger(HelloWorld.class);

    logger.debug("Hello world, I'm a DEBUG level message");
    logger.info("Hello world, I'm an INFO level message");
    logger.warn("Hello world, I'm a WARNING level message");
    logger.error("Hello world, I'm an ERROR level message");
  }
}

Zwróć uwagę na użycie bieżącej klasy do stworzenia dedykowanego programu rejestrującego, który pozwoliłby SLF4J / LogBack sformatować dane wyjściowe i wskazać, skąd pochodzi komunikat rejestrowania.

Jak zauważono w podręczniku SLF4J , typowy wzorzec użycia w klasie to zwykle:

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

public class MyClass {

    final Logger logger = LoggerFactory.getLogger(MyCLASS.class);

    public void doSomething() {
        // some code here
        logger.debug("this is useful");

        if (isSomeConditionTrue()) {
            logger.info("I entered by conditional block!");
        }
    }
}

Ale w rzeczywistości jeszcze bardziej powszechne jest deklarowanie programu rejestrującego za pomocą formularza:

private static final Logger LOGGER = LoggerFactory.getLogger(MyClass.class);

Pozwala to na użycie rejestratora również z metod statycznych i jest dzielony między wszystkie instancje klasy. Jest to prawdopodobnie preferowana forma. Jednak, jak zauważył Brendan Long w komentarzach, musisz mieć pewność, że rozumiesz implikacje i odpowiednio decydujesz (dotyczy to wszystkich ram rejestrowania następujących po tych idiomach).

Istnieją inne sposoby tworzenia instancji rejestratorów, na przykład za pomocą parametru ciągu w celu utworzenia nazwanego rejestratora:

Logger logger = LoggerFactory.getLogger("MyModuleName");

Poziomy debugowania

Poziomy debugowania różnią się w zależności od frameworka, ale typowe są (w kolejności krytyczności, od łagodnych do złych gówno-nietoperzy i od prawdopodobnie bardzo powszechnych do, miejmy nadzieję, bardzo rzadkich):

  • TRACE Bardzo szczegółowe informacje. Powinny być zapisywane tylko w dziennikach. Służy tylko do śledzenia przepływu programu w punktach kontrolnych.

  • DEBUG Dokładna informacja. Powinny być zapisywane tylko w dziennikach.

  • INFO Znaczące zdarzenia w czasie wykonywania. Powinny być natychmiast widoczne na konsoli, więc używaj oszczędnie.

  • WARNING Nieprawidłowości w działaniu i błędy, które można naprawić.

  • ERROR Inne błędy w czasie wykonywania lub nieoczekiwane warunki.

  • FATAL Poważne błędy powodujące przedwczesne zakończenie.

Bloki i Strażnicy

Załóżmy, że masz sekcję kodu, w której masz zamiar napisać kilka instrukcji debugowania. Może to szybko wpłynąć na wydajność, zarówno ze względu na wpływ samego rejestrowania, jak i generowania wszelkich parametrów przekazywanych do metody rejestrowania.

Aby uniknąć tego rodzaju problemów, często chcesz napisać coś w formie:

if (LOGGER.isDebugEnabled()) {
   // lots of debug logging here, or even code that
   // is only used in a debugging context.
   LOGGER.debug(" result: " + heavyComputation());
}

Jeśli nie używałeś tego zabezpieczenia przed blokiem instrukcji debugowania, mimo że komunikaty mogą nie być wyprowadzane (jeśli na przykład twój program rejestrujący jest obecnie skonfigurowany do drukowania tylko rzeczy powyżej INFOpoziomu), heavyComputation()metoda nadal byłaby wykonana .

Konfiguracja

Konfiguracja jest dość zależna od struktury rejestrowania, ale oferują one w większości te same techniki:

  • konfiguracja programowa (w czasie wykonywania, poprzez API - pozwala na zmiany w czasie wykonywania ),
  • statyczna konfiguracja deklaratywna (na początku, zwykle za pomocą pliku XML lub pliku właściwości - prawdopodobnie będzie to, czego potrzebujesz na początku ).

Oferują również w większości te same możliwości:

  • konfiguracja formatu komunikatu wyjściowego (znaczniki czasu, znaczniki itp.),
  • konfiguracja poziomów wyjściowych,
  • konfiguracja filtrów drobnoziarnistych (na przykład w celu włączenia / wyłączenia pakietów lub klas),
  • konfiguracja programów dołączających w celu określenia, gdzie należy się zalogować (do konsoli, do pliku, do usługi internetowej ...) i ewentualnie co zrobić ze starszymi dziennikami (na przykład z automatycznymi zwijaniem plików).

Oto typowy przykład konfiguracji deklaratywnej przy użyciu logback.xmlpliku.

<configuration>

  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

Jak wspomniano, zależy to od twojego frameworka i mogą istnieć inne alternatywy (na przykład LogBack pozwala również na użycie skryptu Groovy). Format konfiguracji XML może również różnić się w zależności od implementacji.

Więcej przykładów konfiguracji można znaleźć (między innymi) w:

Trochę historycznej zabawy

Należy pamiętać, że Log4J widzi znaczącą aktualizację w tej chwili, przejście z wersji 1.x do 2.x . Możesz rzucić okiem na więcej historycznej zabawy lub zamieszania, a jeśli wybierzesz Log4J, prawdopodobnie wolisz korzystać z wersji 2.x.

Warto zauważyć, jak wspomniał Mike Partridge w komentarzach, że LogBack został stworzony przez byłego członka zespołu Log4J. Który został stworzony w celu wyeliminowania niedociągnięć platformy Java Logging. I że nadchodząca główna wersja Log4J 2.x sama teraz integruje kilka funkcji zaczerpniętych z LogBack.

Zalecenie

Podsumowując, pozostań oddzielony jak najwięcej, baw się z kilkoma i zobacz, co będzie dla Ciebie najlepsze. Ostatecznie jest to po prostu struktura rejestrowania . Z wyjątkiem przypadku, gdy masz bardzo konkretny powód, oprócz łatwości użycia i osobistych preferencji, każdy z nich byłby raczej w porządku, więc nie ma sensu się nad nim wisieć. Większość z nich można również rozszerzyć na Twoje potrzeby.

Mimo to, gdybym dzisiaj musiał wybrać kombinację, wybrałbym LogBack + SLF4J. Ale jeśli zapytałeś mnie kilka lat później, poleciłbym Log4J z logowaniem Apache Commons, więc miej oko na swoje zależności i rozwijaj się wraz z nimi.

Haylem
źródło
1
SLF4J i LogBack zostały napisane przez faceta, który pierwotnie napisał Log4J.
Mike Partridge
4
Dla tych, którzy mogą martwić się o wpływ rejestrowania na wydajność: slf4j.org/faq.html#logging_performance
Mike Partridge
2
Warto wspomnieć, że nie jest tak jednoznaczne, czy powinieneś tworzyć rejestratory static, ponieważ oszczędza niewielką ilość pamięci, ale w niektórych przypadkach powoduje problemy: slf4j.org/faq.html#declared_static
Przywróć Monikę
1
@MikePartridge: Znam treść linku, ale nadal nie zapobiegnie to na przykład ocenie parametrów. powodem sparametryzowanego rejestrowania byt jest bardziej wydajne, ponieważ przetwarzanie komunikatu dziennika nie nastąpi (łańcuch w szczególności konkatenacji). Jednak każde wywołanie metody zostanie wykonane, jeśli zostanie przekazane jako parametr. W zależności od przypadku użycia bloki mogą być przydatne. I jak wspomniano w poście, mogą być również przydatne do grupowania innych działań związanych z debugowaniem (nie tylko rejestrowania), które występują, gdy włączony jest poziom DEBUG.
haylem
1
@haylem - to prawda, mój błąd.
Mike Partridge
2

użyj środowiska rejestrowania

przez większość czasu istnieje statyczna metoda fabryczna

private static final Logger logger = Logger.create("classname");

następnie możesz wyprowadzić kod logowania na różnych poziomach:

logger.warning("error message");
logger.info("informational message");
logger.trace("detailed message");

wtedy będzie jeden plik, w którym możesz zdefiniować, które komunikaty dla każdej klasy powinny być zapisywane na wyjściu dziennika (plik lub stderr)

maniak zapadkowy
źródło
1

Właśnie do tego służą frameworki rejestrowania, takie jak log4j lub nowszy slf4j. Pozwalają na bardzo szczegółowe kontrolowanie rejestrowania i konfigurowanie go nawet podczas działania aplikacji.

Michael Borgwardt
źródło
0

Struktura rejestrowania jest zdecydowanie najlepszym rozwiązaniem. Musisz także mieć dobry zestaw testów. Dobre pokrycie testowe może często wyeliminować potrzebę debugowania danych wyjściowych łącznie.

Dima
źródło
Jeśli korzystasz ze środowiska rejestrowania i masz dostęp do rejestrowania debugowania - nadejdzie czas, kiedy pozwoli ci to uniknąć naprawdę złego dnia.
Fortyrunner,
1
Nie powiedziałem, że nie powinieneś logować się. Powiedziałem, że najpierw musisz mieć testy.
Dima,