2009-06-03 12 views
21

Potrzebuję czegoś podobnego do metody String.format(...), ale z leniwy oceny.String.format z leniwe oceny

Ta metoda lazyFormat powinna zwrócić obiekt, którego metoda toString() oszacowałaby wzór formatu.

Podejrzewam, że ktoś już to zrobił. Czy jest to dostępne w dowolnych bibliotekach?

chcę zastąpić ten (Logger jest wystąpienie log4j):

if(logger.isDebugEnabled()) { 
    logger.debug(String.format("some texts %s with patterns %s", object1, object2)); 
} 

z tym:

logger.debug(lazyFormat("some texts %s with patterns %s", object1, object2)); 

muszę lazyFormat sformatować ciąg tylko wtedy, gdy włączone jest rejestrowanie debugowania.

Odpowiedz

22

jeśli szukasz "proste" rozwiązanie:

public class LazyFormat { 

    public static void main(String[] args) { 
     Object o = lazyFormat("some texts %s with patterns %s", "looong string", "another loooong string"); 
     System.out.println(o); 
    } 

    private static Object lazyFormat(final String s, final Object... o) { 
     return new Object() { 
      @Override 
      public String toString() { 
       return String.format(s,o); 
      } 
     }; 
    } 
} 

wyjścia:

Niektóre teksty dłuuugi ciąg z wzorów inną loooong ciąg

można oczywiście dodaj instrukcję isDebugEnabled() do lazyFormat, jeśli chcesz.

+0

Nowsze wersje Log4J pozwalają na zastępowanie parametrów, patrz http://stackoverflow.com/a/14078904/620113 – computermacgyver

+0

Czy to faktycznie działa - to sformatuje ciąg, ale czy robi to "leniwe"? Zrobiłem mały test, w którym argumenty lazyFormat są wywołaniami funkcji, która drukuje do System.err i wydawało się, że argumenty w Object ... o zostaną ocenione, nawet jeśli String.format (s, o) nie jest. – OneSolitaryNoob

+0

OneSolitaryNoob - To nie robi leniwe rzeczy. Dostawca odpowiedzi wskazuje, że można to zrobić za pomocą metody "isDebugEnabled()" w metodzie toString(). W mojej odpowiedzi dostarczyłem bardziej ogólny, leniwy wzorzec logowania: http://stackoverflow.com/a/18317629/501113 – chaotic3quilibrium

13

jeśli szukasz leniwe konkatenacji dla zachowania efektywnego pozyskiwania drewna, przyjrzeć Slf4J Pozwala to napisać:

LOGGER.debug("this is my long string {}", fatObject); 

konkatenacją ciąg będzie mieć miejsce tylko wtedy, gdy poziom debugowania jest ustawiony .

+2

problemem leniwego oszacowania było właśnie to, dlaczego wprowadzono składnię {1} slf4j –

+0

W przeciwnym razie dobrze, ale utknąłem z log4j. Mam rodzaj złożonej konfiguracji logowania, więc nie mogę po prostu upuścić slf4j w. –

+2

SLF4J ma powiązanie dla log4j. Niezależnie od tego, jaka jest "złożona konfiguracja logowania", SLF4J poradzi sobie z tym ładnie. Możesz nadal używać log4j bezpośrednio i SLF4J tylko wtedy, gdy wymagana jest leniwa analiza ciągów. – Ceki

0

Można zdefiniować opakowanie, aby wywołać String.format() tylko w razie potrzeby.

Aby uzyskać szczegółowy przykład kodu, patrz: this question.

To samo pytanie ma również variadic function example, jak zasugerowano w odpowiedzi Andreasa.

3

Opierając Andreas' answer, mogę pomyśleć o kilku podejściach do kwestii jedynie wykonaniem formatowania jeśli Logger.isDebugEnabled powraca true:

Wariant 1: Proszę przejść do „temat formatowania” flaga

Jedną z opcji jest podanie argumentu metody, który mówi, czy faktycznie należy wykonać formatowanie.Przypadek użycia może być:

System.out.println(lazyFormat(true, "Hello, %s.", "Bob")); 
System.out.println(lazyFormat(false, "Hello, %s.", "Dave")); 

gdzie wyjście byłoby:

Hello, Bob. 
null 

Kod dla lazyFormat jest:

private String lazyFormat(boolean format, final String s, final Object... o) { 
    if (format) { 
    return String.format(s, o); 
    } 
    else { 
    return null; 
    } 
} 

