Merge branch 'master' into reuse_color_in_default_logger

This commit is contained in:
田欧 2019-02-22 16:31:30 +08:00 committed by GitHub
commit 3277f40729
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 196 additions and 15 deletions

View File

@ -21,7 +21,10 @@ test:
exit 1; \ exit 1; \
elif grep -q "build failed" tmp.out; then \ elif grep -q "build failed" tmp.out; then \
rm tmp.out; \ rm tmp.out; \
exit; \ exit 1; \
elif grep -q "setup failed" tmp.out; then \
rm tmp.out; \
exit 1; \
fi; \ fi; \
if [ -f profile.out ]; then \ if [ -f profile.out ]; then \
cat profile.out | grep -v "mode:" >> coverage.out; \ cat profile.out | grep -v "mode:" >> coverage.out; \

View File

@ -215,9 +215,6 @@ $ go build -tags=jsoniter .
```go ```go
func main() { func main() {
// Disable Console Color
// gin.DisableConsoleColor()
// Creates a gin router with default middleware: // Creates a gin router with default middleware:
// logger and recovery (crash-free) middleware // logger and recovery (crash-free) middleware
router := gin.Default() router := gin.Default()
@ -570,6 +567,48 @@ func main() {
::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" " ::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" "
``` ```
### Controlling Log output coloring
By default, logs output on console should be colorized depending on the detected TTY.
Never colorize logs:
```go
func main() {
// Disable log's color
gin.DisableConsoleColor()
// Creates a gin router with default middleware:
// logger and recovery (crash-free) middleware
router := gin.Default()
router.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
router.Run(":8080")
}
```
Always colorize logs:
```go
func main() {
// Force log's color
gin.ForceConsoleColor()
// Creates a gin router with default middleware:
// logger and recovery (crash-free) middleware
router := gin.Default()
router.GET("/ping", func(c *gin.Context) {
c.String(200, "pong")
})
router.Run(":8080")
}
```
### Model binding and validation ### Model binding and validation
To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz). To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz).
@ -1633,6 +1672,7 @@ import (
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
"syscall"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -1660,7 +1700,10 @@ func main() {
// Wait for interrupt signal to gracefully shutdown the server with // Wait for interrupt signal to gracefully shutdown the server with
// a timeout of 5 seconds. // a timeout of 5 seconds.
quit := make(chan os.Signal) quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt) // kill (no param) default send syscanll.SIGTERM
// kill -2 is syscall.SIGINT
// kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit <-quit
log.Println("Shutdown Server ...") log.Println("Shutdown Server ...")
@ -1669,6 +1712,11 @@ func main() {
if err := srv.Shutdown(ctx); err != nil { if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err) log.Fatal("Server Shutdown:", err)
} }
// catching ctx.Done(). timeout of 5 seconds.
select {
case <-ctx.Done():
log.Println("timeout of 5 seconds.")
}
log.Println("Server exiting") log.Println("Server exiting")
} }
``` ```

View File

@ -61,6 +61,10 @@ type FooStructForMapType struct {
MapFoo map[string]interface{} `form:"map_foo"` MapFoo map[string]interface{} `form:"map_foo"`
} }
type FooStructForIgnoreFormTag struct {
Foo *string `form:"-"`
}
type InvalidNameType struct { type InvalidNameType struct {
TestName string `invalid_name:"test_name"` TestName string `invalid_name:"test_name"`
} }
@ -278,6 +282,12 @@ func TestBindingFormForTime2(t *testing.T) {
"", "") "", "")
} }
func TestFormBindingIgnoreField(t *testing.T) {
testFormBindingIgnoreField(t, "POST",
"/", "/",
"-=bar", "")
}
func TestBindingFormInvalidName(t *testing.T) { func TestBindingFormInvalidName(t *testing.T) {
testFormBindingInvalidName(t, "POST", testFormBindingInvalidName(t, "POST",
"/", "/", "/", "/",
@ -860,6 +870,21 @@ func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, bod
assert.Error(t, err) assert.Error(t, err)
} }
func testFormBindingIgnoreField(t *testing.T, method, path, badPath, body, badBody string) {
b := Form
assert.Equal(t, "form", b.Name())
obj := FooStructForIgnoreFormTag{}
req := requestWithBody(method, path, body)
if method == "POST" {
req.Header.Add("Content-Type", MIMEPOSTForm)
}
err := b.Bind(req, &obj)
assert.NoError(t, err)
assert.Nil(t, obj.Foo)
}
func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBody string) { func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBody string) {
b := Form b := Form
assert.Equal(t, "form", b.Name()) assert.Equal(t, "form", b.Name())

View File

@ -41,6 +41,9 @@ func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
defaultValue = defaultList[1] defaultValue = defaultList[1]
} }
} }
if inputFieldName == "-" {
continue
}
if inputFieldName == "" { if inputFieldName == "" {
inputFieldName = typeField.Name inputFieldName = typeField.Name

View File

@ -8,6 +8,7 @@ import (
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
"syscall"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -35,7 +36,10 @@ func main() {
// Wait for interrupt signal to gracefully shutdown the server with // Wait for interrupt signal to gracefully shutdown the server with
// a timeout of 5 seconds. // a timeout of 5 seconds.
quit := make(chan os.Signal) quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt) // kill (no param) default send syscanll.SIGTERM
// kill -2 is syscall.SIGINT
// kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit <-quit
log.Println("Shutdown Server ...") log.Println("Shutdown Server ...")
@ -44,5 +48,10 @@ func main() {
if err := srv.Shutdown(ctx); err != nil { if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err) log.Fatal("Server Shutdown:", err)
} }
// catching ctx.Done(). timeout of 5 seconds.
select {
case <-ctx.Done():
log.Println("timeout of 5 seconds.")
}
log.Println("Server exiting") log.Println("Server exiting")
} }

