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;
014
015import static org.apache.juneau.internal.StringUtils.*;
016import static org.apache.juneau.internal.ObjectUtils.*;
017import static org.apache.juneau.rest.RestCallLoggingDetail.*;
018
019import java.util.*;
020import java.util.logging.*;
021
022import javax.servlet.http.*;
023
024import org.apache.juneau.internal.*;
025import org.apache.juneau.rest.util.*;
026import org.apache.juneau.utils.*;
027
028/**
029 * Default implementation of the {@link RestCallLogger} interface.
030 *
031 * <p>
032 * Subclasses can override these methods to tailor logging of HTTP requests.
033 * <br>Subclasses MUST implement a no-arg public constructor or constructor that takes in a {@link RestContext} arg.
034 *
035 * <ul class='seealso'>
036 *    <li class='link'>{@doc juneau-rest-server.LoggingAndDebugging}
037 * </ul>
038 */
039public class BasicRestCallLogger implements RestCallLogger {
040
041   private final StackTraceDatabase stackTraceDb = new StackTraceDatabase(RestMethodContext.class);
042
043   private final Logger logger;
044   private final RestContext context;
045
046   /**
047    * Constructor.
048    *
049    * @param context The context of the resource object.
050    */
051   public BasicRestCallLogger(RestContext context) {
052      this.context = context;
053      this.logger = Logger.getLogger(getLoggerName());
054   }
055
056   /**
057    * Constructor.
058    *
059    * @param context The context of the resource object.
060    * @param logger The logger to use for logging.
061    */
062   public BasicRestCallLogger(RestContext context, Logger logger) {
063      this.context = context;
064      this.logger = logger;
065   }
066
067   /**
068    * Returns the logger name.
069    *
070    * <p>
071    * By default returns the class name of the servlet class passed in to the context.
072    *
073    * <p>
074    * Subclasses can override this to provide their own customized logger names.
075    *
076    * @return The logger name.
077    */
078   protected String getLoggerName() {
079      return context == null ? getClass().getName() : context.getResource().getClass().getName();
080   }
081
082   /**
083    * Returns the Java logger used for logging.
084    *
085    * <p>
086    * Subclasses can provide their own logger.
087    * The default implementation returns the logger created using <c>Logger.getLogger(getClass())</c>.
088    *
089    * @return The logger used for logging.
090    */
091   protected Logger getLogger() {
092      return logger;
093   }
094
095   /**
096    * Clears out the stack trace database.
097    *
098    * @return This object (for method chaining).
099    */
100   public BasicRestCallLogger resetStackTraces() {
101      stackTraceDb.reset();
102      return this;
103   }
104
105   @Override /* RestCallLogger */
106   public void log(RestCallLoggerConfig config, HttpServletRequest req, HttpServletResponse res) {
107
108      if (config.isDisabled(req))
109         return;
110
111      RestCallLoggerRule rule = config.getRule(req, res);
112      if (rule == null)
113         return;
114
115      Level level = rule.getLevel();
116      if (level == null)
117         level = config.getLevel();
118
119      if (level == Level.OFF)
120         return;
121
122      Throwable e = castOrNull(req.getAttribute("Exception"), Throwable.class);
123      Long execTime = castOrNull(req.getAttribute("ExecTime"), Long.class);
124
125      RestCallLoggingDetail reqd = rule.getReqDetail(), resd = rule.getResDetail();
126
127      String method = req.getMethod();
128      int status = res.getStatus();
129      String uri = req.getRequestURI();
130      byte[] reqBody = getRequestBody(req);
131      byte[] resBody = getResponseBody(req, res);
132
133      StringBuilder sb = new StringBuilder();
134
135      if (reqd != SHORT || resd != SHORT)
136         sb.append("\n=== HTTP Request (incoming) ===================================================\n");
137
138      StackTraceInfo sti = getStackTraceInfo(config, e);
139
140      sb.append('[').append(status);
141
142      if (sti != null) {
143         int count = sti.getCount();
144         sb.append(',').append(sti.getHash()).append('.').append(count);
145         if (count > 1)
146            e = null;
147      }
148
149      sb.append("] ");
150
151      sb.append("HTTP ").append(method).append(' ').append(uri);
152
153      if (reqd != SHORT || resd != SHORT) {
154
155         if (reqd.isOneOf(MEDIUM, LONG)) {
156            String qs = req.getQueryString();
157            if (qs != null)
158               sb.append('?').append(qs);
159         }
160
161         if (reqBody != null && reqd.isOneOf(MEDIUM ,LONG))
162            sb.append("\n\tRequest length: ").append(reqBody.length).append(" bytes");
163
164         if (resd.isOneOf(MEDIUM, LONG))
165            sb.append("\n\tResponse code: ").append(status);
166
167         if (resBody != null && resd.isOneOf(MEDIUM, LONG))
168            sb.append("\n\tResponse length: ").append(resBody.length).append(" bytes");
169
170         if (execTime != null && resd.isOneOf(MEDIUM, LONG))
171            sb.append("\n\tExec time: ").append(execTime).append("ms");
172
173         if (reqd.isOneOf(MEDIUM, LONG)) {
174            Enumeration<String> hh = req.getHeaderNames();
175            if (hh.hasMoreElements()) {
176               sb.append("\n---Request Headers---");
177               while (hh.hasMoreElements()) {
178                  String h = hh.nextElement();
179                  sb.append("\n\t").append(h).append(": ").append(req.getHeader(h));
180               }
181            }
182         }
183
184         if (context != null && reqd.isOneOf(MEDIUM, LONG)) {
185            Map<String,Object> hh = context.getDefaultRequestHeaders();
186            if (! hh.isEmpty()) {
187               sb.append("\n---Default Servlet Headers---");
188               for (Map.Entry<String,Object> h : hh.entrySet()) {
189                  sb.append("\n\t").append(h.getKey()).append(": ").append(h.getValue());
190               }
191            }
192         }
193
194         if (resd.isOneOf(MEDIUM, LONG)) {
195            Collection<String> hh = res.getHeaderNames();
196            if (hh.size() > 0) {
197               sb.append("\n---Response Headers---");
198               for (String h : hh) {
199                  sb.append("\n\t").append(h).append(": ").append(res.getHeader(h));
200               }
201            }
202         }
203
204         if (reqBody != null && reqBody.length > 0 && reqd == LONG) {
205            try {
206               sb.append("\n---Request Body UTF-8---");
207               sb.append("\n").append(new String(reqBody, IOUtils.UTF8));
208               sb.append("\n---Request Body Hex---");
209               sb.append("\n").append(toSpacedHex(reqBody));
210            } catch (Exception e1) {
211               sb.append("\n").append(e1.getLocalizedMessage());
212            }
213         }
214
215         if (resBody != null && resBody.length > 0 && resd == LONG) {
216            try {
217               sb.append("\n---Response Body UTF-8---");
218               sb.append("\n").append(new String(resBody, IOUtils.UTF8));
219               sb.append("\n---Response Body Hex---");
220               sb.append("\n").append(toSpacedHex(resBody));
221            } catch (Exception e1) {
222               sb.append(e1.getLocalizedMessage());
223            }
224         }
225         sb.append("\n=== END ===================================================================");
226      }
227
228      getLogger().log(level, sb.toString(), e);
229   }
230
231   private byte[] getRequestBody(HttpServletRequest req) {
232      if (req instanceof CachingHttpServletRequest)
233         return ((CachingHttpServletRequest)req).getBody();
234      return castOrNull(req.getAttribute("RequestBody"), byte[].class);
235   }
236
237   private byte[] getResponseBody(HttpServletRequest req, HttpServletResponse res) {
238      if (res instanceof CachingHttpServletResponse)
239         return ((CachingHttpServletResponse)res).getBody();
240      return castOrNull(req.getAttribute("ResponseBody"), byte[].class);
241   }
242
243   private StackTraceInfo getStackTraceInfo(RestCallLoggerConfig config, Throwable e) {
244      if (e == null || ! config.isUseStackTraceHashing())
245         return null;
246      return stackTraceDb.getStackTraceInfo(e, config.getStackTraceHashingTimeout());
247   }
248}