001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.rest.mock; 014 015import static org.apache.juneau.common.internal.ThrowableUtils.*; 016import static org.apache.juneau.internal.CollectionUtils.*; 017 018import java.io.*; 019import java.util.*; 020import java.util.logging.*; 021import java.util.logging.Formatter; 022 023import org.apache.juneau.assertions.*; 024 025/** 026 * Simplified logger for intercepting and asserting logging messages. 027 * 028 * <h5 class='figure'>Example:</h5> 029 * <p class='bjava'> 030 * <jc>// Instantiate a mock logger.</jc> 031 * MockLogger <jv>logger</jv> = <jk>new</jk> MockLogger(); 032 * 033 * <jc>// Associate it with a MockRestClient.</jc> 034 * MockRestClient 035 * .<jsm>create</jsm>(MyRestResource.<jk>class</jk>) 036 * .json5() 037 * .logger(<jv>logger</jv>) 038 * .logRequests(DetailLevel.<jsf>FULL</jsf>, Level.<jsf>SEVERE</jsf>) 039 * .build() 040 * .post(<js>"/bean"</js>, <jv>bean</jv>) 041 * .complete(); 042 * 043 * <jc>// Assert that logging occurred.</jc> 044 * <jv>logger</jv>.assertLastLevel(Level.<jsf>SEVERE</jsf>); 045 * <jv>logger</jv>.assertLastMessage().is( 046 * <js>"=== HTTP Call (outgoing) ======================================================"</js>, 047 * <js>"=== REQUEST ==="</js>, 048 * <js>"POST http://localhost/bean"</js>, 049 * <js>"---request headers---"</js>, 050 * <js>" Accept: application/json5"</js>, 051 * <js>"---request entity---"</js>, 052 * <js>" Content-Type: application/json5"</js>, 053 * <js>"---request content---"</js>, 054 * <js>"{f:1}"</js>, 055 * <js>"=== RESPONSE ==="</js>, 056 * <js>"HTTP/1.1 200 "</js>, 057 * <js>"---response headers---"</js>, 058 * <js>" Content-Type: application/json"</js>, 059 * <js>"---response content---"</js>, 060 * <js>"{f:1}"</js>, 061 * <js>"=== END ======================================================================="</js>, 062 * <js>""</js> 063 * ); 064 * </p> 065 * 066 * <h5 class='section'>See Also:</h5><ul> 067 * <li class='link'><a class="doclink" href="../../../../../index.html#juneau-rest-mock">juneau-rest-mock</a> 068 * </ul> 069 */ 070public class MockLogger extends Logger { 071 072 private static final String FORMAT_PROPERTY = "java.util.logging.SimpleFormatter.format"; 073 074 private final List<LogRecord> logRecords = list(); 075 private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 076 private volatile Formatter formatter; 077 private volatile String format = "%4$s: %5$s%6$s%n"; 078 079 /** 080 * Constructor. 081 */ 082 public MockLogger() { 083 super("Mock", null); 084 } 085 086 /** 087 * Creator. 088 * 089 * @return A new {@link MockLogger} object. 090 */ 091 public static MockLogger create() { 092 return new MockLogger(); 093 } 094 095 @Override /* Logger */ 096 public synchronized void log(LogRecord record) { 097 logRecords.add(record); 098 try { 099 baos.write(getFormatter().format(record).getBytes("UTF-8")); 100 } catch (Exception e) { 101 throw asRuntimeException(e); 102 } 103 } 104 105 private Formatter getFormatter() { 106 if (formatter == null) { 107 synchronized(this) { 108 String oldFormat = System.getProperty(FORMAT_PROPERTY); 109 System.setProperty(FORMAT_PROPERTY, format); 110 formatter = new SimpleFormatter(); 111 if (oldFormat == null) 112 System.clearProperty(FORMAT_PROPERTY); 113 else 114 System.setProperty(FORMAT_PROPERTY, oldFormat); 115 } 116 } 117 return formatter; 118 } 119 120 /** 121 * Sets the level for this logger. 122 * 123 * @param level The new level for this logger. 124 * @return This object. 125 */ 126 public synchronized MockLogger level(Level level) { 127 super.setLevel(level); 128 return this; 129 } 130 131 /** 132 * Specifies the format for messages sent to the log file. 133 * 134 * <p> 135 * See {@link SimpleFormatter#format(LogRecord)} for the syntax of this string. 136 * 137 * @param format The format string. 138 * @return This object. 139 */ 140 public synchronized MockLogger format(String format) { 141 this.format = format; 142 return this; 143 } 144 145 /** 146 * Overrides the formatter to use for formatting messages. 147 * 148 * <p> 149 * The default uses {@link SimpleFormatter}. 150 * 151 * @param formatter The log record formatter. 152 * @return This object. 153 */ 154 public synchronized MockLogger formatter(Formatter formatter) { 155 this.formatter = formatter; 156 return this; 157 } 158 159 /** 160 * Resets this logger. 161 * 162 * @return This object. 163 */ 164 public synchronized MockLogger reset() { 165 logRecords.clear(); 166 baos.reset(); 167 return this; 168 } 169 170 /** 171 * Asserts that this logger was called. 172 * 173 * @return This object. 174 */ 175 public synchronized MockLogger assertLogged() { 176 if (logRecords.isEmpty()) 177 throw new AssertionError("Message not logged"); 178 return this; 179 } 180 181 /** 182 * Asserts that the last message was logged at the specified level. 183 * 184 * @param level The level to match against. 185 * @return This object. 186 */ 187 public synchronized MockLogger assertLastLevel(Level level) { 188 assertLogged(); 189 if (last().getLevel() != level) 190 throw new AssertionError("Message logged at [" + last().getLevel() + "] instead of [" + level + "]"); 191 return this; 192 } 193 194 /** 195 * Asserts that the last message matched the specified message. 196 * 197 * @return This object. 198 */ 199 public synchronized FluentStringAssertion<MockLogger> assertLastMessage() { 200 assertLogged(); 201 return new FluentStringAssertion<>(last().getMessage(), this); 202 } 203 204 /** 205 * Asserts that the specified number of messages have been logged. 206 * 207 * @return This object. 208 */ 209 public synchronized FluentIntegerAssertion<MockLogger> assertRecordCount() { 210 return new FluentIntegerAssertion<>(logRecords.size(), this); 211 } 212 213 /** 214 * Allows you to perform fluent-style assertions on the contents of the log file. 215 * 216 * @return A new fluent-style assertion object. 217 */ 218 public synchronized FluentStringAssertion<MockLogger> assertContents() { 219 return new FluentStringAssertion<>(baos.toString(), this); 220 } 221 222 private LogRecord last() { 223 if (logRecords.isEmpty()) 224 throw new AssertionError("Message not logged"); 225 return logRecords.get(logRecords.size()-1); 226 } 227 228 /** 229 * Returns the contents of this log file as a string. 230 */ 231 @Override 232 public String toString() { 233 return baos.toString(); 234 } 235}