Skip to content

Commit d2a064e

Browse files
authored
Complete the implementation of moving panics to the Error log level (#353)
* cleanup panic logging flag Don't shadow the builtin `panic` symbol. Remove the redundant `false` assignments. Flatten the if block since we are already using a switch. * add Panic to grpc test service * update impls with new Panic method * test that server panics go to ERROR * CHANGELOG * fix test plugin mapping * use Debug for logger test to avoid timed output * Use buf to generate test proto (#354) It wasn't clear which versions of the protobuf tooling needed to be used to generate the test code, and updating them required some additional changes. Since the `buf` tool was adopted to generate the example code, use the same tool to make generating the test code easier too.
1 parent b02adb3 commit d2a064e

File tree

9 files changed

+421
-361
lines changed

9 files changed

+421
-361
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## v1.7.0
2+
3+
CHANGES:
4+
5+
* When go-plugin encounters a stack trace on the server stderr stream, it now raises output to a log-level of Error instead of Debug. [[GH-292](https://github.com/hashicorp/go-plugin/pull/292)]
6+
7+
18
## v1.6.2
29

310
ENHANCEMENTS:

client.go

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,10 +1175,13 @@ func (c *Client) logStderr(name string, r io.Reader) {
11751175
reader := bufio.NewReaderSize(r, c.config.PluginLogBufferSize)
11761176
// continuation indicates the previous line was a prefix
11771177
continuation := false
1178-
// panic indicates the previous line was the start of a panic output
1179-
panic := false
1178+
1179+
// inPanic indicates we saw the start of a stack trace and should divert all
1180+
// remaining untagged lines to stderr
1181+
var inPanic bool
11801182

11811183
for {
1184+
11821185
line, isPrefix, err := reader.ReadLine()
11831186
switch {
11841187
case err == io.EOF:
@@ -1225,33 +1228,24 @@ func (c *Client) logStderr(name string, r io.Reader) {
12251228
// string prefixes
12261229
switch line := string(line); {
12271230
case strings.HasPrefix(line, "[TRACE]"):
1228-
panic = false
12291231
l.Trace(line)
12301232
case strings.HasPrefix(line, "[DEBUG]"):
1231-
panic = false
12321233
l.Debug(line)
12331234
case strings.HasPrefix(line, "[INFO]"):
1234-
panic = false
12351235
l.Info(line)
12361236
case strings.HasPrefix(line, "[WARN]"):
1237-
panic = false
12381237
l.Warn(line)
12391238
case strings.HasPrefix(line, "[ERROR]"):
1240-
panic = false
12411239
l.Error(line)
1242-
case strings.HasPrefix(line, "panic:"):
1243-
panic = true
1240+
case strings.HasPrefix(line, "panic: ") || strings.HasPrefix(line, "fatal error: "):
1241+
inPanic = true
1242+
fallthrough
1243+
case inPanic:
12441244
l.Error(line)
12451245
default:
1246-
if panic {
1247-
l.Error(line)
1248-
} else {
1249-
l.Debug(line)
1250-
}
1246+
l.Debug(line)
12511247
}
12521248
} else {
1253-
panic = false
1254-
12551249
logLevel := hclog.LevelFromString(entry.Level)
12561250
if logLevel != hclog.NoLevel && logLevel < loggerLevel {
12571251
// The logger will ignore this log entry anyway, so we

client_test.go

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1443,7 +1443,7 @@ func TestClient_mtlsNetRPCClient(t *testing.T) {
14431443
}
14441444

14451445
func TestClient_logger(t *testing.T) {
1446-
t.Run("net/rpc", func(t *testing.T) { testClient_logger(t, "netrpc") })
1446+
t.Run("netrpc", func(t *testing.T) { testClient_logger(t, "netrpc") })
14471447
t.Run("grpc", func(t *testing.T) { testClient_logger(t, "grpc") })
14481448
}
14491449

@@ -1454,16 +1454,21 @@ func testClient_logger(t *testing.T, proto string) {
14541454
// Custom hclog.Logger
14551455
clientLogger := hclog.New(&hclog.LoggerOptions{
14561456
Name: "test-logger",
1457-
Level: hclog.Trace,
1457+
Level: hclog.Debug,
14581458
Output: stderr,
14591459
Mutex: mutex,
14601460
})
14611461

1462+
plugins := map[string]map[string]Plugin{
1463+
"netrpc": testPluginMap,
1464+
"grpc": testGRPCPluginMap,
1465+
}
1466+
14621467
process := helperProcess("test-interface-logger-" + proto)
14631468
c := NewClient(&ClientConfig{
14641469
Cmd: process,
14651470
HandshakeConfig: testHandshake,
1466-
Plugins: testGRPCPluginMap,
1471+
Plugins: plugins[proto],
14671472
Logger: clientLogger,
14681473
AllowedProtocols: []Protocol{ProtocolNetRPC, ProtocolGRPC},
14691474
})
@@ -1486,7 +1491,7 @@ func testClient_logger(t *testing.T, proto string) {
14861491
t.Fatalf("bad: %#v", raw)
14871492
}
14881493

1489-
{
1494+
t.Run("-1", func(t *testing.T) {
14901495
// Discard everything else, and capture the output we care about
14911496
mutex.Lock()
14921497
buffer.Reset()
@@ -1502,9 +1507,9 @@ func testClient_logger(t *testing.T, proto string) {
15021507
if !strings.Contains(line, "foo=bar") {
15031508
t.Fatalf("bad: %q", line)
15041509
}
1505-
}
1510+
})
15061511

1507-
{
1512+
t.Run("-2", func(t *testing.T) {
15081513
// Try an integer type
15091514
mutex.Lock()
15101515
buffer.Reset()
@@ -1520,7 +1525,7 @@ func testClient_logger(t *testing.T, proto string) {
15201525
if !strings.Contains(line, "foo=12") {
15211526
t.Fatalf("bad: %q", line)
15221527
}
1523-
}
1528+
})
15241529

15251530
// Kill it
15261531
c.Kill()
@@ -1535,6 +1540,84 @@ func testClient_logger(t *testing.T, proto string) {
15351540
}
15361541
}
15371542

1543+
func TestServerLogPanic(t *testing.T) {
1544+
var buffer bytes.Buffer
1545+
mutex := new(sync.Mutex)
1546+
stderr := io.MultiWriter(os.Stderr, &buffer)
1547+
// Custom hclog.Logger
1548+
clientLogger := hclog.New(&hclog.LoggerOptions{
1549+
Name: "test-logger",
1550+
Level: hclog.Error,
1551+
Output: stderr,
1552+
Mutex: mutex,
1553+
})
1554+
1555+
process := helperProcess("test-interface-logger-grpc")
1556+
c := NewClient(&ClientConfig{
1557+
Cmd: process,
1558+
HandshakeConfig: testHandshake,
1559+
Plugins: testGRPCPluginMap,
1560+
Logger: clientLogger,
1561+
AllowedProtocols: []Protocol{ProtocolNetRPC, ProtocolGRPC},
1562+
})
1563+
defer c.Kill()
1564+
1565+
// Grab the RPC client
1566+
client, err := c.Client()
1567+
if err != nil {
1568+
t.Fatalf("err should be nil, got %s", err)
1569+
}
1570+
1571+
// Grab the impl
1572+
raw, err := client.Dispense("test")
1573+
if err != nil {
1574+
t.Fatalf("err should be nil, got %s", err)
1575+
}
1576+
1577+
impl, ok := raw.(testInterface)
1578+
if !ok {
1579+
t.Fatalf("bad: %#v", raw)
1580+
}
1581+
1582+
mutex.Lock()
1583+
buffer.Reset()
1584+
mutex.Unlock()
1585+
err = impl.Panic("invalid foo bar")
1586+
time.Sleep(100 * time.Millisecond)
1587+
mutex.Lock()
1588+
1589+
panicFound := false
1590+
stackLines := 0
1591+
1592+
for _, line := range strings.Split(buffer.String(), "\n") {
1593+
if strings.Contains(line, "[ERROR] test-logger.go-plugin.test: panic: invalid foo bar") {
1594+
panicFound = true
1595+
continue
1596+
}
1597+
1598+
// make sure we are not just capturing the panic line, and have the rest
1599+
// of the output too
1600+
if panicFound {
1601+
// there should only be [ERROR] lines past this point, but the log
1602+
// level is Error, so we can just verify there are lines
1603+
stackLines++
1604+
}
1605+
}
1606+
1607+
if !panicFound {
1608+
t.Fatal("failed to find panic in error log output")
1609+
}
1610+
1611+
if stackLines < 10 {
1612+
t.Fatalf("only found %d stack lines after panic", stackLines)
1613+
}
1614+
1615+
mutex.Unlock()
1616+
if err == nil {
1617+
t.Fatal("expected error due to panic")
1618+
}
1619+
}
1620+
15381621
// Test that we continue to consume stderr over long lines.
15391622
func TestClient_logStderr(t *testing.T) {
15401623
stderr := bytes.Buffer{}

plugin_test.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type testInterface interface {
4343
PrintKV(string, interface{})
4444
Bidirectional() error
4545
PrintStdio(stdout, stderr []byte)
46+
Panic(msg string) error
4647
}
4748

4849
// testStreamer is used to test the grpc streaming interface
@@ -150,6 +151,10 @@ func (i *testInterfaceImpl) PrintStdio(stdout, stderr []byte) {
150151
}
151152
}
152153

154+
func (i *testInterfaceImpl) Panic(msg string) error {
155+
panic(msg)
156+
}
157+
153158
// testInterfaceClient implements testInterface to communicate over RPC
154159
type testInterfaceClient struct {
155160
Client *rpc.Client
@@ -186,6 +191,10 @@ func (impl *testInterfaceClient) PrintStdio(stdout, stderr []byte) {
186191
// put in the effort.
187192
}
188193

194+
func (impl *testInterfaceClient) Panic(msg string) error {
195+
return nil
196+
}
197+
189198
// testInterfaceServer is the RPC server for testInterfaceClient
190199
type testInterfaceServer struct {
191200
Broker *MuxBroker
@@ -214,6 +223,7 @@ var testGRPCPluginMap = map[string]Plugin{
214223

215224
// testGRPCServer is the implementation of our GRPC service.
216225
type testGRPCServer struct {
226+
grpctest.UnimplementedTestServer
217227
Impl testInterface
218228
broker *GRPCBroker
219229
}
@@ -280,7 +290,14 @@ func (s *testGRPCServer) PrintStdio(
280290
return &empty.Empty{}, nil
281291
}
282292

283-
type pingPongServer struct{}
293+
func (s *testGRPCServer) Panic(ctx context.Context, req *grpctest.PanicRequest) (*empty.Empty, error) {
294+
err := s.Impl.Panic(req.Message)
295+
return &empty.Empty{}, err
296+
}
297+
298+
type pingPongServer struct {
299+
grpctest.UnimplementedPingPongServer
300+
}
284301

285302
func (p *pingPongServer) Ping(ctx context.Context, req *grpctest.PingRequest) (*grpctest.PongResponse, error) {
286303
return &grpctest.PongResponse{
@@ -416,6 +433,11 @@ func (c *testGRPCClient) PrintStdio(stdout, stderr []byte) {
416433
}
417434
}
418435

436+
func (c *testGRPCClient) Panic(msg string) error {
437+
_, err := c.Client.Panic(context.Background(), &grpctest.PanicRequest{Message: msg})
438+
return err
439+
}
440+
419441
func helperProcess(s ...string) *exec.Cmd {
420442
cs := []string{"-test.run=TestHelperProcess", "--"}
421443
cs = append(cs, s...)
@@ -577,7 +599,7 @@ func TestHelperProcess(*testing.T) {
577599
case "test-interface-logger-grpc":
578600
Serve(&ServeConfig{
579601
HandshakeConfig: testHandshake,
580-
Plugins: testPluginMap,
602+
Plugins: testGRPCPluginMap,
581603
GRPCServer: DefaultGRPCServer,
582604
})
583605
// Shouldn't reach here but make sure we exit anyways

test/grpc/buf.gen.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Copyright (c) HashiCorp, Inc.
2+
# SPDX-License-Identifier: MPL-2.0
3+
4+
version: v1
5+
plugins:
6+
- plugin: buf.build/protocolbuffers/go
7+
out: .
8+
opt:
9+
- paths=source_relative
10+
- plugin: buf.build/grpc/go:v1.3.0
11+
out: .
12+
opt:
13+
- paths=source_relative
14+
- require_unimplemented_servers=false

test/grpc/buf.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Copyright (c) HashiCorp, Inc.
2+
# SPDX-License-Identifier: MPL-2.0
3+
4+
version: v1

0 commit comments

Comments
 (0)