divyadesai | 1900913 | 2020-03-04 12:58:08 +0000 | [diff] [blame] | 1 | // Copyright (c) 2016 Uber Technologies, Inc. |
| 2 | // |
| 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy |
| 4 | // of this software and associated documentation files (the "Software"), to deal |
| 5 | // in the Software without restriction, including without limitation the rights |
| 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 7 | // copies of the Software, and to permit persons to whom the Software is |
| 8 | // furnished to do so, subject to the following conditions: |
| 9 | // |
| 10 | // The above copyright notice and this permission notice shall be included in |
| 11 | // all copies or substantial portions of the Software. |
| 12 | // |
| 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 19 | // THE SOFTWARE. |
| 20 | |
| 21 | package zap |
| 22 | |
| 23 | import ( |
| 24 | "encoding/json" |
| 25 | "fmt" |
David K. Bainbridge | bd6b288 | 2021-08-26 13:31:02 +0000 | [diff] [blame] | 26 | "io" |
divyadesai | 1900913 | 2020-03-04 12:58:08 +0000 | [diff] [blame] | 27 | "net/http" |
| 28 | |
| 29 | "go.uber.org/zap/zapcore" |
| 30 | ) |
| 31 | |
| 32 | // ServeHTTP is a simple JSON endpoint that can report on or change the current |
| 33 | // logging level. |
| 34 | // |
David K. Bainbridge | bd6b288 | 2021-08-26 13:31:02 +0000 | [diff] [blame] | 35 | // GET |
| 36 | // |
| 37 | // The GET request returns a JSON description of the current logging level like: |
divyadesai | 1900913 | 2020-03-04 12:58:08 +0000 | [diff] [blame] | 38 | // {"level":"info"} |
| 39 | // |
David K. Bainbridge | bd6b288 | 2021-08-26 13:31:02 +0000 | [diff] [blame] | 40 | // PUT |
| 41 | // |
| 42 | // The PUT request changes the logging level. It is perfectly safe to change the |
| 43 | // logging level while a program is running. Two content types are supported: |
| 44 | // |
| 45 | // Content-Type: application/x-www-form-urlencoded |
| 46 | // |
| 47 | // With this content type, the level can be provided through the request body or |
| 48 | // a query parameter. The log level is URL encoded like: |
| 49 | // |
| 50 | // level=debug |
| 51 | // |
| 52 | // The request body takes precedence over the query parameter, if both are |
| 53 | // specified. |
| 54 | // |
| 55 | // This content type is the default for a curl PUT request. Following are two |
| 56 | // example curl requests that both set the logging level to debug. |
| 57 | // |
| 58 | // curl -X PUT localhost:8080/log/level?level=debug |
| 59 | // curl -X PUT localhost:8080/log/level -d level=debug |
| 60 | // |
| 61 | // For any other content type, the payload is expected to be JSON encoded and |
| 62 | // look like: |
| 63 | // |
| 64 | // {"level":"info"} |
| 65 | // |
| 66 | // An example curl request could look like this: |
| 67 | // |
| 68 | // curl -X PUT localhost:8080/log/level -H "Content-Type: application/json" -d '{"level":"debug"}' |
| 69 | // |
divyadesai | 1900913 | 2020-03-04 12:58:08 +0000 | [diff] [blame] | 70 | func (lvl AtomicLevel) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
| 71 | type errorResponse struct { |
| 72 | Error string `json:"error"` |
| 73 | } |
| 74 | type payload struct { |
David K. Bainbridge | bd6b288 | 2021-08-26 13:31:02 +0000 | [diff] [blame] | 75 | Level zapcore.Level `json:"level"` |
divyadesai | 1900913 | 2020-03-04 12:58:08 +0000 | [diff] [blame] | 76 | } |
| 77 | |
| 78 | enc := json.NewEncoder(w) |
| 79 | |
| 80 | switch r.Method { |
divyadesai | 1900913 | 2020-03-04 12:58:08 +0000 | [diff] [blame] | 81 | case http.MethodGet: |
David K. Bainbridge | bd6b288 | 2021-08-26 13:31:02 +0000 | [diff] [blame] | 82 | enc.Encode(payload{Level: lvl.Level()}) |
divyadesai | 1900913 | 2020-03-04 12:58:08 +0000 | [diff] [blame] | 83 | case http.MethodPut: |
David K. Bainbridge | bd6b288 | 2021-08-26 13:31:02 +0000 | [diff] [blame] | 84 | requestedLvl, err := decodePutRequest(r.Header.Get("Content-Type"), r) |
| 85 | if err != nil { |
divyadesai | 1900913 | 2020-03-04 12:58:08 +0000 | [diff] [blame] | 86 | w.WriteHeader(http.StatusBadRequest) |
David K. Bainbridge | bd6b288 | 2021-08-26 13:31:02 +0000 | [diff] [blame] | 87 | enc.Encode(errorResponse{Error: err.Error()}) |
divyadesai | 1900913 | 2020-03-04 12:58:08 +0000 | [diff] [blame] | 88 | return |
| 89 | } |
David K. Bainbridge | bd6b288 | 2021-08-26 13:31:02 +0000 | [diff] [blame] | 90 | lvl.SetLevel(requestedLvl) |
| 91 | enc.Encode(payload{Level: lvl.Level()}) |
divyadesai | 1900913 | 2020-03-04 12:58:08 +0000 | [diff] [blame] | 92 | default: |
| 93 | w.WriteHeader(http.StatusMethodNotAllowed) |
| 94 | enc.Encode(errorResponse{ |
| 95 | Error: "Only GET and PUT are supported.", |
| 96 | }) |
| 97 | } |
| 98 | } |
David K. Bainbridge | bd6b288 | 2021-08-26 13:31:02 +0000 | [diff] [blame] | 99 | |
| 100 | // Decodes incoming PUT requests and returns the requested logging level. |
| 101 | func decodePutRequest(contentType string, r *http.Request) (zapcore.Level, error) { |
| 102 | if contentType == "application/x-www-form-urlencoded" { |
| 103 | return decodePutURL(r) |
| 104 | } |
| 105 | return decodePutJSON(r.Body) |
| 106 | } |
| 107 | |
| 108 | func decodePutURL(r *http.Request) (zapcore.Level, error) { |
| 109 | lvl := r.FormValue("level") |
| 110 | if lvl == "" { |
| 111 | return 0, fmt.Errorf("must specify logging level") |
| 112 | } |
| 113 | var l zapcore.Level |
| 114 | if err := l.UnmarshalText([]byte(lvl)); err != nil { |
| 115 | return 0, err |
| 116 | } |
| 117 | return l, nil |
| 118 | } |
| 119 | |
| 120 | func decodePutJSON(body io.Reader) (zapcore.Level, error) { |
| 121 | var pld struct { |
| 122 | Level *zapcore.Level `json:"level"` |
| 123 | } |
| 124 | if err := json.NewDecoder(body).Decode(&pld); err != nil { |
| 125 | return 0, fmt.Errorf("malformed request body: %v", err) |
| 126 | } |
| 127 | if pld.Level == nil { |
| 128 | return 0, fmt.Errorf("must specify logging level") |
| 129 | } |
| 130 | return *pld.Level, nil |
| 131 | |
| 132 | } |