qmp-testing-suite/golang-producer-consumer/vendor/gitlab-app.eng.qops.net/reporting-framework/ripsaw-logger-golang/access.go

192 lines
4.5 KiB
Go
Executable File

package ripsawlogger
import (
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
)
var (
MagicHeaderPrefix = "X-Log-"
DefaultTokenReader = func(key string, r *http.Request) interface{} {
return nil
}
tokens = []string{
"issuer",
"transactionId", "requestId", "parentRequestId",
"userId", "brandId",
"details",
}
)
type TokenReader func(key string, r *http.Request) interface{}
// Because we use X-Log- headers to add stuff to the access logs, we might wind up
// forwarding them elsewhere too if we try to proxy the request. This dumb piece of
// work strips them out, calls the handler, and then adds them back in to avoid badness.
type GhettoLogConcealer struct {
http.Handler
}
func (ghsp GhettoLogConcealer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// collect all the log headers
logs := make(map[string][]string)
for k, v := range r.Header {
if strings.HasPrefix(k, MagicHeaderPrefix) {
logs[k] = v
delete(r.Header, k)
}
}
// call the normal handler
ghsp.Handler.ServeHTTP(w, r)
// add the log headers back in
for k, v := range logs {
r.Header[k] = v
}
}
type accessLogger struct {
Delegate http.Handler
TokenReader TokenReader
Logger Logger
}
type monitoredResponseWriter struct {
http.ResponseWriter
Status int
Length int
}
func (rw *monitoredResponseWriter) Write(data []byte) (int, error) {
if rw.Status == 0 {
rw.Status = 200
}
rw.Length += len(data)
return rw.ResponseWriter.Write(data)
}
func (rw *monitoredResponseWriter) WriteHeader(status int) {
rw.Status = status
rw.ResponseWriter.WriteHeader(status)
}
func (al accessLogger) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// start a timer
start := time.Now()
// determine the host and via headers
via := make([]string, 0, 1)
if r.Header.Get("X-Forwarded-For") != "" {
via = append(via, strings.Split(r.Header.Get("X-Forwarded-For"), ",")...)
}
via = append(via, r.RemoteAddr)
// trim whitespace from each IP
for idx, v := range via {
via[idx] = strings.TrimSpace(v)
}
accessLog := map[string]interface{}{
"clientIP": via[0],
"method": r.Method,
"url": r.URL.String(),
"httpVersion": fmt.Sprintf("%d.%d", r.ProtoMajor, r.ProtoMinor),
}
// add a variety of custom tokens, if any value is provided
for _, key := range tokens {
if value := al.TokenReader(key, r); value != nil {
accessLog[key] = value
}
}
// perform the request
watcher := monitoredResponseWriter{w, 0, 0}
al.Delegate.ServeHTTP(&watcher, r)
// add post-response metadata
accessLog["time"] = float64(time.Now().Sub(start)) / float64(time.Millisecond)
accessLog["status"] = watcher.Status
accessLog["bytes"] = watcher.Length
// add the User Agent if there is one
if len(r.Header["User-Agent"]) > 0 {
accessLog["userAgent"] = strings.Join(r.Header["User-Agent"], ", ")
}
// add the Referer if there is one
if len(r.Header["Referer"]) > 0 {
accessLog["referer"] = strings.Join(r.Header["Referer"], ", ")
}
// add the proxy hops if there are any
if len(via[1:]) > 0 {
accessLog["via"] = via[1:]
}
// read magic request headers and add them to the access logs
for key, values := range r.Header {
if strings.HasPrefix(key, MagicHeaderPrefix) {
key := denormalizeHeaderName(key[len(MagicHeaderPrefix):])
accessLog[key] = strings.Join(values, ", ")
}
}
// send the log line to the writer
al.Logger.Log(LogEntry{Level: LOG_ACCESS, Contents: accessLog})
}
func AccessLogger(h http.Handler, tokens TokenReader) http.Handler {
// default token implementation
if tokens == nil {
tokens = DefaultTokenReader
}
return accessLogger{h, tokens, getDefaultLogger()}
}
func denormalizeHeaderName(key string) string {
normalizedKey := strings.Split(key, "-")
normalizedKey[0] = strings.ToLower(normalizedKey[0])
return strings.Join(normalizedKey, "")
}
func GetTokenFromJWT(key string, r *http.Request) interface{} {
jwtTokens := map[string]string{
"issuer": "iss",
"userId": "userId",
"brandId": "brandId",
}
for tokenName, claimName := range jwtTokens {
if tokenName == key {
if tokenParts := strings.Split(r.Header.Get("X-JWT"), "."); len(tokenParts) == 3 {
if claimStr, err := base64.RawURLEncoding.DecodeString(tokenParts[1]); err == nil {
// parse out the JWT claims
claims := make(map[string]interface{})
if err := json.Unmarshal([]byte(claimStr), &claims); err != nil {
return nil
}
// check for our key in the claims
if value, ok := claims[claimName]; ok {
return value
}
}
}
}
}
return nil
}