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 RestLoggingAndDebugging} 037 * </ul> 038 */ 039public class BasicRestCallLogger implements RestCallLogger { 040 041 private final String loggerName; 042 private final Logger logger; 043 private final RestContext context; 044 private final StackTraceDatabase stackTraceDb; 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.loggerName = context == null ? getClass().getName() : context.getResource().getClass().getName(); 054 this.logger = Logger.getLogger(getLoggerName()); 055 this.stackTraceDb = context == null ? null : context.getStackTraceDb(); 056 } 057 058 /** 059 * Constructor. 060 * 061 * @param logger The logger to use for logging. 062 * @param stackTraceDb The stack trace database for maintaining stack traces. 063 */ 064 protected BasicRestCallLogger(Logger logger, StackTraceDatabase stackTraceDb) { 065 this.context = null; 066 this.loggerName = getClass().getName(); 067 this.logger = logger; 068 this.stackTraceDb = stackTraceDb; 069 } 070 071 /** 072 * Returns the logger name. 073 * 074 * <p> 075 * By default returns the class name of the servlet class passed in to the context. 076 * 077 * <p> 078 * Subclasses can override this to provide their own customized logger names. 079 * 080 * @return The logger name. 081 */ 082 protected String getLoggerName() { 083 return loggerName; 084 } 085 086 /** 087 * Returns the Java logger used for logging. 088 * 089 * <p> 090 * Subclasses can provide their own logger. 091 * The default implementation returns the logger created using <c>Logger.getLogger(getClass())</c>. 092 * 093 * @return The logger used for logging. 094 */ 095 protected Logger getLogger() { 096 return logger; 097 } 098 099 /** 100 * Clears out the stack trace database. 101 * 102 * @return This object (for method chaining). 103 */ 104 public BasicRestCallLogger resetStackTraces() { 105 if (stackTraceDb != null) 106 stackTraceDb.reset(); 107 return this; 108 } 109 110 @Override /* RestCallLogger */ 111 public void log(RestCallLoggerConfig config, HttpServletRequest req, HttpServletResponse res) { 112 113 if (config.isDisabled(req)) 114 return; 115 116 RestCallLoggerRule rule = config.getRule(req, res); 117 if (rule == null) 118 return; 119 120 Level level = rule.getLevel(); 121 if (level == null) 122 level = config.getLevel(); 123 124 if (level == Level.OFF) 125 return; 126 127 Throwable e = castOrNull(req.getAttribute("Exception"), Throwable.class); 128 Long execTime = castOrNull(req.getAttribute("ExecTime"), Long.class); 129 130 RestCallLoggingDetail reqd = rule.getReqDetail(), resd = rule.getResDetail(); 131 132 String method = req.getMethod(); 133 int status = res.getStatus(); 134 String uri = req.getRequestURI(); 135 byte[] reqBody = getRequestBody(req); 136 byte[] resBody = getResponseBody(req, res); 137 138 StringBuilder sb = new StringBuilder(); 139 140 if (reqd != SHORT || resd != SHORT) 141 sb.append("\n=== HTTP Call (incoming) ======================================================\n"); 142 143 StackTraceInfo sti = getStackTraceInfo(config, e); 144 145 sb.append('[').append(status); 146 147 if (sti != null) { 148 int count = sti.getCount(); 149 sb.append(',').append(sti.getHash()).append('.').append(count); 150 if (count > 1) 151 e = null; 152 } 153 154 sb.append("] "); 155 156 sb.append("HTTP ").append(method).append(' ').append(uri); 157 158 if (reqd != SHORT || resd != SHORT) { 159 160 if (reqd.isOneOf(MEDIUM, LONG)) { 161 String qs = req.getQueryString(); 162 if (qs != null) 163 sb.append('?').append(qs); 164 } 165 166 if (reqBody != null && reqd.isOneOf(MEDIUM ,LONG)) 167 sb.append("\n\tRequest length: ").append(reqBody.length).append(" bytes"); 168 169 if (resd.isOneOf(MEDIUM, LONG)) 170 sb.append("\n\tResponse code: ").append(status); 171 172 if (resBody != null && resd.isOneOf(MEDIUM, LONG)) 173 sb.append("\n\tResponse length: ").append(resBody.length).append(" bytes"); 174 175 if (execTime != null && resd.isOneOf(MEDIUM, LONG)) 176 sb.append("\n\tExec time: ").append(execTime).append("ms"); 177 178 if (reqd.isOneOf(MEDIUM, LONG)) { 179 Enumeration<String> hh = req.getHeaderNames(); 180 if (hh.hasMoreElements()) { 181 sb.append("\n---Request Headers---"); 182 while (hh.hasMoreElements()) { 183 String h = hh.nextElement(); 184 sb.append("\n\t").append(h).append(": ").append(req.getHeader(h)); 185 } 186 } 187 } 188 189 if (context != null && reqd.isOneOf(MEDIUM, LONG)) { 190 Map<String,Object> hh = context.getReqHeaders(); 191 if (! hh.isEmpty()) { 192 sb.append("\n---Default Servlet Headers---"); 193 for (Map.Entry<String,Object> h : hh.entrySet()) { 194 sb.append("\n\t").append(h.getKey()).append(": ").append(h.getValue()); 195 } 196 } 197 } 198 199 if (resd.isOneOf(MEDIUM, LONG)) { 200 Collection<String> hh = res.getHeaderNames(); 201 if (hh.size() > 0) { 202 sb.append("\n---Response Headers---"); 203 for (String h : hh) { 204 sb.append("\n\t").append(h).append(": ").append(res.getHeader(h)); 205 } 206 } 207 } 208 209 if (reqBody != null && reqBody.length > 0 && reqd == LONG) { 210 try { 211 sb.append("\n---Request Body UTF-8---"); 212 sb.append("\n").append(new String(reqBody, IOUtils.UTF8)); 213 sb.append("\n---Request Body Hex---"); 214 sb.append("\n").append(toSpacedHex(reqBody)); 215 } catch (Exception e1) { 216 sb.append("\n").append(e1.getLocalizedMessage()); 217 } 218 } 219 220 if (resBody != null && resBody.length > 0 && resd == LONG) { 221 try { 222 sb.append("\n---Response Body UTF-8---"); 223 sb.append("\n").append(new String(resBody, IOUtils.UTF8)); 224 sb.append("\n---Response Body Hex---"); 225 sb.append("\n").append(toSpacedHex(resBody)); 226 } catch (Exception e1) { 227 sb.append(e1.getLocalizedMessage()); 228 } 229 } 230 sb.append("\n=== END ======================================================================"); 231 } 232 233 log(level, sb.toString(), e); 234 } 235 236 /** 237 * Logs the specified message to the logger. 238 * 239 * <p> 240 * Subclasses can override this method to capture messages being sent to the logger. 241 * 242 * @param level The log level. 243 * @param msg The log message. 244 * @param e The exception. 245 */ 246 protected void log(Level level, String msg, Throwable e) { 247 getLogger().log(level, msg, e); 248 } 249 250 private byte[] getRequestBody(HttpServletRequest req) { 251 if (req instanceof RestRequest) 252 req = ((RestRequest)req).getInner(); 253 if (req instanceof CachingHttpServletRequest) 254 return ((CachingHttpServletRequest)req).getBody(); 255 return castOrNull(req.getAttribute("RequestBody"), byte[].class); 256 } 257 258 private byte[] getResponseBody(HttpServletRequest req, HttpServletResponse res) { 259 if (res instanceof RestResponse) 260 res = ((RestResponse)res).getInner(); 261 if (res instanceof CachingHttpServletResponse) 262 return ((CachingHttpServletResponse)res).getBody(); 263 return castOrNull(req.getAttribute("ResponseBody"), byte[].class); 264 } 265 266 private StackTraceInfo getStackTraceInfo(RestCallLoggerConfig config, Throwable e) { 267 if (e == null || stackTraceDb == null || ! config.isUseStackTraceHashing()) 268 return null; 269 stackTraceDb.add(e); 270 return stackTraceDb.getStackTraceInfo(e); 271 } 272}