diff --git a/context.go b/context.go index 3ff29de..95b3885 100644 --- a/context.go +++ b/context.go @@ -25,6 +25,7 @@ type objectItem struct { // safe, copy context to use in other goroutines type Context struct { enableAccessLog bool + debug bool response Response input *http.Request output http.ResponseWriter @@ -43,7 +44,8 @@ type Context struct { logs.Logger } -func (c *Context) HttpRW(enableAccessLog bool, r *http.Request, w http.ResponseWriter) { +func (c *Context) HttpRW(debug, enableAccessLog bool, r *http.Request, w http.ResponseWriter) { + c.debug = debug c.enableAccessLog = enableAccessLog c.input = r c.output = &c.response @@ -72,7 +74,7 @@ func (c *Context) Process(plugins []iface.IPlugin) { } -func (c *Context) Start(plugins []iface.IPlugin){ +func (c *Context) Start(plugins []iface.IPlugin) { // generate request id logId := c.Header("X-Log-Id", "") if logId == "" { @@ -103,7 +105,7 @@ func (c *Context) finish(goLog bool) { c.End(status, []byte(http.StatusText(status))) } - c.Error("%s, trace[%s]", util.ToString(v), util.PanicTrace(TraceMaxDepth, false)) + c.Error("%s, trace[%s]", util.ToString(v), util.PanicTrace(TraceMaxDepth, false, c.debug)) } if !goLog { diff --git a/context_test.go b/context_test.go index f3a185f..e9e78bd 100644 --- a/context_test.go +++ b/context_test.go @@ -121,7 +121,7 @@ func TestContext_Getter(t *testing.T) { rb := bytes.NewBuffer([]byte(str)) w.Body = rb - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) context.Output().WriteHeader(rICode) context.Output().Write([]byte(str)) @@ -189,7 +189,7 @@ func TestContext_ClientIp(t *testing.T) { r := httptest.NewRequest(method, path, body) ip := "129.1.1.1" r.Header.Set("X-Forwarded-For", ip+",") - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) if context.ClientIp() != ip { t.FailNow() } @@ -199,7 +199,7 @@ func TestContext_ClientIp(t *testing.T) { r := httptest.NewRequest(method, path, body) ip := "129.1.1.1" r.Header.Set("X-Forwarded-For", ip) - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) if context.ClientIp() != ip { t.FailNow() } @@ -209,7 +209,7 @@ func TestContext_ClientIp(t *testing.T) { r := httptest.NewRequest(method, path, body) ip := "129.1.1.1" r.Header.Set("X-Client-Ip", ip) - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) if context.ClientIp() != ip { t.FailNow() } @@ -219,7 +219,7 @@ func TestContext_ClientIp(t *testing.T) { r := httptest.NewRequest(method, path, body) ip := "129.1.1.1" r.Header.Set("X-Real-Ip", ip) - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) if context.ClientIp() != ip { t.FailNow() } @@ -229,7 +229,7 @@ func TestContext_ClientIp(t *testing.T) { r := httptest.NewRequest(method, path, body) ip := "129.1.1.1" r.RemoteAddr = ip - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) if context.ClientIp() != ip { t.FailNow() } @@ -255,7 +255,7 @@ func TestContext_Cookie(t *testing.T) { cookieUser1 := &http.Cookie{Name: cName + "1", Value: cNameV + "1", Path: "/"} r.AddCookie(cookieUser1) - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) t.Run("GetCookie", func(t *testing.T) { if context.Cookie(cName, "") != cNameV { t.FailNow() @@ -296,7 +296,7 @@ func TestContext_Header(t *testing.T) { r.Header.Set(cName, cNameV) r.Header.Set(cName+"1", cNameV+"1") - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) t.Run("GetHeader", func(t *testing.T) { if context.Header(cName, "") != cNameV { t.FailNow() @@ -337,7 +337,7 @@ func TestContext_Query(t *testing.T) { body := bytes.NewReader([]byte(str)) w := httptest.NewRecorder() r := httptest.NewRequest(method, path, body) - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) t.Run("Query", func(t *testing.T) { if context.Query(param1, "") != paramV1 { t.FailNow() @@ -370,7 +370,7 @@ func TestContext_QueryArray(t *testing.T) { body := bytes.NewReader([]byte(str)) w := httptest.NewRecorder() r := httptest.NewRequest(method, path, body) - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) if len(context.QueryArray(param1)) != 2 { t.FailNow() } @@ -397,7 +397,7 @@ func TestContext_Post(t *testing.T) { r := httptest.NewRequest(method, path, body) r.Header.Set("Content-type", "application/x-www-form-urlencoded") - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) t.Run("Post", func(t *testing.T) { if context.Post(param1, "") != paramV1 { @@ -434,7 +434,7 @@ func TestContext_PostArray(t *testing.T) { r := httptest.NewRequest(method, path, body) r.Header.Set("Content-type", "application/x-www-form-urlencoded") - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) if len(context.PostArray(param1)) != 2 { t.FailNow() @@ -472,7 +472,7 @@ func TestContext_Param(t *testing.T) { r := httptest.NewRequest(method, path, body) r.Header.Set("Content-type", "application/x-www-form-urlencoded") - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) if context.Param(param1, "") != paramV1 { t.Fatal("context.Param(param1, \"\") != ", paramV1) @@ -518,7 +518,7 @@ func TestContext_ParamArray(t *testing.T) { r := httptest.NewRequest(method, path, body) r.Header.Set("Content-type", "application/x-www-form-urlencoded") - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) if len(context.ParamArray(param1)) != 2 { t.Fatal("len(context.ParamArray(param1)) !=", 2) diff --git a/controller.go b/controller.go index 2c5450b..119817c 100644 --- a/controller.go +++ b/controller.go @@ -44,7 +44,7 @@ func (c *Controller) AfterAction(action string) { } // HandlePanic process unhandled action panic -func (c *Controller) HandlePanic(v interface{}) { +func (c *Controller) HandlePanic(v interface{}, debug bool) { status := http.StatusInternalServerError switch e := v.(type) { case *perror.Error: @@ -54,7 +54,7 @@ func (c *Controller) HandlePanic(v interface{}) { c.Json(EmptyObject, status) } - c.Context().Error("%s, trace[%s]", util.ToString(v), util.PanicTrace(TraceMaxDepth, false)) + c.Context().Error("%s, trace[%s]", util.ToString(v), util.PanicTrace(TraceMaxDepth, false, debug)) } // Redirect output redirect response diff --git a/controller_test.go b/controller_test.go index c78bb4c..990e37f 100644 --- a/controller_test.go +++ b/controller_test.go @@ -49,7 +49,7 @@ func TestController_HandlePanic(t *testing.T) { }) defer patchesController.Reset() - mockC.HandlePanic("testerr") + mockC.HandlePanic("testerr", false) } @@ -64,7 +64,7 @@ func TestController_Json(t *testing.T) { r := httptest.NewRequest(method, path, body) w := httptest.NewRecorder() - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) mockC := &Controller{} mockC.SetContext(context) @@ -100,7 +100,7 @@ func TestController_Jsonp(t *testing.T) { r := httptest.NewRequest(method, path, body) w := httptest.NewRecorder() - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) mockC := &Controller{} mockC.SetContext(context) @@ -141,7 +141,7 @@ func TestController_Data(t *testing.T) { r := httptest.NewRequest(method, path, body) w := httptest.NewRecorder() - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) mockC := &Controller{} mockC.SetContext(context) @@ -171,7 +171,7 @@ func TestController_Xml(t *testing.T) { r := httptest.NewRequest(method, path, body) w := httptest.NewRecorder() - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) mockC := &Controller{} mockC.SetContext(context) @@ -208,7 +208,7 @@ func TestController_Xml(t *testing.T) { // r := httptest.NewRequest(method, path, body) // w := httptest.NewRecorder() // -// context.HttpRW(true, r, w) +// context.HttpRW(false,true, r, w) // // mockC := &Controller{} // mockC.SetContext(context) @@ -243,7 +243,7 @@ func TestController_Render(t *testing.T) { r := httptest.NewRequest(method, path, body) w := httptest.NewRecorder() - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) mockC := &Controller{} mockC.SetContext(context) @@ -275,7 +275,7 @@ func TestController_View(t *testing.T) { r := httptest.NewRequest(method, path, body) w := httptest.NewRecorder() - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) mockC := &Controller{} mockC.SetContext(context) diff --git a/file_test.go b/file_test.go index 0128965..738cfff 100644 --- a/file_test.go +++ b/file_test.go @@ -43,7 +43,7 @@ func TestFile_HandleRequest(t *testing.T) { r := httptest.NewRequest(method, "/view", body) w := httptest.NewRecorder() context := &Context{} - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) file := NewFile(nil) file.HandleRequest(context) @@ -61,7 +61,7 @@ func TestFile_HandleRequest(t *testing.T) { r := httptest.NewRequest(method, "/view.html", body) w := httptest.NewRecorder() context := &Context{} - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) file := NewFile(nil) file.SetExcludeExtensions([]interface{}{".html"}) file.HandleRequest(context) @@ -79,7 +79,7 @@ func TestFile_HandleRequest(t *testing.T) { r := httptest.NewRequest("POST", "/view.html", body) w := httptest.NewRecorder() context := &Context{} - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) file := NewFile(nil) file.HandleRequest(context) @@ -93,7 +93,7 @@ func TestFile_HandleRequest(t *testing.T) { r := httptest.NewRequest(method, "/viewNotExists.html", body) w := httptest.NewRecorder() context := &Context{} - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) file := NewFile(nil) file.HandleRequest(context) @@ -108,7 +108,7 @@ func TestFile_HandleRequest(t *testing.T) { r := httptest.NewRequest(method, "/view.html", body) w := httptest.NewRecorder() context := &Context{} - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) file := NewFile(nil) file.HandleRequest(context) diff --git a/gzip_test.go b/gzip_test.go index f3fd3a1..d6d7cb7 100644 --- a/gzip_test.go +++ b/gzip_test.go @@ -22,7 +22,7 @@ func TestGzip_HandleRequest(t *testing.T) { r := httptest.NewRequest("GET", "/view", body) w := httptest.NewRecorder() context := &Context{} - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) gzip := NewGzip() gzip.HandleRequest(context) @@ -37,7 +37,7 @@ func TestGzip_HandleRequest(t *testing.T) { r.Header.Set("Accept-Encoding", "gzip") w := httptest.NewRecorder() context := &Context{} - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) gzip := NewGzip() gzip.HandleRequest(context) @@ -52,7 +52,7 @@ func TestGzip_HandleRequest(t *testing.T) { r.Header.Set("Accept-Encoding", "gzip") w := httptest.NewRecorder() context := &Context{} - context.HttpRW(true, r, w) + context.HttpRW(false,true, r, w) gzip := NewGzip() gzip.HandleRequest(context) diff --git a/iface/interface.go b/iface/interface.go index 0d68ebf..cd8d539 100644 --- a/iface/interface.go +++ b/iface/interface.go @@ -31,7 +31,7 @@ type IObject interface { type IController interface { BeforeAction(action string) AfterAction(action string) - HandlePanic(v interface{}) + HandlePanic(v interface{}, debug bool) } type IPlugin interface { @@ -73,7 +73,7 @@ type IView interface { } type IContext interface { - HttpRW(enableAccessLog bool, r *http.Request, w http.ResponseWriter) + HttpRW(debug, enableAccessLog bool, r *http.Request, w http.ResponseWriter) Process(plugins []IPlugin) Notice(format string, v ...interface{}) Debug(format string, v ...interface{}) diff --git a/server.go b/server.go index d981951..60a2098 100644 --- a/server.go +++ b/server.go @@ -49,6 +49,7 @@ type ServerConfig struct { // statsInterval: "60s" // enableAccessLog: true // maxPostBodySize: 1048576 +// debug:true func NewServer(config map[string]interface{}) *Server { server := &Server{ maxHeaderBytes: DefaultHeaderBytes, @@ -86,6 +87,7 @@ type Server struct { servers []*http.Server // http server list pool sync.Pool // Context pool maxPostBodySize int64 // max post body size + debug bool // debug=true not recover panic } // SetHttpAddr set http addr, if both httpAddr and httpsAddr @@ -156,6 +158,11 @@ func (s *Server) SetEnableAccessLog(v bool) { s.enableAccessLog = v } +// SetDebug set debug +func (s *Server) SetDebug(v bool) { + s.debug = v +} + // SetPlugins set plugin by names func (s *Server) SetPlugins(v []interface{}) { for _, vv := range v { @@ -251,11 +258,9 @@ func (s *Server) Serve() { wg.Wait() } - - // ServeCMD serve command request func (s *Server) ServeCMD() { - ctx := Context{enableAccessLog: s.enableAccessLog} + ctx := Context{debug: s.debug, enableAccessLog: s.enableAccessLog} // only apply the last plugin for command ctx.Process(s.plugins[len(s.plugins)-1:]) } @@ -271,7 +276,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { ctx := s.pool.Get().(iface.IContext) - ctx.HttpRW(s.enableAccessLog, r, w) + ctx.HttpRW(s.debug, s.enableAccessLog, r, w) ctx.Process(s.plugins) s.pool.Put(ctx) } @@ -308,7 +313,7 @@ func (s *Server) HandleRequest(ctx iface.IContext) { defer func() { if v := recover(); v != nil { - controller.HandlePanic(v) + controller.HandlePanic(v, s.debug) } // after action hook diff --git a/test/mock/iface/interface.go b/test/mock/iface/interface.go index 567b720..5635e44 100644 --- a/test/mock/iface/interface.go +++ b/test/mock/iface/interface.go @@ -253,15 +253,15 @@ func (mr *MockIControllerMockRecorder) AfterAction(action interface{}) *gomock.C } // HandlePanic mocks base method -func (m *MockIController) HandlePanic(v interface{}) { +func (m *MockIController) HandlePanic(v interface{}, debug bool) { m.ctrl.T.Helper() - m.ctrl.Call(m, "HandlePanic", v) + m.ctrl.Call(m, "HandlePanic", v, debug) } // HandlePanic indicates an expected call of HandlePanic -func (mr *MockIControllerMockRecorder) HandlePanic(v interface{}) *gomock.Call { +func (mr *MockIControllerMockRecorder) HandlePanic(v, debug interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandlePanic", reflect.TypeOf((*MockIController)(nil).HandlePanic), v) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HandlePanic", reflect.TypeOf((*MockIController)(nil).HandlePanic), v, debug) } // MockIPlugin is a mock of IPlugin interface @@ -508,15 +508,15 @@ func (m *MockIContext) EXPECT() *MockIContextMockRecorder { } // HttpRW mocks base method -func (m *MockIContext) HttpRW(enableAccessLog bool, r *http.Request, w http.ResponseWriter) { +func (m *MockIContext) HttpRW(debug, enableAccessLog bool, r *http.Request, w http.ResponseWriter) { m.ctrl.T.Helper() - m.ctrl.Call(m, "HttpRW", enableAccessLog, r, w) + m.ctrl.Call(m, "HttpRW", debug, enableAccessLog, r, w) } // HttpRW indicates an expected call of HttpRW -func (mr *MockIContextMockRecorder) HttpRW(enableAccessLog, r, w interface{}) *gomock.Call { +func (mr *MockIContextMockRecorder) HttpRW(debug, enableAccessLog, r, w interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HttpRW", reflect.TypeOf((*MockIContext)(nil).HttpRW), enableAccessLog, r, w) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HttpRW", reflect.TypeOf((*MockIContext)(nil).HttpRW), debug, enableAccessLog, r, w) } // Process mocks base method diff --git a/util/misc.go b/util/misc.go index b80cd24..42481e2 100644 --- a/util/misc.go +++ b/util/misc.go @@ -26,7 +26,7 @@ var ( langRe = regexp.MustCompile(`(?i)([a-z]+)(?:[_-]([a-z]+))?`) // stack trace regexp: /path/to/src/file.go:line - traceRe = regexp.MustCompile(`^\t(.*)/src/(.*:\d+)\s`) + traceRe = regexp.MustCompile(`^\t(.*)/pkg/(.*:\d+)\s`) // version format regexp: v10.1.0 verFmtRe = regexp.MustCompile(`(?i)^v?(\d+\.*)+`) @@ -112,35 +112,41 @@ func FormatLanguage(lang string) string { } // PanicTrace get panic trace -func PanicTrace(maxDepth int, multiLine bool) string { +func PanicTrace(maxDepth int, multiLine, debug bool) string { buf := make([]byte, 1024) if n := runtime.Stack(buf, false); n < len(buf) { buf = buf[:n] } stack := bytes.NewBuffer(buf) + if debug{ + return stack.String() + } + sources := make([]string, 0, maxDepth) meetPanic := false for { line, err := stack.ReadString('\n') - if err != nil || len(sources) >= maxDepth { + + if line == "" || err != nil || len(sources) >= maxDepth { break } - mat := traceRe.FindStringSubmatch(line) - if mat == nil { + // skip until first panic + if strings.Index(line, "runtime/panic.go") !=-1 { + meetPanic = true continue } - // skip until first panic - if strings.HasPrefix(mat[2], "runtime/panic.go") { - meetPanic = true + mat := traceRe.FindStringSubmatch(line) + if mat == nil { continue } // skip system file if strings.HasPrefix(mat[1], runtime.GOROOT()) { + continue } @@ -152,7 +158,6 @@ func PanicTrace(maxDepth int, multiLine bool) string { if multiLine { return strings.Join(sources, "\n") } - return strings.Join(sources, ",") }