001/* 002 * Copyright 2023 the original author or authors. 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package de.cuioss.test.juli; 017 018import static de.cuioss.tools.string.MoreStrings.isEmpty; 019import static org.junit.jupiter.api.Assertions.assertFalse; 020import static org.junit.jupiter.api.Assertions.assertNotNull; 021 022import java.util.ArrayList; 023import java.util.Collections; 024import java.util.Comparator; 025import java.util.List; 026import java.util.logging.Handler; 027import java.util.logging.LogRecord; 028 029import lombok.Getter; 030 031/** 032 * Handler for storing and querying {@link LogRecord}s 033 * 034 * @author Oliver Wolff 035 * 036 */ 037public class TestLogHandler extends Handler { 038 039 private static final String TEST_LOG_LEVEL_MUST_NOT_BE_NULL = "TestLogLevel must not be null"; 040 041 private static final String LOGGER_MUST_NOT_BE_NULL = "Logger must not be null"; 042 043 private static final String MESSAGE_MUST_NOT_BE_NULL = "Message must not be null"; 044 045 private static final String THROWABLE_CLASS_MUST_NOT_BE_NULL = "ThrowableClass must not be null"; 046 047 private static final String THROWABLE_MUST_NOT_BE_NULL = "Throwable must not be null"; 048 049 @Getter 050 private final List<LogRecord> records = Collections.synchronizedList(new ArrayList<>()); 051 052 @Override 053 public void publish(LogRecord logRecord) { 054 // Silently ignore null records. 055 if (logRecord == null) { 056 return; 057 } 058 records.add(logRecord); 059 060 } 061 062 @Override 063 public void close() { 064 // There is no need to close 065 } 066 067 @Override 068 public void flush() { 069 // There is no need to flush 070 } 071 072 /** 073 * @param level to be checked for message, must not be null 074 * @param message to be checked, must not be null 075 * @param throwableClass to be checked, must not be null 076 * @return a {@link List} of found {@link LogRecord}s 077 */ 078 public List<LogRecord> resolveLogMessages(TestLogLevel level, String message, 079 Class<? extends Throwable> throwableClass) { 080 assertNotNull(throwableClass, THROWABLE_CLASS_MUST_NOT_BE_NULL); 081 return resolveLogMessages(level, message).stream().filter(r -> logRecordContains(r, throwableClass)).toList(); 082 } 083 084 /** 085 * @param level to be checked for message, must not be null 086 * @param message to be checked, must not be null 087 * @param throwable to be checked, must not be null 088 * @return a {@link List} of found {@link LogRecord}s 089 */ 090 public List<LogRecord> resolveLogMessages(TestLogLevel level, String message, Throwable throwable) { 091 assertNotNull(throwable, THROWABLE_MUST_NOT_BE_NULL); 092 return resolveLogMessages(level, message).stream().filter(r -> logRecordContains(r, throwable)).toList(); 093 } 094 095 /** 096 * @param level to be checked for message, must not be null 097 * @param message to be checked, must not be null 098 * @return a {@link List} of found {@link LogRecord}s 099 */ 100 public List<LogRecord> resolveLogMessages(TestLogLevel level, String message) { 101 assertNotNull(message, MESSAGE_MUST_NOT_BE_NULL); 102 return resolveLogMessages(level).stream().filter(r -> message.equals(r.getMessage())).toList(); 103 } 104 105 /** 106 * @param logger to be checked, must not be null 107 * @return a {@link List} of found {@link LogRecord}s 108 */ 109 public List<LogRecord> resolveLogMessagesForLogger(String logger) { 110 assertFalse(isEmpty(logger), LOGGER_MUST_NOT_BE_NULL); 111 return records.stream().filter(r -> logger.equalsIgnoreCase(r.getLoggerName())).toList(); 112 } 113 114 /** 115 * @param level to be checked for message, must not be null 116 * @param logger to be checked, must not be null 117 * @return a {@link List} of found {@link LogRecord}s 118 */ 119 public List<LogRecord> resolveLogMessagesForLogger(TestLogLevel level, String logger) { 120 assertNotNull(level, TEST_LOG_LEVEL_MUST_NOT_BE_NULL); 121 return resolveLogMessagesForLogger(logger).stream().filter(r -> level.getJuliLevel().equals(r.getLevel())) 122 .toList(); 123 } 124 125 /** 126 * @param level to be checked for message, must not be null 127 * @param logger to be checked, must not be null 128 * @return a {@link List} of found {@link LogRecord}s 129 */ 130 public List<LogRecord> resolveLogMessagesForLogger(TestLogLevel level, Class<?> logger) { 131 assertNotNull(level, TEST_LOG_LEVEL_MUST_NOT_BE_NULL); 132 assertNotNull(logger, LOGGER_MUST_NOT_BE_NULL); 133 return resolveLogMessagesForLogger(level, logger.getName()); 134 } 135 136 /** 137 * @param logger to be checked, must not be null 138 * @return a {@link List} of found {@link LogRecord}s 139 */ 140 public List<LogRecord> resolveLogMessagesForLogger(Class<?> logger) { 141 assertNotNull(logger, LOGGER_MUST_NOT_BE_NULL); 142 return resolveLogMessagesForLogger(logger.getName()); 143 } 144 145 /** 146 * @param level to be checked for message, must not be null 147 * @param messagePart to be checked, must not be null. Compared to 148 * {@link TestLogHandler#resolveLogMessages(TestLogLevel, String)} 149 * this method check whether the given text is contained 150 * within a {@link LogRecord} 151 * @return a {@link List} of found {@link LogRecord}s 152 */ 153 public List<LogRecord> resolveLogMessagesContaining(TestLogLevel level, String messagePart) { 154 assertNotNull(messagePart, MESSAGE_MUST_NOT_BE_NULL); 155 return resolveLogMessages(level).stream().filter(r -> logRecordContains(r, messagePart)).toList(); 156 } 157 158 /** 159 * @param level to be checked for message, must not be null 160 * @param messagePart to be checked, must not be null. Compared to 161 * {@link TestLogHandler#resolveLogMessages(TestLogLevel, String)} 162 * this method check whether the given text is contained 163 * within a {@link LogRecord} 164 * @param throwable to be looked for 165 * @return a {@link List} of found {@link LogRecord}s 166 */ 167 public List<LogRecord> resolveLogMessagesContaining(TestLogLevel level, String messagePart, Throwable throwable) { 168 assertNotNull(throwable, THROWABLE_MUST_NOT_BE_NULL); 169 return resolveLogMessagesContaining(level, messagePart).stream().filter(r -> logRecordContains(r, throwable)) 170 .toList(); 171 } 172 173 /** 174 * @param level to be checked for message, must not be null 175 * @param messagePart to be checked, must not be null. Compared to 176 * {@link TestLogHandler#resolveLogMessages(TestLogLevel, String)} 177 * this method check whether the given text is contained 178 * within a {@link LogRecord} 179 * @param throwableClass to be looked for 180 * @return a {@link List} of found {@link LogRecord}s 181 */ 182 public List<LogRecord> resolveLogMessagesContaining(TestLogLevel level, String messagePart, 183 Class<? extends Throwable> throwableClass) { 184 assertNotNull(throwableClass, THROWABLE_CLASS_MUST_NOT_BE_NULL); 185 return resolveLogMessagesContaining(level, messagePart).stream() 186 .filter(r -> logRecordContains(r, throwableClass)).toList(); 187 } 188 189 /** 190 * @param level to be checked for message, must not be null 191 * @return a {@link List} of found {@link LogRecord}s 192 */ 193 public List<LogRecord> resolveLogMessages(TestLogLevel level) { 194 assertNotNull(level, TEST_LOG_LEVEL_MUST_NOT_BE_NULL); 195 synchronized (records) { 196 return records.stream().filter(r -> logRecordContains(r, level)).toList(); 197 } 198 } 199 200 /** 201 * Clears the contained records 202 */ 203 public void clearRecords() { 204 records.clear(); 205 } 206 207 private static boolean logRecordContains(LogRecord logRecord, String messagePart) { 208 final var msg = logRecord.getMessage(); 209 return null != msg && msg.contains(messagePart); 210 } 211 212 private static boolean logRecordContains(LogRecord logRecord, Class<? extends Throwable> throwableClass) { 213 var thrown = logRecord.getThrown(); 214 return null != thrown && thrown.getClass().equals(throwableClass); 215 } 216 217 private static boolean logRecordContains(LogRecord logRecord, Throwable throwable) { 218 var thrown = logRecord.getThrown(); 219 return null != thrown && thrown.equals(throwable); 220 } 221 222 private static boolean logRecordContains(LogRecord logRecord, TestLogLevel level) { 223 var loggedLevel = logRecord.getLevel(); 224 return null != loggedLevel && loggedLevel.equals(level.getJuliLevel()); 225 } 226 227 @Override 228 public String toString() { 229 return getClass().getName() + " with " + getRecords().size() + " entries"; 230 } 231 232 /** 233 * @return String representation of the records within this handler. 234 */ 235 public String getRecordsAsString() { 236 if (records.isEmpty()) { 237 return "No log messages available"; 238 } 239 List<LogRecord> all = new ArrayList<>(records); 240 241 all.sort(Comparator.comparing(l -> l.getLevel().intValue())); 242 243 List<String> elements = new ArrayList<>(); 244 245 all.forEach( 246 l -> elements.add(TestLogLevel.parse(l.getLevel()) + ": " + l.getLoggerName() + "-" + l.getMessage())); 247 var builder = new StringBuilder(); 248 builder.append("Available Messages:"); 249 for (String element : elements) { 250 builder.append("\n").append(element); 251 } 252 return builder.toString(); 253 } 254}