Skip to content

Commit 9712b4d

Browse files
DaanHooglandDaan Hoogland
andauthored
custom AccessLogger (#9733)
* custom AccessLogger Co-authored-by: Daan Hoogland <[email protected]>
1 parent e5f6116 commit 9712b4d

File tree

4 files changed

+129
-54
lines changed

4 files changed

+129
-54
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
//
19+
package org.apache.cloudstack;
20+
21+
import com.cloud.utils.StringUtils;
22+
import org.eclipse.jetty.server.NCSARequestLog;
23+
import org.eclipse.jetty.server.Request;
24+
import org.eclipse.jetty.server.Response;
25+
import org.eclipse.jetty.util.DateCache;
26+
import org.eclipse.jetty.util.component.LifeCycle;
27+
28+
import java.util.Locale;
29+
import java.util.TimeZone;
30+
31+
import static org.apache.commons.configuration.DataConfiguration.DEFAULT_DATE_FORMAT;
32+
33+
public class ACSRequestLog extends NCSARequestLog {
34+
private static final ThreadLocal<StringBuilder> buffers =
35+
ThreadLocal.withInitial(() -> new StringBuilder(256));
36+
37+
private final DateCache dateCache;
38+
39+
public ACSRequestLog() {
40+
super();
41+
42+
TimeZone timeZone = TimeZone.getTimeZone("GMT");
43+
Locale locale = Locale.getDefault();
44+
dateCache = new DateCache(DEFAULT_DATE_FORMAT, locale, timeZone);
45+
}
46+
47+
@Override
48+
public void log(Request request, Response response) {
49+
String requestURI = StringUtils.cleanString(request.getOriginalURI());
50+
try {
51+
StringBuilder sb = buffers.get();
52+
sb.setLength(0);
53+
54+
sb.append(request.getHttpChannel().getEndPoint()
55+
.getRemoteAddress().getAddress()
56+
.getHostAddress())
57+
.append(" - - [")
58+
.append(dateCache.format(request.getTimeStamp()))
59+
.append("] \"")
60+
.append(request.getMethod())
61+
.append(" ")
62+
.append(requestURI)
63+
.append(" ")
64+
.append(request.getProtocol())
65+
.append("\" ")
66+
.append(response.getStatus())
67+
.append(" ")
68+
.append(response.getHttpChannel().getBytesWritten()) // apply filter here?
69+
.append(" \"-\" \"")
70+
.append(request.getHeader("User-Agent"))
71+
.append("\"");
72+
73+
write(sb.toString());
74+
} catch (Exception e) {
75+
LOG.warn("Unable to log request", e);
76+
}
77+
}
78+
79+
@Override
80+
protected void stop(LifeCycle lifeCycle) throws Exception {
81+
buffers.remove();
82+
super.stop(lifeCycle);
83+
}
84+
}

client/src/main/java/org/apache/cloudstack/ServerDaemon.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import org.eclipse.jetty.jmx.MBeanContainer;
3333
import org.eclipse.jetty.server.HttpConfiguration;
3434
import org.eclipse.jetty.server.HttpConnectionFactory;
35-
import org.eclipse.jetty.server.NCSARequestLog;
3635
import org.eclipse.jetty.server.RequestLog;
3736
import org.eclipse.jetty.server.SecureRequestCustomizer;
3837
import org.eclipse.jetty.server.Server;
@@ -299,7 +298,7 @@ private Pair<SessionHandler,HandlerCollection> createHandlers() {
299298
}
300299

301300
private RequestLog createRequestLog() {
302-
final NCSARequestLog log = new NCSARequestLog();
301+
final ACSRequestLog log = new ACSRequestLog();
303302
final File logPath = new File(accessLogFile);
304303
final File parentFile = logPath.getParentFile();
305304
if (parentFile != null) {

utils/src/main/java/com/cloud/utils/StringUtils.java

Lines changed: 29 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import java.nio.charset.Charset;
2727
import java.util.Arrays;
28+
import java.nio.charset.StandardCharsets;
2829
import java.util.ArrayList;
2930
import java.util.HashMap;
3031
import java.util.List;
@@ -39,12 +40,12 @@
3940
public class StringUtils extends org.apache.commons.lang3.StringUtils {
4041
private static final char[] hexChar = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
4142

42-
private static Charset preferredACSCharset;
43+
private static final Charset preferredACSCharset;
4344
private static final String UTF8 = "UTF-8";
4445

4546
static {
4647
if (isUtf8Supported()) {
47-
preferredACSCharset = Charset.forName(UTF8);
48+
preferredACSCharset = StandardCharsets.UTF_8;
4849
} else {
4950
preferredACSCharset = Charset.defaultCharset();
5051
}
@@ -66,8 +67,8 @@ public static String cleanupTags(String tags) {
6667
if (tags != null) {
6768
final String[] tokens = tags.split(",");
6869
final StringBuilder t = new StringBuilder();
69-
for (int i = 0; i < tokens.length; i++) {
70-
t.append(tokens[i].trim()).append(",");
70+
for (String token : tokens) {
71+
t.append(token.trim()).append(",");
7172
}
7273
t.delete(t.length() - 1, t.length());
7374
tags = t.toString();
@@ -77,16 +78,16 @@ public static String cleanupTags(String tags) {
7778
}
7879

7980
/**
80-
* @param tags
81+
* @param tags a {code}String{code} containing a list of comma separated tags
8182
* @return List of tags
8283
*/
8384
public static List<String> csvTagsToList(final String tags) {
84-
final List<String> tagsList = new ArrayList<String>();
85+
final List<String> tagsList = new ArrayList<>();
8586

8687
if (tags != null) {
8788
final String[] tokens = tags.split(",");
88-
for (int i = 0; i < tokens.length; i++) {
89-
tagsList.add(tokens[i].trim());
89+
for (String token : tokens) {
90+
tagsList.add(token.trim());
9091
}
9192
}
9293

@@ -95,13 +96,13 @@ public static List<String> csvTagsToList(final String tags) {
9596

9697
/**
9798
* Converts a List of tags to a comma separated list
98-
* @param tagsList
99+
* @param tagsList List of tags to convert to a comma separated list in a {code}String{code}
99100
* @return String containing a comma separated list of tags
100101
*/
101102

102103
public static String listToCsvTags(final List<String> tagsList) {
103104
final StringBuilder tags = new StringBuilder();
104-
if (tagsList.size() > 0) {
105+
if (!tagsList.isEmpty()) {
105106
for (int i = 0; i < tagsList.size(); i++) {
106107
tags.append(tagsList.get(i));
107108
if (i != tagsList.size() - 1) {
@@ -113,22 +114,6 @@ public static String listToCsvTags(final List<String> tagsList) {
113114
return tags.toString();
114115
}
115116

116-
public static String getExceptionStackInfo(final Throwable e) {
117-
final StringBuffer sb = new StringBuffer();
118-
119-
sb.append(e.toString()).append("\n");
120-
final StackTraceElement[] elemnents = e.getStackTrace();
121-
for (final StackTraceElement element : elemnents) {
122-
sb.append(element.getClassName()).append(".");
123-
sb.append(element.getMethodName()).append("(");
124-
sb.append(element.getFileName()).append(":");
125-
sb.append(element.getLineNumber()).append(")");
126-
sb.append("\n");
127-
}
128-
129-
return sb.toString();
130-
}
131-
132117
public static String unicodeEscape(final String s) {
133118
final StringBuilder sb = new StringBuilder();
134119
for (int i = 0; i < s.length(); i++) {
@@ -151,25 +136,22 @@ public static String getMaskedPasswordForDisplay(final String password) {
151136
return "*";
152137
}
153138

154-
final StringBuffer sb = new StringBuffer();
155-
sb.append(password.charAt(0));
156-
for (int i = 1; i < password.length(); i++) {
157-
sb.append("*");
158-
}
159-
160-
return sb.toString();
139+
return password.charAt(0) +
140+
"*".repeat(password.length() - 1);
161141
}
162142

163143
// removes a password request param and it's value, also considering password is in query parameter value which has been url encoded
164-
private static final Pattern REGEX_PASSWORD_QUERYSTRING = Pattern.compile("(&|%26)?[^(&|%26)]*((p|P)assword|accesskey|secretkey)(=|%3D).*?(?=(%26|[&'\"]|$))");
144+
private static final Pattern REGEX_PASSWORD_QUERYSTRING = Pattern.compile("(&|%26)?[^(&|%26)]*(([pP])assword|accesskey|secretkey)(=|%3D).*?(?=(%26|[&'\"]|$))");
165145

166146
// removes a password/accesskey/ property from a response json object
167-
private static final Pattern REGEX_PASSWORD_JSON = Pattern.compile("\"((p|P)assword|privatekey|accesskey|secretkey)\":\\s?\".*?\",?");
147+
private static final Pattern REGEX_PASSWORD_JSON = Pattern.compile("\"(([pP])assword|privatekey|accesskey|secretkey)\":\\s?\".*?\",?");
168148

169-
private static final Pattern REGEX_PASSWORD_DETAILS = Pattern.compile("(&|%26)?details(\\[|%5B)\\d*(\\]|%5D)\\.key(=|%3D)((p|P)assword|accesskey|secretkey)(?=(%26|[&'\"]))");
149+
private static final Pattern REGEX_PASSWORD_DETAILS = Pattern.compile("(&|%26)?details(\\[|%5B)\\d*(\\]|%5D)\\.key(=|%3D)(([pP])assword|accesskey|secretkey)(?=(%26|[&'\"]))");
170150

171151
private static final Pattern REGEX_PASSWORD_DETAILS_INDEX = Pattern.compile("details(\\[|%5B)\\d*(\\]|%5D)");
172152

153+
private static final Pattern REGEX_SESSION_KEY = Pattern.compile("sessionkey=[A-Za-z0-9_-]+");
154+
173155
private static final Pattern REGEX_REDUNDANT_AND = Pattern.compile("(&|%26)(&|%26)+");
174156

175157
// Responsible for stripping sensitive content from request and response strings
@@ -178,6 +160,7 @@ public static String cleanString(final String stringToClean) {
178160
if (stringToClean != null) {
179161
cleanResult = REGEX_PASSWORD_QUERYSTRING.matcher(stringToClean).replaceAll("");
180162
cleanResult = REGEX_PASSWORD_JSON.matcher(cleanResult).replaceAll("");
163+
cleanResult = REGEX_SESSION_KEY.matcher(cleanResult).replaceAll("");
181164
final Matcher detailsMatcher = REGEX_PASSWORD_DETAILS.matcher(cleanResult);
182165
while (detailsMatcher.find()) {
183166
final Matcher detailsIndexMatcher = REGEX_PASSWORD_DETAILS_INDEX.matcher(detailsMatcher.group());
@@ -205,24 +188,20 @@ public static boolean areTagsEqual(final String tags1, final String tags2) {
205188
return true;
206189
}
207190

208-
if (tags1 != null && tags2 == null) {
209-
return false;
210-
}
211-
212-
if (tags1 == null && tags2 != null) {
191+
if (tags1 == null ^ tags2 == null) {
213192
return false;
214193
}
215194

216195
final String delimiter = ",";
217196

218-
final List<String> lstTags1 = new ArrayList<String>();
197+
final List<String> lstTags1 = new ArrayList<>();
219198
final String[] aTags1 = tags1.split(delimiter);
220199

221200
for (final String tag1 : aTags1) {
222201
lstTags1.add(tag1.toLowerCase());
223202
}
224203

225-
final List<String> lstTags2 = new ArrayList<String>();
204+
final List<String> lstTags2 = new ArrayList<>();
226205
final String[] aTags2 = tags2.split(delimiter);
227206

228207
for (final String tag2 : aTags2) {
@@ -233,7 +212,7 @@ public static boolean areTagsEqual(final String tags1, final String tags2) {
233212
}
234213

235214
public static Map<String, String> stringToMap(final String s) {
236-
final Map<String, String> map = new HashMap<String, String>();
215+
final Map<String, String> map = new HashMap<>();
237216
final String[] elements = s.split(";");
238217
for (final String parts : elements) {
239218
final String[] keyValue = parts.split(":");
@@ -243,14 +222,14 @@ public static Map<String, String> stringToMap(final String s) {
243222
}
244223

245224
public static String mapToString(final Map<String, String> map) {
246-
String s = "";
225+
StringBuilder s = new StringBuilder();
247226
for (final Map.Entry<String, String> entry : map.entrySet()) {
248-
s += entry.getKey() + ":" + entry.getValue() + ";";
227+
s.append(entry.getKey()).append(":").append(entry.getValue()).append(";");
249228
}
250229
if (s.length() > 0) {
251-
s = s.substring(0, s.length() - 1);
230+
s = new StringBuilder(s.substring(0, s.length() - 1));
252231
}
253-
return s;
232+
return s.toString();
254233
}
255234

256235
public static <T> List<T> applyPagination(final List<T> originalList, final Long startIndex, final Long pageSizeVal) {
@@ -271,7 +250,7 @@ public static <T> List<T> applyPagination(final List<T> originalList, final Long
271250
}
272251

273252
private static <T> List<List<T>> partitionList(final List<T> originalList, final int chunkSize) {
274-
final List<List<T>> listOfChunks = new ArrayList<List<T>>();
253+
final List<List<T>> listOfChunks = new ArrayList<>();
275254
for (int i = 0; i < originalList.size() / chunkSize; i++) {
276255
listOfChunks.add(originalList.subList(i * chunkSize, i * chunkSize + chunkSize));
277256
}
@@ -299,9 +278,7 @@ public static Map<String, String> parseJsonToMap(String jsonString) {
299278
if (org.apache.commons.lang3.StringUtils.isNotBlank(jsonString)) {
300279
try {
301280
JsonNode jsonNode = objectMapper.readTree(jsonString);
302-
jsonNode.fields().forEachRemaining(entry -> {
303-
mapResult.put(entry.getKey(), entry.getValue().asText());
304-
});
281+
jsonNode.fields().forEachRemaining(entry -> mapResult.put(entry.getKey(), entry.getValue().asText()));
305282
} catch (Exception e) {
306283
throw new CloudRuntimeException("Error while parsing json to convert it to map " + e.getMessage());
307284
}

utils/src/test/java/com/cloud/utils/StringUtilsTest.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,21 @@ public void testCleanSecretkeyFromRequestString() {
240240
assertEquals(result, expected);
241241
}
242242

243+
@Test
244+
public void testCleanSessionkeyFromAccessLogString() {
245+
final String input = "GET /client/api/?managementserverid=cad7010f-216f-48cb-af11-280588863c4e&command=readyForShutdown&response=json&sessionkey=-FrgnKy6pj-JB4BI2sXqo HTTP/1.1\" 200 180 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Safari/605.1.15 Ddg/18.5";
246+
final String expected = "GET /client/api/?managementserverid=cad7010f-216f-48cb-af11-280588863c4e&command=readyForShutdown&response=json& HTTP/1.1\" 200 180 \"-\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Safari/605.1.15 Ddg/18.5";
247+
final String result = StringUtils.cleanString(input);
248+
assertEquals(expected, result);
249+
}
250+
@Test
251+
public void testCleanSessionkeyFromRequestJsonString() {
252+
final String input = "{id=64b5e71d-2ae8-11ef-9466-1e00c400042b, showicon=true, command=listUsers, response=json, sessionkey=lXfAicKQXPBzt7KjLx6DwVfcOuA}";
253+
final String expected = "{id=64b5e71d-2ae8-11ef-9466-1e00c400042b, showicon=true, command=listUsers, response=json, }";
254+
final String result = StringUtils.cleanString(input);
255+
assertEquals(expected, result);
256+
}
257+
243258
@Test
244259
public void listToCsvTags() {
245260
assertEquals("a,b,c", StringUtils.listToCsvTags(Arrays.asList("a","b", "c")));

0 commit comments

Comments
 (0)