Allow to trace REST requests that match a URI regex by config
Sometimes there are issues that are hard to catch, but which only occur
for a certain REST requests. In this case it is convenient if tracing
for these requests can be enabled by configuration.
It is now possible to configure a regular expression for a request URI
to trigger automatic tracing for matching REST requests.
Change-Id: I264c75259fe1f254fed059bfaa3767af407546f1
Signed-off-by: Edwin Kempin <ekempin@google.com>
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index b27c33c..1601d7a 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -4570,6 +4570,16 @@
+
By default, unset (all request types are matched).
+[[tracing.traceid.requestUriPattern]]tracing.<trace-id>.requestUriPattern::
++
+Regular expression to match request URIs for which request tracing
+should be always enabled. Request URIs are only available for REST
+requests. Request URIs never include the '/a' prefix.
++
+May be specified multiple times.
++
+By default, unset (all request URIs are matched).
+
[[tracing.traceid.account]]tracing.<trace-id>.account::
+
Account ID of an account for which request tracing should be always
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index 736018a..2128777b 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -317,7 +317,7 @@
try (TraceContext traceContext = enableTracing(req, res)) {
List<IdString> path = splitPath(req);
- RequestInfo requestInfo = createRequestInfo(traceContext, path);
+ RequestInfo requestInfo = createRequestInfo(traceContext, requestUri(req), path);
globals.requestListeners.runEach(l -> l.onRequest(requestInfo));
try (PerThreadCache ignored = PerThreadCache.create()) {
@@ -1424,9 +1424,11 @@
return traceContext;
}
- private RequestInfo createRequestInfo(TraceContext traceContext, List<IdString> path) {
+ private RequestInfo createRequestInfo(
+ TraceContext traceContext, String requestUri, List<IdString> path) {
RequestInfo.Builder requestInfo =
- RequestInfo.builder(RequestInfo.RequestType.REST, globals.currentUser.get(), traceContext);
+ RequestInfo.builder(RequestInfo.RequestType.REST, globals.currentUser.get(), traceContext)
+ .requestUri(requestUri);
if (path.size() < 1) {
return requestInfo.build();
diff --git a/java/com/google/gerrit/server/RequestInfo.java b/java/com/google/gerrit/server/RequestInfo.java
index c86d34d..f5749fc 100644
--- a/java/com/google/gerrit/server/RequestInfo.java
+++ b/java/com/google/gerrit/server/RequestInfo.java
@@ -45,6 +45,15 @@
*/
public abstract String requestType();
+ /**
+ * Request URI.
+ *
+ * <p>Only set if request type is {@link RequestType#REST}.
+ *
+ * <p>Never includes the "/a" prefix.
+ */
+ public abstract Optional<String> requestUri();
+
/** The user that has sent the request. */
public abstract CurrentUser callingUser();
@@ -74,6 +83,8 @@
return requestType(requestType.name());
}
+ public abstract Builder requestUri(String requestUri);
+
public abstract Builder callingUser(CurrentUser callingUser);
public abstract Builder traceContext(TraceContext traceContext);
diff --git a/java/com/google/gerrit/server/TraceRequestListener.java b/java/com/google/gerrit/server/TraceRequestListener.java
index 8651915..773f712 100644
--- a/java/com/google/gerrit/server/TraceRequestListener.java
+++ b/java/com/google/gerrit/server/TraceRequestListener.java
@@ -68,6 +68,7 @@
TraceConfig.Builder traceConfig = TraceConfig.builder();
traceConfig.traceId(traceId);
traceConfig.requestTypes(parseRequestTypes(traceId));
+ traceConfig.requestUriPatterns(parseRequestUriPatterns(traceId));
traceConfig.accountIds(parseAccounts(traceId));
traceConfig.projectPatterns(parseProjectPatterns(traceId));
traceConfigs.add(traceConfig.build());
@@ -83,6 +84,11 @@
return ImmutableSet.copyOf(cfg.getStringList("tracing", traceId, "requestType"));
}
+ private ImmutableSet<Pattern> parseRequestUriPatterns(String traceId)
+ throws ConfigInvalidException {
+ return parsePatterns(traceId, "requestUriPattern");
+ }
+
private ImmutableSet<Account.Id> parseAccounts(String traceId) throws ConfigInvalidException {
ImmutableSet.Builder<Account.Id> accountIds = ImmutableSet.builder();
String[] accounts = cfg.getStringList("tracing", traceId, "account");
@@ -100,19 +106,24 @@
}
private ImmutableSet<Pattern> parseProjectPatterns(String traceId) throws ConfigInvalidException {
- ImmutableSet.Builder<Pattern> projectPatterns = ImmutableSet.builder();
- String[] projectPatternRegExs = cfg.getStringList("tracing", traceId, "projectPattern");
- for (String projectPatternRegEx : projectPatternRegExs) {
+ return parsePatterns(traceId, "projectPattern");
+ }
+
+ private ImmutableSet<Pattern> parsePatterns(String traceId, String name)
+ throws ConfigInvalidException {
+ ImmutableSet.Builder<Pattern> patterns = ImmutableSet.builder();
+ String[] patternRegExs = cfg.getStringList("tracing", traceId, name);
+ for (String patternRegEx : patternRegExs) {
try {
- projectPatterns.add(Pattern.compile(projectPatternRegEx));
+ patterns.add(Pattern.compile(patternRegEx));
} catch (PatternSyntaxException e) {
throw new ConfigInvalidException(
String.format(
- "Invalid tracing config ('tracing.%s.projectPattern = %s'): %s",
- traceId, projectPatternRegEx, e.getMessage()));
+ "Invalid tracing config ('tracing.%s.%s = %s'): %s",
+ traceId, name, patternRegEx, e.getMessage()));
}
}
- return projectPatterns.build();
+ return patterns.build();
}
@AutoValue
@@ -123,6 +134,9 @@
/** request types that should be traced */
abstract ImmutableSet<String> requestTypes();
+ /** pattern matching request URIs */
+ abstract ImmutableSet<Pattern> requestUriPatterns();
+
/** accounts IDs matching calling user */
abstract ImmutableSet<Account.Id> accountIds();
@@ -148,6 +162,20 @@
return false;
}
+ // If in the trace config request URI patterns are set and none of them matches, then the
+ // request is not matched.
+ if (!requestUriPatterns().isEmpty()) {
+ if (!requestInfo.requestUri().isPresent()) {
+ // The request has no request URI, hence it cannot match a request URI pattern.
+ return false;
+ }
+
+ if (requestUriPatterns().stream()
+ .noneMatch(p -> p.matcher(requestInfo.requestUri().get()).matches())) {
+ return false;
+ }
+ }
+
// If in the trace config accounts are set and none of them matches, then the request is not
// matched.
if (!accountIds().isEmpty()) {
@@ -176,8 +204,9 @@
}
}
- // For any match criteria (request type, account, project pattern) that was specified in the
- // trace config, at least one of the configured value matched the request.
+ // For any match criteria (request type, request URI pattern, account, project pattern) that
+ // was specified in the trace config, at least one of the configured value matched the
+ // request.
return true;
}
@@ -187,6 +216,8 @@
abstract Builder requestTypes(ImmutableSet<String> requestTypes);
+ abstract Builder requestUriPatterns(ImmutableSet<Pattern> requestUriPatterns);
+
abstract Builder accountIds(ImmutableSet<Account.Id> accountIds);
abstract Builder projectPatterns(ImmutableSet<Pattern> projectPatterns);
diff --git a/javatests/com/google/gerrit/acceptance/rest/TraceIT.java b/javatests/com/google/gerrit/acceptance/rest/TraceIT.java
index 8f36191..3a01517 100644
--- a/javatests/com/google/gerrit/acceptance/rest/TraceIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/TraceIT.java
@@ -542,6 +542,43 @@
assertThat(projectCreationListener.tags.get("project")).containsExactly("new24");
}
+ @Test
+ @GerritConfig(name = "tracing.issue123.requestUriPattern", value = "/projects/.*")
+ public void traceRequestUri() throws Exception {
+ RestResponse response = adminRestSession.put("/projects/new23");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isEqualTo("issue123");
+ assertThat(projectCreationListener.isLoggingForced).isTrue();
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new23");
+ }
+
+ @Test
+ @GerritConfig(name = "tracing.issue123.requestUriPattern", value = "/projects/.*/foo")
+ public void traceRequestUriNoMatch() throws Exception {
+ RestResponse response = adminRestSession.put("/projects/new23");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isNull();
+ assertThat(projectCreationListener.isLoggingForced).isFalse();
+
+ // The logging tag with the project name is also set if tracing is off.
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new23");
+ }
+
+ @Test
+ @GerritConfig(name = "tracing.issue123.requestUriPattern", value = "][")
+ public void traceRequestUriInvalidRegEx() throws Exception {
+ RestResponse response = adminRestSession.put("/projects/new24");
+ assertThat(response.getStatusCode()).isEqualTo(SC_CREATED);
+ assertThat(response.getHeader(RestApiServlet.X_GERRIT_TRACE)).isNull();
+ assertThat(projectCreationListener.traceId).isNull();
+ assertThat(projectCreationListener.isLoggingForced).isFalse();
+
+ // The logging tag with the project name is also set if tracing is off.
+ assertThat(projectCreationListener.tags.get("project")).containsExactly("new24");
+ }
+
private void assertForceLogging(boolean expected) {
assertThat(LoggingContext.getInstance().shouldForceLogging(null, null, false))
.isEqualTo(expected);