You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

198 lines
4.8 KiB

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