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}