001package ca.uhn.fhir.rest.client.interceptor;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2016 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.ByteArrayInputStream;
024import java.io.IOException;
025import java.io.InputStream;
026import java.io.OutputStream;
027
028import org.apache.commons.io.IOUtils;
029import org.apache.commons.lang3.Validate;
030import org.apache.http.Header;
031import org.apache.http.HttpEntity;
032import org.apache.http.HttpEntityEnclosingRequest;
033import org.apache.http.HttpResponse;
034import org.apache.http.client.methods.HttpRequestBase;
035import org.apache.http.entity.HttpEntityWrapper;
036import org.slf4j.Logger;
037
038import ca.uhn.fhir.rest.client.IClientInterceptor;
039import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
040
041public class LoggingInterceptor implements IClientInterceptor {
042        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LoggingInterceptor.class);
043
044        private Logger myLog = ourLog;
045        private boolean myLogRequestBody = false;
046        private boolean myLogRequestHeaders = false;
047        private boolean myLogRequestSummary = true;
048        private boolean myLogResponseBody = false;
049        private boolean myLogResponseHeaders = false;
050        private boolean myLogResponseSummary = true;
051
052        /**
053         * Constructor
054         */
055        public LoggingInterceptor() {
056                super();
057        }
058
059        /**
060         * Constructor
061         * 
062         * @param theVerbose
063         *            If set to true, all logging is enabled
064         */
065        public LoggingInterceptor(boolean theVerbose) {
066                if (theVerbose) {
067                        setLogRequestBody(true);
068                        setLogRequestSummary(true);
069                        setLogResponseBody(true);
070                        setLogResponseSummary(true);
071                        setLogRequestHeaders(true);
072                        setLogResponseHeaders(true);
073                }
074        }
075
076        @Override
077        public void interceptRequest(HttpRequestBase theRequest) {
078                if (myLogRequestSummary) {
079                        myLog.info("Client request: {}", theRequest);
080                }
081
082                if (myLogRequestHeaders) {
083                        StringBuilder b = new StringBuilder();
084                        for (int i = 0; i < theRequest.getAllHeaders().length; i++) {
085                                Header next = theRequest.getAllHeaders()[i];
086                                b.append(next.getName() + ": " + next.getValue());
087                                if (i + 1 < theRequest.getAllHeaders().length) {
088                                        b.append('\n');
089                                }
090                        }
091                        myLog.info("Client request headers:\n{}", b.toString());
092                }
093
094                if (myLogRequestBody) {
095                        if (theRequest instanceof HttpEntityEnclosingRequest) {
096                                HttpEntity entity = ((HttpEntityEnclosingRequest) theRequest).getEntity();
097                                if (entity.isRepeatable()) {
098                                        try {
099                                                String content = IOUtils.toString(entity.getContent());
100                                                myLog.info("Client request body:\n{}", content);
101                                        } catch (IllegalStateException e) {
102                                                myLog.warn("Failed to replay request contents (during logging attempt, actual FHIR call did not fail)", e);
103                                        } catch (IOException e) {
104                                                myLog.warn("Failed to replay request contents (during logging attempt, actual FHIR call did not fail)", e);
105                                        }
106                                }
107                        }
108                }
109
110        }
111
112        @Override
113        public void interceptResponse(HttpResponse theResponse) throws IOException {
114                if (myLogResponseSummary) {
115                        String message = "HTTP " + theResponse.getStatusLine().getStatusCode() + " " + theResponse.getStatusLine().getReasonPhrase();
116                        myLog.info("Client response: {}", message);
117                }
118
119                if (myLogResponseHeaders) {
120                        StringBuilder b = new StringBuilder();
121                        if (theResponse.getAllHeaders() != null) {
122                                for (int i = 0; i < theResponse.getAllHeaders().length; i++) {
123                                        Header next = theResponse.getAllHeaders()[i];
124                                        b.append(next.getName() + ": " + next.getValue());
125                                        if (i + 1 < theResponse.getAllHeaders().length) {
126                                                b.append('\n');
127                                        }
128                                }
129                        }
130                        // if (theResponse.getEntity() != null && theResponse.getEntity().getContentEncoding() != null) {
131                        // Header next = theResponse.getEntity().getContentEncoding();
132                        // b.append(next.getName() + ": " + next.getValue());
133                        // }
134                        // if (theResponse.getEntity() != null && theResponse.getEntity().getContentType() != null) {
135                        // Header next = theResponse.getEntity().getContentType();
136                        // b.append(next.getName() + ": " + next.getValue());
137                        // }
138                        if (b.length() == 0) {
139                                myLog.info("Client response headers: (none)");
140                        } else {
141                                myLog.info("Client response headers:\n{}", b.toString());
142                        }
143                }
144
145                if (myLogResponseBody) {
146                        HttpEntity respEntity = theResponse.getEntity();
147                        if (respEntity != null) {
148                        final byte[] bytes;
149                        try {
150                                bytes = IOUtils.toByteArray(respEntity.getContent());
151                        } catch (IllegalStateException e) {
152                                throw new InternalErrorException(e);
153                        }
154
155                        myLog.info("Client response body:\n{}", new String(bytes, "UTF-8"));
156                        theResponse.setEntity(new MyEntityWrapper(respEntity, bytes));
157                        } else {
158                                myLog.info("Client response body: (none)");
159                        }
160                }
161        }
162
163        /**
164         * Sets a logger to use to log messages (default is a logger with this class' name). This can be used to redirect
165         * logs to a differently named logger instead.
166         * 
167         * @param theLogger
168         *            The logger to use. Must not be null.
169         */
170        public void setLogger(Logger theLogger) {
171                Validate.notNull(theLogger, "theLogger can not be null");
172                myLog = theLogger;
173        }
174
175        /**
176         * Should a summary (one line) for each request be logged, containing the URL and other information
177         */
178        public void setLogRequestBody(boolean theValue) {
179                myLogRequestBody = theValue;
180        }
181
182        /**
183         * Should headers for each request be logged, containing the URL and other information
184         */
185        public void setLogRequestHeaders(boolean theValue) {
186                myLogRequestHeaders = theValue;
187        }
188
189        /**
190         * Should a summary (one line) for each request be logged, containing the URL and other information
191         */
192        public void setLogRequestSummary(boolean theValue) {
193                myLogRequestSummary = theValue;
194        }
195
196        /**
197         * Should a summary (one line) for each request be logged, containing the URL and other information
198         */
199        public void setLogResponseBody(boolean theValue) {
200                myLogResponseBody = theValue;
201        }
202
203        /**
204         * Should headers for each request be logged, containing the URL and other information
205         */
206        public void setLogResponseHeaders(boolean theValue) {
207                myLogResponseHeaders = theValue;
208        }
209
210        /**
211         * Should a summary (one line) for each request be logged, containing the URL and other information
212         */
213        public void setLogResponseSummary(boolean theValue) {
214                myLogResponseSummary = theValue;
215        }
216
217        private static class MyEntityWrapper extends HttpEntityWrapper {
218
219                private byte[] myBytes;
220
221                public MyEntityWrapper(HttpEntity theWrappedEntity, byte[] theBytes) {
222                        super(theWrappedEntity);
223                        myBytes = theBytes;
224                }
225
226                @Override
227                public InputStream getContent() throws IOException {
228                        return new ByteArrayInputStream(myBytes);
229                }
230
231                @Override
232                public void writeTo(OutputStream theOutstream) throws IOException {
233                        theOutstream.write(myBytes);
234                }
235
236        }
237
238}