Skip to content

Commit

Permalink
Add http.Handler to enable runtime changes (#58) (#71)
Browse files Browse the repository at this point in the history
* Add http.Handler to enable runtime changes (#58)

* Add newline at the end of http_handler_test file

* Implement get(set) log level based on the methods

Based on the comments by @prashantv

GET - Returns the current log level as JSON string '{"level":"info"}'
PUT - Updates the log level and returns the effective one as JSON string

Returns 'Method Not Allowed' (405) for any other method.

Returns 'Bad Request' (400) for bad request data or unrecognized level.
  • Loading branch information
pravj authored and akshayjshah committed May 31, 2016
1 parent 1392e24 commit a467de4
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 1 deletion.
73 changes: 73 additions & 0 deletions http_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package zap

import (
"encoding/json"
"net/http"
)

// payload struct defines the format of the request payload received
type payload struct {
Level string `json:"level"`
}

// NewHTTPHandler takes a logger instance and provides methods to enable
// runtime changes to the logger level via http.handler interface.
func NewHTTPHandler(logger Logger) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
var currentLevel string

switch r.Method {
case "GET":
currentLevel = logger.Level().String()
case "PUT":
decoder := json.NewDecoder(r.Body)
var p payload

err := decoder.Decode(&p)
if err != nil {
// received data in wrong format
http.Error(w, "Bad Request", 400)
return
}

var loggerLevel Level
err = loggerLevel.UnmarshalText([]byte(p.Level))
if err != nil {
// unrecognized level provided by the request
http.Error(w, "Unrecognized Level", 400)
return
}

logger.SetLevel(loggerLevel)
currentLevel = loggerLevel.String()
default:
http.Error(w, "Method Not Allowed", 405)
return
}

res := payload{currentLevel}
json.NewEncoder(w).Encode(res)
}

return http.HandlerFunc(fn)
}
111 changes: 111 additions & 0 deletions http_handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright (c) 2016 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package zap

import (
"testing"

"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func sampleHTTPClient(t *testing.T, method string, handler http.Handler, reader io.Reader) (int, []byte) {
ts := httptest.NewServer(handler)
defer ts.Close()

client := &http.Client{}

req, reqErr := http.NewRequest(method, ts.URL, reader)
require.NoError(t, reqErr, fmt.Sprintf("Error returning new %s request.", method))

res, resErr := client.Do(req)
require.NoError(t, resErr, fmt.Sprintf("Error making %s request.", method))

resString, err := ioutil.ReadAll(res.Body)
res.Body.Close()
require.NoError(t, err, "Error reading request body.")

return res.StatusCode, resString
}

func TestHTTPHandlerGetLevel(t *testing.T) {
withJSONLogger(t, nil, func(jl *jsonLogger, _ func() []string) {
handler := NewHTTPHandler(jl)

statusCode, responseStr := sampleHTTPClient(t, "GET", handler, nil)

assert.Equal(t, http.StatusOK, statusCode, "Unexpected response status code.")
assert.Equal(t, fmt.Sprintf("%s", responseStr), fmt.Sprintf("{\"level\":\"%v\"}\n", jl.Level().String()), "Unexpected logger level.")
})
}

func TestHTTPHandlerPutLevel(t *testing.T) {
withJSONLogger(t, nil, func(jl *jsonLogger, _ func() []string) {
handler := NewHTTPHandler(jl)

jsonStr := []byte(`{"level":"warn"}`)
statusCode, responseStr := sampleHTTPClient(t, "PUT", handler, bytes.NewReader(jsonStr))

assert.Equal(t, http.StatusOK, statusCode, "Unexpected response status code.")
assert.Equal(t, fmt.Sprintf("%s", responseStr), fmt.Sprintf("{\"level\":\"%v\"}\n", jl.Level().String()), "Unexpected logger level.")
})
}

func TestHTTPHandlerUnrecognizedLevel(t *testing.T) {
withJSONLogger(t, nil, func(jl *jsonLogger, _ func() []string) {
handler := NewHTTPHandler(jl)

jsonStr := []byte(`{"level":"unrecognized-level"}`)
statusCode, responseStr := sampleHTTPClient(t, "PUT", handler, bytes.NewReader(jsonStr))

assert.Equal(t, http.StatusBadRequest, statusCode, "Unexpected response status code.")
assert.Equal(t, fmt.Sprintf("%s", responseStr), "Unrecognized Level\n", "Unexpected response data.")
})
}

func TestHTTPHandlerBadRequest(t *testing.T) {
withJSONLogger(t, nil, func(jl *jsonLogger, _ func() []string) {
handler := NewHTTPHandler(jl)

statusCode, responseStr := sampleHTTPClient(t, "PUT", handler, nil)

assert.Equal(t, http.StatusBadRequest, statusCode, "Unexpected response status code.")
assert.Equal(t, fmt.Sprintf("%s", responseStr), "Bad Request\n", "Unexpected response data.")
})
}

func TestHTTPHandlerMethodNotAllowed(t *testing.T) {
withJSONLogger(t, nil, func(jl *jsonLogger, _ func() []string) {
handler := NewHTTPHandler(jl)

statusCode, responseStr := sampleHTTPClient(t, "POST", handler, nil)

assert.Equal(t, http.StatusMethodNotAllowed, statusCode, "Unexpected response status code.")
assert.Equal(t, fmt.Sprintf("%s", responseStr), "Method Not Allowed\n", "Unexpected response data.")
})
}
2 changes: 1 addition & 1 deletion logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type Logger interface {
// Check the minimum enabled log level.
Level() Level
// Change the level of this logger, as well as all its ancestors and
// descendants. This makes makes it easy to change the log level at runtime
// descendants. This makes it easy to change the log level at runtime
// without restarting your application.
SetLevel(Level)
// Create a child logger, and optionally add some context to that logger.
Expand Down

0 comments on commit a467de4

Please sign in to comment.