github.com/google/martian/v3@v3.3.3/martianlog/logger.go (about) 1 // Copyright 2015 Google Inc. All rights reserved. 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 // http://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 martianlog provides a Martian modifier that logs the request and response. 16 package martianlog 17 18 import ( 19 "bytes" 20 "encoding/json" 21 "fmt" 22 "io" 23 "net/http" 24 "strings" 25 26 "github.com/google/martian/v3" 27 "github.com/google/martian/v3/log" 28 "github.com/google/martian/v3/messageview" 29 "github.com/google/martian/v3/parse" 30 ) 31 32 // Logger is a modifier that logs requests and responses. 33 type Logger struct { 34 log func(line string) 35 headersOnly bool 36 decode bool 37 } 38 39 type loggerJSON struct { 40 Scope []parse.ModifierType `json:"scope"` 41 HeadersOnly bool `json:"headersOnly"` 42 Decode bool `json:"decode"` 43 } 44 45 func init() { 46 parse.Register("log.Logger", loggerFromJSON) 47 } 48 49 // NewLogger returns a logger that logs requests and responses, optionally 50 // logging the body. Log function defaults to martian.Infof. 51 func NewLogger() *Logger { 52 return &Logger{ 53 log: func(line string) { 54 log.Infof(line) 55 }, 56 } 57 } 58 59 // SetHeadersOnly sets whether to log the request/response body in the log. 60 func (l *Logger) SetHeadersOnly(headersOnly bool) { 61 l.headersOnly = headersOnly 62 } 63 64 // SetDecode sets whether to decode the request/response body in the log. 65 func (l *Logger) SetDecode(decode bool) { 66 l.decode = decode 67 } 68 69 // SetLogFunc sets the logging function for the logger. 70 func (l *Logger) SetLogFunc(logFunc func(line string)) { 71 l.log = logFunc 72 } 73 74 // ModifyRequest logs the request, optionally including the body. 75 // 76 // The format logged is: 77 // -------------------------------------------------------------------------------- 78 // Request to http://www.google.com/path?querystring 79 // -------------------------------------------------------------------------------- 80 // GET /path?querystring HTTP/1.1 81 // Host: www.google.com 82 // Connection: close 83 // Other-Header: values 84 // 85 // request content 86 // -------------------------------------------------------------------------------- 87 func (l *Logger) ModifyRequest(req *http.Request) error { 88 ctx := martian.NewContext(req) 89 if ctx.SkippingLogging() { 90 return nil 91 } 92 93 b := &bytes.Buffer{} 94 95 fmt.Fprintln(b, "") 96 fmt.Fprintln(b, strings.Repeat("-", 80)) 97 fmt.Fprintf(b, "Request to %s\n", req.URL) 98 fmt.Fprintln(b, strings.Repeat("-", 80)) 99 100 mv := messageview.New() 101 mv.SkipBody(l.headersOnly) 102 if err := mv.SnapshotRequest(req); err != nil { 103 return err 104 } 105 106 var opts []messageview.Option 107 if l.decode { 108 opts = append(opts, messageview.Decode()) 109 } 110 111 r, err := mv.Reader(opts...) 112 if err != nil { 113 return err 114 } 115 116 io.Copy(b, r) 117 118 fmt.Fprintln(b, "") 119 fmt.Fprintln(b, strings.Repeat("-", 80)) 120 121 l.log(b.String()) 122 123 return nil 124 } 125 126 // ModifyResponse logs the response, optionally including the body. 127 // 128 // The format logged is: 129 // -------------------------------------------------------------------------------- 130 // Response from http://www.google.com/path?querystring 131 // -------------------------------------------------------------------------------- 132 // HTTP/1.1 200 OK 133 // Date: Tue, 15 Nov 1994 08:12:31 GMT 134 // Other-Header: values 135 // 136 // response content 137 // -------------------------------------------------------------------------------- 138 func (l *Logger) ModifyResponse(res *http.Response) error { 139 ctx := martian.NewContext(res.Request) 140 if ctx.SkippingLogging() { 141 return nil 142 } 143 144 b := &bytes.Buffer{} 145 fmt.Fprintln(b, "") 146 fmt.Fprintln(b, strings.Repeat("-", 80)) 147 fmt.Fprintf(b, "Response from %s\n", res.Request.URL) 148 fmt.Fprintln(b, strings.Repeat("-", 80)) 149 150 mv := messageview.New() 151 mv.SkipBody(l.headersOnly) 152 if err := mv.SnapshotResponse(res); err != nil { 153 return err 154 } 155 156 var opts []messageview.Option 157 if l.decode { 158 opts = append(opts, messageview.Decode()) 159 } 160 161 r, err := mv.Reader(opts...) 162 if err != nil { 163 return err 164 } 165 166 io.Copy(b, r) 167 168 fmt.Fprintln(b, "") 169 fmt.Fprintln(b, strings.Repeat("-", 80)) 170 171 l.log(b.String()) 172 173 return nil 174 } 175 176 // loggerFromJSON builds a logger from JSON. 177 // 178 // Example JSON: 179 // { 180 // "log.Logger": { 181 // "scope": ["request", "response"], 182 // "headersOnly": true, 183 // "decode": true 184 // } 185 // } 186 func loggerFromJSON(b []byte) (*parse.Result, error) { 187 msg := &loggerJSON{} 188 if err := json.Unmarshal(b, msg); err != nil { 189 return nil, err 190 } 191 192 l := NewLogger() 193 l.SetHeadersOnly(msg.HeadersOnly) 194 l.SetDecode(msg.Decode) 195 196 return parse.NewResult(l, msg.Scope) 197 }