Skip to content

Commit

Permalink
feat: implement context handler to store HTTP request and tracing inf…
Browse files Browse the repository at this point in the history
…ormation (#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>
  • Loading branch information
minherz and gcf-owl-bot[bot] committed Nov 23, 2021
1 parent af7d087 commit 86223ff
Show file tree
Hide file tree
Showing 10 changed files with 493 additions and 12 deletions.
@@ -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();
}
}
@@ -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();
}
}
Expand Up @@ -36,6 +36,7 @@
public final class HttpRequest implements Serializable {

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

private final RequestMethod requestMethod;
private final String requestUrl;
Expand Down
Expand Up @@ -27,6 +27,7 @@
* parameter in https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry)
*/
public final class LogDestinationName extends Option {
private static final long serialVersionUID = 7944256748441111191L;

enum DestinationType implements Option.OptionType {
PROJECT,
Expand Down
Expand Up @@ -40,6 +40,7 @@ class LoggingConfig {
private static final String SYNCHRONICITY_TAG = "synchronicity";
private static final String RESOURCE_TYPE_TAG = "resourceType";
private static final String ENHANCERS_TAG = "enhancers";
private static final String USE_INHERITED_CONTEXT = "useInheritedContext";

public LoggingConfig(String className) {
this.className = className;
Expand Down Expand Up @@ -100,6 +101,18 @@ List<LoggingEnhancer> getEnhancers() {
return Collections.emptyList();
}

/**
* Returns boolean value of the property {@code
* com.google.cloud.logging.context.ContextHandler.useInheritedContext}. If no value is defined or
* the property does not represent a valid boolean value returns {@code false}.
*
* @return {@code true} or {@code false}
*/
boolean getUseInheritedContext() {
String flag = getProperty(USE_INHERITED_CONTEXT, "FALSE");
return Boolean.parseBoolean(flag);
}

private String getProperty(String name, String defaultValue) {
return firstNonNull(getProperty(name), defaultValue);
}
Expand All @@ -121,7 +134,7 @@ private Filter getFilterProperty(String name, Filter defaultValue) {
String stringFilter = getProperty(name);
try {
if (stringFilter != null) {
Class clz = ClassLoader.getSystemClassLoader().loadClass(stringFilter);
Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(stringFilter);
return (Filter) clz.getDeclaredConstructor().newInstance();
}
} catch (Exception ex) {
Expand All @@ -134,7 +147,7 @@ private Formatter getFormatterProperty(String name, Formatter defaultValue) {
String stringFilter = getProperty(name);
try {
if (stringFilter != null) {
Class clz = ClassLoader.getSystemClassLoader().loadClass(stringFilter);
Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(stringFilter);
return (Formatter) clz.getDeclaredConstructor().newInstance();
}
} catch (Exception ex) {
Expand Down
Expand Up @@ -200,6 +200,7 @@ public ApiFuture<AsyncPage<Sink>> getNextPage() {
}

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

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

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

ExclusionPageFetcher(
LoggingOptions serviceOptions, String cursor, Map<Option.OptionType, ?> requestOptions) {
Expand Down
Expand Up @@ -19,16 +19,9 @@
/* Adds tracing support for logging with thread-local trace ID tracking. */
public class TraceLoggingEnhancer implements LoggingEnhancer {

private static final String TRACE_ID = "trace_id";
private final String traceIdLabel;
public TraceLoggingEnhancer() {}

public TraceLoggingEnhancer() {
traceIdLabel = TRACE_ID;
}

public TraceLoggingEnhancer(String prefix) {
traceIdLabel = (prefix != null) ? prefix + TRACE_ID : TRACE_ID;
}
public TraceLoggingEnhancer(String prefix) {}

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

Expand Down

0 comments on commit 86223ff

Please sign in to comment.