11using System ;
22using System . Collections . Concurrent ;
33using System . IO ;
4+ using System . Security ;
5+ #if NET45
6+ using System . Security . AccessControl ;
7+ using System . Security . Principal ;
8+ #endif
49using System . Text ;
510using System . Threading ;
11+ using Exceptionless . Extensions ;
612using Exceptionless . Utility ;
713
814namespace Exceptionless . Logging {
915 public class FileExceptionlessLog : IExceptionlessLog , IDisposable {
10- private static Mutex _flushLock = new Mutex ( false , nameof ( FileExceptionlessLog ) ) ;
11-
16+ private Mutex _flushMutex ;
1217 private Timer _flushTimer ;
1318 private readonly bool _append ;
1419 private bool _firstWrite = true ;
15- private bool _isFlushing = false ;
16- private bool _isCheckingFileSize = false ;
20+ private bool _isFlushing ;
21+ private bool _isCheckingFileSize ;
1722
1823 public FileExceptionlessLog ( string filePath , bool append = false ) {
1924 if ( String . IsNullOrEmpty ( filePath ) )
@@ -25,6 +30,7 @@ public FileExceptionlessLog(string filePath, bool append = false) {
2530
2631 Initialize ( ) ;
2732
33+ _flushMutex = CreateSystemFileMutex ( FilePath ) ;
2834 // flush the log every 3 seconds instead of on every write
2935 _flushTimer = new Timer ( OnFlushTimer , null , TimeSpan . FromSeconds ( 3 ) , TimeSpan . FromSeconds ( 3 ) ) ;
3036 }
@@ -57,7 +63,7 @@ protected internal virtual string GetFileContents() {
5763 if ( File . Exists ( FilePath ) )
5864 return File . ReadAllText ( FilePath ) ;
5965 } catch ( IOException ex ) {
60- System . Diagnostics . Trace . WriteLine ( "Exceptionless: Error getting size of file: {0}" , ex . Message ) ;
66+ System . Diagnostics . Trace . WriteLine ( "Exceptionless: Error getting size of file: {0}" , ex . ToString ( ) ) ;
6167 }
6268
6369 return String . Empty ;
@@ -68,7 +74,7 @@ protected internal virtual long GetFileSize() {
6874 if ( File . Exists ( FilePath ) )
6975 return new FileInfo ( FilePath ) . Length ;
7076 } catch ( Exception ex ) {
71- System . Diagnostics . Trace . WriteLine ( "Exceptionless: Error getting size of file: {0}" , ex . Message ) ;
77+ System . Diagnostics . Trace . WriteLine ( "Exceptionless: Error getting size of file: {0}" , ex . ToString ( ) ) ;
7278 }
7379
7480 return - 1 ;
@@ -132,7 +138,7 @@ public void Flush() {
132138 _isFlushing = true ;
133139
134140 Run . WithRetries ( ( ) => {
135- if ( ! _flushLock . WaitOne ( TimeSpan . FromSeconds ( 5 ) ) )
141+ if ( ! _flushMutex . WaitOne ( TimeSpan . FromSeconds ( 5 ) ) )
136142 return ;
137143
138144 hasFlushLock = true ;
@@ -155,10 +161,10 @@ public void Flush() {
155161 }
156162 } ) ;
157163 } catch ( Exception ex ) {
158- System . Diagnostics . Trace . WriteLine ( "Exceptionless: Error flushing log contents to disk: {0}" , ex . Message ) ;
164+ System . Diagnostics . Trace . WriteLine ( "Exceptionless: Error flushing log contents to disk: {0}" , ex . ToString ( ) ) ;
159165 } finally {
160166 if ( hasFlushLock )
161- _flushLock . ReleaseMutex ( ) ;
167+ _flushMutex . ReleaseMutex ( ) ;
162168 _isFlushing = false ;
163169 }
164170 }
@@ -217,15 +223,14 @@ internal void CheckFileSize() {
217223 string lastLines = String . Empty ;
218224 try {
219225 Run . WithRetries ( ( ) => {
220- if ( ! _flushLock . WaitOne ( TimeSpan . FromSeconds ( 5 ) ) )
226+ if ( ! _flushMutex . WaitOne ( TimeSpan . FromSeconds ( 5 ) ) )
221227 return ;
222228
223- lastLines = GetLastLinesFromFile ( FilePath ) ;
224-
225- _flushLock . ReleaseMutex ( ) ;
229+ lastLines = GetLastLinesFromFile ( ) ;
230+ _flushMutex . ReleaseMutex ( ) ;
226231 } ) ;
227232 } catch ( Exception ex ) {
228- System . Diagnostics . Trace . WriteLine ( "Exceptionless: Error getting last X lines from the log file: {0}" , ex . Message ) ;
233+ System . Diagnostics . Trace . WriteLine ( "Exceptionless: Error getting last X lines from the log file: {0}" , ex . ToString ( ) ) ;
229234 }
230235
231236 if ( String . IsNullOrEmpty ( lastLines ) ) {
@@ -236,16 +241,16 @@ internal void CheckFileSize() {
236241 // overwrite the log file and initialize it with the last X lines it had
237242 try {
238243 Run . WithRetries ( ( ) => {
239- if ( ! _flushLock . WaitOne ( TimeSpan . FromSeconds ( 5 ) ) )
244+ if ( ! _flushMutex . WaitOne ( TimeSpan . FromSeconds ( 5 ) ) )
240245 return ;
241246
242247 using ( var writer = GetWriter ( true ) )
243248 writer . Value . Write ( lastLines ) ;
244249
245- _flushLock . ReleaseMutex ( ) ;
250+ _flushMutex . ReleaseMutex ( ) ;
246251 } ) ;
247252 } catch ( Exception ex ) {
248- System . Diagnostics . Trace . WriteLine ( "Exceptionless: Error rewriting the log file after trimming it: {0}" , ex . Message ) ;
253+ System . Diagnostics . Trace . WriteLine ( "Exceptionless: Error rewriting the log file after trimming it: {0}" , ex . ToString ( ) ) ;
249254 }
250255
251256 _isCheckingFileSize = false ;
@@ -263,9 +268,14 @@ public virtual void Dispose() {
263268 }
264269
265270 Flush ( ) ;
271+
272+ if ( _flushMutex != null ) {
273+ _flushMutex . Close ( ) ;
274+ _flushMutex = null ;
275+ }
266276 }
267277
268- protected string GetLastLinesFromFile ( string path , int lines = 100 ) {
278+ protected string GetLastLinesFromFile ( int lines = 100 ) {
269279 byte [ ] buffer = Encoding . ASCII . GetBytes ( "\n " ) ;
270280
271281 using ( var fs = GetReader ( ) ) {
@@ -298,6 +308,49 @@ protected string GetLastLinesFromFile(string path, int lines = 100) {
298308 }
299309 }
300310
311+ /// <summary>
312+ /// Allows file based locking across processes
313+ /// </summary>
314+ private Mutex CreateSystemFileMutex ( string fileNameOrPath ) {
315+ #if NET45
316+ var security = new MutexSecurity ( ) ;
317+ var allowEveryoneRule = new MutexAccessRule ( new SecurityIdentifier ( WellKnownSidType . WorldSid , null ) , MutexRights . FullControl , AccessControlType . Allow ) ;
318+ security . AddAccessRule ( allowEveryoneRule ) ;
319+
320+ string name = GetFileBasedMutexName ( fileNameOrPath ) ;
321+
322+ try {
323+ return new Mutex ( false , name , out bool _ , security ) ;
324+ } catch ( Exception ex ) {
325+ if ( ex is SecurityException || ex is UnauthorizedAccessException || ex is NotSupportedException || ex is NotImplementedException ) {
326+ System . Diagnostics . Trace . WriteLine ( "Exceptionless: Error creating global mutex falling back to previous implementation: {0}" , ex . ToString ( ) ) ;
327+ return new Mutex ( false , nameof ( FileExceptionlessLog ) ) ;
328+ }
329+
330+ System . Diagnostics . Trace . WriteLine ( "Exceptionless: Error creating global mutex: {0}" , ex . ToString ( ) ) ;
331+ throw ;
332+ }
333+ #else
334+ System . Diagnostics . Trace . WriteLine ( "Exceptionless: This platform does not support taking out a global mutex" ) ;
335+ return new Mutex ( false , nameof ( FileExceptionlessLog ) ) ;
336+ #endif
337+ }
338+
339+ private string GetFileBasedMutexName ( string fileNameOrPath ) {
340+ const string prefix = "Global\\ Exceptionless-Log-" ;
341+ string name = fileNameOrPath . ToLowerInvariant ( ) . Replace ( '\\ ' , '_' ) . Replace ( '/' , '_' ) ;
342+
343+ const int maxLength = 260 ;
344+ if ( ( prefix . Length + name . Length ) <= maxLength ) {
345+ return $ "{ prefix } { name } ";
346+ }
347+
348+ // If name exceeds max length hash it and append part of the file name.
349+ string hash = name . ToSHA256 ( ) ;
350+ int startIndex = name . Length - ( maxLength - prefix . Length - hash . Length ) ;
351+ return $ "{ prefix } { hash } { name . Substring ( startIndex ) } ";
352+ }
353+
301354 private class LogEntry {
302355 public LogEntry ( LogLevel level , string message ) {
303356 LogLevel = level ;
0 commit comments