@@ -42,10 +42,13 @@ public static class FileLoggerConfigurationExtensions
4242 /// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param>
4343 /// <param name="outputTemplate">A message template describing the format used to write to the sink.
4444 /// the default is "{Timestamp} [{Level}] {Message}{NewLine}{Exception}".</param>
45- /// <param name="fileSizeLimitBytes">The maximum size, in bytes, to which a log file will be allowed to grow.
46- /// For unrestricted growth, pass null. The default is 1 GB.</param>
45+ /// <param name="fileSizeLimitBytes">The approximate maximum size, in bytes, to which a log file will be allowed to grow.
46+ /// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
47+ /// will be written in full even if it exceeds the limit.</param>
4748 /// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
4849 /// is false.</param>
50+ /// <param name="shared">Allow the log file to be shared by multiple processes. The default is false.</param>
51+ /// <param name="flushToDiskInterval">If provided, a full disk flush will be performed periodically at the specified interval.</param>
4952 /// <returns>Configuration object allowing method chaining.</returns>
5053 /// <remarks>The file will be written using the UTF-8 character set.</remarks>
5154 public static LoggerConfiguration File (
@@ -56,14 +59,16 @@ public static LoggerConfiguration File(
5659 IFormatProvider formatProvider = null ,
5760 long ? fileSizeLimitBytes = DefaultFileSizeLimitBytes ,
5861 LoggingLevelSwitch levelSwitch = null ,
59- bool buffered = false )
62+ bool buffered = false ,
63+ bool shared = false ,
64+ TimeSpan ? flushToDiskInterval = null )
6065 {
6166 if ( sinkConfiguration == null ) throw new ArgumentNullException ( nameof ( sinkConfiguration ) ) ;
6267 if ( path == null ) throw new ArgumentNullException ( nameof ( path ) ) ;
6368 if ( outputTemplate == null ) throw new ArgumentNullException ( nameof ( outputTemplate ) ) ;
6469
6570 var formatter = new MessageTemplateTextFormatter ( outputTemplate , formatProvider ) ;
66- return File ( sinkConfiguration , formatter , path , restrictedToMinimumLevel , fileSizeLimitBytes , levelSwitch , buffered ) ;
71+ return File ( sinkConfiguration , formatter , path , restrictedToMinimumLevel , fileSizeLimitBytes , levelSwitch , buffered : buffered , shared : shared , flushToDiskInterval : flushToDiskInterval ) ;
6772 }
6873
6974 /// <summary>
@@ -72,18 +77,21 @@ public static LoggerConfiguration File(
7277 /// <param name="sinkConfiguration">Logger sink configuration.</param>
7378 /// <param name="formatter">A formatter, such as <see cref="JsonFormatter"/>, to convert the log events into
7479 /// text for the file. If control of regular text formatting is required, use the other
75- /// overload of <see cref="File(LoggerSinkConfiguration, string, LogEventLevel, string, IFormatProvider, long?, LoggingLevelSwitch, bool)"/>
80+ /// overload of <see cref="File(LoggerSinkConfiguration, string, LogEventLevel, string, IFormatProvider, long?, LoggingLevelSwitch, bool, bool, TimeSpan? )"/>
7681 /// and specify the outputTemplate parameter instead.
7782 /// </param>
7883 /// <param name="path">Path to the file.</param>
7984 /// <param name="restrictedToMinimumLevel">The minimum level for
8085 /// events passed through the sink. Ignored when <paramref name="levelSwitch"/> is specified.</param>
8186 /// <param name="levelSwitch">A switch allowing the pass-through minimum level
8287 /// to be changed at runtime.</param>
83- /// <param name="fileSizeLimitBytes">The maximum size, in bytes, to which a log file will be allowed to grow.
84- /// For unrestricted growth, pass null. The default is 1 GB.</param>
88+ /// <param name="fileSizeLimitBytes">The approximate maximum size, in bytes, to which a log file will be allowed to grow.
89+ /// For unrestricted growth, pass null. The default is 1 GB. To avoid writing partial events, the last event within the limit
90+ /// will be written in full even if it exceeds the limit.</param>
8591 /// <param name="buffered">Indicates if flushing to the output file can be buffered or not. The default
8692 /// is false.</param>
93+ /// <param name="shared">Allow the log file to be shared by multiple processes. The default is false.</param>
94+ /// <param name="flushToDiskInterval">If provided, a full disk flush will be performed periodically at the specified interval.</param>
8795 /// <returns>Configuration object allowing method chaining.</returns>
8896 /// <remarks>The file will be written using the UTF-8 character set.</remarks>
8997 public static LoggerConfiguration File (
@@ -93,28 +101,128 @@ public static LoggerConfiguration File(
93101 LogEventLevel restrictedToMinimumLevel = LevelAlias . Minimum ,
94102 long ? fileSizeLimitBytes = DefaultFileSizeLimitBytes ,
95103 LoggingLevelSwitch levelSwitch = null ,
96- bool buffered = false )
104+ bool buffered = false ,
105+ bool shared = false ,
106+ TimeSpan ? flushToDiskInterval = null )
107+ {
108+ return ConfigureFile ( sinkConfiguration . Sink , formatter , path , restrictedToMinimumLevel , fileSizeLimitBytes , levelSwitch , buffered : buffered , shared : shared , flushToDiskInterval : flushToDiskInterval ) ;
109+ }
110+
111+ /// <summary>
112+ /// Write log events to the specified file.
113+ /// </summary>
114+ /// <param name="sinkConfiguration">Logger sink configuration.</param>
115+ /// <param name="path">Path to the file.</param>
116+ /// <param name="restrictedToMinimumLevel">The minimum level for
117+ /// events passed through the sink. Ignored when <paramref name="levelSwitch"/> is specified.</param>
118+ /// <param name="levelSwitch">A switch allowing the pass-through minimum level
119+ /// to be changed at runtime.</param>
120+ /// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param>
121+ /// <param name="outputTemplate">A message template describing the format used to write to the sink.
122+ /// the default is "{Timestamp} [{Level}] {Message}{NewLine}{Exception}".</param>
123+ /// <returns>Configuration object allowing method chaining.</returns>
124+ /// <remarks>The file will be written using the UTF-8 character set.</remarks>
125+ public static LoggerConfiguration File (
126+ this LoggerAuditSinkConfiguration sinkConfiguration ,
127+ string path ,
128+ LogEventLevel restrictedToMinimumLevel = LevelAlias . Minimum ,
129+ string outputTemplate = DefaultOutputTemplate ,
130+ IFormatProvider formatProvider = null ,
131+ LoggingLevelSwitch levelSwitch = null )
97132 {
98133 if ( sinkConfiguration == null ) throw new ArgumentNullException ( nameof ( sinkConfiguration ) ) ;
134+ if ( path == null ) throw new ArgumentNullException ( nameof ( path ) ) ;
135+ if ( outputTemplate == null ) throw new ArgumentNullException ( nameof ( outputTemplate ) ) ;
136+
137+ var formatter = new MessageTemplateTextFormatter ( outputTemplate , formatProvider ) ;
138+ return File ( sinkConfiguration , formatter , path , restrictedToMinimumLevel , levelSwitch ) ;
139+ }
140+
141+ /// <summary>
142+ /// Write log events to the specified file.
143+ /// </summary>
144+ /// <param name="sinkConfiguration">Logger sink configuration.</param>
145+ /// <param name="formatter">A formatter, such as <see cref="JsonFormatter"/>, to convert the log events into
146+ /// text for the file. If control of regular text formatting is required, use the other
147+ /// overload of <see cref="File(LoggerAuditSinkConfiguration, string, LogEventLevel, string, IFormatProvider, LoggingLevelSwitch)"/>
148+ /// and specify the outputTemplate parameter instead.
149+ /// </param>
150+ /// <param name="path">Path to the file.</param>
151+ /// <param name="restrictedToMinimumLevel">The minimum level for
152+ /// events passed through the sink. Ignored when <paramref name="levelSwitch"/> is specified.</param>
153+ /// <param name="levelSwitch">A switch allowing the pass-through minimum level
154+ /// to be changed at runtime.</param>
155+ /// <returns>Configuration object allowing method chaining.</returns>
156+ /// <remarks>The file will be written using the UTF-8 character set.</remarks>
157+ public static LoggerConfiguration File (
158+ this LoggerAuditSinkConfiguration sinkConfiguration ,
159+ ITextFormatter formatter ,
160+ string path ,
161+ LogEventLevel restrictedToMinimumLevel = LevelAlias . Minimum ,
162+ LoggingLevelSwitch levelSwitch = null )
163+ {
164+ return ConfigureFile ( sinkConfiguration . Sink , formatter , path , restrictedToMinimumLevel , null , levelSwitch , false , true ) ;
165+ }
166+
167+ static LoggerConfiguration ConfigureFile (
168+ this Func < ILogEventSink , LogEventLevel , LoggingLevelSwitch , LoggerConfiguration > addSink ,
169+ ITextFormatter formatter ,
170+ string path ,
171+ LogEventLevel restrictedToMinimumLevel = LevelAlias . Minimum ,
172+ long ? fileSizeLimitBytes = DefaultFileSizeLimitBytes ,
173+ LoggingLevelSwitch levelSwitch = null ,
174+ bool buffered = false ,
175+ bool propagateExceptions = false ,
176+ bool shared = false ,
177+ TimeSpan ? flushToDiskInterval = null )
178+ {
179+ if ( addSink == null ) throw new ArgumentNullException ( nameof ( addSink ) ) ;
99180 if ( formatter == null ) throw new ArgumentNullException ( nameof ( formatter ) ) ;
100181 if ( path == null ) throw new ArgumentNullException ( nameof ( path ) ) ;
182+ if ( fileSizeLimitBytes . HasValue && fileSizeLimitBytes < 0 ) throw new ArgumentException ( "Negative value provided; file size limit must be non-negative" ) ;
101183
102- FileSink sink ;
103- try
184+ if ( shared )
104185 {
105- sink = new FileSink ( path , formatter , fileSizeLimitBytes , buffered : buffered ) ;
186+ #if ATOMIC_APPEND
187+ if ( buffered )
188+ throw new ArgumentException ( "Buffered writes are not available when file sharing is enabled." , nameof ( buffered ) ) ;
189+ #else
190+ throw new NotSupportedException ( "File sharing is not supported on this platform." ) ;
191+ #endif
106192 }
107- catch ( ArgumentException )
193+
194+ ILogEventSink sink ;
195+ try
108196 {
109- throw ;
197+ #if ATOMIC_APPEND
198+ if ( shared )
199+ {
200+ sink = new SharedFileSink ( path , formatter , fileSizeLimitBytes ) ;
201+ }
202+ else
203+ {
204+ #endif
205+ sink = new FileSink ( path , formatter , fileSizeLimitBytes , buffered : buffered ) ;
206+ #if ATOMIC_APPEND
207+ }
208+ #endif
110209 }
111210 catch ( Exception ex )
112211 {
113212 SelfLog . WriteLine ( "Unable to open file sink for {0}: {1}" , path , ex ) ;
114- return sinkConfiguration . Sink ( new NullSink ( ) ) ;
213+
214+ if ( propagateExceptions )
215+ throw ;
216+
217+ return addSink ( new NullSink ( ) , LevelAlias . Maximum , null ) ;
218+ }
219+
220+ if ( flushToDiskInterval . HasValue )
221+ {
222+ sink = new PeriodicFlushToDiskSink ( sink , flushToDiskInterval . Value ) ;
115223 }
116224
117- return sinkConfiguration . Sink ( sink , restrictedToMinimumLevel , levelSwitch ) ;
225+ return addSink ( sink , restrictedToMinimumLevel , levelSwitch ) ;
118226 }
119227 }
120- }
228+ }
0 commit comments