5
5
"io"
6
6
"math/rand"
7
7
"strings"
8
+ "sync"
8
9
"time"
9
10
10
11
docker "github.com/fsouza/go-dockerclient"
@@ -56,11 +57,12 @@ func NewDockerLogger(logger hclog.Logger) DockerLogger {
56
57
type dockerLogger struct {
57
58
logger hclog.Logger
58
59
59
- stdout io.WriteCloser
60
- stderr io.WriteCloser
61
- cancelCtx context. CancelFunc
60
+ stdout io.WriteCloser
61
+ stderr io.WriteCloser
62
+ stdLock sync. Mutex
62
63
63
- doneCh chan interface {}
64
+ cancelCtx context.CancelFunc
65
+ doneCh chan interface {}
64
66
}
65
67
66
68
// Start log monitoring
@@ -70,35 +72,27 @@ func (d *dockerLogger) Start(opts *StartOpts) error {
70
72
return fmt .Errorf ("failed to open docker client: %v" , err )
71
73
}
72
74
73
- if d .stdout == nil {
74
- stdout , err := fifo .OpenWriter (opts .Stdout )
75
- if err != nil {
76
- return fmt .Errorf ("failed to open fifo for path %s: %v" , opts .Stdout , err )
77
- }
78
- d .stdout = stdout
79
- }
80
- if d .stderr == nil {
81
- stderr , err := fifo .OpenWriter (opts .Stderr )
82
- if err != nil {
83
- return fmt .Errorf ("failed to open fifo for path %s: %v" , opts .Stdout , err )
84
- }
85
- d .stderr = stderr
86
- }
87
75
ctx , cancel := context .WithCancel (context .Background ())
88
76
d .cancelCtx = cancel
89
77
90
78
go func () {
91
79
defer close (d .doneCh )
92
80
81
+ stdout , stderr , err := d .openStreams (ctx , opts )
82
+ if err != nil {
83
+ d .logger .Error ("log streaming ended with terminal error" , "error" , err )
84
+ return
85
+ }
86
+
93
87
sinceTime := time .Unix (opts .StartTime , 0 )
94
88
backoff := 0.0
95
89
96
90
for {
97
91
logOpts := docker.LogsOptions {
98
92
Context : ctx ,
99
93
Container : opts .ContainerID ,
100
- OutputStream : d . stdout ,
101
- ErrorStream : d . stderr ,
94
+ OutputStream : stdout ,
95
+ ErrorStream : stderr ,
102
96
Since : sinceTime .Unix (),
103
97
Follow : true ,
104
98
Stdout : true ,
@@ -138,16 +132,64 @@ func (d *dockerLogger) Start(opts *StartOpts) error {
138
132
139
133
}
140
134
135
+ // openStreams open logger stdout/stderr; should be called in a background goroutine to avoid locking up
136
+ // process to avoid locking goroutine process
137
+ func (d * dockerLogger ) openStreams (ctx context.Context , opts * StartOpts ) (stdout , stderr io.WriteCloser , err error ) {
138
+ d .stdLock .Lock ()
139
+ stdoutF , stderrF := d .stdout , d .stderr
140
+ d .stdLock .Unlock ()
141
+
142
+ if stdoutF != nil && stderrF != nil {
143
+ return stdoutF , stderrF , nil
144
+ }
145
+
146
+ // opening a fifo may block indefinitely until a reader end opens, so
147
+ // we preform open() without holding the stdLock, so Stop and interleave.
148
+ // This a defensive measure - logmon (the reader end) should be up and
149
+ // started before dockerLogger is started
150
+ if stdoutF == nil {
151
+ stdoutF , err = fifo .OpenWriter (opts .Stdout )
152
+ if err != nil {
153
+ return nil , nil , err
154
+ }
155
+ }
156
+
157
+ if stderrF == nil {
158
+ stderrF , err = fifo .OpenWriter (opts .Stderr )
159
+ if err != nil {
160
+ return nil , nil , err
161
+ }
162
+ }
163
+
164
+ if ctx .Err () != nil {
165
+ // Stop was called and don't need files anymore
166
+ stdoutF .Close ()
167
+ stderrF .Close ()
168
+ return nil , nil , ctx .Err ()
169
+ }
170
+
171
+ d .stdLock .Lock ()
172
+ d .stdout , d .stderr = stdoutF , stderrF
173
+ d .stdLock .Unlock ()
174
+
175
+ return stdoutF , stderrF , nil
176
+ }
177
+
141
178
// Stop log monitoring
142
179
func (d * dockerLogger ) Stop () error {
143
180
if d .cancelCtx != nil {
144
181
d .cancelCtx ()
145
182
}
146
- if d .stdout != nil {
147
- d .stdout .Close ()
183
+
184
+ d .stdLock .Lock ()
185
+ stdout , stderr := d .stdout , d .stderr
186
+ d .stdLock .Unlock ()
187
+
188
+ if stdout != nil {
189
+ stdout .Close ()
148
190
}
149
- if d . stderr != nil {
150
- d . stderr .Close ()
191
+ if stderr != nil {
192
+ stderr .Close ()
151
193
}
152
194
return nil
153
195
}
0 commit comments