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}