Skip to content

Commit 7c313e4

Browse files
authored
Configurable buffer size for reading plugin log lines (#265)
- Add `PluginLogBufferSize` option to ClientConfig, defaulting to previous 64KiB size - Added a new test `TestClient_logStderrParseJSON` to verify the parsing of JSON formatted logs.
1 parent d16cec3 commit 7c313e4

File tree

2 files changed

+66
-9
lines changed

2 files changed

+66
-9
lines changed

client.go

+12-3
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ var (
7373
ErrGRPCBrokerMuxNotSupported = errors.New("client requested gRPC broker multiplexing but plugin does not support the feature")
7474
)
7575

76+
// defaultPluginLogBufferSize is the default size of the buffer used to read from stderr for plugin log lines.
77+
const defaultPluginLogBufferSize = 64 * 1024
78+
7679
// Client handles the lifecycle of a plugin application. It launches
7780
// plugins, connects to them, dispenses interface implementations, and handles
7881
// killing the process.
@@ -220,6 +223,10 @@ type ClientConfig struct {
220223
// it will default to hclog's default logger.
221224
Logger hclog.Logger
222225

226+
// PluginLogBufferSize is the buffer size(bytes) to read from stderr for plugin log lines.
227+
// If this is 0, then the default of 64KB is used.
228+
PluginLogBufferSize int
229+
223230
// AutoMTLS has the client and server automatically negotiate mTLS for
224231
// transport authentication. This ensures that only the original client will
225232
// be allowed to connect to the server, and all other connections will be
@@ -416,6 +423,10 @@ func NewClient(config *ClientConfig) (c *Client) {
416423
})
417424
}
418425

426+
if config.PluginLogBufferSize == 0 {
427+
config.PluginLogBufferSize = defaultPluginLogBufferSize
428+
}
429+
419430
c = &Client{
420431
config: config,
421432
logger: config.Logger,
@@ -1146,14 +1157,12 @@ func (c *Client) getGRPCMuxer(addr net.Addr) (*grpcmux.GRPCClientMuxer, error) {
11461157
return c.grpcMuxer, nil
11471158
}
11481159

1149-
var stdErrBufferSize = 64 * 1024
1150-
11511160
func (c *Client) logStderr(name string, r io.Reader) {
11521161
defer c.clientWaitGroup.Done()
11531162
defer c.stderrWaitGroup.Done()
11541163
l := c.logger.Named(filepath.Base(name))
11551164

1156-
reader := bufio.NewReaderSize(r, stdErrBufferSize)
1165+
reader := bufio.NewReaderSize(r, c.config.PluginLogBufferSize)
11571166
// continuation indicates the previous line was a prefix
11581167
continuation := false
11591168

client_test.go

+54-6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"bytes"
88
"context"
99
"crypto/sha256"
10+
"encoding/json"
1011
"errors"
1112
"fmt"
1213
"io"
@@ -1483,18 +1484,13 @@ func testClient_logger(t *testing.T, proto string) {
14831484

14841485
// Test that we continue to consume stderr over long lines.
14851486
func TestClient_logStderr(t *testing.T) {
1486-
orig := stdErrBufferSize
1487-
stdErrBufferSize = 32
1488-
defer func() {
1489-
stdErrBufferSize = orig
1490-
}()
1491-
14921487
stderr := bytes.Buffer{}
14931488
c := NewClient(&ClientConfig{
14941489
Stderr: &stderr,
14951490
Cmd: &exec.Cmd{
14961491
Path: "test",
14971492
},
1493+
PluginLogBufferSize: 32,
14981494
})
14991495
c.clientWaitGroup.Add(1)
15001496

@@ -1515,3 +1511,55 @@ this line is short
15151511
t.Fatalf("\nexpected output: %q\ngot output: %q", msg, read)
15161512
}
15171513
}
1514+
1515+
func TestClient_logStderrParseJSON(t *testing.T) {
1516+
logBuf := bytes.Buffer{}
1517+
c := NewClient(&ClientConfig{
1518+
Stderr: bytes.NewBuffer(nil),
1519+
Cmd: &exec.Cmd{Path: "test"},
1520+
PluginLogBufferSize: 64,
1521+
Logger: hclog.New(&hclog.LoggerOptions{
1522+
Name: "test-logger",
1523+
Level: hclog.Trace,
1524+
Output: &logBuf,
1525+
JSONFormat: true,
1526+
}),
1527+
})
1528+
c.clientWaitGroup.Add(1)
1529+
1530+
msg := `{"@message": "this is a message", "@level": "info"}
1531+
{"@message": "this is a large message that is more than 64 bytes long", "@level": "info"}`
1532+
reader := strings.NewReader(msg)
1533+
1534+
c.stderrWaitGroup.Add(1)
1535+
c.logStderr(c.config.Cmd.Path, reader)
1536+
logs := strings.Split(strings.TrimSpace(logBuf.String()), "\n")
1537+
1538+
wants := []struct {
1539+
wantLevel string
1540+
wantMessage string
1541+
}{
1542+
{"info", "this is a message"},
1543+
{"debug", `{"@message": "this is a large message that is more than 64 bytes`},
1544+
{"debug", ` long", "@level": "info"}`},
1545+
}
1546+
1547+
if len(logs) != len(wants) {
1548+
t.Fatalf("expected %d logs, got %d", len(wants), len(logs))
1549+
}
1550+
1551+
for i, tt := range wants {
1552+
l := make(map[string]interface{})
1553+
if err := json.Unmarshal([]byte(logs[i]), &l); err != nil {
1554+
t.Fatal(err)
1555+
}
1556+
1557+
if l["@level"] != tt.wantLevel {
1558+
t.Fatalf("expected level %q, got %q", tt.wantLevel, l["@level"])
1559+
}
1560+
1561+
if l["@message"] != tt.wantMessage {
1562+
t.Fatalf("expected message %q, got %q", tt.wantMessage, l["@message"])
1563+
}
1564+
}
1565+
}

0 commit comments

Comments
 (0)