001package jmri.util; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import java.util.*; 006 007import javax.annotation.Nonnull; 008 009import org.slf4j.Logger; 010 011/** 012 * Basic utilities for logging special messages. 013 * 014 * @author Randall Wood Copyright 2020 015 */ 016public class LoggingUtil { 017 018 protected static Map<Logger, Set<String>> warnedOnce = new HashMap<>(); 019 protected static Map<Logger, Set<String>> infodOnce = new HashMap<>(); 020 protected static boolean logDeprecations = true; 021 022 /** 023 * Emit a particular WARNING-level message just once. 024 * <p> 025 * Goal is to be lightweight and fast; this will only be used in a few 026 * places, and only those should appear in data structure. 027 * 028 * @param logger the source of the warning 029 * @param msg warning message 030 * @param args message arguments 031 * @return true if the log was emitted this time 032 */ 033 @SuppressFBWarnings( value = {"SLF4J_UNKNOWN_ARRAY", "SLF4J_FORMAT_SHOULD_BE_CONST"}, 034 justification = "Passing arguments through") 035 public static boolean warnOnce(@Nonnull Logger logger, @Nonnull String msg, Object... args) { 036 Set<String> loggerSet = warnedOnce.computeIfAbsent(logger, l -> new HashSet<>()); 037 // if it exists, there was a prior warning given 038 if (loggerSet.contains(msg)) { 039 return false; 040 } 041 loggerSet.add(msg); 042 logger.warn(msg, args); 043 return true; 044 } 045 046 /** 047 * Emit a particular INFO-level message just once. 048 * <p> 049 * Goal is to be lightweight and fast; this will only be used in a few 050 * places, and only those should appear in data structure. 051 * 052 * @param logger the source of the warning 053 * @param msg info message 054 * @param args message arguments 055 * @return true if the log was emitted this time 056 */ 057 @SuppressFBWarnings( value = {"SLF4J_UNKNOWN_ARRAY","SLF4J_FORMAT_SHOULD_BE_CONST"}, 058 justification = "Passing arguments through") 059 public static boolean infoOnce(@Nonnull Logger logger, @Nonnull String msg, Object... args) { 060 Set<String> loggerSet = infodOnce.computeIfAbsent(logger, l -> new HashSet<>()); 061 // if it exists, there was a prior info given 062 if (loggerSet.contains(msg)) { 063 return false; 064 } 065 loggerSet.add(msg); 066 logger.info(msg, args); 067 return true; 068 } 069 070 /** 071 * Warn that a deprecated method has been invoked. 072 * <p> 073 * Can also be used to warn of some deprecated condition, i.e. 074 * obsolete-format input data. 075 * <p> 076 * The logging is turned off by default during testing to simplify updating 077 * tests when warnings are added. 078 * 079 * @param logger The Logger to warn. 080 * @param methodName method name. 081 */ 082 public static void deprecationWarning(@Nonnull Logger logger, @Nonnull String methodName) { 083 if (logDeprecations) { 084 warnOnce(logger, "{} is deprecated, please remove references to it", methodName, shortenStacktrace(new Exception("traceback"))); 085 } 086 } 087 088 /** 089 * Shorten a stack trace to start with the first JMRI method. 090 * <p> 091 * When logged, the stack trace will be more focused. 092 * 093 * @param <T> the type of Throwable 094 * @param t the Throwable containing the stack trace to truncate 095 * @return t with truncated stack trace 096 */ 097 @Nonnull 098 public static <T extends Throwable> T shortenStacktrace(@Nonnull T t) { 099 StackTraceElement[] originalTrace = t.getStackTrace(); 100 int i; 101 for (i = originalTrace.length - 1; i > 0; i--) { 102 // search from deepest 103 String name = originalTrace[i].getClassName(); 104 if (name.equals("jmri.util.junit.TestClassMainMethod")) { 105 continue; // special case to ignore high up in stack 106 } 107 if (name.equals("apps.tests.AllTest")) { 108 continue; // special case to ignore high up in stack 109 } 110 if (name.equals("jmri.HeadLessTest")) { 111 continue; // special case to ignore high up in stack 112 } 113 if (name.startsWith("jmri") || name.startsWith("apps")) { 114 break; // keep those 115 } 116 } 117 return shortenStacktrace(t, i + 1); 118 } 119 120 /** 121 * Shorten a stack trace to a fixed length. 122 * <p> 123 * When logged, the stack trace will be more focused. 124 * 125 * @param <T> the type of Throwable 126 * @param t the Throwable containing the stack trace to truncate 127 * @param len length of stack trace to retain 128 * @return t with truncated stack trace 129 */ 130 @Nonnull 131 public static <T extends Throwable> T shortenStacktrace(@Nonnull T t, int len) { 132 StackTraceElement[] originalTrace = t.getStackTrace(); 133 int newLen = Math.min(len, originalTrace.length); 134 StackTraceElement[] newTrace = new StackTraceElement[newLen]; 135 System.arraycopy(originalTrace, 0, newTrace, 0, newLen); 136 t.setStackTrace(newTrace); 137 return t; 138 } 139 140}