9 changed files with 320 additions and 0 deletions
@ -0,0 +1,6 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -1,3 +1,5 @@
|
||||
module github.com/gorilla/mux |
||||
|
||||
go 1.12 |
||||
|
||||
require github.com/stretchr/testify v1.4.0 |
||||
|
||||
@ -0,0 +1,10 @@
@@ -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 @@
@@ -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 @@
@@ -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