001package ca.uhn.fhir.rest.client.interceptor;
002
003/*
004 * #%L
005 * HAPI FHIR - Client Framework
006 * %%
007 * Copyright (C) 2014 - 2018 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.size() > 0) {
111                                String locationValue = locationHeaders.get(0);
112                                IdDt locationValueId = new IdDt(locationValue);
113                                if (locationValueId.hasBaseUrl() && locationValueId.hasIdPart()) {
114                                        locationValue = locationValueId.toUnqualified().getValue();
115                                }
116                                respLocation = " (" + locationValue + ")";
117                        }
118                        myLog.info("Client response: {}{}", message, respLocation);
119                }
120
121                if (myLogResponseHeaders) {
122                        StringBuilder b = headersToString(theResponse.getAllHeaders());
123                        // if (theResponse.getEntity() != null && theResponse.getEntity().getContentEncoding() != null) {
124                        // Header next = theResponse.getEntity().getContentEncoding();
125                        // b.append(next.getName() + ": " + next.getValue());
126                        // }
127                        // if (theResponse.getEntity() != null && theResponse.getEntity().getContentType() != null) {
128                        // Header next = theResponse.getEntity().getContentType();
129                        // b.append(next.getName() + ": " + next.getValue());
130                        // }
131                        if (b.length() == 0) {
132                                myLog.info("Client response headers: (none)");
133                        } else {
134                                myLog.info("Client response headers:\n{}", b.toString());
135                        }
136                }
137
138                if (myLogResponseBody) {
139                        //TODO: Use of a deprecated method should be resolved.
140                        theResponse.bufferEntitity();
141                        InputStream respEntity = null;
142                        try  {
143                                respEntity = theResponse.readEntity();
144                                if (respEntity != null) {
145                                        final byte[] bytes;
146                                        try {
147                                                bytes = IOUtils.toByteArray(respEntity);
148                                        } catch (IllegalStateException e) {
149                                                throw new InternalErrorException(e);
150                                        }
151                                        myLog.info("Client response body:\n{}", new String(bytes, "UTF-8"));
152                                } else {
153                                        myLog.info("Client response body: (none)");
154                                }
155                        } finally {
156                                IOUtils.closeQuietly(respEntity);
157                        }
158                }
159        }
160
161        private StringBuilder headersToString(Map<String, List<String>> theHeaders) {
162                StringBuilder b = new StringBuilder();
163                if (theHeaders != null && !theHeaders.isEmpty()) {
164                        Iterator<String> nameEntries = theHeaders.keySet().iterator();
165                        while(nameEntries.hasNext()) {
166                                String key = nameEntries.next();
167                                Iterator<String> values = theHeaders.get(key).iterator();
168                                while(values.hasNext()) {
169                                        String value = values.next();
170                                                b.append(key + ": " + value);
171                                                if (nameEntries.hasNext() || values.hasNext()) {
172                                                        b.append('\n');
173                                                }
174                                        }
175                        }
176                }
177                return b;
178        }
179
180        /**
181         * Sets a logger to use to log messages (default is a logger with this class' name). This can be used to redirect
182         * logs to a differently named logger instead.
183         * 
184         * @param theLogger
185         *            The logger to use. Must not be null.
186         */
187        public void setLogger(Logger theLogger) {
188                Validate.notNull(theLogger, "theLogger can not be null");
189                myLog = theLogger;
190        }
191
192        /**
193         * Should a summary (one line) for each request be logged, containing the URL and other information
194         */
195        public void setLogRequestBody(boolean theValue) {
196                myLogRequestBody = theValue;
197        }
198
199        /**
200         * Should headers for each request be logged, containing the URL and other information
201         */
202        public void setLogRequestHeaders(boolean theValue) {
203                myLogRequestHeaders = theValue;
204        }
205
206        /**
207         * Should a summary (one line) for each request be logged, containing the URL and other information
208         */
209        public void setLogRequestSummary(boolean theValue) {
210                myLogRequestSummary = theValue;
211        }
212
213        /**
214         * Should a summary (one line) for each request be logged, containing the URL and other information
215         */
216        public void setLogResponseBody(boolean theValue) {
217                myLogResponseBody = theValue;
218        }
219
220        /**
221         * Should headers for each request be logged, containing the URL and other information
222         */
223        public void setLogResponseHeaders(boolean theValue) {
224                myLogResponseHeaders = theValue;
225        }
226
227        /**
228         * Should a summary (one line) for each request be logged, containing the URL and other information
229         */
230        public void setLogResponseSummary(boolean theValue) {
231                myLogResponseSummary = theValue;
232        }
233
234}