mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-07-04 02:56:18 +00:00
Merge pull request #755 from sboeuf/add_sigs_netmon
netmon: Add signals handler support
This commit is contained in:
commit
d3340f828d
@ -58,7 +58,7 @@ EXAMPLE:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var signals = map[string]syscall.Signal{
|
var signalList = map[string]syscall.Signal{
|
||||||
"SIGABRT": syscall.SIGABRT,
|
"SIGABRT": syscall.SIGABRT,
|
||||||
"SIGALRM": syscall.SIGALRM,
|
"SIGALRM": syscall.SIGALRM,
|
||||||
"SIGBUS": syscall.SIGBUS,
|
"SIGBUS": syscall.SIGBUS,
|
||||||
@ -159,13 +159,13 @@ func kill(ctx context.Context, containerID, signal string, all bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func processSignal(signal string) (syscall.Signal, error) {
|
func processSignal(signal string) (syscall.Signal, error) {
|
||||||
signum, signalOk := signals[signal]
|
signum, signalOk := signalList[signal]
|
||||||
if signalOk {
|
if signalOk {
|
||||||
return signum, nil
|
return signum, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Support for short name signals (INT)
|
// Support for short name signals (INT)
|
||||||
signum, signalOk = signals["SIG"+signal]
|
signum, signalOk = signalList["SIG"+signal]
|
||||||
if signalOk {
|
if signalOk {
|
||||||
return signum, nil
|
return signum, nil
|
||||||
}
|
}
|
||||||
@ -178,7 +178,7 @@ func processSignal(signal string) (syscall.Signal, error) {
|
|||||||
|
|
||||||
signum = syscall.Signal(s)
|
signum = syscall.Signal(s)
|
||||||
// Check whether signal is valid or not
|
// Check whether signal is valid or not
|
||||||
for _, sig := range signals {
|
for _, sig := range signalList {
|
||||||
if sig == signum {
|
if sig == signum {
|
||||||
// signal is a valid signal
|
// signal is a valid signal
|
||||||
return signum, nil
|
return signum, nil
|
||||||
|
23
cli/main.go
23
cli/main.go
@ -17,6 +17,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/kata-containers/runtime/pkg/signals"
|
||||||
vc "github.com/kata-containers/runtime/virtcontainers"
|
vc "github.com/kata-containers/runtime/virtcontainers"
|
||||||
vf "github.com/kata-containers/runtime/virtcontainers/factory"
|
vf "github.com/kata-containers/runtime/virtcontainers/factory"
|
||||||
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
|
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
|
||||||
@ -178,12 +179,18 @@ func init() {
|
|||||||
// first (root) span must be created in beforeSubcommands()): it is simply
|
// first (root) span must be created in beforeSubcommands()): it is simply
|
||||||
// used to pass to the crash handling functions to finalise tracing.
|
// used to pass to the crash handling functions to finalise tracing.
|
||||||
func setupSignalHandler(ctx context.Context) {
|
func setupSignalHandler(ctx context.Context) {
|
||||||
|
signals.SetLogger(kataLog)
|
||||||
|
|
||||||
sigCh := make(chan os.Signal, 8)
|
sigCh := make(chan os.Signal, 8)
|
||||||
|
|
||||||
for _, sig := range handledSignals() {
|
for _, sig := range signals.HandledSignals() {
|
||||||
signal.Notify(sigCh, sig)
|
signal.Notify(sigCh, sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dieCb := func() {
|
||||||
|
stopTracing(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
sig := <-sigCh
|
sig := <-sigCh
|
||||||
@ -195,12 +202,12 @@ func setupSignalHandler(ctx context.Context) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if fatalSignal(nativeSignal) {
|
if signals.FatalSignal(nativeSignal) {
|
||||||
kataLog.WithField("signal", sig).Error("received fatal signal")
|
kataLog.WithField("signal", sig).Error("received fatal signal")
|
||||||
die(ctx)
|
signals.Die(dieCb)
|
||||||
} else if debug && nonFatalSignal(nativeSignal) {
|
} else if debug && signals.NonFatalSignal(nativeSignal) {
|
||||||
kataLog.WithField("signal", sig).Debug("handling signal")
|
kataLog.WithField("signal", sig).Debug("handling signal")
|
||||||
backtrace()
|
signals.Backtrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@ -521,7 +528,11 @@ func main() {
|
|||||||
// create a new empty context
|
// create a new empty context
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
defer handlePanic(ctx)
|
dieCb := func() {
|
||||||
|
stopTracing(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer signals.HandlePanic(dieCb)
|
||||||
|
|
||||||
createRuntime(ctx)
|
createRuntime(ctx)
|
||||||
}
|
}
|
||||||
|
@ -1,99 +0,0 @@
|
|||||||
// Copyright 2018 Intel Corporation.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
//
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os/signal"
|
|
||||||
"runtime/pprof"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// List of handled signals.
|
|
||||||
//
|
|
||||||
// The value is true if receiving the signal should be fatal.
|
|
||||||
var handledSignalsMap = map[syscall.Signal]bool{
|
|
||||||
syscall.SIGABRT: true,
|
|
||||||
syscall.SIGBUS: true,
|
|
||||||
syscall.SIGILL: true,
|
|
||||||
syscall.SIGQUIT: true,
|
|
||||||
syscall.SIGSEGV: true,
|
|
||||||
syscall.SIGSTKFLT: true,
|
|
||||||
syscall.SIGSYS: true,
|
|
||||||
syscall.SIGTRAP: true,
|
|
||||||
syscall.SIGUSR1: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
func handlePanic(ctx context.Context) {
|
|
||||||
r := recover()
|
|
||||||
|
|
||||||
if r != nil {
|
|
||||||
msg := fmt.Sprintf("%s", r)
|
|
||||||
kataLog.WithField("panic", msg).Error("fatal error")
|
|
||||||
|
|
||||||
die(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func backtrace() {
|
|
||||||
profiles := pprof.Profiles()
|
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
|
|
||||||
for _, p := range profiles {
|
|
||||||
// The magic number requests a full stacktrace. See
|
|
||||||
// https://golang.org/pkg/runtime/pprof/#Profile.WriteTo.
|
|
||||||
pprof.Lookup(p.Name()).WriteTo(buf, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, line := range strings.Split(buf.String(), "\n") {
|
|
||||||
kataLog.Error(line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fatalSignal(sig syscall.Signal) bool {
|
|
||||||
s, exists := handledSignalsMap[sig]
|
|
||||||
if !exists {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func nonFatalSignal(sig syscall.Signal) bool {
|
|
||||||
s, exists := handledSignalsMap[sig]
|
|
||||||
if !exists {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return !s
|
|
||||||
}
|
|
||||||
|
|
||||||
func handledSignals() []syscall.Signal {
|
|
||||||
var signals []syscall.Signal
|
|
||||||
|
|
||||||
for sig := range handledSignalsMap {
|
|
||||||
signals = append(signals, sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
return signals
|
|
||||||
}
|
|
||||||
|
|
||||||
func die(ctx context.Context) {
|
|
||||||
stopTracing(ctx)
|
|
||||||
|
|
||||||
backtrace()
|
|
||||||
|
|
||||||
if crashOnError {
|
|
||||||
signal.Reset(syscall.SIGABRT)
|
|
||||||
syscall.Kill(0, syscall.SIGABRT)
|
|
||||||
}
|
|
||||||
|
|
||||||
exit(1)
|
|
||||||
}
|
|
@ -7,6 +7,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -14,10 +15,13 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/kata-containers/runtime/pkg/signals"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
lSyslog "github.com/sirupsen/logrus/hooks/syslog"
|
lSyslog "github.com/sirupsen/logrus/hooks/syslog"
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
@ -203,6 +207,39 @@ func (n *netmon) cleanup() {
|
|||||||
close(n.rtDoneCh)
|
close(n.rtDoneCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setupSignalHandler sets up signal handling, starting a go routine to deal
|
||||||
|
// with signals as they arrive.
|
||||||
|
func (n *netmon) setupSignalHandler() {
|
||||||
|
signals.SetLogger(n.logger())
|
||||||
|
|
||||||
|
sigCh := make(chan os.Signal, 8)
|
||||||
|
|
||||||
|
for _, sig := range signals.HandledSignals() {
|
||||||
|
signal.Notify(sigCh, sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
sig := <-sigCh
|
||||||
|
|
||||||
|
nativeSignal, ok := sig.(syscall.Signal)
|
||||||
|
if !ok {
|
||||||
|
err := errors.New("unknown signal")
|
||||||
|
netmonLog.WithError(err).WithField("signal", sig.String()).Error()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if signals.FatalSignal(nativeSignal) {
|
||||||
|
netmonLog.WithField("signal", sig).Error("received fatal signal")
|
||||||
|
signals.Die(nil)
|
||||||
|
} else if n.debug && signals.NonFatalSignal(nativeSignal) {
|
||||||
|
netmonLog.WithField("signal", sig).Debug("handling signal")
|
||||||
|
signals.Backtrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
func (n *netmon) logger() *logrus.Entry {
|
func (n *netmon) logger() *logrus.Entry {
|
||||||
fields := logrus.Fields{
|
fields := logrus.Fields{
|
||||||
"name": netmonName,
|
"name": netmonName,
|
||||||
@ -641,6 +678,9 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup signal handlers
|
||||||
|
n.setupSignalHandler()
|
||||||
|
|
||||||
// Scan the current interfaces.
|
// Scan the current interfaces.
|
||||||
if err := n.scanNetwork(); err != nil {
|
if err := n.scanNetwork(); err != nil {
|
||||||
n.logger().WithError(err).Fatal("scanNetwork()")
|
n.logger().WithError(err).Fatal("scanNetwork()")
|
||||||
|
127
pkg/signals/signals.go
Normal file
127
pkg/signals/signals.go
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
// Copyright 2018 Intel Corporation.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
package signals
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"runtime/pprof"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var signalLog = logrus.WithField("default-signal-logger", true)
|
||||||
|
|
||||||
|
// CrashOnError causes a coredump to be produced when an internal error occurs
|
||||||
|
// or a fatal signal is received.
|
||||||
|
var CrashOnError = false
|
||||||
|
|
||||||
|
// List of handled signals.
|
||||||
|
//
|
||||||
|
// The value is true if receiving the signal should be fatal.
|
||||||
|
var handledSignalsMap = map[syscall.Signal]bool{
|
||||||
|
syscall.SIGABRT: true,
|
||||||
|
syscall.SIGBUS: true,
|
||||||
|
syscall.SIGILL: true,
|
||||||
|
syscall.SIGQUIT: true,
|
||||||
|
syscall.SIGSEGV: true,
|
||||||
|
syscall.SIGSTKFLT: true,
|
||||||
|
syscall.SIGSYS: true,
|
||||||
|
syscall.SIGTRAP: true,
|
||||||
|
syscall.SIGUSR1: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// DieCb is the callback function type that needs to be defined for every call
|
||||||
|
// into the Die() function. This callback will be run as the first function of
|
||||||
|
// the Die() implementation.
|
||||||
|
type DieCb func()
|
||||||
|
|
||||||
|
// SetLogger sets the custom logger to be used by this package. If not called,
|
||||||
|
// the package will create its own logger.
|
||||||
|
func SetLogger(logger *logrus.Entry) {
|
||||||
|
signalLog = logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlePanic writes a message to the logger and then calls Die().
|
||||||
|
func HandlePanic(dieCb DieCb) {
|
||||||
|
r := recover()
|
||||||
|
|
||||||
|
if r != nil {
|
||||||
|
msg := fmt.Sprintf("%s", r)
|
||||||
|
signalLog.WithField("panic", msg).Error("fatal error")
|
||||||
|
|
||||||
|
Die(dieCb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backtrace writes a multi-line backtrace to the logger.
|
||||||
|
func Backtrace() {
|
||||||
|
profiles := pprof.Profiles()
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
|
for _, p := range profiles {
|
||||||
|
// The magic number requests a full stacktrace. See
|
||||||
|
// https://golang.org/pkg/runtime/pprof/#Profile.WriteTo.
|
||||||
|
pprof.Lookup(p.Name()).WriteTo(buf, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, line := range strings.Split(buf.String(), "\n") {
|
||||||
|
signalLog.Error(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FatalSignal returns true if the specified signal should cause the program
|
||||||
|
// to abort.
|
||||||
|
func FatalSignal(sig syscall.Signal) bool {
|
||||||
|
s, exists := handledSignalsMap[sig]
|
||||||
|
if !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// NonFatalSignal returns true if the specified signal should simply cause the
|
||||||
|
// program to Backtrace() but continue running.
|
||||||
|
func NonFatalSignal(sig syscall.Signal) bool {
|
||||||
|
s, exists := handledSignalsMap[sig]
|
||||||
|
if !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return !s
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandledSignals returns a list of signals the package can deal with.
|
||||||
|
func HandledSignals() []syscall.Signal {
|
||||||
|
var signals []syscall.Signal
|
||||||
|
|
||||||
|
for sig := range handledSignalsMap {
|
||||||
|
signals = append(signals, sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
return signals
|
||||||
|
}
|
||||||
|
|
||||||
|
// Die causes a backtrace to be produced. If CrashOnError is set a coredump
|
||||||
|
// will be produced, else the program will exit.
|
||||||
|
func Die(dieCb DieCb) {
|
||||||
|
dieCb()
|
||||||
|
|
||||||
|
Backtrace()
|
||||||
|
|
||||||
|
if CrashOnError {
|
||||||
|
signal.Reset(syscall.SIGABRT)
|
||||||
|
syscall.Kill(0, syscall.SIGABRT)
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
@ -3,10 +3,11 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
//
|
//
|
||||||
|
|
||||||
package main
|
package signals
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
goruntime "runtime"
|
goruntime "runtime"
|
||||||
"sort"
|
"sort"
|
||||||
@ -14,6 +15,7 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -21,7 +23,7 @@ func TestSignalFatalSignal(t *testing.T) {
|
|||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
for sig, fatal := range handledSignalsMap {
|
for sig, fatal := range handledSignalsMap {
|
||||||
result := nonFatalSignal(sig)
|
result := NonFatalSignal(sig)
|
||||||
if fatal {
|
if fatal {
|
||||||
assert.False(result)
|
assert.False(result)
|
||||||
} else {
|
} else {
|
||||||
@ -34,7 +36,7 @@ func TestSignalHandledSignalsMap(t *testing.T) {
|
|||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
for sig, fatal := range handledSignalsMap {
|
for sig, fatal := range handledSignalsMap {
|
||||||
result := fatalSignal(sig)
|
result := FatalSignal(sig)
|
||||||
if fatal {
|
if fatal {
|
||||||
assert.True(result)
|
assert.True(result)
|
||||||
} else {
|
} else {
|
||||||
@ -52,7 +54,7 @@ func TestSignalHandledSignals(t *testing.T) {
|
|||||||
expected = append(expected, sig)
|
expected = append(expected, sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
got := handledSignals()
|
got := HandledSignals()
|
||||||
|
|
||||||
sort.Slice(expected, func(i, j int) bool {
|
sort.Slice(expected, func(i, j int) bool {
|
||||||
return int(expected[i]) < int(expected[j])
|
return int(expected[i]) < int(expected[j])
|
||||||
@ -69,7 +71,7 @@ func TestSignalNonFatalSignal(t *testing.T) {
|
|||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
for sig, fatal := range handledSignalsMap {
|
for sig, fatal := range handledSignalsMap {
|
||||||
result := nonFatalSignal(sig)
|
result := NonFatalSignal(sig)
|
||||||
if fatal {
|
if fatal {
|
||||||
assert.False(result)
|
assert.False(result)
|
||||||
} else {
|
} else {
|
||||||
@ -83,7 +85,7 @@ func TestSignalFatalSignalInvalidSignal(t *testing.T) {
|
|||||||
|
|
||||||
sig := syscall.SIGXCPU
|
sig := syscall.SIGXCPU
|
||||||
|
|
||||||
result := fatalSignal(sig)
|
result := FatalSignal(sig)
|
||||||
assert.False(result)
|
assert.False(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,23 +94,34 @@ func TestSignalNonFatalSignalInvalidSignal(t *testing.T) {
|
|||||||
|
|
||||||
sig := syscall.SIGXCPU
|
sig := syscall.SIGXCPU
|
||||||
|
|
||||||
result := nonFatalSignal(sig)
|
result := NonFatalSignal(sig)
|
||||||
assert.False(result)
|
assert.False(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSignalBacktrace(t *testing.T) {
|
func TestSignalBacktrace(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
savedLog := signalLog
|
||||||
|
defer func() {
|
||||||
|
signalLog = savedLog
|
||||||
|
}()
|
||||||
|
|
||||||
|
signalLog = logrus.WithFields(logrus.Fields{
|
||||||
|
"name": "name",
|
||||||
|
"pid": os.Getpid(),
|
||||||
|
"source": "throttler",
|
||||||
|
"test-logger": true})
|
||||||
|
|
||||||
// create buffer to save logger output
|
// create buffer to save logger output
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
savedOut := kataLog.Logger.Out
|
savedOut := signalLog.Logger.Out
|
||||||
defer func() {
|
defer func() {
|
||||||
kataLog.Logger.Out = savedOut
|
signalLog.Logger.Out = savedOut
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// capture output to buffer
|
// capture output to buffer
|
||||||
kataLog.Logger.Out = buf
|
signalLog.Logger.Out = buf
|
||||||
|
|
||||||
// determine name of *this* function
|
// determine name of *this* function
|
||||||
pc := make([]uintptr, 1)
|
pc := make([]uintptr, 1)
|
||||||
@ -116,12 +129,12 @@ func TestSignalBacktrace(t *testing.T) {
|
|||||||
fn := goruntime.FuncForPC(pc[0])
|
fn := goruntime.FuncForPC(pc[0])
|
||||||
name := fn.Name()
|
name := fn.Name()
|
||||||
|
|
||||||
backtrace()
|
Backtrace()
|
||||||
|
|
||||||
b := buf.String()
|
b := buf.String()
|
||||||
|
|
||||||
// very basic tests to check if a backtrace was produced
|
// very basic tests to check if a backtrace was produced
|
||||||
assert.True(strings.Contains(b, "contention:"))
|
assert.True(strings.Contains(b, "contention:"))
|
||||||
assert.True(strings.Contains(b, `"level":"error"`))
|
assert.True(strings.Contains(b, `level=error`))
|
||||||
assert.True(strings.Contains(b, name))
|
assert.True(strings.Contains(b, name))
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user