001/* 002 * This is free and unencumbered software released into the public domain. 003 * 004 * Please see https://github.com/binkley/binkley/blob/master/LICENSE.md. 005 */ 006 007package hm.binkley.util.logging; 008 009import ch.qos.logback.classic.PatternLayout; 010import ch.qos.logback.classic.pattern.ClassicConverter; 011import ch.qos.logback.classic.spi.ILoggingEvent; 012import ch.qos.logback.core.boolex.EvaluationException; 013import ch.qos.logback.core.boolex.EventEvaluator; 014import ch.qos.logback.core.status.ErrorStatus; 015 016import javax.annotation.Nonnull; 017import java.util.LinkedHashMap; 018import java.util.List; 019import java.util.Map; 020 021import static ch.qos.logback.core.CoreConstants.EVALUATOR_MAP; 022import static java.util.Collections.emptyMap; 023 024/** 025 * {@code MarkedConverter} provides alternate conversions based on conditions. Enable with: 026 * <pre> 027 * <conversionRule 028 * conversionWord="match" 029 * converterClass="hm.binkley.util.logging.MatchConverter"/></pre> Use as: 030 * <pre> 031 * <pattern>%match{cond1,patt1,...,fallback}</pattern></pre> Example: 032 * <pre> 033 * <evaluator name="WITH_MARKER"> 034 * <expression>null != marker &mp;&mp; "ALERT".equals(marker.getName())</expression> 035 * </evaluator> 036 * <pattern>%match(WITH_MARKER,%marker/%level,%level)</pattern></pre> will log 037 * "ALERT/ERROR" when marker is "ALERT" and level is "ERROR", otherwise just "ERROR". 038 * 039 * @author <a href="mailto:binkley@alumni.rice.edu">B. K. Oxley (binkley)</a> 040 * @todo Fix error reporting - logback swallows 041 */ 042public final class MatchConverter 043 extends ClassicConverter { 044 private static final int MAX_ERROR_COUNT = 4; 045 private Map<String, String> conditions; 046 private String unmatched; 047 private Map<String, EventEvaluator<ILoggingEvent>> evaluators; 048 private int errors; 049 050 @SuppressWarnings("unchecked") 051 @Override 052 public void start() { 053 final List<String> options = getOptionList(); 054 if (null == options || 2 > options.size()) { 055 addError("Missing options for %match - " + (null == options ? "missing options" 056 : options)); 057 conditions = emptyMap(); 058 unmatched = ""; 059 return; 060 } 061 062 conditions = new LinkedHashMap<>(); 063 for (int i = 0; i < options.size() - 1; i += 2) 064 conditions.put(options.get(i), options.get(i + 1)); 065 unmatched = 0 == options.size() % 2 ? "" : options.get(options.size() - 1); 066 067 evaluators = (Map<String, EventEvaluator<ILoggingEvent>>) getContext() 068 .getObject(EVALUATOR_MAP); 069 070 super.start(); 071 } 072 073 @Nonnull 074 @Override 075 public String convert(@Nonnull final ILoggingEvent event) { 076 for (final Map.Entry<String, String> entry : conditions.entrySet()) 077 if (evaluate(entry.getKey(), event)) 078 return relayout(entry.getValue(), event); 079 return relayout(unmatched, event); 080 } 081 082 private boolean evaluate(final String name, final ILoggingEvent event) { 083 final EventEvaluator<ILoggingEvent> evaluator = evaluators.get(name); 084 try { 085 return null != evaluator && evaluator.evaluate(event); 086 } catch (final EvaluationException e) { 087 errors++; 088 if (MAX_ERROR_COUNT > errors) { 089 addError("Exception thrown for evaluator named [" + evaluator.getName() + "]", e); 090 } else if (MAX_ERROR_COUNT == errors) { 091 final ErrorStatus errorStatus = new ErrorStatus( 092 "Exception thrown for evaluator named [" + evaluator.getName() + "].", this, 093 e); 094 errorStatus.add(new ErrorStatus( 095 "This was the last warning about this evaluator's errors." 096 + "We don't want the StatusManager to get flooded.", this)); 097 addStatus(errorStatus); 098 } 099 return false; 100 } 101 } 102 103 private String relayout(final String pattern, final ILoggingEvent event) { 104 final PatternLayout layout = new PatternLayout(); 105 layout.setContext(getContext()); 106 layout.setPattern(pattern); 107 layout.start(); 108 return layout.doLayout(event); 109 } 110}