stacktrace_ext_test.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. // Copyright (c) 2016, 2017 Uber Technologies, Inc.
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy
  4. // of this software and associated documentation files (the "Software"), to deal
  5. // in the Software without restriction, including without limitation the rights
  6. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. // copies of the Software, and to permit persons to whom the Software is
  8. // furnished to do so, subject to the following conditions:
  9. //
  10. // The above copyright notice and this permission notice shall be included in
  11. // all copies or substantial portions of the Software.
  12. //
  13. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. // THE SOFTWARE.
  20. package zap_test
  21. import (
  22. "bytes"
  23. "encoding/json"
  24. "io/ioutil"
  25. "os"
  26. "os/exec"
  27. "path/filepath"
  28. "runtime"
  29. "strings"
  30. "testing"
  31. "go.uber.org/zap"
  32. "go.uber.org/zap/zapcore"
  33. "github.com/stretchr/testify/assert"
  34. "github.com/stretchr/testify/require"
  35. )
  36. // _zapPackages are packages that we search for in the logging output to match a
  37. // zap stack frame. It is different from _zapStacktracePrefixes which is only
  38. // intended to match on the function name, while this is on the full output
  39. // which includes filenames.
  40. var _zapPackages = []string{
  41. "go.uber.org/zap.",
  42. "go.uber.org/zap/zapcore.",
  43. }
  44. func TestStacktraceFiltersZapLog(t *testing.T) {
  45. withLogger(t, func(logger *zap.Logger, out *bytes.Buffer) {
  46. logger.Error("test log")
  47. logger.Sugar().Error("sugar test log")
  48. require.Contains(t, out.String(), "TestStacktraceFiltersZapLog", "Should not strip out non-zap import")
  49. verifyNoZap(t, out.String())
  50. })
  51. }
  52. func TestStacktraceFiltersZapMarshal(t *testing.T) {
  53. withLogger(t, func(logger *zap.Logger, out *bytes.Buffer) {
  54. marshal := func(enc zapcore.ObjectEncoder) error {
  55. logger.Warn("marshal caused warn")
  56. enc.AddString("f", "v")
  57. return nil
  58. }
  59. logger.Error("test log", zap.Object("obj", zapcore.ObjectMarshalerFunc(marshal)))
  60. logs := out.String()
  61. // The marshal function (which will be under the test function) should not be stripped.
  62. const marshalFnPrefix = "TestStacktraceFiltersZapMarshal."
  63. require.Contains(t, logs, marshalFnPrefix, "Should not strip out marshal call")
  64. // There should be no zap stack traces before that point.
  65. marshalIndex := strings.Index(logs, marshalFnPrefix)
  66. verifyNoZap(t, logs[:marshalIndex])
  67. // After that point, there should be zap stack traces - we don't want to strip out
  68. // the Marshal caller information.
  69. for _, fnPrefix := range _zapPackages {
  70. require.Contains(t, logs[marshalIndex:], fnPrefix, "Missing zap caller stack for Marshal")
  71. }
  72. })
  73. }
  74. func TestStacktraceFiltersVendorZap(t *testing.T) {
  75. // We already have the dependencies downloaded so this should be
  76. // instant.
  77. deps := downloadDependencies(t)
  78. // We need to simulate a zap as a vendor library, so we're going to
  79. // create a fake GOPATH and run the above test which will contain zap
  80. // in the vendor directory.
  81. withGoPath(t, func(goPath string) {
  82. zapDir, err := os.Getwd()
  83. require.NoError(t, err, "Failed to get current directory")
  84. testDir := filepath.Join(goPath, "src/go.uber.org/zap_test/")
  85. vendorDir := filepath.Join(testDir, "vendor")
  86. require.NoError(t, os.MkdirAll(testDir, 0777), "Failed to create source director")
  87. curFile := getSelfFilename(t)
  88. setupSymlink(t, curFile, filepath.Join(testDir, curFile))
  89. // Set up symlinks for zap, and for any test dependencies.
  90. setupSymlink(t, zapDir, filepath.Join(vendorDir, "go.uber.org/zap"))
  91. for _, dep := range deps {
  92. setupSymlink(t, dep.Dir, filepath.Join(vendorDir, dep.ImportPath))
  93. }
  94. // Now run the above test which ensures we filter out zap
  95. // stacktraces, but this time zap is in a vendor
  96. cmd := exec.Command("go", "test", "-v", "-run", "TestStacktraceFiltersZap")
  97. cmd.Dir = testDir
  98. cmd.Env = append(os.Environ(), "GO111MODULE=off")
  99. out, err := cmd.CombinedOutput()
  100. require.NoError(t, err, "Failed to run test in vendor directory, output: %s", out)
  101. assert.Contains(t, string(out), "PASS")
  102. })
  103. }
  104. // withLogger sets up a logger with a real encoder set up, so that any marshal functions are called.
  105. // The inbuilt observer does not call Marshal for objects/arrays, which we need for some tests.
  106. func withLogger(t *testing.T, fn func(logger *zap.Logger, out *bytes.Buffer)) {
  107. buf := &bytes.Buffer{}
  108. encoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
  109. core := zapcore.NewCore(encoder, zapcore.AddSync(buf), zapcore.DebugLevel)
  110. logger := zap.New(core, zap.AddStacktrace(zap.DebugLevel))
  111. fn(logger, buf)
  112. }
  113. func verifyNoZap(t *testing.T, logs string) {
  114. for _, fnPrefix := range _zapPackages {
  115. require.NotContains(t, logs, fnPrefix, "Should not strip out marshal call")
  116. }
  117. }
  118. func withGoPath(t *testing.T, f func(goPath string)) {
  119. goPath, err := ioutil.TempDir("", "gopath")
  120. require.NoError(t, err, "Failed to create temporary directory for GOPATH")
  121. //defer os.RemoveAll(goPath)
  122. os.Setenv("GOPATH", goPath)
  123. defer os.Setenv("GOPATH", os.Getenv("GOPATH"))
  124. f(goPath)
  125. }
  126. func getSelfFilename(t *testing.T) string {
  127. _, file, _, ok := runtime.Caller(0)
  128. require.True(t, ok, "Failed to get caller information to identify local file")
  129. return filepath.Base(file)
  130. }
  131. func setupSymlink(t *testing.T, src, dst string) {
  132. // Make sure the destination directory exists.
  133. os.MkdirAll(filepath.Dir(dst), 0777)
  134. // Get absolute path of the source for the symlink, otherwise we can create a symlink
  135. // that uses relative paths.
  136. srcAbs, err := filepath.Abs(src)
  137. require.NoError(t, err, "Failed to get absolute path")
  138. require.NoError(t, os.Symlink(srcAbs, dst), "Failed to set up symlink")
  139. }
  140. type dependency struct {
  141. ImportPath string `json:"Path"` // import path of the dependency
  142. Dir string `json:"Dir"` // location on disk
  143. }
  144. // Downloads all dependencies for the current Go module and reports their
  145. // module paths and locations on disk.
  146. func downloadDependencies(t *testing.T) []dependency {
  147. cmd := exec.Command("go", "mod", "download", "-json")
  148. stdout, err := cmd.Output()
  149. require.NoError(t, err, "Failed to run 'go mod download'")
  150. var deps []dependency
  151. dec := json.NewDecoder(bytes.NewBuffer(stdout))
  152. for dec.More() {
  153. var d dependency
  154. require.NoError(t, dec.Decode(&d), "Failed to decode dependency")
  155. deps = append(deps, d)
  156. }
  157. return deps
  158. }