blob: 77a4de7b2fea214bcade26cfb04b41f14324205c [file] [log] [blame]
// Copyright 2016 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
package main
import (
"crypto/rand"
"encoding/hex"
"log"
"net/http"
"time"
"google.golang.org/cloud/logging"
)
// NewLoggingHandler returns a handler that wraps h but logs each request
// using Google Cloud Logging service.
func NewLoggingHandler(h http.Handler, cli *logging.Client) http.Handler {
return &loggingHandler{h, cli}
}
type loggingHandler struct {
handler http.Handler
cli *logging.Client
}
func (h *loggingHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
t := time.Now().UTC()
const sessionCookieName = "GODOC_ORG_SESSION_ID"
cookie, err := req.Cookie(sessionCookieName)
if err != nil {
// Generates a random session id and sends it in response.
r, err := randomString()
if err != nil {
// If error happens during generating the session id, proceed
// without logging.
log.Println("generating a random session id:", err)
h.handler.ServeHTTP(resp, req)
return
}
// This cookie is intentionally short-lived and contains no information
// that might identify the user. Its sole purpose is to tie query
// terms and destination pages together to measure search quality.
cookie = &http.Cookie{
Name: sessionCookieName,
Value: r,
Expires: time.Now().Add(time.Hour),
}
http.SetCookie(resp, cookie)
}
w := &countingResponseWriter{
ResponseWriter: resp,
responseStatus: http.StatusOK,
}
h.handler.ServeHTTP(w, req)
// We must not record the client's IP address, referrer URL, or any other
// information that might compromise the user's privacy.
entry := logging.Entry{
Time: t,
Payload: map[string]interface{}{
sessionCookieName: cookie.Value,
"latency": time.Since(t),
"path": req.URL.RequestURI(),
"method": req.Method,
"responseBytes": w.responseBytes,
"status": w.responseStatus,
},
}
// Log queues the entry to its internal buffer, or discarding the entry
// if the buffer was full.
h.cli.Log(entry)
}
func randomString() (string, error) {
b := make([]byte, 8)
_, err := rand.Read(b)
return hex.EncodeToString(b), err
}
// A countingResponseWriter is a wrapper around an http.ResponseWriter that
// records the number of bytes written and the status of the response.
type countingResponseWriter struct {
http.ResponseWriter
responseBytes int64
responseStatus int
}
func (w *countingResponseWriter) Write(p []byte) (int, error) {
written, err := w.ResponseWriter.Write(p)
w.responseBytes += int64(written)
return written, err
}
func (w *countingResponseWriter) WriteHeader(status int) {
w.responseStatus = status
w.ResponseWriter.WriteHeader(status)
}