W tym przypadku String.format jest wykonywana tylko wtedy, gdy Flaga format jest ustawiona na true, a jeśli jest ustawiona na false, zwróci ona null. Spowoduje to zatrzymanie formatowania komunikatu rejestrowania i wyśle ​​tylko "pozorne" informacje.

Więc sprawa korzystanie z rejestratora może być:

logger.debug(lazyFormat(logger.isDebugEnabled(), "Message: %s", someValue)); 

Ta metoda nie dokładnie dopasować formatowanie że jest hasła w pytaniu.

Opcja 2: Sprawdź Logger

Innym rozwiązaniem jest zwrócenie się do rejestratora bezpośrednio czy to isDebugEnabled:

private static String lazyFormat(final String s, final Object... o) { 
    if (logger.isDebugEnabled()) { 
    return String.format(s, o); 
    } 
    else { 
    return null; 
    } 
} 

W tym podejściu, można się spodziewać, że logger będą widoczne w lazyFormat metoda. A zaletą tego podejścia jest to, że rozmówca nie trzeba będzie sprawdzenie metody isDebugEnabled gdy lazyFormat nazywa, tak typowe użycie może być:

logger.debug(lazyFormat("Debug message is %s", someMessage)); 
+0

Nie powinien być ostatnim przykładem logger.debug (lazyFormat ("Komunikat debugowania to% s", someMessage)); ? –

+0

@Juha S .: Tak, masz rację. Po opublikowaniu odpowiedzi zauważyłem błąd, więc zostało to naprawione. – coobird

3

Można owinąć instancję rejestratora Log4J wewnątrz własnego Java5- kompatybilna/zgodna klasa String.format. Coś jak:

public class Log4jWrapper { 

    private final Logger inner; 

    private Log4jWrapper(Class<?> clazz) { 
     inner = Logger.getLogger(clazz); 
    } 

    public static Log4jWrapper getLogger(Class<?> clazz) { 
     return new Log4jWrapper(clazz); 
    } 

    public void trace(String format, Object... args) { 
     if(inner.isTraceEnabled()) { 
      inner.trace(String.format(format, args));  
     } 
    } 

    public void debug(String format, Object... args) { 
     if(inner.isDebugEnabled()) { 
      inner.debug(String.format(format, args));  
     } 
    } 

    public void warn(String format, Object... args) { 
     inner.warn(String.format(format, args));  
    } 

    public void error(String format, Object... args) { 
     inner.error(String.format(format, args));  
    } 

    public void fatal(String format, Object... args) { 
     inner.fatal(String.format(format, args));  
    }  
} 

Aby użyć opakowania, zmienić deklarację Rejestrator do:

klasy
private final static Log4jWrapper logger = Log4jWrapper.getLogger(ClassUsingLogging.class); 

owijki musiałby kilka dodatkowych metod, na przykład, że obecnie nie obsługuje wyjątków rejestrowania (tj. logger.debug (message, exception)), ale nie powinno to być trudne.

Korzystanie z klasy byłby niemal identyczny log4j, z wyjątkiem struny są sformatowane:

logger.debug("User {0} is not authorized to access function {1}", user, accessFunction) 
1

Albo można napisać go jako

debug(logger, "some texts %s with patterns %s", object1, object2); 

z

public static void debug(Logger logger, String format, Object... args) { 
    if(logger.isDebugEnabled()) 
     logger.debug(String.format("some texts %s with patterns %s", args)); 
} 
16

Może być wykonane przy użyciu podstawiania parametrów w najnowszej wersji log4j 2.X http://logging.apache.org/log4j/2.x/log4j-users-guide.pdf:

4.1.1.2 Zastąpienie parametrów

Często celem logowania jest dostarczenie informacji o tym, co dzieje się w systemie, który wymaga , włączając informacje o manipulowanych obiektach. W Log4j 1.x może to być osiągnięte przez działanie:

if (logger.isDebugEnabled()) {  
    logger.debug("Logging in user " + user.getName() + " with id " + user.getId()); 
} 

Spowoduje to wielokrotnie ma wpływu na udostępnienie kodu poczuć jak to jest więcej o logowaniu niż rzeczywiste zadania. Ponadto powoduje to dwukrotne sprawdzanie poziomu rejestrowania; raz po wywołaniu isDebugEnabled i raz w metodzie debugowania. Lepszym rozwiązaniem byłoby :

logger.debug("Logging in user {} with id {}", user.getName(), user.getId()); 

Z powyższego kodu poziomu rejestrowania będą sprawdzane tylko raz i budowa String tylko gdy występują rejestrowanie debugowania jest włączona.

