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 default settings. 083 * <p> 084 * Uses the same settings as {@link CallLogger}. 085 */ 086 public BasicTestCaptureCallLogger() { 087 super(BeanStore.INSTANCE); 088 } 089 090 /** 091 * Constructor using specific settings. 092 * 093 * @param beanStore The bean store containing injectable beans for this logger. 094 */ 095 public BasicTestCaptureCallLogger(BeanStore beanStore) { 096 super(beanStore); 097 } 098 099 /** 100 * Returns an assertion of the last logged message. 101 * 102 * @return The last logged message as an assertion object. Never <jk>null</jk>. 103 */ 104 public StringAssertion assertMessage() { 105 return new StringAssertion(getMessage()); 106 } 107 108 /** 109 * Returns an assertion of the last logged message and then deletes it internally. 110 * 111 * @return The last logged message as an assertion object. Never <jk>null</jk>. 112 */ 113 public StringAssertion assertMessageAndReset() { 114 return new StringAssertion(getMessageAndReset()); 115 } 116 117 /** 118 * Returns the last logged message level. 119 * 120 * @return The last logged message level, or <jk>null</jk> if nothing was logged. 121 */ 122 public ThrowableAssertion<Throwable> assertThrown() { 123 return new ThrowableAssertion<>(getThrown()); 124 } 125 126 /** 127 * Returns the last logged message level. 128 * 129 * @return The last logged message level, or <jk>null</jk> if nothing was logged. 130 */ 131 public Level getLevel() { 132 LogRecord r = lastRecord.get(); 133 return r == null ? null : r.getLevel(); 134 } 135 136 /** 137 * Returns the last logged message. 138 * 139 * @return The last logged message, or <jk>null</jk> if nothing was logged. 140 */ 141 public String getMessage() { 142 LogRecord r = lastRecord.get(); 143 return r == null ? null : r.getMessage(); 144 } 145 146 /** 147 * Returns the last logged message and then deletes it internally. 148 * 149 * @return The last logged message. 150 */ 151 public String getMessageAndReset() { 152 var msg = getMessage(); 153 reset(); 154 return msg; 155 } 156 157 /** 158 * Returns the last logged message level. 159 * 160 * @return The last logged message level, or <jk>null</jk> if nothing was logged. 161 */ 162 public Throwable getThrown() { 163 LogRecord r = lastRecord.get(); 164 return r == null ? null : r.getThrown(); 165 } 166 167 /** 168 * Resets the internal message buffer. 169 * 170 * @return This object. 171 */ 172 public BasicTestCaptureCallLogger reset() { 173 this.lastRecord.set(null); 174 return this; 175 } 176 177 @Override 178 protected Builder init(BeanStore beanStore) { 179 // @formatter:off 180 return super.init(beanStore) 181 .normalRules( // Rules when debugging is not enabled. 182 CallLoggerRule.create(beanStore) // Log 500+ errors with status-line and header information. 183 .statusFilter(x -> x >= 500) 184 .level(SEVERE) 185 .requestDetail(HEADER) 186 .responseDetail(HEADER) 187 .build(), 188 CallLoggerRule.create(beanStore) // Log 400-500 errors with just status-line information. 189 .statusFilter(x -> x >= 400) 190 .level(WARNING) 191 .requestDetail(STATUS_LINE) 192 .responseDetail(STATUS_LINE) 193 .build() 194 ) 195 .debugRules( // Rules when debugging is enabled. 196 CallLoggerRule.create(beanStore) // Log everything with full details. 197 .level(SEVERE) 198 .requestDetail(ENTITY) 199 .responseDetail(ENTITY) 200 .build() 201 ) 202 ; 203 // @formatter:on 204 } 205 206 @Override 207 protected void log(Level level, String msg, Throwable e) { 208 var r = new LogRecord(level, msg); 209 r.setThrown(e); 210 this.lastRecord.set(r); 211 } 212}