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.logger; 014 015import static java.util.logging.Level.*; 016import static org.apache.juneau.rest.logger.CallLoggingDetail.*; 017 018import java.util.concurrent.atomic.*; 019import java.util.logging.*; 020 021import org.apache.juneau.assertions.*; 022import org.apache.juneau.cp.*; 023 024/** 025 * 026 * Implementation of a {@link CallLogger} that captures log entries for testing logging itself. 027 * 028 * <p> 029 * Instead of logging messages to a log file, messages are simply kept in an internal atomic string reference. 030 * Once a message has been logged, you can use the {@link #getMessage()} or {@link #assertMessage()} methods 031 * to access and evaluate it. 032 * 033 * <p> 034 * The following example shows how the capture logger can be associated with a REST class so that logged messages can 035 * be examined. 036 * 037 * <p class='bjava'> 038 * <jk>public class</jk> MyTests { 039 * 040 * <jk>private static final</jk> CaptureLogger <jsf>LOGGER</jsf> = <jk>new</jk> CaptureLogger(); 041 * 042 * <jk>public static class</jk> CaptureLogger <jk>extends</jk> BasicTestCaptureCallLogger { 043 * <jc>// How our REST class will get the logger.</jc> 044 * <jk>public static</jk> CaptureLogger <jsm>getInstance</jsm>() { 045 * <jk>return</jk> <jsf>LOGGER</jsf>; 046 * } 047 * } 048 * 049 * <ja>@Rest</ja>(callLogger=CaptureLogger.<jk>class</jk>) 050 * <jk>public static class</jk> TestRest <jk>implements</jk> BasicRestServlet { 051 * <ja>@RestGet</ja> 052 * <jk>public boolean</jk> bad() <jk>throws</jk> InternalServerError { 053 * <jk>throw new</jk> InternalServerError(<js>"foo"</js>); 054 * } 055 * } 056 * 057 * <ja>@Test</ja> 058 * <jk>public void</jk> testBadRequestLogging() <jk>throws</jk> Exception { 059 * <jc>// Create client that won't throw exceptions.</jc> 060 * RestClient <jv>client</jv> = MockRestClient.<jsm>create</jsm>(TestRest.<jk>class</jk>).ignoreErrors().build(); 061 * <jc>// Make the REST call.</jc> 062 * <jv>client</jv>.get(<js>"/bad"</js>).run().assertStatusCode(500).assertContent().contains(<js>"foo"</js>); 063 * <jc>// Make sure the message was logged in our expected format.</jc> 064 * <jsf>LOGGER</jsf>.assertMessageAndReset().contains(<js>"[500] HTTP GET /bad"</js>); 065 * } 066 * } 067 * </p> 068 * 069 * <h5 class='section'>See Also:</h5><ul> 070 * <li class='link'><a class="doclink" href="../../../../../index.html#jrs.LoggingAndDebugging">Logging / Debugging</a> 071 * </ul> 072 */ 073public class BasicTestCaptureCallLogger extends CallLogger { 074 075 private AtomicReference<LogRecord> lastRecord = new AtomicReference<>(); 076 077 /** 078 * Constructor using specific settings. 079 * 080 * @param beanStore The bean store containing injectable beans for this logger. 081 */ 082 public BasicTestCaptureCallLogger(BeanStore beanStore) { 083 super(beanStore); 084 } 085 086 /** 087 * Constructor using default settings. 088 * <p> 089 * Uses the same settings as {@link CallLogger}. 090 */ 091 public BasicTestCaptureCallLogger() { 092 super(BeanStore.INSTANCE); 093 } 094 095 @Override 096 protected Builder init(BeanStore beanStore) { 097 return super.init(beanStore) 098 .normalRules( // Rules when debugging is not enabled. 099 CallLoggerRule.create(beanStore) // Log 500+ errors with status-line and header information. 100 .statusFilter(x -> x >= 500) 101 .level(SEVERE) 102 .requestDetail(HEADER) 103 .responseDetail(HEADER) 104 .build(), 105 CallLoggerRule.create(beanStore) // Log 400-500 errors with just status-line information. 106 .statusFilter(x -> x >= 400) 107 .level(WARNING) 108 .requestDetail(STATUS_LINE) 109 .responseDetail(STATUS_LINE) 110 .build() 111 ) 112 .debugRules( // Rules when debugging is enabled. 113 CallLoggerRule.create(beanStore) // Log everything with full details. 114 .level(SEVERE) 115 .requestDetail(ENTITY) 116 .responseDetail(ENTITY) 117 .build() 118 ) 119 ; 120 } 121 122 @Override 123 protected void log(Level level, String msg, Throwable e) { 124 LogRecord r = new LogRecord(level, msg); 125 r.setThrown(e); 126 this.lastRecord.set(r); 127 } 128 129 /** 130 * Returns the last logged message. 131 * 132 * @return The last logged message, or <jk>null</jk> if nothing was logged. 133 */ 134 public String getMessage() { 135 LogRecord r = lastRecord.get(); 136 return r == null ? null : r.getMessage(); 137 } 138 139 /** 140 * Returns the last logged message and then deletes it internally. 141 * 142 * @return The last logged message. 143 */ 144 public String getMessageAndReset() { 145 String msg = getMessage(); 146 reset(); 147 return msg; 148 } 149 150 /** 151 * Returns an assertion of the last logged message. 152 * 153 * @return The last logged message as an assertion object. Never <jk>null</jk>. 154 */ 155 public StringAssertion assertMessage() { 156 return new StringAssertion(getMessage()); 157 } 158 159 /** 160 * Returns an assertion of the last logged message and then deletes it internally. 161 * 162 * @return The last logged message as an assertion object. Never <jk>null</jk>. 163 */ 164 public StringAssertion assertMessageAndReset() { 165 return new StringAssertion(getMessageAndReset()); 166 } 167 168 /** 169 * Returns the last logged message level. 170 * 171 * @return The last logged message level, or <jk>null</jk> if nothing was logged. 172 */ 173 public Level getLevel() { 174 LogRecord r = lastRecord.get(); 175 return r == null ? null : r.getLevel(); 176 } 177 178 /** 179 * Returns the last logged message level. 180 * 181 * @return The last logged message level, or <jk>null</jk> if nothing was logged. 182 */ 183 public Throwable getThrown() { 184 LogRecord r = lastRecord.get(); 185 return r == null ? null : r.getThrown(); 186 } 187 188 /** 189 * Returns the last logged message level. 190 * 191 * @return The last logged message level, or <jk>null</jk> if nothing was logged. 192 */ 193 public ThrowableAssertion<Throwable> assertThrown() { 194 return new ThrowableAssertion<>(getThrown()); 195 } 196 197 /** 198 * Resets the internal message buffer. 199 * 200 * @return This object. 201 */ 202 public BasicTestCaptureCallLogger reset() { 203 this.lastRecord.set(null); 204 return this; 205 } 206}