tessl install tessl/golang-cloud-google-com--go--logging@1.13.0Cloud Logging client library for Go that enables writing log entries to Google Cloud Logging service with buffered asynchronous and synchronous logging capabilities.
This document describes how to associate log entries with HTTP requests using the HTTPRequest type for correlation and grouping in Cloud Logging.
type HTTPRequest struct {
Request *http.Request
RequestSize int64
Status int
ResponseSize int64
Latency time.Duration
LocalIP string
RemoteIP string
CacheHit bool
CacheValidatedWithOriginServer bool
CacheFillBytes int64
CacheLookup bool
}HTTPRequest contains an http.Request as well as additional information about the request and its response. When included in a log entry, Cloud Logging groups related log entries together and provides request-specific analysis.
Fields:
Request *http.Request - The http.Request passed to the handler. This field is required if HTTPRequest is provided.
RequestSize int64 - Size of the HTTP request message in bytes, including the request headers and the request body.
Status int - Response code indicating the status of the response. Examples: 200, 404.
ResponseSize int64 - Size of the HTTP response message sent back to the client, in bytes, including the response headers and the response body.
Latency time.Duration - Request processing latency on the server, from the time the request was received until the response was sent.
LocalIP string - IP address (IPv4 or IPv6) of the origin server that the request was sent to.
RemoteIP string - IP address (IPv4 or IPv6) of the client that issued the HTTP request. Examples: "192.168.1.1", "FE80::0202:B3FF:FE1E:8329".
CacheHit bool - Reports whether an entity was served from cache (with or without validation).
CacheValidatedWithOriginServer bool - Reports whether the response was validated with the origin server before being served from cache. This field is only meaningful if CacheHit is true.
CacheFillBytes int64 - Number of HTTP response bytes inserted into cache. Set only when a cache fill was attempted.
CacheLookup bool - Whether or not a cache lookup was attempted.
import (
"net/http"
"time"
"cloud.google.com/go/logging"
)
func handler(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Process the request
// ...
duration := time.Since(start)
logger.Log(logging.Entry{
Payload: "request processed",
Severity: logging.Info,
HTTPRequest: &logging.HTTPRequest{
Request: r,
Status: 200,
Latency: duration,
},
})
}Log comprehensive HTTP request information:
func handlerWithFullMetadata(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Calculate request size
requestSize := r.ContentLength
if requestSize < 0 {
requestSize = 0
}
// Process the request
responseBody := []byte("response data")
w.WriteHeader(http.StatusOK)
w.Write(responseBody)
duration := time.Since(start)
logger.Log(logging.Entry{
Payload: "HTTP request completed",
Severity: logging.Info,
HTTPRequest: &logging.HTTPRequest{
Request: r,
RequestSize: requestSize,
Status: http.StatusOK,
ResponseSize: int64(len(responseBody)),
Latency: duration,
RemoteIP: r.RemoteAddr,
},
})
}Create middleware to automatically log all HTTP requests:
func loggingMiddleware(logger *logging.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Wrap ResponseWriter to capture status code and size
rw := &responseWriter{
ResponseWriter: w,
statusCode: http.StatusOK,
}
// Process request
next.ServeHTTP(rw, r)
// Log the request
logger.Log(logging.Entry{
Payload: map[string]interface{}{
"method": r.Method,
"path": r.URL.Path,
"status": rw.statusCode,
},
Severity: logging.Info,
HTTPRequest: &logging.HTTPRequest{
Request: r,
Status: rw.statusCode,
ResponseSize: int64(rw.bytesWritten),
Latency: time.Since(start),
RemoteIP: r.RemoteAddr,
},
})
})
}
}
type responseWriter struct {
http.ResponseWriter
statusCode int
bytesWritten int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
func (rw *responseWriter) Write(b []byte) (int, error) {
n, err := rw.ResponseWriter.Write(b)
rw.bytesWritten += n
return n, err
}func handleAPIRequest(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Process request
err := processRequest(r)
if err != nil {
// Log error with HTTP context
logger.Log(logging.Entry{
Payload: map[string]interface{}{
"error": err.Error(),
"message": "request processing failed",
},
Severity: logging.Error,
HTTPRequest: &logging.HTTPRequest{
Request: r,
Status: http.StatusInternalServerError,
Latency: time.Since(start),
},
})
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
// Success case
w.WriteHeader(http.StatusOK)
w.Write([]byte("success"))
logger.Log(logging.Entry{
Payload: "request successful",
Severity: logging.Info,
HTTPRequest: &logging.HTTPRequest{
Request: r,
Status: http.StatusOK,
Latency: time.Since(start),
},
})
}Log cache-related information for CDN or caching layers:
func cacheAwareHandler(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Check cache
cached, cacheHit := checkCache(r.URL.Path)
var cacheValidated bool
if cacheHit {
// Serve from cache
w.Write(cached)
} else {
// Generate response
response := generateResponse()
w.Write(response)
// Cache the response
cacheResponse(r.URL.Path, response)
}
logger.Log(logging.Entry{
Payload: "cache-aware request",
Severity: logging.Info,
HTTPRequest: &logging.HTTPRequest{
Request: r,
Status: http.StatusOK,
Latency: time.Since(start),
CacheHit: cacheHit,
CacheLookup: true,
CacheValidatedWithOriginServer: cacheValidated,
},
})
}Log client and server IP addresses:
func ipTrackingHandler(w http.ResponseWriter, r *http.Request) {
// Extract client IP (considering proxies)
clientIP := r.Header.Get("X-Forwarded-For")
if clientIP == "" {
clientIP = r.RemoteAddr
}
// Get server IP
serverIP := getServerIP()
logger.Log(logging.Entry{
Payload: "request from client",
Severity: logging.Info,
HTTPRequest: &logging.HTTPRequest{
Request: r,
Status: http.StatusOK,
RemoteIP: clientIP,
LocalIP: serverIP,
},
})
}When you include HTTPRequest in log entries, Cloud Logging automatically groups logs from the same request together. This is especially useful for distributed systems:
func complexHandler(w http.ResponseWriter, r *http.Request) {
// Log request start
logger.Log(logging.Entry{
Payload: "processing request",
Severity: logging.Info,
HTTPRequest: &logging.HTTPRequest{
Request: r,
},
})
// Log database query
logger.Log(logging.Entry{
Payload: "querying database",
Severity: logging.Debug,
HTTPRequest: &logging.HTTPRequest{
Request: r,
},
})
// Log external API call
logger.Log(logging.Entry{
Payload: "calling external API",
Severity: logging.Debug,
HTTPRequest: &logging.HTTPRequest{
Request: r,
},
})
// Log completion
logger.Log(logging.Entry{
Payload: "request completed",
Severity: logging.Info,
HTTPRequest: &logging.HTTPRequest{
Request: r,
Status: http.StatusOK,
},
})
}All these log entries will be grouped together in Cloud Logging because they reference the same HTTP request.
package main
import (
"context"
"log"
"net/http"
"time"
"cloud.google.com/go/logging"
)
var logger *logging.Logger
func main() {
ctx := context.Background()
client, err := logging.NewClient(ctx, "my-project")
if err != nil {
log.Fatalf("failed to create client: %v", err)
}
defer client.Close()
logger = client.Logger("http-log")
http.HandleFunc("/api/users", userHandler)
http.ListenAndServe(":8080", nil)
}
func userHandler(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Log incoming request
logger.Log(logging.Entry{
Payload: map[string]interface{}{
"event": "request_received",
"method": r.Method,
"path": r.URL.Path,
},
Severity: logging.Info,
HTTPRequest: &logging.HTTPRequest{
Request: r,
},
})
// Process request
var statusCode int
var responseBytes []byte
if r.Method == "GET" {
statusCode = http.StatusOK
responseBytes = []byte(`{"users": []}`)
} else {
statusCode = http.StatusMethodNotAllowed
responseBytes = []byte(`{"error": "method not allowed"}`)
}
// Send response
w.WriteHeader(statusCode)
w.Write(responseBytes)
duration := time.Since(start)
// Log request completion
severity := logging.Info
if statusCode >= 400 {
severity = logging.Warning
}
if statusCode >= 500 {
severity = logging.Error
}
logger.Log(logging.Entry{
Payload: map[string]interface{}{
"event": "request_completed",
"method": r.Method,
"path": r.URL.Path,
"status": statusCode,
"duration": duration.Milliseconds(),
},
Severity: severity,
HTTPRequest: &logging.HTTPRequest{
Request: r,
Status: statusCode,
ResponseSize: int64(len(responseBytes)),
Latency: duration,
RemoteIP: r.RemoteAddr,
},
})
}