You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

panic.go 3.5 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. // Copyright 2013 Martini Authors
  2. // Copyright 2014 The Macaron Authors
  3. // Copyright 2019 The Gitea Authors. All rights reserved.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License"): you may
  6. // not use this file except in compliance with the License. You may obtain
  7. // a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  13. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  14. // License for the specific language governing permissions and limitations
  15. // under the License.
  16. package context
  17. import (
  18. "bytes"
  19. "fmt"
  20. "io/ioutil"
  21. "runtime"
  22. macaron "gopkg.in/macaron.v1"
  23. )
  24. // Recovery returns a middleware that recovers from any panics and writes a 500 and a log if so.
  25. // Although similar to macaron.Recovery() the main difference is that this error will be created
  26. // with the gitea 500 page.
  27. func Recovery() macaron.Handler {
  28. return func(ctx *Context) {
  29. defer func() {
  30. if err := recover(); err != nil {
  31. combinedErr := fmt.Errorf("%s\n%s", err, string(stack(3)))
  32. ctx.ServerError("PANIC:", combinedErr)
  33. }
  34. }()
  35. ctx.Next()
  36. }
  37. }
  38. var (
  39. unknown = []byte("???")
  40. )
  41. // Although we could just use debug.Stack(), this routine will return the source code
  42. // skip the provided number of frames - i.e. allowing us to ignore this function call
  43. // and the preceding function call.
  44. // If the problem is a lack of memory of course all this is not going to work...
  45. func stack(skip int) []byte {
  46. buf := new(bytes.Buffer)
  47. // Store the last file we opened as its probable that the preceding stack frame
  48. // will be in the same file
  49. var lines [][]byte
  50. var lastFilename string
  51. for i := skip; ; i++ { // Skip over frames
  52. programCounter, filename, lineNumber, ok := runtime.Caller(i)
  53. // If we can't retrieve the information break - basically we're into go internals at this point.
  54. if !ok {
  55. break
  56. }
  57. // Print equivalent of debug.Stack()
  58. fmt.Fprintf(buf, "%s:%d (0x%x)\n", filename, lineNumber, programCounter)
  59. // Now try to print the offending line
  60. if filename != lastFilename {
  61. data, err := ioutil.ReadFile(filename)
  62. if err != nil {
  63. // can't read this sourcefile
  64. // likely we don't have the sourcecode available
  65. continue
  66. }
  67. lines = bytes.Split(data, []byte{'\n'})
  68. lastFilename = filename
  69. }
  70. fmt.Fprintf(buf, "\t%s: %s\n", functionName(programCounter), source(lines, lineNumber))
  71. }
  72. return buf.Bytes()
  73. }
  74. // functionName converts the provided programCounter into a function name
  75. func functionName(programCounter uintptr) []byte {
  76. function := runtime.FuncForPC(programCounter)
  77. if function == nil {
  78. return unknown
  79. }
  80. name := []byte(function.Name())
  81. // Because we provide the filename we can drop the preceding package name.
  82. if lastslash := bytes.LastIndex(name, []byte("/")); lastslash >= 0 {
  83. name = name[lastslash+1:]
  84. }
  85. // And the current package name.
  86. if period := bytes.Index(name, []byte(".")); period >= 0 {
  87. name = name[period+1:]
  88. }
  89. // And we should just replace the interpunct with a dot
  90. name = bytes.Replace(name, []byte("·"), []byte("."), -1)
  91. return name
  92. }
  93. // source returns a space-trimmed slice of the n'th line.
  94. func source(lines [][]byte, n int) []byte {
  95. n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
  96. if n < 0 || n >= len(lines) {
  97. return unknown
  98. }
  99. return bytes.TrimSpace(lines[n])
  100. }