+1

Twój przykład jest nieco mylący, ponieważ między niesformatowanym i sformatowanym przykładem jest niewielka różnica; tj. w formatyzatorze nie ma żadnej rzeczywistej wartości dla operacji user.getName() lub user.getId(), ponieważ są one wywoływane natychmiast, a ich wartości przekazywane są do metody logger.debug. Oryginalny plakat przekazywał dwie instancje Object, licząc na to, że metoda toString() na tych instancjach nie zostanie wywołana, chyba że będą potrzebne. Opublikowalem odpowiedź, która dokładniej przechwytuje to "tylko wywołanie danych, jeśli będzie używane". – chaotic3quilibrium

+0

Log4j 2.4 [dodaje obsługę lambdas] (http://logging.apache.org/log4j/2.x/manual/api.html#LambdaSupport). –

5

UWAGA: Zaleca wszystko zalogowaniu Kod zostać przeniesione do korzystania SLF4J (zwłaszcza log4j 1.x). Chroni Cię przed utknięciem w jakimkolwiek typie problemów (np. Błędów) w określonych implementacjach rejestrowania. Nie tylko "naprawia" dobrze znane problemy z implementacją backendu, ale także działa z nowszymi, szybszymi implementacjami, które pojawiły się na przestrzeni lat.


W bezpośredniej odpowiedzi na swoje pytanie, o co to będzie wyglądać przy użyciu SLF4J:

LOGGER.debug("some texts {} with patterns {}", object1, object2); 

Najważniejszą nieco od tego, co podałeś jest fakt, trzeba przejechać dwie instancje obiektu. Metody object1.toString() i object2.toString() nie są natychmiast analizowane. Co ważniejsze, metody toString() są oceniane tylko wtedy, gdy dane, które zwrócą, będą faktycznie używane; tj. prawdziwe znaczenie leniwej oceny.

Próbowałem wymyślić bardziej ogólny wzór, który mógłbym użyć, który nie wymagałby mojego przesłonięcia toString() w tonach klas (i są klasy, w których nie mam dostępu do nadpisania). Wymyśliłem proste rozwiązanie typu "drop-in-place". Ponownie, używając SLF4J, komponuję ciąg znaków tylko wtedy, gdy/logowanie dla poziomu jest włączone. Oto mój kod:

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

     ... 

     public void someCodeSomewhereInTheClass() { 
//all the code between here 
     LOGGER.debug(
      "{}" 
      , new Object() { 
       @Override 
       public String toString() { 
       return "someExpensiveInternalState=" + getSomeExpensiveInternalState(); 
       } 
      } 
//and here can be turned into a one liner 
     ); 
     } 

     private String getSomeExpensiveInternalState() { 
     //do expensive string generation/concatenation here 
     } 
    } 

I uprościć do jedno-liner, można skrócić linię Logger w someCodeSomewhereInTheClass(), aby być:

LOGGER.debug("{}", new Object(){@Override public String toString(){return "someExpensiveInternalState=" + getSomeExpensiveInternalState();}}); 

Mam teraz refactored cały mój kod logowania podążać za tym prostym modelem. To znacznie uprościło sprawę. A teraz, gdy widzę jakikolwiek kod logowania, który tego nie używa, zmieniam kod logowania, aby użyć tego nowego wzorca, nawet jeśli jest on jeszcze potrzebny. W ten sposób, jeśli/kiedy wprowadzona zostanie później zmiana, wymagająca dodania jakiejś "kosztownej" operacji, to element infrastruktury jest już w tym miejscu, co upraszcza zadanie polegające na dodaniu operacji.

2

Wprowadzono w Log4j 1.2.16 są dwie klasy, które zrobi to za Ciebie.

org.apache.log4j.LogMF który wykorzystuje java.text.MessageFormat o formacie komunikatów i org.apache.log4j.LogSF który wykorzystuje „składni SLF4J wzór” i mówi się, że szybciej.

Oto przykłady:

LogSF.debug(log, "Processing request {}", req); 

i

LogMF.debug(logger, "The {0} jumped over the moon {1} times", "cow", 5); 
0

Jeśli lubisz string.Format Składnia lepiej niż {0} składni i mogą korzystać Java 8/JDK 8 można użyj lambdas/Dostawcy:

logger.log(Level.FINER,() -> String.format("SomeOperation %s took %04dms to complete", name, duration));

()->... działa tutaj jako dostawca i zostanie oceniony leniwie.