Bylo celkem jisté, že při psaní poznatků z logování si nevzpomenu na všechno. Jedna z nejnáročnějších a nejrizikovějších aktivit mi přijde integrace systémů. Osvědčilo se mi logovat začátek volání a pak jeho výsledek (alespoň úspěch/neúspěch). Než se vše odladí, ocenili byste možná podrobnější logování. V lepším případě má systém klientskou knihovnu. A to je problém, který mě přivedl k sepsání tohoto tipu. Konkrétně jsem zrovna používal jfrog artifactory java klienta. Nechci je pomlouvat, spíš jako ukázka, že jsem si příklad nevycucal z prstu. Ale nejsou ani první, ani poslední, kde jsem se s tím setkal. Jde o to, že poskytují nějaké modelové třídy, ale bez toString metod.

Úvod

Jak známo, výchozí implementace toString metody vrací název třídy a hash, například User@41906a77, což nám s integrací zrovna dvakrát nepomůže. Doplnit si implementaci je snadné, ovšem za předpokladu, že máte zdrojové kódy pod kontrolou. Můžete samozřejmě vyjmenovávat property pro každou konkrétní třídu a každou logovací zprávu, ale to se vám asi nechce. V současné chvíli preferuji následující řešení, ke kterému mě přivedlo jak jinak než Stack Overflow.

Implementace

Využijeme Apache Commons Lang a jejich ReflectionToStringBuilder ve spojení s RecursiveToStringStyle.

Byť jsem psal, že díky parametrizovaným logovacím zprávám se lze většinou vyhnout testování úrovně logování, tak tady nejspíš chcete používat reflexi, jen když je to potřeba.

if (logger.isTraceEnabled()) {
    logger.trace("Calling {}", ReflectionToStringBuilder.toString(request, SHORT_RECURSIVE_PREFIX_STYLE));
}

Mluvím o SLF4J. V Log4j2 mají pěknou podporu pro lambdy, takže by šlo psát:

logger.trace("Calling {}", () -> ReflectionToStringBuilder.toString(request, SHORT_RECURSIVE_PREFIX_STYLE));

A teď už celý příklad:

import org.apache.commons.lang3.builder.RecursiveToStringStyle;
import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FooClass {

    private static final Logger logger = LoggerFactory.getLogger(FooClass.class);

    private static final ToStringStyle SHORT_RECURSIVE_PREFIX_STYLE = new FooClass.ShortRecursiveToStringStyle();

    public Object doSomeMojo(final Object request) {
        if (logger.isTraceEnabled()) {
            logger.trace("Calling {}", ReflectionToStringBuilder.toString(request, SHORT_RECURSIVE_PREFIX_STYLE));
        }
        final Object response = null; // call the 3rd party
        if (logger.isTraceEnabled()) {
            logger.trace("Got {}", ReflectionToStringBuilder.toString(response, SHORT_RECURSIVE_PREFIX_STYLE));
        }
        return response;
    }

    /**
     * The 3rd party library does not provide toString methods. Be careful not to log sensitive information!
     */
    private static final class ShortRecursiveToStringStyle extends RecursiveToStringStyle {

        private static final long serialVersionUID = 1L;

        ShortRecursiveToStringStyle() {
            this.setUseShortClassName(true);
            this.setUseIdentityHashCode(false);
        }
    }
}

Výstup v logu pak může vypadat takto: INFO FooClass - Calling User[email=chuck.norris@example.com,name=Chuck Norris]

Závěr

S malou dávkou úsilí lze dynamicky logovat i třídy knihoven třetích stran pomocí toString builderu využívajícího reflexi. Kvůli výkonu můžete v SLF4J zvážit testování úrovně logování if (logger.isTraceEnabled()) nebo využít Log4j podpory pro lambdu logger.trace("{}", () -> ...).

Související