| // Copyright 2016 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 |
| // |
| // http://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 internal |
| |
| import ( |
| "bytes" |
| "encoding/json" |
| "fmt" |
| "regexp" |
| "strings" |
| "unicode/utf8" |
| |
| "google.golang.org/protobuf/types/known/structpb" |
| ) |
| |
| const ( |
| // ProdAddr is the production address. |
| ProdAddr = "logging.googleapis.com:443" |
| |
| // TraceHeader is the HTTP header trace information is stored in. |
| TraceHeader = "X-Cloud-Trace-Context" |
| ) |
| |
| // LogPath creates a formatted path from a parent and a logID. |
| func LogPath(parent, logID string) string { |
| logID = strings.Replace(logID, "/", "%2F", -1) |
| return fmt.Sprintf("%s/logs/%s", parent, logID) |
| } |
| |
| // LogIDFromPath parses and returns the ID from a log path. |
| func LogIDFromPath(parent, path string) string { |
| start := len(parent) + len("/logs/") |
| if len(path) < start { |
| return "" |
| } |
| logID := path[start:] |
| return strings.Replace(logID, "%2F", "/", -1) |
| } |
| |
| 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))?`) |
| |
| // DeconstructXCloudTraceContext extracts trace information from the trace |
| // header. |
| // |
| // 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/1;o=1" |
| // |
| // We expect: |
| // * traceID (optional): "105445aa7843bc8bf206b120001000" |
| // * spanID (optional): "1" |
| // * traceSampled (optional): true |
| func DeconstructXCloudTraceContext(s string) (traceID, spanID string, traceSampled bool) { |
| matches := reCloudTraceContext.FindStringSubmatch(s) |
| |
| traceID, spanID, traceSampled = matches[1], matches[2], matches[3] == "1" |
| |
| if spanID == "0" { |
| spanID = "" |
| } |
| |
| return |
| } |
| |
| // FixUTF8 is a helper that fixes an invalid UTF-8 string by replacing |
| // invalid UTF-8 runes with the Unicode replacement character (U+FFFD). |
| // See Issue https://github.com/googleapis/google-cloud-go/issues/1383. |
| func FixUTF8(s string) string { |
| if utf8.ValidString(s) { |
| return s |
| } |
| |
| // Otherwise time to build the sequence. |
| buf := new(bytes.Buffer) |
| buf.Grow(len(s)) |
| for _, r := range s { |
| if utf8.ValidRune(r) { |
| buf.WriteRune(r) |
| } else { |
| buf.WriteRune('\uFFFD') |
| } |
| } |
| return buf.String() |
| } |
| |
| // ToProtoStruct converts v, which must marshal into a JSON object, |
| // into a Google Struct proto. |
| func ToProtoStruct(v interface{}) (*structpb.Struct, error) { |
| // Fast path: if v is already a *structpb.Struct, nothing to do. |
| if s, ok := v.(*structpb.Struct); ok { |
| return s, nil |
| } |
| // v is a Go value that supports JSON marshalling. We want a Struct |
| // protobuf. Some day we may have a more direct way to get there, but right |
| // now the only way is to marshal the Go value to JSON, unmarshal into a |
| // map, and then build the Struct proto from the map. |
| var jb []byte |
| var err error |
| if raw, ok := v.(json.RawMessage); ok { // needed for Go 1.7 and below |
| jb = []byte(raw) |
| } else { |
| jb, err = json.Marshal(v) |
| if err != nil { |
| return nil, fmt.Errorf("logging: json.Marshal: %v", err) |
| } |
| } |
| var m map[string]interface{} |
| err = json.Unmarshal(jb, &m) |
| if err != nil { |
| return nil, fmt.Errorf("logging: json.Unmarshal: %v", err) |
| } |
| return jsonMapToProtoStruct(m), nil |
| } |
| |
| func jsonMapToProtoStruct(m map[string]interface{}) *structpb.Struct { |
| fields := map[string]*structpb.Value{} |
| for k, v := range m { |
| fields[k] = jsonValueToStructValue(v) |
| } |
| return &structpb.Struct{Fields: fields} |
| } |
| |
| func jsonValueToStructValue(v interface{}) *structpb.Value { |
| switch x := v.(type) { |
| case bool: |
| return &structpb.Value{Kind: &structpb.Value_BoolValue{BoolValue: x}} |
| case float64: |
| return &structpb.Value{Kind: &structpb.Value_NumberValue{NumberValue: x}} |
| case string: |
| return &structpb.Value{Kind: &structpb.Value_StringValue{StringValue: x}} |
| case nil: |
| return &structpb.Value{Kind: &structpb.Value_NullValue{}} |
| case map[string]interface{}: |
| return &structpb.Value{Kind: &structpb.Value_StructValue{StructValue: jsonMapToProtoStruct(x)}} |
| case []interface{}: |
| var vals []*structpb.Value |
| for _, e := range x { |
| vals = append(vals, jsonValueToStructValue(e)) |
| } |
| return &structpb.Value{Kind: &structpb.Value_ListValue{ListValue: &structpb.ListValue{Values: vals}}} |
| default: |
| return &structpb.Value{Kind: &structpb.Value_NullValue{}} |
| } |
| } |