github.com/thiagoyeds/go-cloud@v0.26.0/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  }