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}