...

Source file src/go.uber.org/zap/http_handler.go

Documentation: go.uber.org/zap

     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  	"errors"
    26  	"fmt"
    27  	"io"
    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  //
    36  // # GET
    37  //
    38  // The GET request returns a JSON description of the current logging level like:
    39  //
    40  //	{"level":"info"}
    41  //
    42  // # PUT
    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  //
    47  //	Content-Type: application/x-www-form-urlencoded
    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  //
    52  //	level=debug
    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  //
    60  //	curl -X PUT localhost:8080/log/level?level=debug
    61  //	curl -X PUT localhost:8080/log/level -d level=debug
    62  //
    63  // For any other content type, the payload is expected to be JSON encoded and
    64  // look like:
    65  //
    66  //	{"level":"info"}
    67  //
    68  // An example curl request could look like this:
    69  //
    70  //	curl -X PUT localhost:8080/log/level -H "Content-Type: application/json" -d '{"level":"debug"}'
    71  func (lvl AtomicLevel) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    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 {
    79  	type errorResponse struct {
    80  		Error string `json:"error"`
    81  	}
    82  	type payload struct {
    83  		Level zapcore.Level `json:"level"`
    84  	}
    85  
    86  	enc := json.NewEncoder(w)
    87  
    88  	switch r.Method {
    89  	case http.MethodGet:
    90  		return enc.Encode(payload{Level: lvl.Level()})
    91  
    92  	case http.MethodPut:
    93  		requestedLvl, err := decodePutRequest(r.Header.Get("Content-Type"), r)
    94  		if err != nil {
    95  			w.WriteHeader(http.StatusBadRequest)
    96  			return enc.Encode(errorResponse{Error: err.Error()})
    97  		}
    98  		lvl.SetLevel(requestedLvl)
    99  		return enc.Encode(payload{Level: lvl.Level()})
   100  
   101  	default:
   102  		w.WriteHeader(http.StatusMethodNotAllowed)
   103  		return enc.Encode(errorResponse{
   104  			Error: "Only GET and PUT are supported.",
   105  		})
   106  	}
   107  }
   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 == "" {
   120  		return 0, errors.New("must specify logging level")
   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 {
   137  		return 0, errors.New("must specify logging level")
   138  	}
   139  	return *pld.Level, nil
   140  }
   141  

View as plain text