Skip to content

Commit 13a0bb3

Browse files
authored
Capture output from stdlib log library. (letsencrypt#4665)
Some components, particularly net/http, occasionally output log lines via log.Print. We'd like to capture these and send them to rsyslog so all our log data goes to the same place, and so that we can attach log line checksums to them. This uses log.SetOutput to change the log output to an io.Pipe, then consumes that buffer line-by-line in a goroutine and sends it to our rsyslog logger. This seems to tickle an unrelated race condition in test/ocsp/helper.go, so I fixed that too. Also filters out a noisy and unimportant error from the grpcLog handler. Fixes letsencrypt#4664 Fixes letsencrypt#4628
1 parent e352227 commit 13a0bb3

File tree

3 files changed

+47
-3
lines changed

3 files changed

+47
-3
lines changed

cmd/shell.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
package cmd
33

44
import (
5+
"bufio"
56
"encoding/json"
67
"encoding/pem"
78
"errors"
89
"expvar"
910
"fmt"
11+
"io"
1012
"io/ioutil"
1113
"log"
1214
"log/syslog"
@@ -93,6 +95,9 @@ func (log grpcLogger) Error(args ...interface{}) {
9395
}
9496
func (log grpcLogger) Errorf(format string, args ...interface{}) {
9597
output := fmt.Sprintf(format, args...)
98+
if output == `ccResolverWrapper: error parsing service config: no JSON service config provided` {
99+
return
100+
}
96101
if output == `grpc: Server.processUnaryRPC failed to write status: connection error: desc = "transport is closing"` {
97102
return
98103
}
@@ -129,6 +134,23 @@ func (log promLogger) Println(args ...interface{}) {
129134
log.AuditErr(fmt.Sprintln(args...))
130135
}
131136

137+
// captureStdlibLog captures the logs generated by log.Print*
138+
// and, instead of writing them to stdout, writes them to the provided logger
139+
// at "Info" level.
140+
func captureStdlibLog(logger blog.Logger) {
141+
r, w := io.Pipe()
142+
log.SetOutput(w)
143+
go func() {
144+
scanner := bufio.NewScanner(r)
145+
for scanner.Scan() {
146+
logger.Info(scanner.Text())
147+
}
148+
if err := scanner.Err(); err != nil {
149+
logger.AuditErrf("reading stdlib log: %s", err)
150+
}
151+
}()
152+
}
153+
132154
// StatsAndLogging constructs a prometheus registerer and an AuditLogger based
133155
// on its config parameters, and return them both. It also spawns off an HTTP
134156
// server on the provided port to report the stats and provide pprof profiling
@@ -167,6 +189,9 @@ func NewLogger(logConf SyslogConfig) blog.Logger {
167189
cfsslLog.SetLogger(cfsslLogger{logger})
168190
_ = mysql.SetLogger(mysqlLogger{logger})
169191
grpclog.SetLoggerV2(grpcLogger{logger})
192+
193+
captureStdlibLog(logger)
194+
170195
return logger
171196
}
172197

cmd/shell_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ package cmd
33
import (
44
"encoding/json"
55
"fmt"
6+
"log"
67
"runtime"
8+
"strings"
79
"testing"
10+
"time"
811

912
"github.com/letsencrypt/boulder/core"
1013
blog "github.com/letsencrypt/boulder/log"
@@ -121,6 +124,20 @@ func TestCfsslLogger(t *testing.T) {
121124
}
122125
}
123126

127+
func TestCaptureStdlibLog(t *testing.T) {
128+
logger := blog.UseMock()
129+
captureStdlibLog(logger)
130+
log.Print("thisisatest")
131+
// Since the handler for captureStdlibLog runs in a goroutine, we have to wait
132+
// for a bit to ensure it processed the log line we sent.
133+
time.Sleep(250 * time.Millisecond)
134+
results := logger.GetAllMatching("thisisatest")
135+
if len(results) != 1 {
136+
t.Fatalf("Expected logger to receive 'thisisatest', got: %s",
137+
strings.Join(logger.GetAllMatching(".*"), "\n"))
138+
}
139+
}
140+
124141
func TestVersionString(t *testing.T) {
125142
core.BuildID = "TestBuildID"
126143
core.BuildTime = "RightNow!"

test/ocsp/helper/helper.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,6 @@ func ReqDER(der []byte, expectStatus int) (*ocsp.Response, error) {
127127
return nil, err
128128
}
129129

130-
http.DefaultClient.Timeout = 5 * time.Second
131-
132130
httpResp, err := sendHTTPRequest(req, ocspURL)
133131
if err != nil {
134132
return nil, err
@@ -175,7 +173,11 @@ func sendHTTPRequest(req []byte, ocspURL *url.URL) (*http.Response, error) {
175173
if *hostOverride != "" {
176174
httpRequest.Host = *hostOverride
177175
}
178-
return http.DefaultClient.Do(httpRequest)
176+
client := http.Client{
177+
Timeout: 5 * time.Second,
178+
}
179+
180+
return client.Do(httpRequest)
179181
}
180182

181183
func getOCSPURL(cert *x509.Certificate) (*url.URL, error) {

0 commit comments

Comments
 (0)