17
17
package logging
18
18
19
19
import (
20
- "bufio "
20
+ "context "
21
21
"errors"
22
22
"fmt"
23
23
"io"
24
24
"os"
25
- "os/exec"
26
25
"path/filepath"
27
26
"strconv"
28
- "strings"
29
27
"time"
30
28
31
29
"github.com/containerd/containerd/errdefs"
32
30
"github.com/containerd/containerd/runtime/v2/logging"
33
31
"github.com/containerd/log"
34
32
"github.com/containerd/nerdctl/v2/pkg/logging/jsonfile"
33
+ "github.com/containerd/nerdctl/v2/pkg/logging/tail"
35
34
"github.com/containerd/nerdctl/v2/pkg/strutil"
36
35
"github.com/docker/go-units"
37
36
"github.com/fahedouch/go-logrotate"
37
+ "github.com/fsnotify/fsnotify"
38
38
)
39
39
40
40
var JSONDriverLogOpts = []string {
@@ -142,9 +142,6 @@ func viewLogsJSONFile(lvopts LogViewOptions, stdout, stderr io.Writer, stopChann
142
142
}
143
143
}
144
144
145
- if checkExecutableAvailableInPath ("tail" ) {
146
- return viewLogsJSONFileThroughTailExec (lvopts , logFilePath , stdout , stderr , stopChannel )
147
- }
148
145
return viewLogsJSONFileDirect (lvopts , logFilePath , stdout , stderr , stopChannel )
149
146
}
150
147
@@ -156,118 +153,91 @@ func viewLogsJSONFileDirect(lvopts LogViewOptions, jsonLogFilePath string, stdou
156
153
if err != nil {
157
154
return err
158
155
}
159
- defer fin .Close ()
160
- err = jsonfile .Decode (stdout , stderr , fin , lvopts .Timestamps , lvopts .Since , lvopts .Until , lvopts .Tail )
161
- if err != nil {
162
- return fmt .Errorf ("error occurred while doing initial read of JSON logfile %q: %s" , jsonLogFilePath , err )
163
- }
156
+ defer func () { fin .Close () }()
164
157
165
- if lvopts .Follow {
166
- // Get the current file handler's seek.
167
- lastPos , err := fin .Seek (0 , io .SeekCurrent )
168
- if err != nil {
169
- return fmt .Errorf ("error occurred while trying to seek JSON logfile %q at position %d: %s" , jsonLogFilePath , lastPos , err )
170
- }
171
- fin .Close ()
172
- for {
173
- select {
174
- case <- stopChannel :
175
- log .L .Debugf ("received stop signal while re-reading JSON logfile, returning" )
158
+ // Search start point based on tail line.
159
+ start , err := tail .FindTailLineStartIndex (fin , lvopts .Tail )
160
+ if err != nil {
161
+ return fmt .Errorf ("failed to tail %d lines of JSON logfile %q: %v" , lvopts .Tail , jsonLogFilePath , err )
162
+ }
163
+
164
+ if _ , err := fin .Seek (start , io .SeekStart ); err != nil {
165
+ return fmt .Errorf ("failed to seek in log file %q: %v" , jsonLogFilePath , err )
166
+ }
167
+
168
+ limitedMode := (lvopts .Tail > 0 ) && (! lvopts .Follow )
169
+ limitedNum := lvopts .Tail
170
+ var stop bool
171
+ var watcher * fsnotify.Watcher
172
+ baseName := filepath .Base (jsonLogFilePath )
173
+ dir := filepath .Dir (jsonLogFilePath )
174
+ retryTimes := 2
175
+ backBytes := 0
176
+
177
+ for {
178
+ select {
179
+ case <- stopChannel :
180
+ log .L .Debugf ("received stop signal while re-reading JSON logfile, returning" )
181
+ return nil
182
+ default :
183
+ if stop || (limitedMode && limitedNum == 0 ) {
184
+ log .L .Debugf ("finished parsing log JSON filefile, path: %s" , jsonLogFilePath )
176
185
return nil
177
- default :
178
- // Re-open the file and seek to the last-consumed offset.
179
- fin , err = os .OpenFile (jsonLogFilePath , os .O_RDONLY , 0400 )
180
- if err != nil {
181
- fin .Close ()
182
- return fmt .Errorf ("error occurred while trying to re-open JSON logfile %q: %s" , jsonLogFilePath , err )
186
+ }
187
+
188
+ if line , err := jsonfile .Decode (stdout , stderr , fin , lvopts .Timestamps , lvopts .Since , lvopts .Until ); err != nil {
189
+ if len (line ) > 0 {
190
+ time .Sleep (5 * time .Millisecond )
191
+ if retryTimes == 0 {
192
+ log .L .Infof ("finished parsing log JSON filefile, path: %s, line: %s" , jsonLogFilePath , string (line ))
193
+ return fmt .Errorf ("error occurred while doing read of JSON logfile %q: %s, retryTimes: %d" , jsonLogFilePath , err , retryTimes )
194
+ }
195
+ retryTimes --
196
+ backBytes = len (line )
197
+ } else {
198
+ return fmt .Errorf ("error occurred while doing read of JSON logfile %q: %s" , jsonLogFilePath , err )
183
199
}
184
- _ , err = fin .Seek (lastPos , 0 )
200
+ } else {
201
+ retryTimes = 2
202
+ backBytes = 0
203
+ }
204
+
205
+ if lvopts .Follow {
206
+ // Get the current file handler's seek.
207
+ lastPos , err := fin .Seek (int64 (- backBytes ), io .SeekCurrent )
185
208
if err != nil {
186
- fin .Close ()
187
209
return fmt .Errorf ("error occurred while trying to seek JSON logfile %q at position %d: %s" , jsonLogFilePath , lastPos , err )
188
210
}
189
211
190
- err = jsonfile .Decode (stdout , stderr , fin , lvopts .Timestamps , lvopts .Since , lvopts .Until , 0 )
191
- if err != nil {
192
- fin .Close ()
193
- return fmt .Errorf ("error occurred while doing follow-up decoding of JSON logfile %q at starting position %d: %s" , jsonLogFilePath , lastPos , err )
212
+ if watcher == nil {
213
+ // Initialize the watcher if it has not been initialized yet.
214
+ if watcher , err = NewLogFileWatcher (dir ); err != nil {
215
+ return err
216
+ }
217
+ defer watcher .Close ()
218
+ // If we just created the watcher, try again to read as we might have missed
219
+ // the event.
220
+ continue
194
221
}
195
222
196
- // Record current file seek position before looping again.
197
- lastPos , err = fin .Seek (0 , io .SeekCurrent )
223
+ var recreated bool
224
+ // Wait until the next log change.
225
+ recreated , err = startTail (context .Background (), baseName , watcher )
198
226
if err != nil {
227
+ return err
228
+ }
229
+ if recreated {
230
+ newF , err := openFileShareDelete (jsonLogFilePath )
231
+ if err != nil {
232
+ return fmt .Errorf ("failed to open JSON logfile %q: %v" , jsonLogFilePath , err )
233
+ }
199
234
fin .Close ()
200
- return fmt . Errorf ( "error occurred while trying to seek JSON logfile %q at current position: %s" , jsonLogFilePath , err )
235
+ fin = newF
201
236
}
202
- fin . Close ()
237
+ continue
203
238
}
239
+ stop = true
204
240
// Give the OS a second to breathe before re-opening the file:
205
- time .Sleep (time .Second )
206
- }
207
- }
208
- return nil
209
- }
210
-
211
- // Loads logs through the `tail` executable.
212
- func viewLogsJSONFileThroughTailExec (lvopts LogViewOptions , jsonLogFilePath string , stdout , stderr io.Writer , stopChannel chan os.Signal ) error {
213
- var args []string
214
-
215
- args = append (args , "-n" )
216
- if lvopts .Tail == 0 {
217
- args = append (args , "+0" )
218
- } else {
219
- args = append (args , strconv .FormatUint (uint64 (lvopts .Tail ), 10 ))
220
- }
221
-
222
- if lvopts .Follow {
223
- // using the `-F` to follow the file name instead of descriptor and retry if inaccessible
224
- args = append (args , "-F" )
225
- }
226
- args = append (args , jsonLogFilePath )
227
- cmd := exec .Command ("tail" , args ... )
228
-
229
- cmdStdout , err := cmd .StdoutPipe ()
230
- if err != nil {
231
- return err
232
- }
233
-
234
- cmdStderr , err := cmd .StderrPipe ()
235
- if err != nil {
236
- return err
237
- }
238
-
239
- if err := cmd .Start (); err != nil {
240
- return err
241
- }
242
-
243
- // filter the unwanted error message of the tail
244
- go filterTailStderr (cmdStderr )
245
-
246
- // Setup killing goroutine:
247
- go func () {
248
- <- stopChannel
249
- log .L .Debugf ("killing tail logs process with PID: %d" , cmd .Process .Pid )
250
- cmd .Process .Kill ()
251
- }()
252
-
253
- return jsonfile .Decode (stdout , stderr , cmdStdout , lvopts .Timestamps , lvopts .Since , lvopts .Until , 0 )
254
- }
255
-
256
- func filterTailStderr (reader io.Reader ) error {
257
- scanner := bufio .NewScanner (reader )
258
- for scanner .Scan () {
259
- line := scanner .Text ()
260
- if strings .HasSuffix (line , "has appeared; following new file" ) ||
261
- strings .HasSuffix (line , "has become inaccessible: No such file or directory" ) ||
262
- strings .HasSuffix (line , "has been replaced; following new file" ) ||
263
- strings .HasSuffix (line , ": No such file or directory" ) {
264
- continue
265
241
}
266
- fmt .Fprintln (os .Stderr , line )
267
242
}
268
-
269
- if err := scanner .Err (); err != nil {
270
- return err
271
- }
272
- return nil
273
243
}
0 commit comments