diff --git a/HISTORY.md b/HISTORY.md index 3ce99ed9..05e40dd7 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -17,6 +17,19 @@ 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`. +# Mo, 17 July 2017 | v8.0.4 + +No API changes. + +### HTTP Errors + +Fix a rare behavior: error handlers are not executed correctly +when a before-handler by-passes the order of execution, relative to the [previous feature](https://github.com/kataras/iris/blob/master/HISTORY.md#su-16-july-2017--v803). + +### Request Logger + +Add `Configuration#MessageContextKey`. Example can be found at [_examples/http_request/request-logger/main.go](https://github.com/kataras/iris/blob/master/_examples/http_request/request-logger/main.go#L48). + # Su, 16 July 2017 | v8.0.3 No API changes. @@ -29,14 +42,14 @@ Relative issues: ### 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). +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](https://github.com/kataras/iris/blob/master/_examples/http_request/request-logger/main.go#L41). -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). +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](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). +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/main.go](https://github.com/kataras/iris/blob/master/_examples/http_request/request-logger/request-logger-file/main.go). 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! diff --git a/README.md b/README.md index c463b714..65945b54 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ Iris is a fast, simple and efficient micro web framework for Go. It provides a beautifully expressive and easy to use foundation for your next website, API, or distributed app. -Web applications powered by Iris run everywhere, even [from an android device](https://medium.com/@kataras/how-to-turn-an-android-device-into-a-web-server-9816b28ab199). - [](https://travis-ci.org/kataras/iris) [](http://goreportcard.com/report/kataras/iris) [](https://godoc.org/github.com/kataras/iris) @@ -11,6 +9,43 @@ Web applications powered by Iris run everywhere, even [from an android device](h [](https://github.com/kataras/iris/tree/master/_examples) [](https://kataras.rocket.chat/channel/iris) +
+
+
-
-
-
-
-* [Installation](#-installation)
-* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#su-16-july-2017--v803)
-* [Learn](#-learn)
- * [HTTP Listening](_examples/#http-listening)
- * [Configuration](_examples/#configuration)
- * [Routing, Grouping, Dynamic Path Parameters, "Macros" and Custom Context](_examples/#routing-grouping-dynamic-path-parameters-macros-and-custom-context)
- * [Subdomains](_examples/#subdomains)
- * [Wrap `http.Handler/HandlerFunc`](_examples/#convert-httphandlerhandlerfunc)
- * [View](_examples/#view)
- * [Authentication](_examples/#authentication)
- * [File Server](_examples/#file-server)
- * [How to Read from `context.Request() *http.Request`](_examples/#how-to-read-from-contextrequest-httprequest)
- * [How to Write to `context.ResponseWriter() http.ResponseWriter`](_examples/#how-to-write-to-contextresponsewriter-httpresponsewriter)
- * [Test](_examples/#testing)
- * [Cache](cache/#table-of-contents)
- * [Sessions](sessions/#table-of-contents)
- * [Websockets](websocket/#table-of-contents)
- * [Miscellaneous](_examples/#miscellaneous)
- * [Typescript Automation Tools](typescript/#table-of-contents)
- * [Tutorial: Online Visitors](_examples/tutorial/online-visitors)
- * [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)
-* [Middleware](middleware/)
-* [Dockerize](https://github.com/iris-contrib/cloud-native-go)
-* [Philosophy](#-philosophy)
-* [Support](#-support)
-* [Versioning](#-version)
- * [When should I upgrade?](#should-i-upgrade-my-iris)
- * [Where can I find older versions?](#where-can-i-find-older-versions)
-* [People](#-people)
-
### 🚀 Installation
The only requirement is the [Go Programming Language](https://golang.org/dl/), at least version 1.8
@@ -215,7 +169,76 @@ go version go1.9beta2 windows/amd64
+
The _iris_ philosophy is to provide robust tooling for HTTP, making it a great solution for single page applications, web sites, hybrids, or public HTTP APIs. Keep note that, today, iris is faster than apache+nginx itself.
_iris_ does not force you to use any specific ORM. With support for the most popular template engines, websocket server and a fast sessions manager you can quickly craft your perfect application.
-### 💙 Support
+
+
+The awesome _iris_ community is always adding new examples, [_examples](_examples/) is a great place to get started!
+
+Read the [godocs](https://godoc.org/github.com/kataras/iris) for a better understanding.
+
+### 👥 Community
+
+Join the welcoming community of fellow _iris_ developers in [rocket.chat](https://kataras.rocket.chat/channel/iris)
- [Post](http://support.iris-go.com) a feature request or report a bug
- :star: and watch the public [repository](https://github.com/kataras/iris/stargazers), will keep you up to date
-- :earth_americas: publish [an article](https://medium.com/search?q=iris) or share a [tweet](https://twitter.com/hashtag/golang) about your personal experience with iris
+- :earth_americas: publish [an article](https://medium.com/search?q=iris) or share a [tweet](https://twitter.com/hashtag/golang) about your personal experience with iris.
+
+
+The most useful community repository for _iris_ developers is the
+[iris-contrib/middleware](https://github.com/iris-contrib/middleware) which contains some HTTP handlers that can help you finish a lot of your tasks even easier. Feel free to push your own middleware there!
+
+```sh
+$ go get -u github.com/iris-contrib/middleware/...
+```
+
+#### 📈 One and a half years with You...
+
+- 7070 github stars
+- 749 github forks
+- 1m total views at its documentation
+- ~800$ at donations (there're a lot for a golang open-source project, thanks to you)
+- ~550 reported bugs fixed
+- ~30 community feature requests have been implemented
+
+Thank You for your trust!
### 📌 Version
-Current: **8.0.3**
+Current: **8.0.4**
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".
diff --git a/_examples/http_request/request-logger/main.go b/_examples/http_request/request-logger/main.go
index 8f9585d2..5b3486bc 100644
--- a/_examples/http_request/request-logger/main.go
+++ b/_examples/http_request/request-logger/main.go
@@ -18,6 +18,11 @@ func main() {
Method: true,
// Path displays the request path
Path: true,
+ // Columns: true,
+
+ // if !empty then its contents derives from `ctx.Values().Get("logger_message")
+ // will be added to the logs.
+ MessageContextKey: "logger_message",
})
app.Use(customLogger)
@@ -40,6 +45,10 @@ func main() {
*/
// or catch all http errors:
app.OnAnyErrorCode(customLogger, func(ctx context.Context) {
+ // this should be added to the logs, at the end because of the `logger.Config#MessageContextKey`
+ ctx.Values().Set("logger_message",
+ "a dynamic message passed to the logs")
+
ctx.Writef("My Custom error page")
})
diff --git a/_examples/http_request/request-logger/request-logger-file/main.go b/_examples/http_request/request-logger/request-logger-file/main.go
index 5edfd11b..6f50644d 100644
--- a/_examples/http_request/request-logger/request-logger-file/main.go
+++ b/_examples/http_request/request-logger/request-logger-file/main.go
@@ -86,8 +86,8 @@ func newRequestLogger() (h context.Handler, close func() error) {
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)
+ c.LogFunc = func(now time.Time, latency time.Duration, status, ip, method, path string, message interface{}) {
+ output := logger.Columnize(now.Format("2006/01/02 - 15:04:05"), latency, status, ip, method, path, message)
logFile.Write([]byte(output))
}
diff --git a/context/context.go b/context/context.go
index b15d214f..9c786bff 100644
--- a/context/context.go
+++ b/context/context.go
@@ -803,10 +803,17 @@ func (ctx *context) BeginRequest(w http.ResponseWriter, r *http.Request) {
// 2. release the response writer
// and any other optional steps, depends on dev's application type.
func (ctx *context) EndRequest() {
- if ctx.GetStatusCode() >= 400 && ctx.writer.Written() == -1 {
- if !ctx.Application().ConfigurationReadOnly().GetDisableAutoFireStatusCode() {
+ if ctx.GetStatusCode() >= 400 &&
+ !ctx.Application().ConfigurationReadOnly().GetDisableAutoFireStatusCode() {
+ // author's note:
+ // if recording, the error handler can handle
+ // the rollback and remove any response written before,
+ // we don't have to do anything here, written is -1 when Recording
+ // because we didn't flush the response yet
+ // if !recording then check if the previous handler didn't send something
+ // to the client
+ if ctx.writer.Written() == -1 {
ctx.Application().FireErrorCode(ctx)
- return
}
}
diff --git a/core/router/fs.go b/core/router/fs.go
index 6a2e8345..e25850de 100644
--- a/core/router/fs.go
+++ b/core/router/fs.go
@@ -101,7 +101,7 @@ func StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error
// StaticHandler returns a new Handler which is ready
// to serve all kind of static files.
//
-// Developers can wrap this handler using the `iris.StripPrefix`
+// Developers can wrap this handler using the `router.StripPrefix`
// for a fixed static path when the result handler is being, finally, registered to a route.
//
//
@@ -109,7 +109,7 @@ func StaticEmbeddedHandler(vdir string, assetFn func(name string) ([]byte, error
// app := iris.New()
// ...
// fileserver := iris.StaticHandler("./static_files", false, false)
-// h := iris.StripPrefix("/static", fileserver)
+// h := router.StripPrefix("/static", fileserver)
// /* http://mydomain.com/static/css/style.css */
// app.Get("/static", h)
// ...
@@ -288,7 +288,7 @@ func (w *fsHandler) Build() context.Handler {
//
// Usage:
// fileserver := iris.StaticHandler("./static_files", false, false)
-// h := iris.StripPrefix("/static", fileserver)
+// h := router.StripPrefix("/static", fileserver)
// app.Get("/static", h)
//
func StripPrefix(prefix string, h context.Handler) context.Handler {
diff --git a/core/router/handler.go b/core/router/handler.go
index 92a40520..ab660493 100644
--- a/core/router/handler.go
+++ b/core/router/handler.go
@@ -218,11 +218,15 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
continue
}
}
- // RCF rfc2616 https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
- // The response MUST include an Allow header containing a list of valid methods for the requested resource.
- ctx.Header("Allow", methodAllowed)
- ctx.StatusCode(http.StatusMethodNotAllowed)
- return
+
+ if ctx.Method() != methodAllowed {
+ // RCF rfc2616 https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
+ // The response MUST include an Allow header containing a list of valid methods for the requested resource.
+ ctx.Header("Allow", methodAllowed)
+ ctx.StatusCode(http.StatusMethodNotAllowed)
+ return
+ }
+
}
ctx.StatusCode(http.StatusNotFound)
}
diff --git a/core/router/status.go b/core/router/status.go
index 0f7c5e99..8da0d4a4 100644
--- a/core/router/status.go
+++ b/core/router/status.go
@@ -21,8 +21,12 @@ type ErrorCodeHandler struct {
func (ch *ErrorCodeHandler) Fire(ctx context.Context) {
// if we can reset the body
if w, ok := ctx.IsRecording(); ok {
- // reset if previous content and it's recorder
- w.Reset()
+ if w.StatusCode() < 400 { // if not an error status code
+ w.WriteHeader(ch.StatusCode) // then set it manually here, otherwise it should be setted via ctx.StatusCode(...)
+ }
+ // reset if previous content and it's recorder, keep the status code.
+ w.ClearHeaders()
+ w.ResetBody()
} else if w, ok := ctx.ResponseWriter().(*context.GzipResponseWriter); ok {
// reset and disable the gzip in order to be an expected form of http error result
w.ResetBody()
@@ -41,6 +45,18 @@ func (ch *ErrorCodeHandler) Fire(ctx context.Context) {
// i.e
// users := app.Party("/users")
// users.Done(func(ctx context.Context){ if ctx.StatusCode() == 400 { /* custom error code for /users */ }})
+
+ // use .HandlerIndex
+ // that sets the current handler index to zero
+ // in order to:
+ // ignore previous runs that may changed the handler index,
+ // via ctx.Next or ctx.StopExecution, if any.
+ //
+ // use .Do
+ // that overrides the existing handlers and sets and runs these error handlers.
+ // in order to:
+ // ignore the route's after-handlers, if any.
+ ctx.HandlerIndex(0)
ctx.Do(ch.Handlers)
}
@@ -76,10 +92,7 @@ func defaultErrorCodeHandlers() *ErrorCodeHandlers {
func statusText(statusCode int) context.Handler {
return func(ctx context.Context) {
- if _, err := ctx.WriteString(http.StatusText(statusCode)); err != nil {
- // ctx.Application().Logger().Infof("(status code: %d) %s",
- // err.Error(), statusCode)
- }
+ ctx.WriteString(http.StatusText(statusCode))
}
}
@@ -113,10 +126,12 @@ func (s *ErrorCodeHandlers) Register(statusCode int, handlers ...context.Handler
StatusCode: statusCode,
Handlers: handlers,
}
+
s.handlers = append(s.handlers, ch)
return ch
}
+
// otherwise update the handlers
h.updateHandlers(handlers)
return h
@@ -136,6 +151,5 @@ func (s *ErrorCodeHandlers) Fire(ctx context.Context) {
if ch == nil {
ch = s.Register(statusCode, statusText(statusCode))
}
-
ch.Fire(ctx)
}
diff --git a/core/router/status_test.go b/core/router/status_test.go
new file mode 100644
index 00000000..c18e2ff2
--- /dev/null
+++ b/core/router/status_test.go
@@ -0,0 +1,73 @@
+// black-box testing
+package router_test
+
+import (
+ "bytes"
+ "net/http"
+ "testing"
+
+ "github.com/kataras/iris"
+ "github.com/kataras/iris/context"
+
+ "github.com/kataras/iris/httptest"
+)
+
+var defaultErrHandler = func(ctx context.Context) {
+ text := http.StatusText(ctx.GetStatusCode())
+ ctx.WriteString(text)
+}
+
+func TestOnAnyErrorCode(t *testing.T) {
+ app := iris.New()
+ app.Configure(iris.WithFireMethodNotAllowed)
+
+ buff := &bytes.Buffer{}
+ expectedPrintBeforeExecuteErr := "printed before error"
+
+ // with a middleware
+ app.OnAnyErrorCode(func(ctx context.Context) {
+ buff.WriteString(expectedPrintBeforeExecuteErr)
+ ctx.Next()
+ }, defaultErrHandler)
+
+ expectedFoundResponse := "found"
+ app.Get("/found", func(ctx context.Context) {
+ ctx.WriteString(expectedFoundResponse)
+ })
+
+ app.Get("/406", func(ctx context.Context) {
+ ctx.Record()
+ ctx.WriteString("this should not be sent, only status text will be sent")
+ ctx.WriteString("the handler can handle 'rollback' of the text when error code fired because of the recorder")
+ ctx.StatusCode(iris.StatusNotAcceptable)
+ })
+
+ e := httptest.New(t, app)
+
+ e.GET("/found").Expect().Status(iris.StatusOK).
+ Body().Equal(expectedFoundResponse)
+
+ e.GET("/notfound").Expect().Status(iris.StatusNotFound).
+ Body().Equal(http.StatusText(iris.StatusNotFound))
+
+ checkAndClearBuf(t, buff, expectedPrintBeforeExecuteErr)
+
+ e.POST("/found").Expect().Status(iris.StatusMethodNotAllowed).
+ Body().Equal(http.StatusText(iris.StatusMethodNotAllowed))
+
+ checkAndClearBuf(t, buff, expectedPrintBeforeExecuteErr)
+
+ e.GET("/406").Expect().Status(iris.StatusNotAcceptable).
+ Body().Equal(http.StatusText(iris.StatusNotAcceptable))
+
+ checkAndClearBuf(t, buff, expectedPrintBeforeExecuteErr)
+
+}
+
+func checkAndClearBuf(t *testing.T, buff *bytes.Buffer, expected string) {
+ if got, expected := buff.String(), expected; got != expected {
+ t.Fatalf("expected middleware to run before the error handler, expected %s but got %s", expected, got)
+ }
+
+ buff.Reset()
+}
diff --git a/doc.go b/doc.go
index 310ccfcb..f701150e 100644
--- a/doc.go
+++ b/doc.go
@@ -35,7 +35,7 @@ Source code and other details for the project are available at GitHub:
Current Version
-8.0.3
+8.0.4
Installation
diff --git a/iris.go b/iris.go
index 7a700daa..70796dd5 100644
--- a/iris.go
+++ b/iris.go
@@ -33,7 +33,7 @@ import (
const (
// Version is the current version number of the Iris Web Framework.
- Version = "8.0.3"
+ Version = "8.0.4"
)
// HTTP status codes as registered with IANA.
diff --git a/middleware/logger/config.go b/middleware/logger/config.go
index 8a871e24..6a3bacc4 100644
--- a/middleware/logger/config.go
+++ b/middleware/logger/config.go
@@ -10,36 +10,48 @@ import (
// See `Configuration` too.
type SkipperFunc func(ctx context.Context) bool
-// Config are the options of the logger middlweare
-// contains 4 bools
-// Status, IP, Method, Path
-// if set to true then these will print
+// Config contains the options for the logger middleware
+// can be optionally be passed to the `New`.
type Config struct {
- // Status displays status code (bool)
+ // Status displays status code (bool).
//
- // Defaults to true
+ // Defaults to true.
Status bool
- // IP displays request's remote address (bool)
+ // IP displays request's remote address (bool).
//
- // Defaults to true
+ // Defaults to true.
IP bool
- // Method displays the http method (bool)
+ // Method displays the http method (bool).
//
- // Defaults to true
+ // Defaults to true.
Method bool
- // Path displays the request path (bool)
+ // Path displays the request path (bool).
//
- // Defaults to true
+ // Defaults to true.
Path bool
- // Columns will display the logs as well formatted columns (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
+ // Defaults to true.
Columns bool
+
+ // MessageContextKey if not empty,
+ // the middleware will try to fetch
+ // the contents with `ctx.Values().Get(MessageContextKey)`
+ // and if available then these contents will be
+ // appended as part of the logs (with `%v`, in order to be able to set a struct too),
+ // if Columns field was setted to true then
+ // a new column will be added named 'Message'.
+ //
+ // Defaults to empty.
+ MessageContextKey string
+
// 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)
+ // Note that message argument can be empty.
+ LogFunc func(now time.Time, latency time.Duration, status, ip, method, path string, message interface{})
// Skippers used to skip the logging i.e by `ctx.Path()` and serve
// the next/main handler immediately.
Skippers []SkipperFunc
@@ -48,12 +60,22 @@ type Config struct {
skip SkipperFunc
}
-// DefaultConfiguration returns a default configuration
+// DefaultConfig returns a default config
// that have all boolean fields to true,
-// LogFunc to nil,
-// and Skippers to nil.
-func DefaultConfiguration() Config {
- return Config{true, true, true, true, true, nil, nil, nil}
+// all strings are empty,
+// LogFunc and Skippers to nil as well.
+func DefaultConfig() Config {
+ return Config{
+ Status: true,
+ IP: true,
+ Method: true,
+ Path: true,
+ Columns: true,
+ MessageContextKey: "",
+ LogFunc: nil,
+ Skippers: nil,
+ skip: nil,
+ }
}
// AddSkipper adds a skipper to the configuration.
diff --git a/middleware/logger/logger.go b/middleware/logger/logger.go
index 2d940301..3ec01d26 100644
--- a/middleware/logger/logger.go
+++ b/middleware/logger/logger.go
@@ -21,7 +21,7 @@ type requestLoggerMiddleware struct {
//
// Receives an optional configuation.
func New(cfg ...Config) context.Handler {
- c := DefaultConfiguration()
+ c := DefaultConfig()
if len(cfg) > 0 {
c = cfg[0]
}
@@ -48,6 +48,7 @@ func (l *requestLoggerMiddleware) ServeHTTP(ctx context.Context) {
startTime = time.Now()
ctx.Next()
+
//no time.Since in order to format it well after
endTime = time.Now()
latency = endTime.Sub(startTime)
@@ -68,27 +69,44 @@ func (l *requestLoggerMiddleware) ServeHTTP(ctx context.Context) {
path = ctx.Path()
}
+ var message interface{}
+ if ctxKey := l.config.MessageContextKey; ctxKey != "" {
+ message = ctx.Values().Get(ctxKey)
+ }
+
// print the logs
if logFunc := l.config.LogFunc; logFunc != nil {
- logFunc(endTime, latency, status, ip, method, path)
+ logFunc(endTime, latency, status, ip, method, path, message)
return
}
endTimeFormatted := endTime.Format("2006/01/02 - 15:04:05")
if l.config.Columns {
- output := Columnize(endTimeFormatted, latency, status, ip, method, path)
+ output := Columnize(endTimeFormatted, latency, status, ip, method, path, message)
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)
+ line := fmt.Sprintf("%s | %v %4v %s %s %s", endTimeFormatted, status, latency, ip, method, path)
+ if message != nil {
+ line += fmt.Sprintf(" %v", message)
+ }
+ ctx.Application().Logger().Info(line)
}
// Columnize formats the given arguments as columns and returns the formatted output,
// note that it appends a new line to the end.
-func Columnize(nowFormatted string, latency time.Duration, status, ip, method, path string) string {
+func Columnize(nowFormatted string, latency time.Duration, status, ip, method, path string, message interface{}) string {
+
+ titles := "Time | Status | Latency | IP | Method | Path"
+ line := fmt.Sprintf("%s | %v | %4v | %s | %s | %s", nowFormatted, status, latency, ip, method, path)
+ if message != nil {
+ titles += " | Message"
+ line += fmt.Sprintf(" | %v", message)
+ }
+
outputC := []string{
- "Time | Status | Latency | IP | Method | Path",
- fmt.Sprintf("%s | %v | %4v | %s | %s | %s", nowFormatted, status, latency, ip, method, path),
+ titles,
+ line,
}
output := columnize.SimpleFormat(outputC) + "\n"
return output