View File

@ -0,0 +1,30 @@
The [New Relic Go Agent](https://github.com/newrelic/go-agent) provides a nice middleware for the stdlib handler signature.
The following is an adaptation of that middleware for Gin.
```golang
const (
// NewRelicTxnKey is the key used to retrieve the NewRelic Transaction from the context
NewRelicTxnKey = "NewRelicTxnKey"
)
// NewRelicMonitoring is a middleware that starts a newrelic transaction, stores it in the context, then calls the next handler
func NewRelicMonitoring(app newrelic.Application) gin.HandlerFunc {
return func(ctx *gin.Context) {
txn := app.StartTransaction(ctx.Request.URL.Path, ctx.Writer, ctx.Request)
defer txn.End()
ctx.Set(NewRelicTxnKey, txn)
ctx.Next()
}
}
```
and in `main.go` or equivalent...
```golang
router := gin.Default()
cfg := newrelic.NewConfig(os.Getenv("APP_NAME"), os.Getenv("NEW_RELIC_API_KEY"))
app, err := newrelic.NewApplication(cfg)
if err != nil {
log.Printf("failed to make new_relic app: %v", err)
} else {
router.Use(adapters.NewRelicMonitoring(app))
}
```

View File

@ -0,0 +1,42 @@
package main
import (
"log"
"net/http"
"os"
"github.com/gin-gonic/gin"
"github.com/newrelic/go-agent"
)
const (
// NewRelicTxnKey is the key used to retrieve the NewRelic Transaction from the context
NewRelicTxnKey = "NewRelicTxnKey"
)
// NewRelicMonitoring is a middleware that starts a newrelic transaction, stores it in the context, then calls the next handler
func NewRelicMonitoring(app newrelic.Application) gin.HandlerFunc {
return func(ctx *gin.Context) {
txn := app.StartTransaction(ctx.Request.URL.Path, ctx.Writer, ctx.Request)
defer txn.End()
ctx.Set(NewRelicTxnKey, txn)
ctx.Next()
}
}
func main() {
router := gin.Default()
cfg := newrelic.NewConfig(os.Getenv("APP_NAME"), os.Getenv("NEW_RELIC_API_KEY"))
app, err := newrelic.NewApplication(cfg)
if err != nil {
log.Printf("failed to make new_relic app: %v", err)
} else {
router.Use(NewRelicMonitoring(app))
}
router.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Hello World!\n")
})
router.Run()
}

1
gin.go
View File

@ -318,6 +318,7 @@ func (engine *Engine) RunUnix(file string) (err error) {
return return
} }
defer listener.Close() defer listener.Close()
os.Chmod(file, 0777)
err = http.Serve(listener, engine) err = http.Serve(listener, engine)
return return
} }

1
go.mod
View File

