Update to 8.0.3 | Request logger improvements, error handlers improvements. Read HISTORY.md

Former-commit-id: fb5eca0dc955d8c07fdba35b47068008565dbbd1
This commit is contained in:
kataras 2017-07-16 13:58:10 +03:00
parent 91bb768d4a
commit 56aa3de645
11 changed files with 339 additions and 61 deletions

View File

@ -17,7 +17,31 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene
**How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`. **How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris`.
# Su, 15 July 2017 | v8.0.2 # Su, 16 July 2017 | v8.0.3
No API changes.
Relative issues:
- https://github.com/kataras/iris/issues/674
- https://github.com/kataras/iris/issues/675
- https://github.com/kataras/iris/issues/676
### HTTP Errors
Able to register a chain of Handlers (and middleware with `ctx.Next()` support like routes) for a specific error code, read more at [issues/674](https://github.com/kataras/iris/issues/674). Usage example can be found at [_examples/http_request/request-logger/main.go#L37](https://github.com/kataras/iris/blob/master/_examples/http_request/request-logger/main.go#L37).
New function to register a Handler or a chain of Handlers for all official http error codes, by calling the new `app.OnAnyErrorCode(func(ctx context.Context){})`, read more at [issues/675](https://github.com/kataras/iris/issues/675). Usage example can be found at [_examples/http_request/request-logger/main.go#L42](https://github.com/kataras/iris/blob/master/_examples/http_request/request-logger/main.go#L42).
### Request Logger
Add `Configuration#LogFunc` and `Configuration#Columns` fields, read more at [issues/676](https://github.com/kataras/iris/issues/676). Example can be found at [_examples/http_request/request-logger/request-logger-file](https://github.com/kataras/iris/tree/master/_examples/http_request/request-logger/request-logger-file).
Have fun and don't forget to [star](https://github.com/kataras/iris/stargazers) the github repository, it gives me power to continue publishing my work!
# Sa, 15 July 2017 | v8.0.2
Okay my friends, this is a good time to upgrade, I did implement a feature that you were asking many times at the past. Okay my friends, this is a good time to upgrade, I did implement a feature that you were asking many times at the past.

View File

@ -89,7 +89,7 @@ These types of projects need heart and sacrifices to continue offer the best dev
</a> </a>
* [Installation](#-installation) * [Installation](#-installation)
* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#su-15-july-2017--v802) * [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#su-16-july-2017--v803)
* [Learn](#-learn) * [Learn](#-learn)
* [HTTP Listening](_examples/#http-listening) * [HTTP Listening](_examples/#http-listening)
* [Configuration](_examples/#configuration) * [Configuration](_examples/#configuration)
@ -262,7 +262,7 @@ _iris_ does not force you to use any specific ORM. With support for the most pop
### 📌 Version ### 📌 Version
Current: **8.0.2** Current: **8.0.3**
Each new release is pushed to the master. It stays there until the next version. When a next version is released then the previous version goes to its own branch with `gopkg.in` as its import path (and its own vendor folder), in order to keep it working "for-ever". Each new release is pushed to the master. It stays there until the next version. When a next version is released then the previous version goes to its own branch with `gopkg.in` as its import path (and its own vendor folder), in order to keep it working "for-ever".

View File

@ -11,7 +11,8 @@ It doesn't always contain the "best ways" but it does cover each important featu
- [Hello world!](hello-world/main.go) - [Hello world!](hello-world/main.go)
- [Glimpse](overview/main.go) - [Glimpse](overview/main.go)
- [Tutorial: Online Visitors](tutorial/online-visitors/main.go) - [Tutorial: Online Visitors](tutorial/online-visitors/main.go)
- [Tutorial: URL Shortener using BoltDB](tutorial/url-shortener/main.go) - [Tutorial: URL Shortener using BoltDB](https://medium.com/@kataras/a-url-shortener-service-using-go-iris-and-bolt-4182f0b00ae7)
- [Tutorial: How to turn your Android Device into a fully featured Web Server](https://medium.com/@kataras/how-to-turn-an-android-device-into-a-web-server-9816b28ab199)
### HTTP Listening ### HTTP Listening
@ -24,7 +25,7 @@ It doesn't always contain the "best ways" but it does cover each important featu
* [common net.Listener](http-listening/custom-listener/main.go) * [common net.Listener](http-listening/custom-listener/main.go)
* [SO_REUSEPORT for unix systems](http-listening/custom-listener/unix-reuseport/main.go) * [SO_REUSEPORT for unix systems](http-listening/custom-listener/unix-reuseport/main.go)
- Custom HTTP Server - Custom HTTP Server
* [iris way](http-listening/custom-httpserver/easy-way/main.go) * [easy way](http-listening/custom-httpserver/easy-way/main.go)
* [std way](http-listening/custom-httpserver/std-way/main.go) * [std way](http-listening/custom-httpserver/std-way/main.go)
* [multi server instances](http-listening/custom-httpserver/multi/main.go) * [multi server instances](http-listening/custom-httpserver/multi/main.go)
- Graceful Shutdown - Graceful Shutdown
@ -81,12 +82,12 @@ Navigate through examples for a better understanding.
- [Basic](routing/basic/main.go) - [Basic](routing/basic/main.go)
- [Custom HTTP Errors](routing/http-errors/main.go) - [Custom HTTP Errors](routing/http-errors/main.go)
- [Dynamic Path](routing/dynamic-path/main.go) - [Dynamic Path](routing/dynamic-path/main.go)
* [Root Level Wildcard Path](routing/dynamic-path/root-wildcard/main.go) * [root level wildcard path](routing/dynamic-path/root-wildcard/main.go)
- [Reverse routing](routing/reverse/main.go) - [Reverse routing](routing/reverse/main.go)
- [Custom wrapper](routing/custom-wrapper/main.go) - [Custom wrapper](routing/custom-wrapper/main.go)
- Custom Context - Custom Context
* [Method Overriding](routing/custom-context/method-overriding/main.go) * [method overriding](routing/custom-context/method-overriding/main.go)
* [New Implementation](routing/custom-context/new-implementation/main.go) * [new implementation](routing/custom-context/new-implementation/main.go)
- [Route State](routing/route-state/main.go) - [Route State](routing/route-state/main.go)
### Subdomains ### Subdomains
@ -134,8 +135,8 @@ Navigate through examples for a better understanding.
- [Embedding Files Into App Executable File](file-server/embedding-files-into-app/main.go) - [Embedding Files Into App Executable File](file-server/embedding-files-into-app/main.go)
- [Send/Force-Download Files](file-server/send-files/main.go) - [Send/Force-Download Files](file-server/send-files/main.go)
- Single Page Applications - Single Page Applications
* [Single Page Application](file-server/single-page-application/basic/main.go) * [single Page Application](file-server/single-page-application/basic/main.go)
* [Embedded Single Page Application](file-server/single-page-application/embedded-single-page-application/main.go) * [embedded Single Page Application](file-server/single-page-application/embedded-single-page-application/main.go)
### How to Read from `context.Request() *http.Request` ### How to Read from `context.Request() *http.Request`
@ -156,6 +157,7 @@ Navigate through examples for a better understanding.
### Miscellaneous ### Miscellaneous
- [Request Logger](http_request/request-logger/main.go) - [Request Logger](http_request/request-logger/main.go)
* [log requests to a file](http_request/request-logger/request-logger-file/main.go)
- [Localization and Internationalization](miscellaneous/i18n/main.go) - [Localization and Internationalization](miscellaneous/i18n/main.go)
- [Recovery](miscellaneous/recover/main.go) - [Recovery](miscellaneous/recover/main.go)
- [Profiling (pprof)](miscellaneous/pprof/main.go) - [Profiling (pprof)](miscellaneous/pprof/main.go)

View File

@ -22,24 +22,25 @@ func main() {
app.Use(customLogger) app.Use(customLogger)
app.Get("/", func(ctx context.Context) { h := func(ctx context.Context) {
ctx.Writef("hello") ctx.Writef("Hello from %s", ctx.Path())
}) }
app.Get("/", h)
app.Get("/1", func(ctx context.Context) { app.Get("/1", h)
ctx.Writef("hello")
})
app.Get("/2", func(ctx context.Context) { app.Get("/2", h)
ctx.Writef("hello")
})
// log http errors should be done manually // http errors have their own handlers, therefore
errorLogger := logger.New() // registering a middleare should be done manually.
/*
app.OnErrorCode(iris.StatusNotFound, func(ctx context.Context) { app.OnErrorCode(404 ,customLogger, func(ctx context.Context) {
errorLogger(ctx) ctx.Writef("My Custom 404 error page ")
ctx.Writef("My Custom 404 error page ") })
*/
// or catch all http errors:
app.OnAnyErrorCode(customLogger, func(ctx context.Context) {
ctx.Writef("My Custom error page")
}) })
// http://localhost:8080 // http://localhost:8080

View File

@ -0,0 +1,109 @@
package main
import (
"os"
"strings"
"time"
"github.com/kataras/iris"
"github.com/kataras/iris/context"
"github.com/kataras/iris/middleware/logger"
)
const deleteFileOnExit = true
func main() {
app := iris.New()
r, close := newRequestLogger()
defer close()
app.Use(r)
app.OnAnyErrorCode(r, func(ctx context.Context) {
ctx.HTML("<h1> Error: Please try <a href ='/'> this </a> instead.</h1>")
})
h := func(ctx context.Context) {
ctx.Writef("Hello from %s", ctx.Path())
}
app.Get("/", h)
app.Get("/1", h)
app.Get("/2", h)
// http://localhost:8080
// http://localhost:8080/1
// http://localhost:8080/2
// http://lcoalhost:8080/notfoundhere
app.Run(iris.Addr(":8080"))
}
// get a filename based on the date, file logs works that way the most times
// but these are just a sugar.
func todayFilename() string {
today := time.Now().Format("Jan 02 2006")
return today + ".txt"
}
func newLogFile() *os.File {
filename := todayFilename()
// open an output file, this will append to the today's file if server restarted.
f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
panic(err)
}
return f
}
var excludeExtensions = [...]string{
".js",
".css",
".jpg",
".png",
".ico",
".svg",
}
func newRequestLogger() (h context.Handler, close func() error) {
close = func() error { return nil }
c := logger.Config{
Status: true,
IP: true,
Method: true,
Path: true,
Columns: true,
}
logFile := newLogFile()
close = func() error {
err := logFile.Close()
if deleteFileOnExit {
err = os.Remove(logFile.Name())
}
return err
}
c.LogFunc = func(now time.Time, latency time.Duration, status, ip, method, path string) {
output := logger.Columnize(now.Format("2006/01/02 - 15:04:05"), latency, status, ip, method, path)
logFile.Write([]byte(output))
}
// we don't want to use the logger
// to log requests to assets and etc
c.AddSkipper(func(ctx context.Context) bool {
path := ctx.Path()
for _, ext := range excludeExtensions {
if strings.HasSuffix(path, ext) {
return true
}
}
return false
})
h = logger.New(c)
return
}

View File

@ -620,8 +620,61 @@ func (rb *APIBuilder) StaticWeb(requestPath string, systemPath string) *Route {
// the body if recorder was enabled // the body if recorder was enabled
// and/or disable the gzip if gzip response recorder // and/or disable the gzip if gzip response recorder
// was active. // was active.
func (rb *APIBuilder) OnErrorCode(statusCode int, handler context.Handler) { func (rb *APIBuilder) OnErrorCode(statusCode int, handlers ...context.Handler) {
rb.errorCodeHandlers.Register(statusCode, handler) rb.errorCodeHandlers.Register(statusCode, handlers...)
}
// OnAnyErrorCode registers a handler which called when error status code written.
// Same as `OnErrorCode` but registers all http error codes.
// See: http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
func (rb *APIBuilder) OnAnyErrorCode(handlers ...context.Handler) {
// we could register all >=400 and <=511 but this way
// could override custom status codes that iris developers can register for their
// web apps whenever needed.
// There fore these are the hard coded http error statuses:
var errStatusCodes = []int{
http.StatusBadRequest,
http.StatusUnauthorized,
http.StatusPaymentRequired,
http.StatusForbidden,
http.StatusNotFound,
http.StatusMethodNotAllowed,
http.StatusNotAcceptable,
http.StatusProxyAuthRequired,
http.StatusRequestTimeout,
http.StatusConflict,
http.StatusGone,
http.StatusLengthRequired,
http.StatusPreconditionFailed,
http.StatusRequestEntityTooLarge,
http.StatusRequestURITooLong,
http.StatusUnsupportedMediaType,
http.StatusRequestedRangeNotSatisfiable,
http.StatusExpectationFailed,
http.StatusTeapot,
http.StatusUnprocessableEntity,
http.StatusLocked,
http.StatusFailedDependency,
http.StatusUpgradeRequired,
http.StatusPreconditionRequired,
http.StatusTooManyRequests,
http.StatusRequestHeaderFieldsTooLarge,
http.StatusUnavailableForLegalReasons,
http.StatusInternalServerError,
http.StatusNotImplemented,
http.StatusBadGateway,
http.StatusServiceUnavailable,
http.StatusGatewayTimeout,
http.StatusHTTPVersionNotSupported,
http.StatusVariantAlsoNegotiates,
http.StatusInsufficientStorage,
http.StatusLoopDetected,
http.StatusNotExtended,
http.StatusNetworkAuthenticationRequired}
for _, statusCode := range errStatusCodes {
rb.OnErrorCode(statusCode, handlers...)
}
} }
// FireErrorCode executes an error http status code handler // FireErrorCode executes an error http status code handler

View File

@ -11,7 +11,7 @@ import (
// of the list of all http error code handlers. // of the list of all http error code handlers.
type ErrorCodeHandler struct { type ErrorCodeHandler struct {
StatusCode int StatusCode int
Handler context.Handler Handlers context.Handlers
mu sync.Mutex mu sync.Mutex
} }
@ -41,12 +41,12 @@ func (ch *ErrorCodeHandler) Fire(ctx context.Context) {
// i.e // i.e
// users := app.Party("/users") // users := app.Party("/users")
// users.Done(func(ctx context.Context){ if ctx.StatusCode() == 400 { /* custom error code for /users */ }}) // users.Done(func(ctx context.Context){ if ctx.StatusCode() == 400 { /* custom error code for /users */ }})
ch.Handler(ctx) ctx.Do(ch.Handlers)
} }
func (ch *ErrorCodeHandler) updateHandler(h context.Handler) { func (ch *ErrorCodeHandler) updateHandlers(handlers context.Handlers) {
ch.mu.Lock() ch.mu.Lock()
ch.Handler = h ch.Handlers = handlers
ch.mu.Unlock() ch.mu.Unlock()
} }
@ -101,23 +101,24 @@ func (s *ErrorCodeHandlers) Get(statusCode int) *ErrorCodeHandler {
// the body if recorder was enabled // the body if recorder was enabled
// and/or disable the gzip if gzip response recorder // and/or disable the gzip if gzip response recorder
// was active. // was active.
func (s *ErrorCodeHandlers) Register(statusCode int, handler context.Handler) *ErrorCodeHandler { func (s *ErrorCodeHandlers) Register(statusCode int, handlers ...context.Handler) *ErrorCodeHandler {
if statusCode < 400 { if statusCode < 400 {
return nil return nil
} }
h := s.Get(statusCode) h := s.Get(statusCode)
if h == nil { if h == nil {
// create new and add it
ch := &ErrorCodeHandler{ ch := &ErrorCodeHandler{
StatusCode: statusCode, StatusCode: statusCode,
Handler: handler, Handlers: handlers,
} }
s.handlers = append(s.handlers, ch) s.handlers = append(s.handlers, ch)
// create new and add it
return ch return ch
} }
// otherwise update the handler // otherwise update the handlers
h.updateHandler(handler) h.updateHandlers(handlers)
return h return h
} }

2
doc.go
View File

@ -35,7 +35,7 @@ Source code and other details for the project are available at GitHub:
Current Version Current Version
8.0.2 8.0.3
Installation Installation

View File

@ -33,7 +33,7 @@ import (
const ( const (
// Version is the current version number of the Iris Web Framework. // Version is the current version number of the Iris Web Framework.
Version = "8.0.2" Version = "8.0.3"
) )
// HTTP status codes as registered with IANA. // HTTP status codes as registered with IANA.

View File

@ -1,21 +1,78 @@
package logger package logger
import (
"time"
"github.com/kataras/iris/context"
)
// The SkipperFunc signature, used to serve the main request without logs.
// See `Configuration` too.
type SkipperFunc func(ctx context.Context) bool
// Config are the options of the logger middlweare // Config are the options of the logger middlweare
// contains 4 bools // contains 4 bools
// Status, IP, Method, Path // Status, IP, Method, Path
// if set to true then these will print // if set to true then these will print
type Config struct { type Config struct {
// Status displays status code (bool) // Status displays status code (bool)
//
// Defaults to true
Status bool Status bool
// IP displays request's remote address (bool) // IP displays request's remote address (bool)
//
// Defaults to true
IP bool IP bool
// Method displays the http method (bool) // Method displays the http method (bool)
//
// Defaults to true
Method bool Method bool
// Path displays the request path (bool) // Path displays the request path (bool)
//
// Defaults to true
Path bool Path bool
// Columns will display the logs as well formatted columns (bool)
// If custom `LogFunc` has been provided then this field is useless and users should
// use the `Columinize` function of the logger to get the ouput result as columns.
//
// Defaults to true
Columns bool
// LogFunc is the writer which logs are written to,
// if missing the logger middleware uses the app.Logger().Infof instead.
LogFunc func(now time.Time, latency time.Duration, status, ip, method, path string)
// Skippers used to skip the logging i.e by `ctx.Path()` and serve
// the next/main handler immediately.
Skippers []SkipperFunc
// the Skippers as one function in order to reduce the time needed to
// combine them at serve time.
skip SkipperFunc
} }
// DefaultConfiguration returns an options which all properties are true except EnableColors // DefaultConfiguration returns a default configuration
// that have all boolean fields to true,
// LogFunc to nil,
// and Skippers to nil.
func DefaultConfiguration() Config { func DefaultConfiguration() Config {
return Config{true, true, true, true} return Config{true, true, true, true, true, nil, nil, nil}
}
// AddSkipper adds a skipper to the configuration.
func (c *Config) AddSkipper(sk SkipperFunc) {
c.Skippers = append(c.Skippers, sk)
c.buildSkipper()
}
func (c *Config) buildSkipper() {
if len(c.Skippers) == 0 {
return
}
skippersLocked := c.Skippers[0:]
c.skip = func(ctx context.Context) bool {
for _, s := range skippersLocked {
if s(ctx) {
return true
}
}
return false
}
} }

View File

@ -2,25 +2,49 @@
package logger package logger
import ( import (
"fmt"
"strconv" "strconv"
"time" "time"
"github.com/kataras/iris/context" "github.com/kataras/iris/context"
"github.com/ryanuber/columnize"
) )
type requestLoggerMiddleware struct { type requestLoggerMiddleware struct {
config Config config Config
} }
// New creates and returns a new request logger middleware.
// Do not confuse it with the framework's Logger.
// This is for the http requests.
//
// Receives an optional configuation.
func New(cfg ...Config) context.Handler {
c := DefaultConfiguration()
if len(cfg) > 0 {
c = cfg[0]
}
c.buildSkipper()
l := &requestLoggerMiddleware{config: c}
return l.ServeHTTP
}
// Serve serves the middleware // Serve serves the middleware
func (l *requestLoggerMiddleware) ServeHTTP(ctx context.Context) { func (l *requestLoggerMiddleware) ServeHTTP(ctx context.Context) {
// skip logs and serve the main request immediately
if l.config.skip != nil {
if l.config.skip(ctx) {
ctx.Next()
return
}
}
//all except latency to string //all except latency to string
var status, ip, method, path string var status, ip, method, path string
var latency time.Duration var latency time.Duration
var startTime, endTime time.Time var startTime, endTime time.Time
path = ctx.Path()
method = ctx.Method()
startTime = time.Now() startTime = time.Now()
ctx.Next() ctx.Next()
@ -36,29 +60,36 @@ func (l *requestLoggerMiddleware) ServeHTTP(ctx context.Context) {
ip = ctx.RemoteAddr() ip = ctx.RemoteAddr()
} }
if !l.config.Method { if l.config.Method {
method = "" method = ctx.Method()
} }
if !l.config.Path { if l.config.Path {
path = "" path = ctx.Path()
} }
//finally print the logs, no new line, the framework's logger is responsible how to render each log. // print the logs
ctx.Application().Logger().Infof("%v %4v %s %s %s", status, latency, ip, method, path) if logFunc := l.config.LogFunc; logFunc != nil {
logFunc(endTime, latency, status, ip, method, path)
return
}
endTimeFormatted := endTime.Format("2006/01/02 - 15:04:05")
if l.config.Columns {
output := Columnize(endTimeFormatted, latency, status, ip, method, path)
ctx.Application().Logger().Out.Write([]byte(output))
return
}
// no new line, the framework's logger is responsible how to render each log.
ctx.Application().Logger().Infof("%s | %v %4v %s %s %s", endTimeFormatted, status, latency, ip, method, path)
} }
// New creates and returns a new request logger middleware. // Columnize formats the given arguments as columns and returns the formatted output,
// Do not confuse it with the framework's Logger. // note that it appends a new line to the end.
// This is for the http requests. func Columnize(nowFormatted string, latency time.Duration, status, ip, method, path string) string {
// outputC := []string{
// Receives an optional configuation. "Time | Status | Latency | IP | Method | Path",
func New(cfg ...Config) context.Handler { fmt.Sprintf("%s | %v | %4v | %s | %s | %s", nowFormatted, status, latency, ip, method, path),
c := DefaultConfiguration()
if len(cfg) > 0 {
c = cfg[0]
} }
l := &requestLoggerMiddleware{config: c} output := columnize.SimpleFormat(outputC) + "\n"
return output
return l.ServeHTTP
} }