View Javadoc
1   package ca.uhn.fhir.rest.server.interceptor;
2   
3   /*
4    * #%L
5    * HAPI FHIR - Core Library
6    * %%
7    * Copyright (C) 2014 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
21   */
22  
23  import java.io.UnsupportedEncodingException;
24  import java.net.URLEncoder;
25  import java.util.Map.Entry;
26  
27  import javax.servlet.http.HttpServletRequest;
28  import javax.servlet.http.HttpServletResponse;
29  
30  import org.apache.commons.lang3.StringUtils;
31  import org.apache.commons.lang3.Validate;
32  import org.apache.commons.lang3.text.StrLookup;
33  import org.apache.commons.lang3.text.StrSubstitutor;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  import ca.uhn.fhir.rest.method.RequestDetails;
38  import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
39  
40  /**
41   * Server interceptor which logs each request using a defined format
42   * <p>
43   * The following substitution variables are supported:
44   * </p>
45   * <table>
46   * <tr>
47   * <td>${id}</td>
48   * <td>The resource ID associated with this request (or "" if none)</td>
49   * </tr>
50   * <tr>
51   * <td>${idOrResourceName}</td>
52   * <td>The resource ID associated with this request, or the resource name if the request applies to a type but not an instance, or "" otherwise</td>
53   * </tr>
54   * <tr>
55   * <td>${operationType}</td>
56   * <td>A code indicating the operation type for this request, e.g. "read", "history-instance", etc.)</td>
57   * </tr>
58   * <tr>
59   * <td>${remoteAddr}</td>
60   * <td>The originaring IP of the request</td>
61   * </tr>
62   * <tr>
63   * <td>${requestHeader.XXXX}</td>
64   * <td>The value of the HTTP request header named XXXX. For example, a substitution variable named
65   * "${requestHeader.x-forwarded-for} will yield the value of the first header named "x-forwarded-for", or "" if none.</td>
66   * </tr>
67   * <tr>
68   * <td>${requestParameters}</td>
69   * <td>The HTTP request parameters (or "")</td>
70   * </tr>
71   * </table>
72   */
73  public class LoggingInterceptor extends InterceptorAdapter {
74  
75  	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LoggingInterceptor.class);
76  
77  	private Logger myLogger = ourLog;
78  	private String myMessageFormat = "${operationType} - ${idOrResourceName}";
79  	
80  	@Override
81  	public boolean incomingRequestPostProcessed(final RequestDetails theRequestDetails, final HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException {
82  
83  		// Perform any string substitutions from the message format
84  		StrLookup<?> lookup = new MyLookup(theRequest, theRequestDetails);
85  		StrSubstitutor subs = new StrSubstitutor(lookup, "${", "}", '\\');
86  
87  		// Actuall log the line
88  		String line = subs.replace(myMessageFormat);
89  		myLogger.info(line);
90  
91  		return true;
92  	}
93  
94  	public void setLogger(Logger theLogger) {
95  		Validate.notNull(theLogger, "Logger can not be null");
96  		myLogger = theLogger;
97  	}
98  
99  	public void setLoggerName(String theLoggerName) {
100 		Validate.notBlank(theLoggerName, "Logger name can not be null/empty");
101 		myLogger = LoggerFactory.getLogger(theLoggerName);
102 
103 	}
104 
105 	/**
106 	 * Sets the message format itself. See the {@link LoggingInterceptor class documentation} for information on the format
107 	 */
108 	public void setMessageFormat(String theMessageFormat) {
109 		Validate.notBlank(theMessageFormat, "Message format can not be null/empty");
110 		myMessageFormat = theMessageFormat;
111 	}
112 
113 	private static final class MyLookup extends StrLookup<String> {
114 		private final HttpServletRequest myRequest;
115 		private final RequestDetails myRequestDetails;
116 
117 		private MyLookup(HttpServletRequest theRequest, RequestDetails theRequestDetails) {
118 			myRequest = theRequest;
119 			myRequestDetails = theRequestDetails;
120 		}
121 
122 		@Override
123 		public String lookup(String theKey) {
124 			if ("operationType".equals(theKey)) {
125 				if (myRequestDetails.getResourceOperationType() != null) {
126 					return myRequestDetails.getResourceOperationType().getCode();
127 				}
128 				if (myRequestDetails.getSystemOperationType() != null) {
129 					return myRequestDetails.getSystemOperationType().getCode();
130 				}
131 				if (myRequestDetails.getOtherOperationType() != null) {
132 					return myRequestDetails.getOtherOperationType().getCode();
133 				}
134 				return "";
135 			}
136 			if ("id".equals(theKey)) {
137 				if (myRequestDetails.getId() != null) {
138 					return myRequestDetails.getId().getValue();
139 				}
140 				return "";
141 			}
142 			if ("idOrResourceName".equals(theKey)) {
143 				if (myRequestDetails.getId() != null) {
144 					return myRequestDetails.getId().getValue();
145 				}
146 				if (myRequestDetails.getResourceName() != null) {
147 					return myRequestDetails.getResourceName();
148 				}
149 				return "";
150 			}
151 			if (theKey.equals("requestParameters")) {
152 				StringBuilder b = new StringBuilder();
153 				for (Entry<String, String[]> next : myRequestDetails.getParameters().entrySet()) {
154 					for (String nextValue : next.getValue()) {
155 						if (b.length() == 0) {
156 							b.append('?');
157 						} else {
158 							b.append('&');
159 						}
160 						try {
161 							b.append(URLEncoder.encode(next.getKey(), "UTF-8"));
162 							b.append('=');
163 							b.append(URLEncoder.encode(nextValue, "UTF-8"));
164 						} catch (UnsupportedEncodingException e) {
165 							throw new ca.uhn.fhir.context.ConfigurationException("UTF-8 not supported", e);
166 						}
167 					}
168 				}
169 				return b.toString();
170 			}
171 			if (theKey.startsWith("requestHeader.")) {
172 				String val = myRequest.getHeader(theKey.substring("requestHeader.".length()));
173 				return StringUtils.defaultString(val);
174 			}
175 			if (theKey.startsWith("remoteAddr")) {
176 				return StringUtils.defaultString(myRequest.getRemoteAddr());
177 			}
178 			return "!VAL!";
179 		}
180 	}
181 
182 }