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 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.getResource().getClass().getName(); 054 this.logger = Logger.getLogger(getLoggerName()); 055 this.stackTraceDb = 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 stackTraceDb.reset(); 106 return this; 107 } 108 109 @Override /* RestCallLogger */ 110 public void log(RestCallLoggerConfig config, HttpServletRequest req, HttpServletResponse res) { 111 112 if (config.isDisabled(req)) 113 return; 114 115 RestCallLoggerRule rule = config.getRule(req, res); 116 if (rule == null) 117 return; 118 119 Level level = rule.getLevel(); 120 if (level == null) 121 level = config.getLevel(); 122 123 if (level == Level.OFF) 124 return; 125 126 Throwable e = castOrNull(req.getAttribute("Exception"), Throwable.class); 127 Long execTime = castOrNull(req.getAttribute("ExecTime"), Long.class); 128 129 RestCallLoggingDetail reqd = rule.getReqDetail(), resd = rule.getResDetail(); 130 131 String method = req.getMethod(); 132 int status = res.getStatus(); 133 String uri = req.getRequestURI(); 134 byte[] reqBody = getRequestBody(req); 135 byte[] resBody = getResponseBody(req, res); 136 137 StringBuilder sb = new StringBuilder(); 138 139 if (reqd != SHORT || resd != SHORT) 140 sb.append("\n=== HTTP Request (incoming) ===================================================\n"); 141 142 StackTraceInfo sti = getStackTraceInfo(config, e); 143 144 sb.append('[').append(status); 145 146 if (sti != null) { 147 int count = sti.getCount(); 148 sb.append(',').append(sti.getHash()).append('.').append(count); 149 if (count > 1) 150 e = null; 151 } 152 153 sb.append("] "); 154 155 sb.append("HTTP ").append(method).append(' ').append(uri); 156 157 if (reqd != SHORT || resd != SHORT) { 158 159 if (reqd.isOneOf(MEDIUM, LONG)) { 160 String qs = req.getQueryString(); 161 if (qs != null) 162 sb.append('?').append(qs); 163 } 164 165 if (reqBody != null && reqd.isOneOf(MEDIUM ,LONG)) 166 sb.append("\n\tRequest length: ").append(reqBody.length).append(" bytes"); 167 168 if (resd.isOneOf(MEDIUM, LONG)) 169 sb.append("\n\tResponse code: ").append(status); 170 171 if (resBody != null && resd.isOneOf(MEDIUM, LONG)) 172 sb.append("\n\tResponse length: ").append(resBody.length).append(" bytes"); 173 174 if (execTime != null && resd.isOneOf(MEDIUM, LONG)) 175 sb.append("\n\tExec time: ").append(execTime).append("ms"); 176 177 if (reqd.isOneOf(MEDIUM, LONG)) { 178 Enumeration<String> hh = req.getHeaderNames(); 179 if (hh.hasMoreElements()) { 180 sb.append("\n---Request Headers---"); 181 while (hh.hasMoreElements()) { 182 String h = hh.nextElement(); 183 sb.append("\n\t").append(h).append(": ").append(req.getHeader(h)); 184 } 185 } 186 } 187 188 if (context != null && reqd.isOneOf(MEDIUM, LONG)) { 189 Map<String,Object> hh = context.getReqHeaders(); 190 if (! hh.isEmpty()) { 191 sb.append("\n---Default Servlet Headers---"); 192 for (Map.Entry<String,Object> h : hh.entrySet()) { 193 sb.append("\n\t").append(h.getKey()).append(": ").append(h.getValue()); 194 } 195 } 196 } 197 198 if (resd.isOneOf(MEDIUM, LONG)) { 199 Collection<String> hh = res.getHeaderNames(); 200 if (hh.size() > 0) { 201 sb.append("\n---Response Headers---"); 202 for (String h : hh) { 203 sb.append("\n\t").append(h).append(": ").append(res.getHeader(h)); 204 } 205 } 206 } 207 208 if (reqBody != null && reqBody.length > 0 && reqd == LONG) { 209 try { 210 sb.append("\n---Request Body UTF-8---"); 211 sb.append("\n").append(new String(reqBody, IOUtils.UTF8)); 212 sb.append("\n---Request Body Hex---"); 213 sb.append("\n").append(toSpacedHex(reqBody)); 214 } catch (Exception e1) { 215 sb.append("\n").append(e1.getLocalizedMessage()); 216 } 217 } 218 219 if (resBody != null && resBody.length > 0 && resd == LONG) { 220 try { 221 sb.append("\n---Response Body UTF-8---"); 222 sb.append("\n").append(new String(resBody, IOUtils.UTF8)); 223 sb.append("\n---Response Body Hex---"); 224 sb.append("\n").append(toSpacedHex(resBody)); 225 } catch (Exception e1) { 226 sb.append(e1.getLocalizedMessage()); 227 } 228 } 229 sb.append("\n=== END ==================================================================="); 230 } 231 232 getLogger().log(level, sb.toString(), e); 233 } 234 235 private byte[] getRequestBody(HttpServletRequest req) { 236 if (req instanceof CachingHttpServletRequest) 237 return ((CachingHttpServletRequest)req).getBody(); 238 return castOrNull(req.getAttribute("RequestBody"), byte[].class); 239 } 240 241 private byte[] getResponseBody(HttpServletRequest req, HttpServletResponse res) { 242 if (res instanceof CachingHttpServletResponse) 243 return ((CachingHttpServletResponse)res).getBody(); 244 return castOrNull(req.getAttribute("ResponseBody"), byte[].class); 245 } 246 247 private StackTraceInfo getStackTraceInfo(RestCallLoggerConfig config, Throwable e) { 248 if (e == null || ! config.isUseStackTraceHashing()) 249 return null; 250 stackTraceDb.add(e); 251 return stackTraceDb.getStackTraceInfo(e); 252 } 253}