fix(logging): allow X-Cloud-Trace-Context fields to be optional (#3062)

* docs(logging): update doc link explaining x-cloud-trace-context

* fix(logging): X-Cloud-Trace-Context fields can be optional (traceSample defaults to false)

* style: go fmt logging.go

* style(logging): updated inline comments to be clearer

* refactor(logging): reCloudTraceContext regex is more readable
diff --git a/logging/logging.go b/logging/logging.go
index 7543bb0..66fe7b2 100644
--- a/logging/logging.go
+++ b/logging/logging.go
@@ -873,33 +873,31 @@
 // (for example by calling SetFlags or SetPrefix).
 func (l *Logger) StandardLogger(s Severity) *log.Logger { return l.stdLoggers[s] }
 
-var reCloudTraceContext = regexp.MustCompile(`([a-f\d]+)/([a-f\d]+);o=(\d)`)
+var reCloudTraceContext = regexp.MustCompile(
+	// Matches on "TRACE_ID"
+	`([a-f\d]+)?` +
+		// Matches on "/SPAN_ID"
+		`(?:/([a-f\d]+))?` +
+		// Matches on ";0=TRACE_TRUE"
+		`(?:;o=(\d))?`)
 
 func deconstructXCloudTraceContext(s string) (traceID, spanID string, traceSampled bool) {
-	// As per the format described at https://cloud.google.com/trace/docs/troubleshooting#force-trace
+	// As per the format described at https://cloud.google.com/trace/docs/setup#force-trace
 	//    "X-Cloud-Trace-Context: TRACE_ID/SPAN_ID;o=TRACE_TRUE"
 	// for example:
-	//    "X-Cloud-Trace-Context: 105445aa7843bc8bf206b120001000/0;o=1"
+	//    "X-Cloud-Trace-Context: 105445aa7843bc8bf206b120001000/1;o=1"
 	//
 	// We expect:
-	//   * traceID:         "105445aa7843bc8bf206b120001000"
-	//   * spanID:          ""
-	//   * traceSampled:    true
-	matches := reCloudTraceContext.FindAllStringSubmatch(s, -1)
-	if len(matches) != 1 {
-		return
-	}
+	//   * traceID (optional): 			"105445aa7843bc8bf206b120001000"
+	//   * spanID (optional):       	"1"
+	//   * traceSampled (optional): 	true
+	matches := reCloudTraceContext.FindStringSubmatch(s)
 
-	sub := matches[0]
-	if len(sub) != 4 {
-		return
-	}
+	traceID, spanID, traceSampled = matches[1], matches[2], matches[3] == "1"
 
-	traceID, spanID = sub[1], sub[2]
 	if spanID == "0" {
 		spanID = ""
 	}
-	traceSampled = sub[3] == "1"
 
 	return
 }
diff --git a/logging/logging_unexported_test.go b/logging/logging_unexported_test.go
index b764688..2a42625 100644
--- a/logging/logging_unexported_test.go
+++ b/logging/logging_unexported_test.go
@@ -289,18 +289,54 @@
 			logging.LogEntry{Trace: "projects/P/traces/105445aa7843bc8bf206b120001000", SpanId: "000000000000004a", TraceSampled: true},
 		},
 		{
+			"X-Trace-Context header with blank trace",
+			Entry{
+				HTTPRequest: &HTTPRequest{
+					Request: &http.Request{
+						URL:    u,
+						Header: http.Header{"X-Cloud-Trace-Context": {"/0;o=1"}},
+					},
+				},
+			},
+			logging.LogEntry{TraceSampled: true},
+		},
+		{
 			"X-Trace-Context header with blank span",
 			Entry{
 				HTTPRequest: &HTTPRequest{
 					Request: &http.Request{
 						URL:    u,
-						Header: http.Header{"X-Cloud-Trace-Context": {"105445aa7843bc8bf206b120001000/0;o=0"}},
+						Header: http.Header{"X-Cloud-Trace-Context": {"105445aa7843bc8bf206b120001000/;o=0"}},
 					},
 				},
 			},
 			logging.LogEntry{Trace: "projects/P/traces/105445aa7843bc8bf206b120001000"},
 		},
 		{
+			"X-Trace-Context header with missing traceSampled aka ?o=*",
+			Entry{
+				HTTPRequest: &HTTPRequest{
+					Request: &http.Request{
+						URL:    u,
+						Header: http.Header{"X-Cloud-Trace-Context": {"105445aa7843bc8bf206b120001000/0"}},
+					},
+				},
+			},
+			logging.LogEntry{Trace: "projects/P/traces/105445aa7843bc8bf206b120001000"},
+		},
+		{
+			"X-Trace-Context header with all blank fields",
+			Entry{
+				HTTPRequest: &HTTPRequest{
+					Request: &http.Request{
+						URL:    u,
+						Header: http.Header{"X-Cloud-Trace-Context": {""}},
+					},
+				},
+			},
+			logging.LogEntry{},
+		},
+		{
 			"Invalid X-Trace-Context header but already set TraceID",
 			Entry{
 				HTTPRequest: &HTTPRequest{