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