1717package  logging
1818
1919import  (
20- 	"bufio " 
20+ 	"context " 
2121	"errors" 
2222	"fmt" 
2323	"io" 
2424	"os" 
25- 	"os/exec" 
2625	"path/filepath" 
2726	"strconv" 
28- 	"strings" 
2927	"time" 
3028
3129	"github.com/containerd/containerd/errdefs" 
3230	"github.com/containerd/containerd/runtime/v2/logging" 
3331	"github.com/containerd/log" 
3432	"github.com/containerd/nerdctl/v2/pkg/logging/jsonfile" 
33+ 	"github.com/containerd/nerdctl/v2/pkg/logging/tail" 
3534	"github.com/containerd/nerdctl/v2/pkg/strutil" 
3635	"github.com/docker/go-units" 
3736	"github.com/fahedouch/go-logrotate" 
37+ 	"github.com/fsnotify/fsnotify" 
3838)
3939
4040var  JSONDriverLogOpts  =  []string {
@@ -142,9 +142,6 @@ func viewLogsJSONFile(lvopts LogViewOptions, stdout, stderr io.Writer, stopChann
142142		}
143143	}
144144
145- 	if  checkExecutableAvailableInPath ("tail" ) {
146- 		return  viewLogsJSONFileThroughTailExec (lvopts , logFilePath , stdout , stderr , stopChannel )
147- 	}
148145	return  viewLogsJSONFileDirect (lvopts , logFilePath , stdout , stderr , stopChannel )
149146}
150147
@@ -156,118 +153,91 @@ func viewLogsJSONFileDirect(lvopts LogViewOptions, jsonLogFilePath string, stdou
156153	if  err  !=  nil  {
157154		return  err 
158155	}
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 () }()
164157
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 )
176185				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 )
183199				}
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 )
185208				if  err  !=  nil  {
186- 					fin .Close ()
187209					return  fmt .Errorf ("error occurred while trying to seek JSON logfile %q at position %d: %s" , jsonLogFilePath , lastPos , err )
188210				}
189211
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 
194221				}
195222
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 )
198226				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+ 					}
199234					fin .Close ()
200- 					return   fmt . Errorf ( "error occurred while trying to seek JSON logfile %q at current position: %s" ,  jsonLogFilePath ,  err ) 
235+ 					fin   =   newF 
201236				}
202- 				fin . Close () 
237+ 				continue 
203238			}
239+ 			stop  =  true 
204240			// 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 
265241		}
266- 		fmt .Fprintln (os .Stderr , line )
267242	}
268- 
269- 	if  err  :=  scanner .Err (); err  !=  nil  {
270- 		return  err 
271- 	}
272- 	return  nil 
273243}
0 commit comments