Skip to content

Commit 86223ff

Browse files
feat: implement context handler to store HTTP request and tracing information (#752)
Provide context abstraction to instantiate a context with the info about HttpRequest and tracing (trace id and span id). Provide handler to setup current context context per-thread to support Web servers that handle each request in a dedicated thread. Add empty HttpRequest instance as a constant object to reference when building a new Context. Remove compilation warnings related to serialVersionUID and unused objects. Pull latest env-tests-logging submodule. Support (via configuration property) a choice between InheritableThreadLocal and ThreadLocal as holders for current context. * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent af7d087 commit 86223ff

File tree

10 files changed

+493
-12
lines changed

10 files changed

+493
-12
lines changed
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/*
2+
* Copyright 2021 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.logging;
18+
19+
import com.google.cloud.logging.HttpRequest.RequestMethod;
20+
import com.google.common.base.MoreObjects;
21+
import com.google.common.base.Strings;
22+
import java.util.Objects;
23+
24+
/** Class to hold context attributes including information about {@see HttpRequest} and tracing. */
25+
public class Context {
26+
private final HttpRequest request;
27+
private final String traceId;
28+
private final String spanId;
29+
30+
/** A builder for {@see Context} objects. */
31+
public static final class Builder {
32+
private HttpRequest.Builder requestBuilder = HttpRequest.newBuilder();
33+
private String traceId;
34+
private String spanId;
35+
36+
Builder() {}
37+
38+
Builder(Context context) {
39+
this.requestBuilder = context.request.toBuilder();
40+
this.traceId = context.traceId;
41+
this.spanId = context.spanId;
42+
}
43+
44+
/** Sets the HTTP request. */
45+
public Builder setRequest(HttpRequest request) {
46+
this.requestBuilder = request.toBuilder();
47+
return this;
48+
}
49+
50+
public Builder setRequestUrl(String url) {
51+
this.requestBuilder.setRequestUrl(url);
52+
return this;
53+
}
54+
55+
/** Sets the HTTP request method. */
56+
public Builder setRequestMethod(RequestMethod method) {
57+
this.requestBuilder.setRequestMethod(method);
58+
return this;
59+
}
60+
61+
/**
62+
* Sets the referer URL of the request, as defined in HTTP/1.1 Header Field Definitions.
63+
*
64+
* @see <a href= "http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html">HTTP/1.1 Header Field
65+
* Definitions</a>
66+
*/
67+
public Builder setReferer(String referer) {
68+
this.requestBuilder.setReferer(referer);
69+
return this;
70+
}
71+
72+
/**
73+
* Sets the IP address (IPv4 or IPv6) of the client that issued the HTTP request. Examples:
74+
* {@code 192.168.1.1}, {@code FE80::0202:B3FF:FE1E:8329}.
75+
*/
76+
public Builder setRemoteIp(String remoteIp) {
77+
this.requestBuilder.setRemoteIp(remoteIp);
78+
return this;
79+
}
80+
81+
/**
82+
* Sets the IP address (IPv4 or IPv6) of the origin server that the request was sent to.
83+
* Examples: {@code 192.168.1.1}, {@code FE80::0202:B3FF:FE1E:8329}.
84+
*/
85+
public Builder setServerIp(String serverIp) {
86+
this.requestBuilder.setServerIp(serverIp);
87+
return this;
88+
}
89+
90+
/** Sets the string as a trace id value. */
91+
public Builder setTraceId(String traceId) {
92+
this.traceId = traceId;
93+
return this;
94+
}
95+
96+
/** Sets the string as a span id value. */
97+
public Builder setSpanId(String spanId) {
98+
this.spanId = spanId;
99+
return this;
100+
}
101+
102+
/**
103+
* Sets the trace id and span id values by parsing the string which represents xCloud Trace
104+
* Context. The Cloud Trace Context is passed as {@code x-cloud-trace-context} header (can be in
105+
* Pascal case format). The string format is <code>TRACE_ID/SPAN_ID;o=TRACE_TRUE</code>.
106+
*
107+
* @see <a href="https://cloud.google.com/trace/docs/setup#force-trace">Cloud Trace header
108+
* format.</a>
109+
*/
110+
public Builder loadCloudTraceContext(String cloudTrace) {
111+
if (cloudTrace != null) {
112+
cloudTrace = cloudTrace.split(";")[0];
113+
int split = cloudTrace.indexOf('/');
114+
if (split >= 0) {
115+
String traceId = cloudTrace.substring(0, split);
116+
String spanId = cloudTrace.substring(split + 1);
117+
if (!traceId.isEmpty()) {
118+
setTraceId(traceId);
119+
// do not set span Id without trace Id
120+
if (!spanId.isEmpty()) {
121+
setSpanId(spanId);
122+
}
123+
}
124+
} else if (!cloudTrace.isEmpty()) {
125+
setTraceId(cloudTrace);
126+
}
127+
}
128+
return this;
129+
}
130+
131+
/**
132+
* Sets the trace id and span id values by parsing the string which represents the standard W3C
133+
* trace context propagation header. The context propagation header is passed as {@code
134+
* traceparent} header. The method currently supports ONLY version {@code "00"}. The string
135+
* format is <code>00-TRACE_ID-SPAN_ID-FLAGS</code>. field of the {@code version-format} value.
136+
*
137+
* @see <a href=
138+
* "https://www.w3.org/TR/trace-context/#traceparent-header-field-values">traceparent header
139+
* value format</a>
140+
* @throws IllegalArgumentException if passed argument does not follow the @W3C trace format or
141+
* the format version is not supported.
142+
*/
143+
public Builder loadW3CTraceParentContext(String traceParent) throws IllegalArgumentException {
144+
if (traceParent != null) {
145+
String[] fields = traceParent.split("-");
146+
if (fields.length > 3) {
147+
String versionFormat = fields[0];
148+
if (!versionFormat.equals("00")) {
149+
throw new IllegalArgumentException("Not supporting versionFormat other than \"00\"");
150+
}
151+
} else {
152+
throw new IllegalArgumentException(
153+
"Invalid format of the header value. Expected \"00-traceid-spanid-arguments\"");
154+
}
155+
String traceId = fields[1];
156+
if (!traceId.isEmpty()) {
157+
setTraceId(traceId);
158+
}
159+
if (!Strings.isNullOrEmpty(traceId)) {
160+
String spanId = fields[2];
161+
if (!spanId.isEmpty()) {
162+
setSpanId(spanId);
163+
}
164+
}
165+
}
166+
return this;
167+
}
168+
169+
/** Creates a {@see Context} object for this builder. */
170+
public Context build() {
171+
return new Context(this);
172+
}
173+
}
174+
175+
Context(Builder builder) {
176+
HttpRequest request = builder.requestBuilder.build();
177+
if (!HttpRequest.EMPTY.equals(request)) {
178+
this.request = request;
179+
} else {
180+
this.request = null;
181+
}
182+
this.traceId = builder.traceId;
183+
this.spanId = builder.spanId;
184+
}
185+
186+
public HttpRequest getHttpRequest() {
187+
return this.request;
188+
}
189+
190+
public String getTraceId() {
191+
return this.traceId;
192+
}
193+
194+
public String getSpanId() {
195+
return this.spanId;
196+
}
197+
198+
@Override
199+
public int hashCode() {
200+
return Objects.hash(request, traceId, spanId);
201+
}
202+
203+
@Override
204+
public String toString() {
205+
return MoreObjects.toStringHelper(this)
206+
.add("request", request)
207+
.add("traceId", traceId)
208+
.add("spanId", spanId)
209+
.toString();
210+
}
211+
212+
@Override
213+
public boolean equals(Object obj) {
214+
if (obj == this) {
215+
return true;
216+
}
217+
if (!(obj instanceof Context)) {
218+
return false;
219+
}
220+
Context other = (Context) obj;
221+
return Objects.equals(request, other.request)
222+
&& Objects.equals(traceId, other.traceId)
223+
&& Objects.equals(spanId, other.spanId);
224+
}
225+
226+
/** Returns a builder for this object. */
227+
public Builder toBuilder() {
228+
return new Builder(this);
229+
}
230+
231+
/** Returns a builder for {@code HttpRequest} objects. */
232+
public static Builder newBuilder() {
233+
return new Builder();
234+
}
235+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2021 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.logging;
18+
19+
/** Class provides a per-thread storage of the {@see Context} instances. */
20+
public class ContextHandler {
21+
private static final ThreadLocal<Context> contextHolder = initContextHolder();
22+
23+
/**
24+
* Initializes the context holder to {@link InheritableThreadLocal} if {@link LogManager}
25+
* configuration property {@code com.google.cloud.logging.ContextHandler.useInheritedContext} is
26+
* set to {@code true} or to {@link ThreadLocal} otherwise.
27+
*
28+
* @return instance of the context holder.
29+
*/
30+
private static ThreadLocal<Context> initContextHolder() {
31+
LoggingConfig config = new LoggingConfig(ContextHandler.class.getName());
32+
if (config.getUseInheritedContext()) {
33+
return new InheritableThreadLocal<>();
34+
} else {
35+
return new ThreadLocal<>();
36+
}
37+
}
38+
39+
public Context getCurrentContext() {
40+
return contextHolder.get();
41+
}
42+
43+
public void setCurrentContext(Context context) {
44+
contextHolder.set(context);
45+
}
46+
47+
public void removeCurrentContext() {
48+
contextHolder.remove();
49+
}
50+
}

google-cloud-logging/src/main/java/com/google/cloud/logging/HttpRequest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
public final class HttpRequest implements Serializable {
3737

3838
private static final long serialVersionUID = -274998005454709817L;
39+
public static final HttpRequest EMPTY = newBuilder().build();
3940

4041
private final RequestMethod requestMethod;
4142
private final String requestUrl;

google-cloud-logging/src/main/java/com/google/cloud/logging/LogDestinationName.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
* parameter in https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry)
2828
*/
2929
public final class LogDestinationName extends Option {
30+
private static final long serialVersionUID = 7944256748441111191L;
3031

3132
enum DestinationType implements Option.OptionType {
3233
PROJECT,

google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingConfig.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class LoggingConfig {
4040
private static final String SYNCHRONICITY_TAG = "synchronicity";
4141
private static final String RESOURCE_TYPE_TAG = "resourceType";
4242
private static final String ENHANCERS_TAG = "enhancers";
43+
private static final String USE_INHERITED_CONTEXT = "useInheritedContext";
4344

4445
public LoggingConfig(String className) {
4546
this.className = className;
@@ -100,6 +101,18 @@ List<LoggingEnhancer> getEnhancers() {
100101
return Collections.emptyList();
101102
}
102103

104+
/**
105+
* Returns boolean value of the property {@code
106+
* com.google.cloud.logging.context.ContextHandler.useInheritedContext}. If no value is defined or
107+
* the property does not represent a valid boolean value returns {@code false}.
108+
*
109+
* @return {@code true} or {@code false}
110+
*/
111+
boolean getUseInheritedContext() {
112+
String flag = getProperty(USE_INHERITED_CONTEXT, "FALSE");
113+
return Boolean.parseBoolean(flag);
114+
}
115+
103116
private String getProperty(String name, String defaultValue) {
104117
return firstNonNull(getProperty(name), defaultValue);
105118
}
@@ -121,7 +134,7 @@ private Filter getFilterProperty(String name, Filter defaultValue) {
121134
String stringFilter = getProperty(name);
122135
try {
123136
if (stringFilter != null) {
124-
Class clz = ClassLoader.getSystemClassLoader().loadClass(stringFilter);
137+
Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(stringFilter);
125138
return (Filter) clz.getDeclaredConstructor().newInstance();
126139
}
127140
} catch (Exception ex) {
@@ -134,7 +147,7 @@ private Formatter getFormatterProperty(String name, Formatter defaultValue) {
134147
String stringFilter = getProperty(name);
135148
try {
136149
if (stringFilter != null) {
137-
Class clz = ClassLoader.getSystemClassLoader().loadClass(stringFilter);
150+
Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(stringFilter);
138151
return (Formatter) clz.getDeclaredConstructor().newInstance();
139152
}
140153
} catch (Exception ex) {

google-cloud-logging/src/main/java/com/google/cloud/logging/LoggingImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ public ApiFuture<AsyncPage<Sink>> getNextPage() {
200200
}
201201

202202
private static class LogNamePageFetcher extends BasePageFetcher<String> {
203+
private static final long serialVersionUID = 5308841362690185583L;
203204

204205
LogNamePageFetcher(
205206
LoggingOptions serviceOptions, String cursor, Map<Option.OptionType, ?> requestOptions) {
@@ -244,6 +245,7 @@ public ApiFuture<AsyncPage<Metric>> getNextPage() {
244245
}
245246

246247
private static class ExclusionPageFetcher extends BasePageFetcher<Exclusion> {
248+
private static final long serialVersionUID = -1414118808031778916L;
247249

248250
ExclusionPageFetcher(
249251
LoggingOptions serviceOptions, String cursor, Map<Option.OptionType, ?> requestOptions) {

google-cloud-logging/src/main/java/com/google/cloud/logging/TraceLoggingEnhancer.java

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,9 @@
1919
/* Adds tracing support for logging with thread-local trace ID tracking. */
2020
public class TraceLoggingEnhancer implements LoggingEnhancer {
2121

22-
private static final String TRACE_ID = "trace_id";
23-
private final String traceIdLabel;
22+
public TraceLoggingEnhancer() {}
2423

25-
public TraceLoggingEnhancer() {
26-
traceIdLabel = TRACE_ID;
27-
}
28-
29-
public TraceLoggingEnhancer(String prefix) {
30-
traceIdLabel = (prefix != null) ? prefix + TRACE_ID : TRACE_ID;
31-
}
24+
public TraceLoggingEnhancer(String prefix) {}
3225

3326
private static final ThreadLocal<String> traceId = new ThreadLocal<>();
3427

0 commit comments

Comments
 (0)