| khenaidoo | ac63710 | 2019-01-14 15:44:34 -0500 | [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" |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 25 | "errors" |
| khenaidoo | ac63710 | 2019-01-14 15:44:34 -0500 | [diff] [blame] | 26 | "fmt" |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 27 | "io" |
| khenaidoo | ac63710 | 2019-01-14 15:44:34 -0500 | [diff] [blame] | 28 | "net/http" |
| 29 | |
| 30 | "go.uber.org/zap/zapcore" |
| 31 | ) |
| 32 | |
| 33 | // ServeHTTP is a simple JSON endpoint that can report on or change the current |
| 34 | // logging level. |
| 35 | // |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 36 | // # GET |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 37 | // |
| 38 | // The GET request returns a JSON description of the current logging level like: |
| khenaidoo | ac63710 | 2019-01-14 15:44:34 -0500 | [diff] [blame] | 39 | // |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 40 | // {"level":"info"} |
| 41 | // |
| 42 | // # PUT |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 43 | // |
| 44 | // The PUT request changes the logging level. It is perfectly safe to change the |
| 45 | // logging level while a program is running. Two content types are supported: |
| 46 | // |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 47 | // Content-Type: application/x-www-form-urlencoded |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 48 | // |
| 49 | // With this content type, the level can be provided through the request body or |
| 50 | // a query parameter. The log level is URL encoded like: |
| 51 | // |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 52 | // level=debug |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 53 | // |
| 54 | // The request body takes precedence over the query parameter, if both are |
| 55 | // specified. |
| 56 | // |
| 57 | // This content type is the default for a curl PUT request. Following are two |
| 58 | // example curl requests that both set the logging level to debug. |
| 59 | // |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 60 | // curl -X PUT localhost:8080/log/level?level=debug |
| 61 | // curl -X PUT localhost:8080/log/level -d level=debug |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 62 | // |
| 63 | // For any other content type, the payload is expected to be JSON encoded and |
| 64 | // look like: |
| 65 | // |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 66 | // {"level":"info"} |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 67 | // |
| 68 | // An example curl request could look like this: |
| 69 | // |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 70 | // curl -X PUT localhost:8080/log/level -H "Content-Type: application/json" -d '{"level":"debug"}' |
| khenaidoo | ac63710 | 2019-01-14 15:44:34 -0500 | [diff] [blame] | 71 | func (lvl AtomicLevel) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 72 | if err := lvl.serveHTTP(w, r); err != nil { |
| 73 | w.WriteHeader(http.StatusInternalServerError) |
| 74 | fmt.Fprintf(w, "internal error: %v", err) |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | func (lvl AtomicLevel) serveHTTP(w http.ResponseWriter, r *http.Request) error { |
| khenaidoo | ac63710 | 2019-01-14 15:44:34 -0500 | [diff] [blame] | 79 | type errorResponse struct { |
| 80 | Error string `json:"error"` |
| 81 | } |
| 82 | type payload struct { |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 83 | Level zapcore.Level `json:"level"` |
| khenaidoo | ac63710 | 2019-01-14 15:44:34 -0500 | [diff] [blame] | 84 | } |
| 85 | |
| 86 | enc := json.NewEncoder(w) |
| 87 | |
| 88 | switch r.Method { |
| khenaidoo | ac63710 | 2019-01-14 15:44:34 -0500 | [diff] [blame] | 89 | case http.MethodGet: |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 90 | return enc.Encode(payload{Level: lvl.Level()}) |
| 91 | |
| khenaidoo | ac63710 | 2019-01-14 15:44:34 -0500 | [diff] [blame] | 92 | case http.MethodPut: |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 93 | requestedLvl, err := decodePutRequest(r.Header.Get("Content-Type"), r) |
| 94 | if err != nil { |
| khenaidoo | ac63710 | 2019-01-14 15:44:34 -0500 | [diff] [blame] | 95 | w.WriteHeader(http.StatusBadRequest) |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 96 | return enc.Encode(errorResponse{Error: err.Error()}) |
| khenaidoo | ac63710 | 2019-01-14 15:44:34 -0500 | [diff] [blame] | 97 | } |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 98 | lvl.SetLevel(requestedLvl) |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 99 | return enc.Encode(payload{Level: lvl.Level()}) |
| 100 | |
| khenaidoo | ac63710 | 2019-01-14 15:44:34 -0500 | [diff] [blame] | 101 | default: |
| 102 | w.WriteHeader(http.StatusMethodNotAllowed) |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 103 | return enc.Encode(errorResponse{ |
| khenaidoo | ac63710 | 2019-01-14 15:44:34 -0500 | [diff] [blame] | 104 | Error: "Only GET and PUT are supported.", |
| 105 | }) |
| 106 | } |
| 107 | } |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 108 | |
| 109 | // Decodes incoming PUT requests and returns the requested logging level. |
| 110 | func decodePutRequest(contentType string, r *http.Request) (zapcore.Level, error) { |
| 111 | if contentType == "application/x-www-form-urlencoded" { |
| 112 | return decodePutURL(r) |
| 113 | } |
| 114 | return decodePutJSON(r.Body) |
| 115 | } |
| 116 | |
| 117 | func decodePutURL(r *http.Request) (zapcore.Level, error) { |
| 118 | lvl := r.FormValue("level") |
| 119 | if lvl == "" { |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 120 | return 0, errors.New("must specify logging level") |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 121 | } |
| 122 | var l zapcore.Level |
| 123 | if err := l.UnmarshalText([]byte(lvl)); err != nil { |
| 124 | return 0, err |
| 125 | } |
| 126 | return l, nil |
| 127 | } |
| 128 | |
| 129 | func decodePutJSON(body io.Reader) (zapcore.Level, error) { |
| 130 | var pld struct { |
| 131 | Level *zapcore.Level `json:"level"` |
| 132 | } |
| 133 | if err := json.NewDecoder(body).Decode(&pld); err != nil { |
| 134 | return 0, fmt.Errorf("malformed request body: %v", err) |
| 135 | } |
| 136 | if pld.Level == nil { |
| Abhay Kumar | a2ae599 | 2025-11-10 14:02:24 +0000 | [diff] [blame^] | 137 | return 0, errors.New("must specify logging level") |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 138 | } |
| 139 | return *pld.Level, nil |
| khenaidoo | d948f77 | 2021-08-11 17:49:24 -0400 | [diff] [blame] | 140 | } |