001package ca.uhn.fhir.rest.client.interceptor; 002 003/* 004 * #%L 005 * HAPI FHIR - Client Framework 006 * %% 007 * Copyright (C) 2014 - 2019 University Health Network 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import java.io.IOException; 024import java.io.InputStream; 025import java.util.Iterator; 026import java.util.List; 027import java.util.Map; 028 029import ca.uhn.fhir.model.primitive.IdDt; 030import ca.uhn.fhir.rest.api.Constants; 031import org.apache.commons.io.IOUtils; 032import org.apache.commons.lang3.Validate; 033import org.slf4j.Logger; 034 035import ca.uhn.fhir.rest.client.api.IClientInterceptor; 036import ca.uhn.fhir.rest.client.api.IHttpRequest; 037import ca.uhn.fhir.rest.client.api.IHttpResponse; 038import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 039 040public class LoggingInterceptor implements IClientInterceptor { 041 private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LoggingInterceptor.class); 042 043 private Logger myLog = ourLog; 044 private boolean myLogRequestBody = false; 045 private boolean myLogRequestHeaders = false; 046 private boolean myLogRequestSummary = true; 047 private boolean myLogResponseBody = false; 048 private boolean myLogResponseHeaders = false; 049 private boolean myLogResponseSummary = true; 050 051 /** 052 * Constructor for client logging interceptor 053 */ 054 public LoggingInterceptor() { 055 super(); 056 } 057 058 /** 059 * Constructor for client logging interceptor 060 * 061 * @param theVerbose 062 * If set to true, all logging is enabled 063 */ 064 public LoggingInterceptor(boolean theVerbose) { 065 if (theVerbose) { 066 setLogRequestBody(true); 067 setLogRequestSummary(true); 068 setLogResponseBody(true); 069 setLogResponseSummary(true); 070 setLogRequestHeaders(true); 071 setLogResponseHeaders(true); 072 } 073 } 074 075 @Override 076 public void interceptRequest(IHttpRequest theRequest) { 077 if (myLogRequestSummary) { 078 myLog.info("Client request: {}", theRequest); 079 } 080 081 if (myLogRequestHeaders) { 082 StringBuilder b = headersToString(theRequest.getAllHeaders()); 083 myLog.info("Client request headers:\n{}", b.toString()); 084 } 085 086 if (myLogRequestBody) { 087 try { 088 String content = theRequest.getRequestBodyFromStream(); 089 if (content != null) { 090 myLog.info("Client request body:\n{}", content); 091 } 092 } catch (IllegalStateException e) { 093 myLog.warn("Failed to replay request contents (during logging attempt, actual FHIR call did not fail)", e); 094 } catch (IOException e) { 095 myLog.warn("Failed to replay request contents (during logging attempt, actual FHIR call did not fail)", e); 096 } 097 } 098 } 099 100 @Override 101 public void interceptResponse(IHttpResponse theResponse) throws IOException { 102 if (myLogResponseSummary) { 103 String message = "HTTP " + theResponse.getStatus() + " " + theResponse.getStatusInfo(); 104 String respLocation = ""; 105 106 /* 107 * Add response location 108 */ 109 List<String> locationHeaders = theResponse.getHeaders(Constants.HEADER_LOCATION); 110 if (locationHeaders == null || locationHeaders.isEmpty()) { 111 locationHeaders = theResponse.getHeaders(Constants.HEADER_CONTENT_LOCATION); 112 } 113 if (locationHeaders != null && locationHeaders.size() > 0) { 114 String locationValue = locationHeaders.get(0); 115 IdDt locationValueId = new IdDt(locationValue); 116 if (locationValueId.hasBaseUrl() && locationValueId.hasIdPart()) { 117 locationValue = locationValueId.toUnqualified().getValue(); 118 } 119 respLocation = " (" + locationValue + ")"; 120 } 121 122 String timing = " in " + theResponse.getRequestStopWatch().toString(); 123 myLog.info("Client response: {}{}{}", message, respLocation, timing); 124 } 125 126 if (myLogResponseHeaders) { 127 StringBuilder b = headersToString(theResponse.getAllHeaders()); 128 // if (theResponse.getEntity() != null && theResponse.getEntity().getContentEncoding() != null) { 129 // Header next = theResponse.getEntity().getContentEncoding(); 130 // b.append(next.getName() + ": " + next.getValue()); 131 // } 132 // if (theResponse.getEntity() != null && theResponse.getEntity().getContentType() != null) { 133 // Header next = theResponse.getEntity().getContentType(); 134 // b.append(next.getName() + ": " + next.getValue()); 135 // } 136 if (b.length() == 0) { 137 myLog.info("Client response headers: (none)"); 138 } else { 139 myLog.info("Client response headers:\n{}", b.toString()); 140 } 141 } 142 143 if (myLogResponseBody) { 144 //TODO: Use of a deprecated method should be resolved. 145 theResponse.bufferEntitity(); 146 InputStream respEntity = null; 147 try { 148 respEntity = theResponse.readEntity(); 149 if (respEntity != null) { 150 final byte[] bytes; 151 try { 152 bytes = IOUtils.toByteArray(respEntity); 153 } catch (IllegalStateException e) { 154 throw new InternalErrorException(e); 155 } 156 myLog.info("Client response body:\n{}", new String(bytes, "UTF-8")); 157 } else { 158 myLog.info("Client response body: (none)"); 159 } 160 } finally { 161 IOUtils.closeQuietly(respEntity); 162 } 163 } 164 } 165 166 private StringBuilder headersToString(Map<String, List<String>> theHeaders) { 167 StringBuilder b = new StringBuilder(); 168 if (theHeaders != null && !theHeaders.isEmpty()) { 169 Iterator<String> nameEntries = theHeaders.keySet().iterator(); 170 while(nameEntries.hasNext()) { 171 String key = nameEntries.next(); 172 Iterator<String> values = theHeaders.get(key).iterator(); 173 while(values.hasNext()) { 174 String value = values.next(); 175 b.append(key + ": " + value); 176 if (nameEntries.hasNext() || values.hasNext()) { 177 b.append('\n'); 178 } 179 } 180 } 181 } 182 return b; 183 } 184 185 /** 186 * Sets a logger to use to log messages (default is a logger with this class' name). This can be used to redirect 187 * logs to a differently named logger instead. 188 * 189 * @param theLogger 190 * The logger to use. Must not be null. 191 */ 192 public void setLogger(Logger theLogger) { 193 Validate.notNull(theLogger, "theLogger can not be null"); 194 myLog = theLogger; 195 } 196 197 /** 198 * Should a summary (one line) for each request be logged, containing the URL and other information 199 */ 200 public void setLogRequestBody(boolean theValue) { 201 myLogRequestBody = theValue; 202 } 203 204 /** 205 * Should headers for each request be logged, containing the URL and other information 206 */ 207 public void setLogRequestHeaders(boolean theValue) { 208 myLogRequestHeaders = theValue; 209 } 210 211 /** 212 * Should a summary (one line) for each request be logged, containing the URL and other information 213 */ 214 public void setLogRequestSummary(boolean theValue) { 215 myLogRequestSummary = theValue; 216 } 217 218 /** 219 * Should a summary (one line) for each request be logged, containing the URL and other information 220 */ 221 public void setLogResponseBody(boolean theValue) { 222 myLogResponseBody = theValue; 223 } 224 225 /** 226 * Should headers for each request be logged, containing the URL and other information 227 */ 228 public void setLogResponseHeaders(boolean theValue) { 229 myLogResponseHeaders = theValue; 230 } 231 232 /** 233 * Should a summary (one line) for each request be logged, containing the URL and other information 234 */ 235 public void setLogResponseSummary(boolean theValue) { 236 myLogResponseSummary = theValue; 237 } 238 239}