@ -24,6 +24,7 @@ exclude (
github.com/gin-gonic/autotls v0.0.0-20190119125636-0b5f4fc15768 github.com/gin-gonic/autotls v0.0.0-20190119125636-0b5f4fc15768
github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15
github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227 github.com/manucorporat/stats v0.0.0-20180402194714-3ba42d56d227
github.com/newrelic/go-agent v2.5.0+incompatible
github.com/thinkerou/favicon v0.1.0 github.com/thinkerou/favicon v0.1.0
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b
golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1 golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1

View File

@ -24,6 +24,7 @@ var (
cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109})
reset = string([]byte{27, 91, 48, 109}) reset = string([]byte{27, 91, 48, 109})
disableColor = false disableColor = false
forceColor = false
) )
// LoggerConfig defines the config for Logger middleware. // LoggerConfig defines the config for Logger middleware.
@ -63,6 +64,8 @@ type LogFormatterParams struct {
ErrorMessage string ErrorMessage string
// IsTerm shows whether does gin's output descriptor refers to a terminal. // IsTerm shows whether does gin's output descriptor refers to a terminal.
IsTerm bool IsTerm bool
// BodySize is the size of the Response Body
BodySize int
} }
// StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal. // StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal.
@ -135,6 +138,11 @@ func DisableConsoleColor() {
disableColor = true disableColor = true
} }
// ForceConsoleColor force color output in the console.
func ForceConsoleColor() {
forceColor = true
}
// ErrorLogger returns a handlerfunc for any error type. // ErrorLogger returns a handlerfunc for any error type.
func ErrorLogger() HandlerFunc { func ErrorLogger() HandlerFunc {
return ErrorLoggerT(ErrorTypeAny) return ErrorLoggerT(ErrorTypeAny)
@ -189,9 +197,9 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc {
isTerm := true isTerm := true
if w, ok := out.(*os.File); !ok || if w, ok := out.(*os.File); (!ok ||
(os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd()))) || (os.Getenv("TERM") == "dumb" || (!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd()))) ||
disableColor { disableColor) && !forceColor {
isTerm = false isTerm = false
} }
@ -230,6 +238,8 @@ func LoggerWithConfig(conf LoggerConfig) HandlerFunc {
param.StatusCode = c.Writer.Status() param.StatusCode = c.Writer.Status()
param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String() param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String()
param.BodySize = c.Writer.Size()
if raw != "" { if raw != "" {
path = path + "?" + raw path = path + "?" + raw
} }

View File

@ -359,3 +359,10 @@ func TestDisableConsoleColor(t *testing.T) {
DisableConsoleColor() DisableConsoleColor()
assert.True(t, disableColor) assert.True(t, disableColor)
} }
func TestForceConsoleColor(t *testing.T) {
New()
assert.False(t, forceColor)
ForceConsoleColor()
assert.True(t, forceColor)
}

View File

@ -11,8 +11,8 @@ import (
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
) )
// ENV_GIN_MODE indicates environment name for gin mode. // EnvGinMode indicates environment name for gin mode.
const ENV_GIN_MODE = "GIN_MODE" const EnvGinMode = "GIN_MODE"
const ( const (
// DebugMode indicates gin mode is debug. // DebugMode indicates gin mode is debug.
@ -44,7 +44,7 @@ var ginMode = debugCode
var modeName = DebugMode var modeName = DebugMode
func init() { func init() {
mode := os.Getenv(ENV_GIN_MODE) mode := os.Getenv(EnvGinMode)
SetMode(mode) SetMode(mode)
} }

View File

@ -13,13 +13,13 @@ import (
) )
func init() { func init() {
os.Setenv(ENV_GIN_MODE, TestMode) os.Setenv(EnvGinMode, TestMode)
} }
func TestSetMode(t *testing.T) { func TestSetMode(t *testing.T) {
assert.Equal(t, testCode, ginMode) assert.Equal(t, testCode, ginMode)
assert.Equal(t, TestMode, Mode()) assert.Equal(t, TestMode, Mode())
os.Unsetenv(ENV_GIN_MODE) os.Unsetenv(EnvGinMode)
SetMode("") SetMode("")
assert.Equal(t, debugCode, ginMode) assert.Equal(t, debugCode, ginMode)

View File

@ -36,8 +36,8 @@ func (r Reader) WriteContentType(w http.ResponseWriter) {
func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) { func (r Reader) writeHeaders(w http.ResponseWriter, headers map[string]string) {
header := w.Header() header := w.Header()
for k, v := range headers { for k, v := range headers {
if val := header[k]; len(val) == 0 { if header.Get(k) == "" {
header[k] = []string{v} header.Set(k, v)
} }
} }
} }

View File

@ -470,6 +470,7 @@ func TestRenderReader(t *testing.T) {
body := "#!PNG some raw data" body := "#!PNG some raw data"
headers := make(map[string]string) headers := make(map[string]string)
headers["Content-Disposition"] = `attachment; filename="filename.png"` headers["Content-Disposition"] = `attachment; filename="filename.png"`
headers["x-request-id"] = "requestId"
err := (Reader{ err := (Reader{
ContentLength: int64(len(body)), ContentLength: int64(len(body)),
@ -483,4 +484,5 @@ func TestRenderReader(t *testing.T) {
assert.Equal(t, "image/png", w.Header().Get("Content-Type")) assert.Equal(t, "image/png", w.Header().Get("Content-Type"))
assert.Equal(t, strconv.Itoa(len(body)), w.Header().Get("Content-Length")) assert.Equal(t, strconv.Itoa(len(body)), w.Header().Get("Content-Length"))
assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition")) assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition"))
assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id"))
} }