mirror of
https://github.com/kataras/iris.git
synced 2025-03-14 08:16:28 +01:00
add a way to customize the handler names (recommended: before server execution) so route logging can be more human-friendly on handlers like standard iris middlewares, e.g. request logger
Former-commit-id: 039c233f2d4da0d52b1d6fc86b6d73be14b15608
This commit is contained in:
parent
f75ec4e67c
commit
d1f18501e8
|
@ -2,12 +2,16 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kataras/iris/v12"
|
"github.com/kataras/iris/v12"
|
||||||
|
"github.com/kataras/iris/v12/middleware/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := iris.New()
|
app := iris.New()
|
||||||
app.Logger().SetLevel("debug")
|
app.Logger().SetLevel("debug")
|
||||||
|
|
||||||
|
// Register a request logger middleware to the application.
|
||||||
|
app.Use(logger.New())
|
||||||
|
|
||||||
// GET: http://localhost:8080
|
// GET: http://localhost:8080
|
||||||
app.Get("/", info)
|
app.Get("/", info)
|
||||||
|
|
||||||
|
@ -112,6 +116,9 @@ func main() {
|
||||||
ctx.Writef(name)
|
ctx.Writef(name)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.None("/secret", privateHandler)
|
||||||
|
app.Get("/public", execPrivateHandler)
|
||||||
|
|
||||||
// GET: http://localhost:8080/
|
// GET: http://localhost:8080/
|
||||||
// GET: http://localhost:8080/profile/anyusername
|
// GET: http://localhost:8080/profile/anyusername
|
||||||
// GET: http://localhost:8080/profile/anyusername/backups/any/number/of/paths/here
|
// GET: http://localhost:8080/profile/anyusername/backups/any/number/of/paths/here
|
||||||
|
@ -130,6 +137,15 @@ func main() {
|
||||||
app.Listen(":8080")
|
app.Listen(":8080")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func privateHandler(ctx iris.Context) {
|
||||||
|
ctx.WriteString(`This can only be executed programmatically through server's another route:
|
||||||
|
ctx.Exec(iris.MethodNone, "/secret")`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func execPrivateHandler(ctx iris.Context) {
|
||||||
|
ctx.Exec(iris.MethodNone, "/secret")
|
||||||
|
}
|
||||||
|
|
||||||
func info(ctx iris.Context) {
|
func info(ctx iris.Context) {
|
||||||
method := ctx.Method() // the http method requested a server's resource.
|
method := ctx.Method() // the http method requested a server's resource.
|
||||||
subdomain := ctx.Subdomain() // the subdomain, if any.
|
subdomain := ctx.Subdomain() // the subdomain, if any.
|
||||||
|
|
|
@ -3,10 +3,40 @@ package context
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
handlerNames = make(map[*regexp.Regexp]string)
|
||||||
|
handlerNamesMu sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// PackageName is the Iris Go module package name.
|
||||||
|
var PackageName = strings.TrimSuffix(reflect.TypeOf(Handlers{}).PkgPath(), "/context")
|
||||||
|
|
||||||
|
// SetHandlerName sets a handler name that could be
|
||||||
|
// fetched through `HandlerName`. The "original" should be
|
||||||
|
// the Go's original regexp-featured (can be retrieved through a `HandlerName` call) function name.
|
||||||
|
// The "replacement" should be the custom, human-text of that function name.
|
||||||
|
//
|
||||||
|
// If the name starts with "iris" then it replaces that string with the
|
||||||
|
// full Iris module package name,
|
||||||
|
// e.g. iris/middleware/logger.(*requestLoggerMiddleware).ServeHTTP-fm to
|
||||||
|
// github.com/kataras/iris/v12/middleware/logger.(*requestLoggerMiddleware).ServeHTTP-fm
|
||||||
|
// for convenient between Iris versions.
|
||||||
|
func SetHandlerName(original string, replacement string) {
|
||||||
|
if strings.HasPrefix(original, "iris") {
|
||||||
|
original = PackageName + strings.TrimPrefix(original, "iris")
|
||||||
|
}
|
||||||
|
|
||||||
|
handlerNamesMu.Lock()
|
||||||
|
handlerNames[regexp.MustCompile(original)] = replacement
|
||||||
|
handlerNamesMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// A Handler responds to an HTTP request.
|
// A Handler responds to an HTTP request.
|
||||||
// It writes reply headers and data to the Context.ResponseWriter() and then return.
|
// It writes reply headers and data to the Context.ResponseWriter() and then return.
|
||||||
// Returning signals that the request is finished;
|
// Returning signals that the request is finished;
|
||||||
|
@ -14,7 +44,7 @@ import (
|
||||||
//
|
//
|
||||||
// Depending on the HTTP client software, HTTP protocol version,
|
// Depending on the HTTP client software, HTTP protocol version,
|
||||||
// and any intermediaries between the client and the iris server,
|
// and any intermediaries between the client and the iris server,
|
||||||
// it may not be possible to read from the Context.Request().Body after writing to the context.ResponseWriter().
|
// it may not be possible to read from the Context.Request().Body after writing to the Context.ResponseWriter().
|
||||||
// Cautious handlers should read the Context.Request().Body first, and then reply.
|
// Cautious handlers should read the Context.Request().Body first, and then reply.
|
||||||
//
|
//
|
||||||
// Except for reading the body, handlers should not modify the provided Context.
|
// Except for reading the body, handlers should not modify the provided Context.
|
||||||
|
@ -37,10 +67,21 @@ func valueOf(v interface{}) reflect.Value {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandlerName returns the handler's function name.
|
// HandlerName returns the handler's function name.
|
||||||
// See `context.HandlerName` to get function name of the current running handler in the chain.
|
// See `Context.HandlerName` method to get function name of the current running handler in the chain.
|
||||||
|
// See `SetHandlerName` too.
|
||||||
func HandlerName(h interface{}) string {
|
func HandlerName(h interface{}) string {
|
||||||
pc := valueOf(h).Pointer()
|
pc := valueOf(h).Pointer()
|
||||||
return runtime.FuncForPC(pc).Name()
|
name := runtime.FuncForPC(pc).Name()
|
||||||
|
handlerNamesMu.RLock()
|
||||||
|
for regex, newName := range handlerNames {
|
||||||
|
if regex.MatchString(name) {
|
||||||
|
name = newName
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handlerNamesMu.RUnlock()
|
||||||
|
|
||||||
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandlerFileLine returns the handler's file and line information.
|
// HandlerFileLine returns the handler's file and line information.
|
||||||
|
|
|
@ -58,6 +58,9 @@ type RouteReadOnly interface {
|
||||||
// MainHandlerName returns the first registered handler for the route.
|
// MainHandlerName returns the first registered handler for the route.
|
||||||
MainHandlerName() string
|
MainHandlerName() string
|
||||||
|
|
||||||
|
// MainHandlerIndex returns the first registered handler's index for the route.
|
||||||
|
MainHandlerIndex() int
|
||||||
|
|
||||||
// StaticSites if not empty, refers to the system (or virtual if embedded) directory
|
// StaticSites if not empty, refers to the system (or virtual if embedded) directory
|
||||||
// and sub directories that this "GET" route was registered to serve files and folders
|
// and sub directories that this "GET" route was registered to serve files and folders
|
||||||
// that contain index.html (a site). The index handler may registered by other
|
// that contain index.html (a site). The index handler may registered by other
|
||||||
|
|
|
@ -452,10 +452,13 @@ func (api *APIBuilder) CreateRoutes(methods []string, relativePath string, handl
|
||||||
mainHandlers := context.Handlers(handlers)
|
mainHandlers := context.Handlers(handlers)
|
||||||
// before join the middleware + handlers + done handlers and apply the execution rules.
|
// before join the middleware + handlers + done handlers and apply the execution rules.
|
||||||
|
|
||||||
possibleMainHandlerName, mainHandlerIndex := context.MainHandlerName(mainHandlers)
|
mainHandlerName, mainHandlerIndex := context.MainHandlerName(mainHandlers)
|
||||||
wd, _ := os.Getwd()
|
wd, _ := os.Getwd()
|
||||||
mainHandlerFileName, mainHandlerFileNumber := context.HandlerFileLineRel(handlers[mainHandlerIndex], wd)
|
mainHandlerFileName, mainHandlerFileNumber := context.HandlerFileLineRel(handlers[mainHandlerIndex], wd)
|
||||||
|
|
||||||
|
// re-calculate mainHandlerIndex in favor of the middlewares.
|
||||||
|
mainHandlerIndex = len(api.middleware) + len(api.beginGlobalHandlers) + mainHandlerIndex
|
||||||
|
|
||||||
// TODO: for UseGlobal/DoneGlobal that doesn't work.
|
// TODO: for UseGlobal/DoneGlobal that doesn't work.
|
||||||
applyExecutionRules(api.handlerExecutionRules, &beginHandlers, &doneHandlers, &mainHandlers)
|
applyExecutionRules(api.handlerExecutionRules, &beginHandlers, &doneHandlers, &mainHandlers)
|
||||||
|
|
||||||
|
@ -474,18 +477,23 @@ func (api *APIBuilder) CreateRoutes(methods []string, relativePath string, handl
|
||||||
routes := make([]*Route, len(methods))
|
routes := make([]*Route, len(methods))
|
||||||
|
|
||||||
for i, m := range methods {
|
for i, m := range methods {
|
||||||
route, err := NewRoute(m, subdomain, path, possibleMainHandlerName, routeHandlers, *api.macros)
|
route, err := NewRoute(m, subdomain, path, routeHandlers, *api.macros)
|
||||||
if err != nil { // template path parser errors:
|
if err != nil { // template path parser errors:
|
||||||
api.errors.Addf("[%s:%d] %v -> %s:%s:%s", filename, line, err, m, subdomain, path)
|
api.errors.Addf("[%s:%d] %v -> %s:%s:%s", filename, line, err, m, subdomain, path)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
route.SourceFileName = mainHandlerFileName
|
// The caller tiself, if anonymous, it's the first line of `app.X("/path", here)`
|
||||||
route.SourceLineNumber = mainHandlerFileNumber
|
|
||||||
|
|
||||||
route.RegisterFileName = filename
|
route.RegisterFileName = filename
|
||||||
route.RegisterLineNumber = line
|
route.RegisterLineNumber = line
|
||||||
|
|
||||||
|
route.MainHandlerName = mainHandlerName
|
||||||
|
route.MainHandlerIndex = mainHandlerIndex
|
||||||
|
|
||||||
|
// The main handler source, could be the same as the register's if anonymous.
|
||||||
|
route.SourceFileName = mainHandlerFileName
|
||||||
|
route.SourceLineNumber = mainHandlerFileNumber
|
||||||
|
|
||||||
// Add UseGlobal & DoneGlobal Handlers
|
// Add UseGlobal & DoneGlobal Handlers
|
||||||
route.Use(api.beginGlobalHandlers...)
|
route.Use(api.beginGlobalHandlers...)
|
||||||
route.Done(api.doneGlobalHandlers...)
|
route.Done(api.doneGlobalHandlers...)
|
||||||
|
|
|
@ -174,7 +174,7 @@ func (h *routerHandler) Build(provider RoutesProvider) error {
|
||||||
defer golog.SetTimeFormat(bckpTimeFormat)
|
defer golog.SetTimeFormat(bckpTimeFormat)
|
||||||
golog.SetTimeFormat("")
|
golog.SetTimeFormat("")
|
||||||
|
|
||||||
for _, method := range AllMethods { // TODO: MethodNone "NONE" with an (offline) status on r.Trace.
|
for _, method := range append(AllMethods, MethodNone) {
|
||||||
methodRoutes := collect(method)
|
methodRoutes := collect(method)
|
||||||
for _, r := range methodRoutes {
|
for _, r := range methodRoutes {
|
||||||
golog.Println(r.Trace())
|
golog.Println(r.Trace())
|
||||||
|
|
|
@ -29,8 +29,9 @@ type Route struct {
|
||||||
beginHandlers context.Handlers
|
beginHandlers context.Handlers
|
||||||
// Handlers are the main route's handlers, executed by order.
|
// Handlers are the main route's handlers, executed by order.
|
||||||
// Cannot be empty.
|
// Cannot be empty.
|
||||||
Handlers context.Handlers `json:"-"`
|
Handlers context.Handlers `json:"-"`
|
||||||
MainHandlerName string `json:"mainHandlerName"`
|
MainHandlerName string `json:"mainHandlerName"`
|
||||||
|
MainHandlerIndex int `json:"mainHandlerIndex"`
|
||||||
// temp storage, they're appended to the Handlers on build.
|
// temp storage, they're appended to the Handlers on build.
|
||||||
// Execution happens after Begin and main Handler(s), can be empty.
|
// Execution happens after Begin and main Handler(s), can be empty.
|
||||||
doneHandlers context.Handlers
|
doneHandlers context.Handlers
|
||||||
|
@ -67,7 +68,7 @@ type Route struct {
|
||||||
// handlers and the macro container which all routes should share.
|
// handlers and the macro container which all routes should share.
|
||||||
// It parses the path based on the "macros",
|
// It parses the path based on the "macros",
|
||||||
// handlers are being changed to validate the macros at serve time, if needed.
|
// handlers are being changed to validate the macros at serve time, if needed.
|
||||||
func NewRoute(method, subdomain, unparsedPath, mainHandlerName string,
|
func NewRoute(method, subdomain, unparsedPath string,
|
||||||
handlers context.Handlers, macros macro.Macros) (*Route, error) {
|
handlers context.Handlers, macros macro.Macros) (*Route, error) {
|
||||||
tmpl, err := macro.Parse(unparsedPath, macros)
|
tmpl, err := macro.Parse(unparsedPath, macros)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -87,15 +88,14 @@ func NewRoute(method, subdomain, unparsedPath, mainHandlerName string,
|
||||||
formattedPath := formatPath(path)
|
formattedPath := formatPath(path)
|
||||||
|
|
||||||
route := &Route{
|
route := &Route{
|
||||||
Name: defaultName,
|
Name: defaultName,
|
||||||
Method: method,
|
Method: method,
|
||||||
methodBckp: method,
|
methodBckp: method,
|
||||||
Subdomain: subdomain,
|
Subdomain: subdomain,
|
||||||
tmpl: tmpl,
|
tmpl: tmpl,
|
||||||
Path: path,
|
Path: path,
|
||||||
Handlers: handlers,
|
Handlers: handlers,
|
||||||
MainHandlerName: mainHandlerName,
|
FormattedPath: formattedPath,
|
||||||
FormattedPath: formattedPath,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return route, nil
|
return route, nil
|
||||||
|
@ -357,20 +357,17 @@ func ignoreHandlerTrace(name string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func traceHandlerFile(name, line string, number int, seen map[string]struct{}) string {
|
func traceHandlerFile(name, line string, number int) string {
|
||||||
file := fmt.Sprintf("(%s:%d)", line, number)
|
file := fmt.Sprintf("(%s:%d)", line, number)
|
||||||
|
|
||||||
if _, printed := seen[file]; printed {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
seen[file] = struct{}{}
|
|
||||||
|
|
||||||
// trim the path for Iris' internal middlewares, e.g.
|
// trim the path for Iris' internal middlewares, e.g.
|
||||||
// irs/mvc.GRPC.Apply.func1
|
// irs/mvc.GRPC.Apply.func1
|
||||||
if internalName := "github.com/kataras/iris/v12"; strings.HasPrefix(name, internalName) {
|
if internalName := context.PackageName; strings.HasPrefix(name, internalName) {
|
||||||
name = strings.Replace(name, internalName, "iris", 1)
|
name = strings.Replace(name, internalName, "iris", 1)
|
||||||
}
|
}
|
||||||
|
if internalName := "github.com/iris-contrib/middleware"; strings.HasPrefix(name, internalName) {
|
||||||
|
name = strings.Replace(name, internalName, "iris-contrib", 1)
|
||||||
|
}
|
||||||
|
|
||||||
if ignoreHandlerTrace(name) {
|
if ignoreHandlerTrace(name) {
|
||||||
return ""
|
return ""
|
||||||
|
@ -408,6 +405,8 @@ func (r *Route) Trace() string {
|
||||||
color = pio.Gray
|
color = pio.Gray
|
||||||
case http.MethodTrace:
|
case http.MethodTrace:
|
||||||
color = pio.Yellow
|
color = pio.Yellow
|
||||||
|
case MethodNone:
|
||||||
|
color = 196 // full red.
|
||||||
}
|
}
|
||||||
|
|
||||||
path := r.Tmpl().Src
|
path := r.Tmpl().Src
|
||||||
|
@ -419,36 +418,60 @@ func (r *Route) Trace() string {
|
||||||
s := fmt.Sprintf("%s: %s", pio.Rich(r.Method, color), path)
|
s := fmt.Sprintf("%s: %s", pio.Rich(r.Method, color), path)
|
||||||
|
|
||||||
// (@description)
|
// (@description)
|
||||||
if r.Description != "" {
|
description := r.Description
|
||||||
s += fmt.Sprintf(" %s", pio.Rich(r.Description, pio.Cyan, pio.Underline))
|
if description == "" && r.Method == MethodNone {
|
||||||
|
description = "offline"
|
||||||
|
}
|
||||||
|
|
||||||
|
if description != "" {
|
||||||
|
s += fmt.Sprintf(" %s", pio.Rich(description, pio.Cyan, pio.Underline))
|
||||||
}
|
}
|
||||||
|
|
||||||
// (@route_rel_location)
|
// (@route_rel_location)
|
||||||
s += fmt.Sprintf(" (%s:%d)", r.RegisterFileName, r.RegisterLineNumber)
|
s += fmt.Sprintf(" (%s:%d)", r.RegisterFileName, r.RegisterLineNumber)
|
||||||
|
|
||||||
seen := make(map[string]struct{})
|
|
||||||
|
|
||||||
// if the main handler is not an anonymous function (so, differs from @route_rel_location)
|
|
||||||
// then * @handler_name (@handler_rel_location)
|
|
||||||
if r.SourceFileName != r.RegisterFileName || r.SourceLineNumber != r.RegisterLineNumber {
|
|
||||||
s += traceHandlerFile(r.MainHandlerName, r.SourceFileName, r.SourceLineNumber, seen)
|
|
||||||
}
|
|
||||||
|
|
||||||
wd, _ := os.Getwd()
|
wd, _ := os.Getwd()
|
||||||
|
|
||||||
for _, h := range r.Handlers {
|
for i, h := range r.Handlers {
|
||||||
name := context.HandlerName(h)
|
// main handler info can be programmatically
|
||||||
if name == r.MainHandlerName {
|
// changed to be more specific, respect those options.
|
||||||
continue
|
var (
|
||||||
|
name string
|
||||||
|
file string
|
||||||
|
line int
|
||||||
|
)
|
||||||
|
|
||||||
|
// name = context.HandlerName(h)
|
||||||
|
// file, line = context.HandlerFileLineRel(h, wd)
|
||||||
|
// fmt.Printf("---handler index: %d, name: %s, line: %s:%d\n", i, name, file, line)
|
||||||
|
|
||||||
|
if i == r.MainHandlerIndex {
|
||||||
|
name = r.MainHandlerName
|
||||||
|
file = r.SourceFileName
|
||||||
|
line = r.SourceLineNumber
|
||||||
|
// fmt.Printf("main handler index: %d, name: %s, line: %d\n", i, name, line)
|
||||||
|
} else {
|
||||||
|
name = context.HandlerName(h)
|
||||||
|
file, line = context.HandlerFileLineRel(h, wd)
|
||||||
|
|
||||||
|
// If a middleware, e.g (macro) which changes the main handler index,
|
||||||
|
// skip it.
|
||||||
|
if file == r.SourceFileName && line == r.SourceLineNumber {
|
||||||
|
// fmt.Printf("[0] SKIP: handler index: %d, name: %s, line: %s:%d\n", i, name, file, line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// fmt.Printf("handler index: %d, name: %s, line: %d\n", i, name, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
file, line := context.HandlerFileLineRel(h, wd)
|
// If a handler is an anonymous function then it was already
|
||||||
|
// printed in the first line, skip it.
|
||||||
if file == r.RegisterFileName && line == r.RegisterLineNumber {
|
if file == r.RegisterFileName && line == r.RegisterLineNumber {
|
||||||
|
// fmt.Printf("[1] SKIP: handler index: %d, name: %s, line: %s:%d\n", i, name, file, line)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// * @handler_name (@handler_rel_location)
|
// * @handler_name (@handler_rel_location)
|
||||||
s += traceHandlerFile(name, file, line, seen)
|
s += traceHandlerFile(name, file, line)
|
||||||
}
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
@ -486,6 +509,10 @@ func (rd routeReadOnlyWrapper) MainHandlerName() string {
|
||||||
return rd.Route.MainHandlerName
|
return rd.Route.MainHandlerName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rd routeReadOnlyWrapper) MainHandlerIndex() int {
|
||||||
|
return rd.Route.MainHandlerIndex
|
||||||
|
}
|
||||||
|
|
||||||
func (rd routeReadOnlyWrapper) StaticSites() []context.StaticSite {
|
func (rd routeReadOnlyWrapper) StaticSites() []context.StaticSite {
|
||||||
return rd.Route.StaticSites
|
return rd.Route.StaticSites
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,10 @@ import (
|
||||||
"github.com/ryanuber/columnize"
|
"github.com/ryanuber/columnize"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
context.SetHandlerName("iris/middleware/logger.*", "Request Logger")
|
||||||
|
}
|
||||||
|
|
||||||
type requestLoggerMiddleware struct {
|
type requestLoggerMiddleware struct {
|
||||||
config Config
|
config Config
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user