-
Notifications
You must be signed in to change notification settings - Fork 39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: implement context handler to store HTTP request and tracing information #752
Merged
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
ebc8189
chore: removing compilation warning
minherz f1e08bb
feat: add support for request and tracing context
minherz 71d0aa9
pull latest env-tests-logging submodule
minherz 3ad9f36
chore: add empty HttpRequest constant
minherz ff53f73
chore: add unit testing for context classes
minherz 831590f
chore: improve code clarity
minherz ba61b6d
chore: refactoring code
minherz 238c9b5
chore: add tests to validate strict W3C trace parent formatting
minherz 2a6af06
chore: remove context scope
minherz 0cc14d4
chore: refactor ContextHolder implementation
minherz caef1c1
chore: minor fix of method comments
minherz 3105d17
🦉 Updates from OwlBot
gcf-owl-bot[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Submodule env-tests-logging
updated
5 files
235 changes: 235 additions & 0 deletions
235
google-cloud-logging/src/main/java/com/google/cloud/logging/Context.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,235 @@ | ||
/* | ||
* Copyright 2021 Google LLC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.google.cloud.logging; | ||
|
||
import com.google.cloud.logging.HttpRequest.RequestMethod; | ||
import com.google.common.base.MoreObjects; | ||
import com.google.common.base.Strings; | ||
import java.util.Objects; | ||
|
||
/** Class to hold context attributes including information about {@see HttpRequest} and tracing. */ | ||
public class Context { | ||
private final HttpRequest request; | ||
private final String traceId; | ||
private final String spanId; | ||
|
||
/** A builder for {@see Context} objects. */ | ||
public static final class Builder { | ||
private HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(); | ||
private String traceId; | ||
private String spanId; | ||
|
||
Builder() {} | ||
|
||
Builder(Context context) { | ||
this.requestBuilder = context.request.toBuilder(); | ||
this.traceId = context.traceId; | ||
this.spanId = context.spanId; | ||
} | ||
|
||
/** Sets the HTTP request. */ | ||
public Builder setRequest(HttpRequest request) { | ||
this.requestBuilder = request.toBuilder(); | ||
return this; | ||
} | ||
|
||
public Builder setRequestUrl(String url) { | ||
this.requestBuilder.setRequestUrl(url); | ||
return this; | ||
} | ||
|
||
/** Sets the HTTP request method. */ | ||
public Builder setRequestMethod(RequestMethod method) { | ||
this.requestBuilder.setRequestMethod(method); | ||
return this; | ||
} | ||
|
||
/** | ||
* Sets the referer URL of the request, as defined in HTTP/1.1 Header Field Definitions. | ||
* | ||
* @see <a href= "http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html">HTTP/1.1 Header Field | ||
* Definitions</a> | ||
*/ | ||
public Builder setReferer(String referer) { | ||
this.requestBuilder.setReferer(referer); | ||
return this; | ||
} | ||
|
||
/** | ||
* Sets the IP address (IPv4 or IPv6) of the client that issued the HTTP request. Examples: | ||
* {@code 192.168.1.1}, {@code FE80::0202:B3FF:FE1E:8329}. | ||
*/ | ||
public Builder setRemoteIp(String remoteIp) { | ||
this.requestBuilder.setRemoteIp(remoteIp); | ||
return this; | ||
} | ||
|
||
/** | ||
* Sets the IP address (IPv4 or IPv6) of the origin server that the request was sent to. | ||
* Examples: {@code 192.168.1.1}, {@code FE80::0202:B3FF:FE1E:8329}. | ||
*/ | ||
public Builder setServerIp(String serverIp) { | ||
this.requestBuilder.setServerIp(serverIp); | ||
return this; | ||
} | ||
|
||
/** Sets the string as a trace id value. */ | ||
public Builder setTraceId(String traceId) { | ||
this.traceId = traceId; | ||
return this; | ||
} | ||
|
||
/** Sets the string as a span id value. */ | ||
public Builder setSpanId(String spanId) { | ||
this.spanId = spanId; | ||
return this; | ||
} | ||
|
||
/** | ||
* Sets the trace id and span id values by parsing the string which represents xCloud Trace | ||
* Context. The Cloud Trace Context is passed as {@code x-cloud-trace-context} header (can be in | ||
* Pascal case format). The string format is <code>TRACE_ID/SPAN_ID;o=TRACE_TRUE</code>. | ||
* | ||
* @see <a href="https://cloud.google.com/trace/docs/setup#force-trace">Cloud Trace header | ||
* format.</a> | ||
*/ | ||
public Builder loadCloudTraceContext(String cloudTrace) { | ||
if (cloudTrace != null) { | ||
cloudTrace = cloudTrace.split(";")[0]; | ||
int split = cloudTrace.indexOf('/'); | ||
if (split >= 0) { | ||
String traceId = cloudTrace.substring(0, split); | ||
String spanId = cloudTrace.substring(split + 1); | ||
if (!traceId.isEmpty()) { | ||
setTraceId(traceId); | ||
// do not set span Id without trace Id | ||
if (!spanId.isEmpty()) { | ||
setSpanId(spanId); | ||
} | ||
} | ||
} else if (!cloudTrace.isEmpty()) { | ||
setTraceId(cloudTrace); | ||
} | ||
} | ||
return this; | ||
} | ||
|
||
/** | ||
* Sets the trace id and span id values by parsing the string which represents the standard W3C | ||
* trace context propagation header. The context propagation header is passed as {@code | ||
* traceparent} header. The method currently supports ONLY version {@code "00"}. The string | ||
* format is <code>00-TRACE_ID-SPAN_ID-FLAGS</code>. field of the {@code version-format} value. | ||
* | ||
* @see <a href= | ||
* "https://www.w3.org/TR/trace-context/#traceparent-header-field-values">traceparent header | ||
* value format</a> | ||
* @throws IllegalArgumentException if passed argument does not follow the @W3C trace format or | ||
* the format version is not supported. | ||
*/ | ||
public Builder loadW3CTraceParentContext(String traceParent) throws IllegalArgumentException { | ||
if (traceParent != null) { | ||
String[] fields = traceParent.split("-"); | ||
if (fields.length > 3) { | ||
String versionFormat = fields[0]; | ||
if (!versionFormat.equals("00")) { | ||
throw new IllegalArgumentException("Not supporting versionFormat other than \"00\""); | ||
} | ||
} else { | ||
throw new IllegalArgumentException( | ||
"Invalid format of the header value. Expected \"00-traceid-spanid-arguments\""); | ||
} | ||
String traceId = fields[1]; | ||
if (!traceId.isEmpty()) { | ||
setTraceId(traceId); | ||
} | ||
if (!Strings.isNullOrEmpty(traceId)) { | ||
String spanId = fields[2]; | ||
if (!spanId.isEmpty()) { | ||
setSpanId(spanId); | ||
} | ||
} | ||
} | ||
return this; | ||
} | ||
|
||
/** Creates a {@see Context} object for this builder. */ | ||
public Context build() { | ||
return new Context(this); | ||
} | ||
} | ||
|
||
Context(Builder builder) { | ||
HttpRequest request = builder.requestBuilder.build(); | ||
if (!HttpRequest.EMPTY.equals(request)) { | ||
this.request = request; | ||
} else { | ||
this.request = null; | ||
} | ||
this.traceId = builder.traceId; | ||
this.spanId = builder.spanId; | ||
} | ||
|
||
public HttpRequest getHttpRequest() { | ||
return this.request; | ||
} | ||
|
||
public String getTraceId() { | ||
return this.traceId; | ||
} | ||
|
||
public String getSpanId() { | ||
return this.spanId; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(request, traceId, spanId); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return MoreObjects.toStringHelper(this) | ||
.add("request", request) | ||
.add("traceId", traceId) | ||
.add("spanId", spanId) | ||
.toString(); | ||
} | ||
|
||
@Override | ||
public boolean equals(Object obj) { | ||
if (obj == this) { | ||
return true; | ||
} | ||
if (!(obj instanceof Context)) { | ||
return false; | ||
} | ||
Context other = (Context) obj; | ||
return Objects.equals(request, other.request) | ||
&& Objects.equals(traceId, other.traceId) | ||
&& Objects.equals(spanId, other.spanId); | ||
} | ||
|
||
/** Returns a builder for this object. */ | ||
public Builder toBuilder() { | ||
return new Builder(this); | ||
} | ||
|
||
/** Returns a builder for {@code HttpRequest} objects. */ | ||
public static Builder newBuilder() { | ||
return new Builder(); | ||
} | ||
} |
50 changes: 50 additions & 0 deletions
50
google-cloud-logging/src/main/java/com/google/cloud/logging/ContextHandler.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/* | ||
* Copyright 2021 Google LLC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.google.cloud.logging; | ||
|
||
/** Class provides a per-thread storage of the {@see Context} instances. */ | ||
public class ContextHandler { | ||
private static final ThreadLocal<Context> contextHolder = initContextHolder(); | ||
|
||
/** | ||
* Initializes the context holder to {@link InheritableThreadLocal} if {@link LogManager} | ||
* configuration property {@code com.google.cloud.logging.ContextHandler.useInheritedContext} is | ||
* set to {@code true} or to {@link ThreadLocal} otherwise. | ||
* | ||
* @return instance of the context holder. | ||
*/ | ||
private static ThreadLocal<Context> initContextHolder() { | ||
LoggingConfig config = new LoggingConfig(ContextHandler.class.getName()); | ||
if (config.getUseInheritedContext()) { | ||
return new InheritableThreadLocal<>(); | ||
} else { | ||
return new ThreadLocal<>(); | ||
} | ||
} | ||
|
||
public Context getCurrentContext() { | ||
return contextHolder.get(); | ||
} | ||
|
||
public void setCurrentContext(Context context) { | ||
contextHolder.set(context); | ||
} | ||
|
||
public void removeCurrentContext() { | ||
contextHolder.remove(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should make it generic by turning this into template to be reused with other objects (not only Context). Seems that
TraceLoggingEnhancer
below also usedThreadLocal
and might benefit from it as wellThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should focus on supporting "our" context since this is what this feature is designed to be. Providing template means we include a generic-purpose implementation into logging library code.
If there will be a need for additional information for the context, the
Context
class can be extended to include additional data fields.