9 changed files with 320 additions and 0 deletions
@ -0,0 +1,6 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="JavaScriptSettings"> |
||||||
|
<option name="languageLevel" value="ES6" /> |
||||||
|
</component> |
||||||
|
</project> |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="ProjectModuleManager"> |
||||||
|
<modules> |
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/mux.iml" filepath="$PROJECT_DIR$/.idea/mux.iml" /> |
||||||
|
</modules> |
||||||
|
</component> |
||||||
|
</project> |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<module type="WEB_MODULE" version="4"> |
||||||
|
<component name="Go" enabled="true" /> |
||||||
|
<component name="NewModuleRootManager"> |
||||||
|
<content url="file://$MODULE_DIR$" /> |
||||||
|
<orderEntry type="inheritedJdk" /> |
||||||
|
<orderEntry type="sourceFolder" forTests="false" /> |
||||||
|
</component> |
||||||
|
</module> |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="VcsDirectoryMappings"> |
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" /> |
||||||
|
</component> |
||||||
|
</project> |
||||||
@ -0,0 +1,67 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<project version="4"> |
||||||
|
<component name="ChangeListManager"> |
||||||
|
<list default="true" id="b10d9bd6-ec23-43f3-bef7-13a524307117" name="Default Changelist" comment=""> |
||||||
|
<change beforePath="$PROJECT_DIR$/go.mod" beforeDir="false" afterPath="$PROJECT_DIR$/go.mod" afterDir="false" /> |
||||||
|
</list> |
||||||
|
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" /> |
||||||
|
<option name="SHOW_DIALOG" value="false" /> |
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" /> |
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" /> |
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" /> |
||||||
|
</component> |
||||||
|
<component name="FileTemplateManagerImpl"> |
||||||
|
<option name="RECENT_TEMPLATES"> |
||||||
|
<list> |
||||||
|
<option value="Go File" /> |
||||||
|
</list> |
||||||
|
</option> |
||||||
|
</component> |
||||||
|
<component name="GOROOT" path="C:\Go" /> |
||||||
|
<component name="Git.Settings"> |
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> |
||||||
|
</component> |
||||||
|
<component name="GoLibraries"> |
||||||
|
<option name="indexEntireGoPath" value="false" /> |
||||||
|
</component> |
||||||
|
<component name="ProjectId" id="1WLRzIIIT7D3VUPCOTTsApByClD" /> |
||||||
|
<component name="PropertiesComponent"> |
||||||
|
<property name="DefaultGoTemplateProperty" value="Go File" /> |
||||||
|
<property name="WebServerToolWindowFactoryState" value="false" /> |
||||||
|
<property name="go.import.settings.migrated" value="true" /> |
||||||
|
<property name="go.sdk.automatically.set" value="true" /> |
||||||
|
<property name="go.tried.to.enable.integration.vgo.integrator" value="true" /> |
||||||
|
<property name="last_opened_file_path" value="$PROJECT_DIR$" /> |
||||||
|
<property name="settings.editor.selected.configurable" value="preferences.lookFeel" /> |
||||||
|
</component> |
||||||
|
<component name="RunDashboard"> |
||||||
|
<option name="ruleStates"> |
||||||
|
<list> |
||||||
|
<RuleState> |
||||||
|
<option name="name" value="ConfigurationTypeDashboardGroupingRule" /> |
||||||
|
</RuleState> |
||||||
|
<RuleState> |
||||||
|
<option name="name" value="StatusDashboardGroupingRule" /> |
||||||
|
</RuleState> |
||||||
|
</list> |
||||||
|
</option> |
||||||
|
</component> |
||||||
|
<component name="TypeScriptGeneratedFilesManager"> |
||||||
|
<option name="version" value="1" /> |
||||||
|
</component> |
||||||
|
<component name="Vcs.Log.Tabs.Properties"> |
||||||
|
<option name="TAB_STATES"> |
||||||
|
<map> |
||||||
|
<entry key="MAIN"> |
||||||
|
<value> |
||||||
|
<State /> |
||||||
|
</value> |
||||||
|
</entry> |
||||||
|
</map> |
||||||
|
</option> |
||||||
|
</component> |
||||||
|
<component name="VgoProject"> |
||||||
|
<integration-enabled>true</integration-enabled> |
||||||
|
<proxy>direct</proxy> |
||||||
|
</component> |
||||||
|
</project> |
||||||
@ -1,3 +1,5 @@ |
|||||||
module github.com/gorilla/mux |
module github.com/gorilla/mux |
||||||
|
|
||||||
go 1.12 |
go 1.12 |
||||||
|
|
||||||
|
require github.com/stretchr/testify v1.4.0 |
||||||
|
|||||||
@ -0,0 +1,10 @@ |
|||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= |
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
||||||
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= |
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= |
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
||||||
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= |
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
||||||
@ -0,0 +1,154 @@ |
|||||||
|
package mux |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"net/http" |
||||||
|
"os" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
green = "\033[97;42m" |
||||||
|
white = "\033[90;47m" |
||||||
|
yellow = "\033[90;43m" |
||||||
|
red = "\033[97;41m" |
||||||
|
blue = "\033[97;44m" |
||||||
|
magenta = "\033[97;45m" |
||||||
|
cyan = "\033[97;46m" |
||||||
|
reset = "\033[0m" |
||||||
|
) |
||||||
|
|
||||||
|
type LogConfig struct { |
||||||
|
Output io.Writer |
||||||
|
} |
||||||
|
|
||||||
|
type LogFormatterParams struct { |
||||||
|
Request *http.Request |
||||||
|
|
||||||
|
// TimeStamp shows the time after the server returns a response.
|
||||||
|
TimeStamp time.Time |
||||||
|
// StatusCode is HTTP response code.
|
||||||
|
StatusCode int |
||||||
|
// Latency is how much time the server cost to process a certain request.
|
||||||
|
Latency time.Duration |
||||||
|
// ClientIP equals Context's ClientIP method.
|
||||||
|
ClientIP string |
||||||
|
// Method is the HTTP method given to the request.
|
||||||
|
Method string |
||||||
|
// Path is a path the client requests.
|
||||||
|
Path string |
||||||
|
} |
||||||
|
|
||||||
|
type statusWriter struct { |
||||||
|
http.ResponseWriter |
||||||
|
status int |
||||||
|
} |
||||||
|
|
||||||
|
func (w *statusWriter) WriteHeader(s int) { |
||||||
|
w.status = s |
||||||
|
w.ResponseWriter.WriteHeader(s) |
||||||
|
} |
||||||
|
|
||||||
|
func (p *LogFormatterParams) StatusCodeColor() string { |
||||||
|
code := p.StatusCode |
||||||
|
|
||||||
|
switch { |
||||||
|
case code >= http.StatusOK && code < http.StatusMultipleChoices: |
||||||
|
return green |
||||||
|
case code >= http.StatusMultipleChoices && code < http.StatusBadRequest: |
||||||
|
return white |
||||||
|
case code >= http.StatusBadRequest && code < http.StatusInternalServerError: |
||||||
|
return yellow |
||||||
|
default: |
||||||
|
return red |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (p *LogFormatterParams) MethodColor() string { |
||||||
|
method := p.Method |
||||||
|
|
||||||
|
switch method { |
||||||
|
case http.MethodGet: |
||||||
|
return blue |
||||||
|
case http.MethodPost: |
||||||
|
return cyan |
||||||
|
case http.MethodPut: |
||||||
|
return yellow |
||||||
|
case http.MethodDelete: |
||||||
|
return red |
||||||
|
case http.MethodPatch: |
||||||
|
return green |
||||||
|
case http.MethodHead: |
||||||
|
return magenta |
||||||
|
case http.MethodOptions: |
||||||
|
return white |
||||||
|
default: |
||||||
|
return reset |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (p *LogFormatterParams) ResetColor() string { |
||||||
|
return reset |
||||||
|
} |
||||||
|
|
||||||
|
func Logger(next http.Handler) http.Handler { |
||||||
|
return LoggerWithConfig(LogConfig{})(next) |
||||||
|
} |
||||||
|
|
||||||
|
func LoggerWithConfig(c LogConfig) MiddlewareFunc { |
||||||
|
return func (next http.Handler) http.Handler { |
||||||
|
out := c.Output |
||||||
|
if out == nil { |
||||||
|
out = os.Stdout |
||||||
|
} |
||||||
|
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
||||||
|
start := time.Now() |
||||||
|
path := r.URL.Path |
||||||
|
raw := r.URL.RawQuery |
||||||
|
|
||||||
|
sw := statusWriter{ |
||||||
|
ResponseWriter: w, |
||||||
|
status: 0, |
||||||
|
} |
||||||
|
next.ServeHTTP(&sw, r) |
||||||
|
|
||||||
|
if raw != "" { |
||||||
|
path = fmt.Sprintf("%s?%s", path, raw) |
||||||
|
} |
||||||
|
|
||||||
|
stop := time.Now() |
||||||
|
p := LogFormatterParams{ |
||||||
|
Request: r, |
||||||
|
TimeStamp: stop, |
||||||
|
Latency: stop.Sub(start), |
||||||
|
ClientIP: "", |
||||||
|
Method: r.Method, |
||||||
|
StatusCode: sw.status, |
||||||
|
Path: path, |
||||||
|
} |
||||||
|
|
||||||
|
fmt.Fprintf(out, formatter(p)) |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func formatter(p LogFormatterParams) string { |
||||||
|
statusColor := p.StatusCodeColor() |
||||||
|
methodColor := p.MethodColor() |
||||||
|
resetColor := p.ResetColor() |
||||||
|
|
||||||
|
if p.Latency > time.Minute { |
||||||
|
p.Latency = p.Latency - p.Latency%time.Second |
||||||
|
} |
||||||
|
|
||||||
|
return fmt.Sprintf("[MUX] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n", |
||||||
|
p.TimeStamp.Format("2006/01/02 - 15:04:05"), |
||||||
|
statusColor, p.StatusCode, resetColor, |
||||||
|
p.Latency, |
||||||
|
p.ClientIP, |
||||||
|
methodColor, p.Method, resetColor, |
||||||
|
p.Path, |
||||||
|
) |
||||||
|
} |
||||||
@ -0,0 +1,58 @@ |
|||||||
|
package mux |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"github.com/stretchr/testify/assert" |
||||||
|
"net/http" |
||||||
|
"testing" |
||||||
|
) |
||||||
|
|
||||||
|
func testHandler(status int) http.HandlerFunc { |
||||||
|
return func(w http.ResponseWriter, r *http.Request) { |
||||||
|
w.WriteHeader(status) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestLogger(t *testing.T) { |
||||||
|
buffer := new(bytes.Buffer) |
||||||
|
r := NewRouter() |
||||||
|
r.Use(LoggerWithConfig(LogConfig{Output:buffer})) |
||||||
|
r.HandleFunc("/example", testHandler(http.StatusOK)). |
||||||
|
Methods("GET", "POST", "PUT", "DELETE", "OPTIONS") |
||||||
|
|
||||||
|
rw := NewRecorder() |
||||||
|
|
||||||
|
req := newRequest("GET", "/example") |
||||||
|
r.ServeHTTP(rw, req) |
||||||
|
assert.Contains(t, buffer.String(), "200") |
||||||
|
assert.Contains(t, buffer.String(), "GET") |
||||||
|
assert.Contains(t, buffer.String(), "/example") |
||||||
|
|
||||||
|
buffer.Reset() |
||||||
|
req = newRequest("POST", "/example") |
||||||
|
r.ServeHTTP(rw, req) |
||||||
|
assert.Contains(t, buffer.String(), "200") |
||||||
|
assert.Contains(t, buffer.String(), "POST") |
||||||
|
assert.Contains(t, buffer.String(), "/example") |
||||||
|
|
||||||
|
buffer.Reset() |
||||||
|
req = newRequest("PUT", "/example") |
||||||
|
r.ServeHTTP(rw, req) |
||||||
|
assert.Contains(t, buffer.String(), "200") |
||||||
|
assert.Contains(t, buffer.String(), "PUT") |
||||||
|
assert.Contains(t, buffer.String(), "/example") |
||||||
|
|
||||||
|
buffer.Reset() |
||||||
|
req = newRequest("DELETE", "/example") |
||||||
|
r.ServeHTTP(rw, req) |
||||||
|
assert.Contains(t, buffer.String(), "200") |
||||||
|
assert.Contains(t, buffer.String(), "DELETE") |
||||||
|
assert.Contains(t, buffer.String(), "/example") |
||||||
|
|
||||||
|
buffer.Reset() |
||||||
|
req = newRequest("OPTIONS", "/example") |
||||||
|
r.ServeHTTP(rw, req) |
||||||
|
assert.Contains(t, buffer.String(), "200") |
||||||
|
assert.Contains(t, buffer.String(), "OPTIONS") |
||||||
|
assert.Contains(t, buffer.String(), "/example") |
||||||
|
} |
||||||
Loading…
Reference in new issue