github.com/cornelk/go-cloud@v0.17.1/server/requestlog/stackdriver.go (about) 1 // Copyright 2018 The Go Cloud Development Kit Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package requestlog 16 17 import ( 18 "bytes" 19 "encoding/json" 20 "io" 21 "strconv" 22 "sync" 23 "time" 24 ) 25 26 // A StackdriverLogger writes log entries in the Stackdriver forward JSON 27 // format. The record's fields are suitable for consumption by 28 // Stackdriver Logging. 29 type StackdriverLogger struct { 30 onErr func(error) 31 32 mu sync.Mutex 33 w io.Writer 34 buf bytes.Buffer 35 enc *json.Encoder 36 } 37 38 // NewStackdriverLogger returns a new logger that writes to w. 39 // A nil onErr is treated the same as func(error) {}. 40 func NewStackdriverLogger(w io.Writer, onErr func(error)) *StackdriverLogger { 41 l := &StackdriverLogger{ 42 w: w, 43 onErr: onErr, 44 } 45 l.enc = json.NewEncoder(&l.buf) 46 return l 47 } 48 49 // Log writes a record to its writer. Multiple concurrent calls will 50 // produce sequential writes to its writer. 51 func (l *StackdriverLogger) Log(ent *Entry) { 52 if err := l.log(ent); err != nil && l.onErr != nil { 53 l.onErr(err) 54 } 55 } 56 57 func (l *StackdriverLogger) log(ent *Entry) error { 58 defer l.mu.Unlock() 59 l.mu.Lock() 60 61 l.buf.Reset() 62 // r represents the fluent-plugin-google-cloud format 63 // See https://github.com/GoogleCloudPlatform/fluent-plugin-google-cloud/blob/f93046d92f7722db2794a042c3f2dde5df91a90b/lib/fluent/plugin/out_google_cloud.rb#L145 64 // to check json tags 65 var r struct { 66 HTTPRequest struct { 67 RequestMethod string `json:"requestMethod"` 68 RequestURL string `json:"requestUrl"` 69 RequestSize int64 `json:"requestSize,string"` 70 Status int `json:"status"` 71 ResponseSize int64 `json:"responseSize,string"` 72 UserAgent string `json:"userAgent"` 73 RemoteIP string `json:"remoteIp"` 74 Referer string `json:"referer"` 75 Latency string `json:"latency"` 76 } `json:"httpRequest"` 77 Timestamp struct { 78 Seconds int64 `json:"seconds"` 79 Nanos int `json:"nanos"` 80 } `json:"timestamp"` 81 TraceID string `json:"logging.googleapis.com/trace"` 82 SpanID string `json:"logging.googleapis.com/spanId"` 83 } 84 r.HTTPRequest.RequestMethod = ent.RequestMethod 85 r.HTTPRequest.RequestURL = ent.RequestURL 86 // TODO(light): determine whether this is the formula LogEntry expects. 87 r.HTTPRequest.RequestSize = ent.RequestHeaderSize + ent.RequestBodySize 88 r.HTTPRequest.Status = ent.Status 89 // TODO(light): determine whether this is the formula LogEntry expects. 90 r.HTTPRequest.ResponseSize = ent.ResponseHeaderSize + ent.ResponseBodySize 91 r.HTTPRequest.UserAgent = ent.UserAgent 92 r.HTTPRequest.RemoteIP = ent.RemoteIP 93 r.HTTPRequest.Referer = ent.Referer 94 r.HTTPRequest.Latency = string(appendLatency(nil, ent.Latency)) 95 96 t := ent.ReceivedTime.Add(ent.Latency) 97 r.Timestamp.Seconds = t.Unix() 98 r.Timestamp.Nanos = t.Nanosecond() 99 r.TraceID = ent.TraceID.String() 100 r.SpanID = ent.SpanID.String() 101 if err := l.enc.Encode(r); err != nil { 102 return err 103 } 104 _, err := l.w.Write(l.buf.Bytes()) 105 106 return err 107 } 108 109 func appendLatency(b []byte, d time.Duration) []byte { 110 // Parses format understood by google-fluentd (which is looser than the documented LogEntry format). 111 // See the comment at https://github.com/GoogleCloudPlatform/fluent-plugin-google-cloud/blob/e2f60cdd1d97e79ffe4e91bdbf6bd84837f27fa5/lib/fluent/plugin/out_google_cloud.rb#L1539 112 b = strconv.AppendFloat(b, d.Seconds(), 'f', 9, 64) 113 b = append(b, 's') 114 return b 115 }