log.go 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. package framework
  2. import (
  3. "bytes"
  4. "fmt"
  5. "regexp"
  6. "runtime/debug"
  7. "time"
  8. "github.com/onsi/ginkgo"
  9. e2eginkgowrapper "github.com/fatedier/frp/test/e2e/framework/ginkgowrapper"
  10. )
  11. func nowStamp() string {
  12. return time.Now().Format(time.StampMilli)
  13. }
  14. func log(level string, format string, args ...interface{}) {
  15. fmt.Fprintf(ginkgo.GinkgoWriter, nowStamp()+": "+level+": "+format+"\n", args...)
  16. }
  17. // Logf logs the info.
  18. func Logf(format string, args ...interface{}) {
  19. log("INFO", format, args...)
  20. }
  21. // Failf logs the fail info, including a stack trace.
  22. func Failf(format string, args ...interface{}) {
  23. FailfWithOffset(1, format, args...)
  24. }
  25. // FailfWithOffset calls "Fail" and logs the error with a stack trace that starts at "offset" levels above its caller
  26. // (for example, for call chain f -> g -> FailfWithOffset(1, ...) error would be logged for "f").
  27. func FailfWithOffset(offset int, format string, args ...interface{}) {
  28. msg := fmt.Sprintf(format, args...)
  29. skip := offset + 1
  30. log("FAIL", "%s\n\nFull Stack Trace\n%s", msg, PrunedStack(skip))
  31. e2eginkgowrapper.Fail(nowStamp()+": "+msg, skip)
  32. }
  33. // Fail is a replacement for ginkgo.Fail which logs the problem as it occurs
  34. // together with a stack trace and then calls ginkgowrapper.Fail.
  35. func Fail(msg string, callerSkip ...int) {
  36. skip := 1
  37. if len(callerSkip) > 0 {
  38. skip += callerSkip[0]
  39. }
  40. log("FAIL", "%s\n\nFull Stack Trace\n%s", msg, PrunedStack(skip))
  41. e2eginkgowrapper.Fail(nowStamp()+": "+msg, skip)
  42. }
  43. var codeFilterRE = regexp.MustCompile(`/github.com/onsi/ginkgo/`)
  44. // PrunedStack is a wrapper around debug.Stack() that removes information
  45. // about the current goroutine and optionally skips some of the initial stack entries.
  46. // With skip == 0, the returned stack will start with the caller of PruneStack.
  47. // From the remaining entries it automatically filters out useless ones like
  48. // entries coming from Ginkgo.
  49. //
  50. // This is a modified copy of PruneStack in
  51. // https://github.com/onsi/ginkgo/blob/f90f37d87fa6b1dd9625e2b1e83c23ffae3de228/internal/codelocation/code_location.go#L25:
  52. // - simplified API and thus renamed (calls debug.Stack() instead of taking a parameter)
  53. // - source code filtering updated to be specific to Kubernetes
  54. // - optimized to use bytes and in-place slice filtering from
  55. // https://github.com/golang/go/wiki/SliceTricks#filter-in-place
  56. func PrunedStack(skip int) []byte {
  57. fullStackTrace := debug.Stack()
  58. stack := bytes.Split(fullStackTrace, []byte("\n"))
  59. // Ensure that the even entries are the method names and the
  60. // odd entries the source code information.
  61. if len(stack) > 0 && bytes.HasPrefix(stack[0], []byte("goroutine ")) {
  62. // Ignore "goroutine 29 [running]:" line.
  63. stack = stack[1:]
  64. }
  65. // The "+2" is for skipping over:
  66. // - runtime/debug.Stack()
  67. // - PrunedStack()
  68. skip += 2
  69. if len(stack) > 2*skip {
  70. stack = stack[2*skip:]
  71. }
  72. n := 0
  73. for i := 0; i < len(stack)/2; i++ {
  74. // We filter out based on the source code file name.
  75. if !codeFilterRE.Match(stack[i*2+1]) {
  76. stack[n] = stack[i*2]
  77. stack[n+1] = stack[i*2+1]
  78. n += 2
  79. }
  80. }
  81. stack = stack[:n]
  82. return bytes.Join(stack, []byte("\n"))
  83. }