/
Context.java
230 lines (204 loc) · 7.3 KB
/
Context.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
/*
* 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 java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** Class to hold context attributes including information about {@see HttpRequest} and tracing. */
public class Context {
// validate W3C trace context value on load according to the existing version format.
// see https://www.w3.org/TR/trace-context/#traceparent-header-field-values for details.
private static final Pattern W3C_TRACE_CONTEXT_FORMAT =
Pattern.compile(
"^00-(?!00000000000000000000000000000000)[0-9a-f]{32}-(?!0000000000000000)[0-9a-f]{16}-[0-9a-f]{2}$");
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) {
Matcher validator = W3C_TRACE_CONTEXT_FORMAT.matcher(traceParent.toLowerCase());
if (!validator.matches()) {
throw new IllegalArgumentException(
"Invalid format of the header value. The value does not match W3C Trace Context version \"00\"");
}
String[] fields = traceParent.split("-");
setTraceId(fields[1]);
setSpanId(fields[2]);
// fields[3] contains flag(s)
}
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();
}
}