[MM-18150] plugin panic trace should not be lost (#13559)
* Transit panic from debug to error * Parse plugin's StdErr and output panic to the mlog.Error * Add unit tests * Change log test * Remove buffer from logger * Remove 'panic' string filter * Change *Buffer to io.Writer Co-authored-by: mattermod <mattermod@users.noreply.github.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
771574b652
Коммит
5d928b4f94
@@ -4,6 +4,7 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -33,11 +34,11 @@ type TestHelper struct {
|
|||||||
BasicChannel *model.Channel
|
BasicChannel *model.Channel
|
||||||
BasicPost *model.Post
|
BasicPost *model.Post
|
||||||
|
|
||||||
SystemAdminUser *model.User
|
SystemAdminUser *model.User
|
||||||
|
LogBuffer *bytes.Buffer
|
||||||
|
IncludeCacheLayer bool
|
||||||
|
|
||||||
tempWorkspace string
|
tempWorkspace string
|
||||||
|
|
||||||
IncludeCacheLayer bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer bool, tb testing.TB, configSet func(*model.Config)) *TestHelper {
|
func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer bool, tb testing.TB, configSet func(*model.Config)) *TestHelper {
|
||||||
@@ -59,10 +60,12 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
|
|||||||
*config.PluginSettings.ClientDirectory = filepath.Join(tempWorkspace, "webapp")
|
*config.PluginSettings.ClientDirectory = filepath.Join(tempWorkspace, "webapp")
|
||||||
memoryStore.Set(config)
|
memoryStore.Set(config)
|
||||||
|
|
||||||
|
buffer := &bytes.Buffer{}
|
||||||
|
|
||||||
var options []Option
|
var options []Option
|
||||||
options = append(options, ConfigStore(memoryStore))
|
options = append(options, ConfigStore(memoryStore))
|
||||||
options = append(options, StoreOverride(dbStore))
|
options = append(options, StoreOverride(dbStore))
|
||||||
options = append(options, SetLogger(mlog.NewTestingLogger(tb)))
|
options = append(options, SetLogger(mlog.NewTestingLogger(tb, buffer)))
|
||||||
|
|
||||||
s, err := NewServer(options...)
|
s, err := NewServer(options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -77,6 +80,7 @@ func setupTestHelper(dbStore store.Store, enterprise bool, includeCacheLayer boo
|
|||||||
th := &TestHelper{
|
th := &TestHelper{
|
||||||
App: s.FakeApp(),
|
App: s.FakeApp(),
|
||||||
Server: s,
|
Server: s,
|
||||||
|
LogBuffer: buffer,
|
||||||
IncludeCacheLayer: includeCacheLayer,
|
IncludeCacheLayer: includeCacheLayer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -626,6 +627,49 @@ func TestPluginSync(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPluginPanicLogs(t *testing.T) {
|
||||||
|
t.Run("should panic", func(t *testing.T) {
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
tearDown, _, _ := SetAppEnvironmentWithPlugins(t, []string{
|
||||||
|
`
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mattermost/mattermost-server/v5/plugin"
|
||||||
|
"github.com/mattermost/mattermost-server/v5/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MyPlugin struct {
|
||||||
|
plugin.MattermostPlugin
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *MyPlugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*model.Post, string) {
|
||||||
|
panic("some text from panic")
|
||||||
|
return nil, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
plugin.ClientMain(&MyPlugin{})
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}, th.App, th.App.NewPluginAPI)
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
post := &model.Post{
|
||||||
|
UserId: th.BasicUser.Id,
|
||||||
|
ChannelId: th.BasicChannel.Id,
|
||||||
|
Message: "message_",
|
||||||
|
CreateAt: model.GetMillis() - 10000,
|
||||||
|
}
|
||||||
|
_, err := th.App.CreatePost(post, th.BasicChannel, false)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
logs := th.LogBuffer.String()
|
||||||
|
assert.True(t, strings.Contains(logs, "some text from panic"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestProcessPrepackagedPlugins(t *testing.T) {
|
func TestProcessPrepackagedPlugins(t *testing.T) {
|
||||||
th := Setup(t).InitBasic()
|
th := Setup(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|||||||
@@ -144,6 +144,15 @@ func (l *Logger) StdLogWriter() io.Writer {
|
|||||||
return &loggerWriter{f}
|
return &loggerWriter{f}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StdErrPanicLogWriter returns a writer that can be hooked up to the output of a golang standard logger
|
||||||
|
// all of the stderr will be interpreted as log entry.
|
||||||
|
func (l *Logger) StdErrPanicLogWriter() io.Writer {
|
||||||
|
newLogger := *l
|
||||||
|
newLogger.zap = newLogger.zap.WithOptions(zap.AddCallerSkip(4), getStdLogOption())
|
||||||
|
f := newLogger.Error
|
||||||
|
return &panicLoggerWriter{f}
|
||||||
|
}
|
||||||
|
|
||||||
func (l *Logger) WithCallerSkip(skip int) *Logger {
|
func (l *Logger) WithCallerSkip(skip int) *Logger {
|
||||||
newlogger := *l
|
newlogger := *l
|
||||||
newlogger.zap = newlogger.zap.WithOptions(zap.AddCallerSkip(skip))
|
newlogger.zap = newlogger.zap.WithOptions(zap.AddCallerSkip(skip))
|
||||||
|
|||||||
@@ -85,3 +85,13 @@ func (l *loggerWriter) Write(p []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
return len(p), nil
|
return len(p), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type panicLoggerWriter struct {
|
||||||
|
logFunc func(msg string, fields ...Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *panicLoggerWriter) Write(p []byte) (int, error) {
|
||||||
|
trimmed := string(bytes.TrimSpace(p))
|
||||||
|
l.logFunc(trimmed)
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package mlog
|
package mlog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -23,9 +24,10 @@ func (tw *testingWriter) Write(b []byte) (int, error) {
|
|||||||
|
|
||||||
// NewTestingLogger creates a Logger that proxies logs through a testing interface.
|
// NewTestingLogger creates a Logger that proxies logs through a testing interface.
|
||||||
// This allows tests that spin up App instances to avoid spewing logs unless the test fails or -verbose is specified.
|
// This allows tests that spin up App instances to avoid spewing logs unless the test fails or -verbose is specified.
|
||||||
func NewTestingLogger(tb testing.TB) *Logger {
|
func NewTestingLogger(tb testing.TB, writer io.Writer) *Logger {
|
||||||
logWriter := &testingWriter{tb}
|
logWriter := &testingWriter{tb}
|
||||||
logWriterSync := zapcore.AddSync(logWriter)
|
multiWriter := io.MultiWriter(logWriter, writer)
|
||||||
|
logWriterSync := zapcore.AddSync(multiWriter)
|
||||||
|
|
||||||
testingLogger := &Logger{
|
testingLogger := &Logger{
|
||||||
consoleLevel: zap.NewAtomicLevelAt(getZapLevel("debug")),
|
consoleLevel: zap.NewAtomicLevelAt(getZapLevel("debug")),
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ func newSupervisor(pluginInfo *model.BundleInfo, apiImpl API, parentLogger *mlog
|
|||||||
HandshakeConfig: handshake,
|
HandshakeConfig: handshake,
|
||||||
Plugins: pluginMap,
|
Plugins: pluginMap,
|
||||||
Cmd: cmd,
|
Cmd: cmd,
|
||||||
|
Stderr: wrappedLogger.With(mlog.String("source", "plugin_stderr_panic")).StdErrPanicLogWriter(),
|
||||||
SyncStdout: wrappedLogger.With(mlog.String("source", "plugin_stdout")).StdLogWriter(),
|
SyncStdout: wrappedLogger.With(mlog.String("source", "plugin_stdout")).StdLogWriter(),
|
||||||
SyncStderr: wrappedLogger.With(mlog.String("source", "plugin_stderr")).StdLogWriter(),
|
SyncStderr: wrappedLogger.With(mlog.String("source", "plugin_stderr")).StdLogWriter(),
|
||||||
Logger: hclogAdaptedLogger,
|
Logger: hclogAdaptedLogger,
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user