@@ -19,7 +19,7 @@ namespace Microsoft.Azure.WebJobs.Script
1919 public class FileTraceWriter : TraceWriter , IDisposable
2020 {
2121 internal const int LastModifiedCutoffDays = 1 ;
22- private const long MaxLogFileSizeBytes = 5 * 1024 * 1024 ;
22+ internal const long MaxLogFileSizeBytes = 5 * 1024 * 1024 ;
2323 internal const int LogFlushIntervalMs = 1000 ;
2424 internal const int MaxLogLinesPerFlushInterval = 250 ;
2525 private readonly string _logFilePath ;
@@ -38,24 +38,7 @@ public FileTraceWriter(string logFilePath, TraceLevel level, LogType logType) :
3838 _logFilePath = logFilePath ;
3939 _instanceId = GetInstanceId ( ) ;
4040 _logType = logType ;
41-
4241 _logDirectory = new DirectoryInfo ( logFilePath ) ;
43- if ( ! _logDirectory . Exists )
44- {
45- _logDirectory . Create ( ) ;
46- }
47- else
48- {
49- // query for all existing log files for this instance
50- // sorted by date, and get the last log file written to (or null)
51- var files = GetLogFiles ( _logDirectory ) ;
52- _currentLogFileInfo = files . FirstOrDefault ( ) ;
53- }
54-
55- if ( _currentLogFileInfo == null )
56- {
57- SetNewLogFile ( ) ;
58- }
5942
6043 // start a timer to flush accumulated logs in batches
6144 _flushTimer = new Timer
@@ -100,30 +83,45 @@ public override void Flush()
10083 sb . AppendLine ( line ) ;
10184 }
10285
86+ if ( _currentLogFileInfo == null )
87+ {
88+ // delay create log file
89+ SetLogFile ( ) ;
90+ }
91+
10392 // write all lines in a single file operation
104- string contents = sb . ToString ( ) ;
93+ string content = sb . ToString ( ) ;
10594 try
10695 {
107- lock ( _syncLock )
108- {
109- File . AppendAllText ( _currentLogFileInfo . FullName , contents ) ;
110- }
96+ AppendToFile ( _currentLogFileInfo , content ) ;
11197 }
112- catch ( DirectoryNotFoundException )
98+ catch ( Exception ex ) when ( ex is DirectoryNotFoundException || ex is FileNotFoundException )
11399 {
114- // need to handle cases where log file directories might
100+ // need to handle cases where log files or directories might
115101 // have been deleted from underneath us
116- Directory . CreateDirectory ( _logFilePath ) ;
117- lock ( _syncLock )
118- {
119- File . AppendAllText ( _currentLogFileInfo . FullName , contents ) ;
120- }
102+ SetLogFile ( ) ;
103+ AppendToFile ( _currentLogFileInfo , content ) ;
121104 }
122105
106+ // check to see if we need to roll to a new log file
123107 _currentLogFileInfo . Refresh ( ) ;
124- if ( _currentLogFileInfo . Length > MaxLogFileSizeBytes )
108+ if ( LogFileIsOverSize ( _currentLogFileInfo ) )
125109 {
126- SetNewLogFile ( ) ;
110+ SetLogFile ( ) ;
111+ }
112+ }
113+
114+ private static void AppendToFile ( FileInfo fileInfo , string content )
115+ {
116+ lock ( _syncLock )
117+ {
118+ var fs = fileInfo . Open ( FileMode . Open , FileAccess . Write ) ;
119+ fs . Seek ( 0 , SeekOrigin . End ) ;
120+
121+ using ( var sw = new StreamWriter ( fs ) )
122+ {
123+ sw . Write ( content ) ;
124+ }
127125 }
128126 }
129127
@@ -250,21 +248,84 @@ private void OnFlushLogs(object sender, ElapsedEventArgs e)
250248 Flush ( ) ;
251249 }
252250
253- internal void SetNewLogFile ( )
251+ /// <summary>
252+ /// Called to set/create the current log file that will be used. Also handles purging of old
253+ /// log files.
254+ /// </summary>
255+ internal void SetLogFile ( )
256+ {
257+ if ( ! _logDirectory . Exists )
258+ {
259+ _logDirectory . Create ( ) ;
260+ }
261+
262+ // Check to see if there is already a log file we can use
263+ bool newLogFileCreated = false ;
264+ var logFile = GetCurrentLogFile ( ) ;
265+ if ( logFile != null )
266+ {
267+ _currentLogFileInfo = logFile ;
268+ }
269+ else
270+ {
271+ lock ( _syncLock )
272+ {
273+ // we perform any file create operations in a lock to avoid
274+ // race conditions with multiple instances of this class on
275+ // multiple threads, where multiple log files might be created
276+ // unnecessarily
277+ logFile = GetCurrentLogFile ( ) ;
278+ if ( logFile != null )
279+ {
280+ _currentLogFileInfo = logFile ;
281+ }
282+ else
283+ {
284+ // create a new log file
285+ // we include a machine identifier in the log file name to ensure we don't have any
286+ // log file contention between scaled out instances
287+ var timestamp = DateTime . UtcNow . ToString ( "yyyy-MM-ddTHH-mm-ssK" ) ;
288+ string filePath = Path . Combine ( _logFilePath , $ "{ timestamp } -{ _instanceId } .log") ;
289+ _currentLogFileInfo = new FileInfo ( filePath ) ;
290+ _currentLogFileInfo . Create ( ) . Close ( ) ;
291+ newLogFileCreated = true ;
292+ }
293+ }
294+ }
295+
296+ if ( newLogFileCreated )
297+ {
298+ // purge any log files (regardless of instance ID) whose last write time was earlier
299+ // than our retention policy
300+ // only do this peridically when we create a new file which will tend to keep
301+ // this off the startup path
302+ var filesToPurge = _logDirectory . GetFiles ( "*.log" ) . Where ( p => LogFileIsOld ( p ) ) ;
303+ DeleteFiles ( filesToPurge ) ;
304+ }
305+ }
306+
307+ private static bool LogFileIsOverSize ( FileInfo fileInfo )
308+ {
309+ // return true if the file is over our size threshold
310+ return fileInfo . Length > MaxLogFileSizeBytes ;
311+ }
312+
313+ private static bool LogFileIsOld ( FileInfo fileInfo )
314+ {
315+ return ( DateTime . UtcNow - fileInfo . LastWriteTimeUtc ) . TotalDays > LastModifiedCutoffDays ;
316+ }
317+
318+ private FileInfo GetCurrentLogFile ( )
254319 {
255- // purge any log files (regardless of instance ID) whose last write time was earlier
256- // than our retention policy
257- var filesToPurge = _logDirectory . GetFiles ( "*.log" ) . Where ( p => ( DateTime . UtcNow - p . LastWriteTimeUtc ) . TotalDays > LastModifiedCutoffDays ) ;
258- DeleteFiles ( filesToPurge ) ;
259-
260- // we include a machine identifier in the log file name to ensure we don't have any
261- // log file contention between scaled out instances
262- string filePath = Path . Combine ( _logFilePath , string . Format ( CultureInfo . InvariantCulture , "{0}-{1}.log" , DateTime . UtcNow . ToString ( "yyyy-MM-ddTHH-mm-ssK" ) , _instanceId ) ) ;
263- _currentLogFileInfo = new FileInfo ( filePath ) ;
320+ // return the last log file written to which is still under
321+ // size threshold
322+ return GetLogFiles ( _logDirectory ) . FirstOrDefault ( p => ! LogFileIsOverSize ( p ) ) ;
264323 }
265324
266325 private IEnumerable < FileInfo > GetLogFiles ( DirectoryInfo directory )
267326 {
327+ // query for all existing log files for this instance
328+ // sorted by date
268329 string pattern = string . Format ( CultureInfo . InvariantCulture , "*-{0}.log" , _instanceId ) ;
269330 return directory . GetFiles ( pattern ) . OrderByDescending ( p => p . LastWriteTime ) ;
270331 }
0 commit comments