Skip to content

Commit 5848f67

Browse files
committed
[FAB-1855] Capture chaincode stdout from docker
This code captures all output from stdout/stderr originating from our peer-launched docker containers and stuffs it into a custom logger on a per-chaincode basis. It is enabled by the configuration knob "CORE_VM_DOCKER_ATTACHSTDOUT=true" and defaults to disabled. This should allow an admin or developer to enable this log at their discretion to assist with debugging container problems, while minimizing risk for malicious/faulty chaincodes to threaten the peer with unmitigated logging. Change-Id: I3f36958c41f14981e2e0f412b7e46589e6fa7110 Signed-off-by: Greg Haskins <[email protected]>
1 parent 8c2fa25 commit 5848f67

File tree

3 files changed

+87
-5
lines changed

3 files changed

+87
-5
lines changed

core/container/dockercontroller/dockercontroller.go

+76-4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import (
2121
"fmt"
2222
"io"
2323
"strings"
24+
"time"
25+
26+
"bufio"
2427

2528
"github.com/fsouza/go-dockerclient"
2629
"github.com/hyperledger/fabric/core/container/ccintf"
@@ -100,8 +103,8 @@ func getDockerHostConfig() *docker.HostConfig {
100103
return hostConfig
101104
}
102105

103-
func (vm *DockerVM) createContainer(ctxt context.Context, client *docker.Client, imageID string, containerID string, args []string, env []string) error {
104-
config := docker.Config{Cmd: args, Image: imageID, Env: env}
106+
func (vm *DockerVM) createContainer(ctxt context.Context, client *docker.Client, imageID string, containerID string, args []string, env []string, attachStdout bool) error {
107+
config := docker.Config{Cmd: args, Image: imageID, Env: env, AttachStdout: attachStdout, AttachStderr: attachStdout}
105108
copts := docker.CreateContainerOptions{Name: containerID, Config: &config, HostConfig: getDockerHostConfig()}
106109
dockerLogger.Debugf("Create container: %s", containerID)
107110
_, err := client.CreateContainer(copts)
@@ -160,13 +163,14 @@ func (vm *DockerVM) Start(ctxt context.Context, ccid ccintf.CCID, args []string,
160163
}
161164

162165
containerID := strings.Replace(imageID, ":", "_", -1)
166+
attachStdout := viper.GetBool("vm.docker.attachStdout")
163167

164168
//stop,force remove if necessary
165169
dockerLogger.Debugf("Cleanup container %s", containerID)
166170
vm.stopInternal(ctxt, client, containerID, 0, false, false)
167171

168172
dockerLogger.Debugf("Start container %s", containerID)
169-
err = vm.createContainer(ctxt, client, imageID, containerID, args, env)
173+
err = vm.createContainer(ctxt, client, imageID, containerID, args, env, attachStdout)
170174
if err != nil {
171175
//if image not found try to create image and retry
172176
if err == docker.ErrNoSuchImage {
@@ -177,7 +181,7 @@ func (vm *DockerVM) Start(ctxt context.Context, ccid ccintf.CCID, args []string,
177181
}
178182

179183
dockerLogger.Debug("start-recreated image successfully")
180-
if err = vm.createContainer(ctxt, client, imageID, containerID, args, env); err != nil {
184+
if err = vm.createContainer(ctxt, client, imageID, containerID, args, env, attachStdout); err != nil {
181185
dockerLogger.Errorf("start-could not recreate container post recreate image: %s", err)
182186
return err
183187
}
@@ -191,6 +195,74 @@ func (vm *DockerVM) Start(ctxt context.Context, ccid ccintf.CCID, args []string,
191195
}
192196
}
193197

198+
if attachStdout {
199+
// Launch a few go-threads to manage output streams from the container.
200+
// They will be automatically destroyed when the container exits
201+
attached := make(chan struct{})
202+
r, w := io.Pipe()
203+
204+
go func() {
205+
// AttachToContainer will fire off a message on the "attached" channel once the
206+
// attachment completes, and then block until the container is terminated.
207+
err = client.AttachToContainer(docker.AttachToContainerOptions{
208+
Container: containerID,
209+
OutputStream: w,
210+
ErrorStream: w,
211+
Logs: true,
212+
Stdout: true,
213+
Stderr: true,
214+
Stream: true,
215+
Success: attached,
216+
})
217+
218+
// If we get here, the container has terminated. Send a signal on the pipe
219+
// so that downstream may clean up appropriately
220+
_ = w.CloseWithError(err)
221+
}()
222+
223+
go func() {
224+
// Block here until the attachment completes or we timeout
225+
select {
226+
case <-attached:
227+
// successful attach
228+
case <-time.After(10 * time.Second):
229+
dockerLogger.Errorf("Timeout while attaching to IO channel in container %s", containerID)
230+
return
231+
}
232+
233+
// Acknowledge the attachment? This was included in the gist I followed
234+
// (http://bit.ly/2jBrCtM). Not sure it's actually needed but it doesn't
235+
// appear to hurt anything.
236+
attached <- struct{}{}
237+
238+
// Establish a buffer for our IO channel so that we may do readline-style
239+
// ingestion of the IO, one log entry per line
240+
is := bufio.NewReader(r)
241+
242+
// Acquire a custom logger for our chaincode, inheriting the level from the peer
243+
containerLogger := logging.MustGetLogger(containerID)
244+
logging.SetLevel(logging.GetLevel("peer"), containerID)
245+
246+
for {
247+
// Loop forever dumping lines of text into the containerLogger
248+
// until the pipe is closed
249+
line, err := is.ReadString('\n')
250+
if err != nil {
251+
switch err {
252+
case io.EOF:
253+
dockerLogger.Infof("Container %s has closed its IO channel", containerID)
254+
default:
255+
dockerLogger.Errorf("Error reading container output: %s", err)
256+
}
257+
258+
return
259+
}
260+
261+
containerLogger.Info(line)
262+
}
263+
}()
264+
}
265+
194266
// start container with HostConfig was deprecated since v1.10 and removed in v1.2
195267
err = client.StartContainer(containerID, nil)
196268
if err != nil {

docs/Setup/logging-control.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,13 @@ settings):
6060

6161
## Go chaincodes
6262

63-
As independently executed programs, user-provided chaincodes can use any appropriate technique to create their private logs - from simple print statements to fully-annotated and level-controlled logs. The chaincode `shim` package provides APIs that allow a chaincode to create and manage logging objects whose logs will be formatted and interleaved consistently with the `shim` logs.
63+
The standard mechanism to log within a chaincode application is to integrate with the logging transport exposed to each chaincode instance via the peer. The chaincode `shim` package provides APIs that allow a chaincode to create and manage logging objects whose logs will be formatted and interleaved consistently with the `shim` logs.
64+
65+
As independently executed programs, user-provided chaincodes may technically also produce output on stdout/stderr. While naturally useful for "devmode", these channels are normally disabled on a production network to mitigate abuse from broken or malicious code. However, it is possible to enable this output even for peer-managed containers (e.g. "netmode") on a per-peer basis via the CORE_VM_DOCKER_ATTACHSTDOUT=true configuration option.
66+
67+
Once enabled, each chaincode will receive its own logging channel keyed by its container-id. Any output written to either stdout or stderr will be integrated with the peer's log on a per-line basis. It is not recommended to enable this for production.
68+
69+
### API
6470

6571
`NewLogger(name string) *ChaincodeLogger` - Create a logging object for use by a chaincode
6672

peer/core.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ vm:
180180
file: /path/to/ca.pem
181181
key:
182182
file: /path/to/server-key.pem
183+
184+
# Enables/disables the standard out/err from chaincode containers for debugging purposes
185+
attachStdout: false
186+
183187
# Parameters of docker container creating. For docker can created by custom parameters
184188
# If you have your own ipam & dns-server for cluster you can use them to create container efficient.
185189
# NetworkMode Sets the networking mode for the container. Supported standard values are: `host`(default),`bridge`,`ipvlan`,`none`

0 commit comments

Comments
 (0)