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}