1
1
using System ;
2
2
using System . Collections . Concurrent ;
3
3
using System . IO ;
4
+ using System . Security ;
5
+ #if NET45
6
+ using System . Security . AccessControl ;
7
+ using System . Security . Principal ;
8
+ #endif
4
9
using System . Text ;
5
10
using System . Threading ;
11
+ using Exceptionless . Extensions ;
6
12
using Exceptionless . Utility ;
7
13
8
14
namespace Exceptionless . Logging {
9
15
public class FileExceptionlessLog : IExceptionlessLog , IDisposable {
10
- private static Mutex _flushLock = new Mutex ( false , nameof ( FileExceptionlessLog ) ) ;
11
-
16
+ private Mutex _flushMutex ;
12
17
private Timer _flushTimer ;
13
18
private readonly bool _append ;
14
19
private bool _firstWrite = true ;
15
- private bool _isFlushing = false ;
16
- private bool _isCheckingFileSize = false ;
20
+ private bool _isFlushing ;
21
+ private bool _isCheckingFileSize ;
17
22
18
23
public FileExceptionlessLog ( string filePath , bool append = false ) {
19
24
if ( String . IsNullOrEmpty ( filePath ) )
@@ -25,6 +30,7 @@ public FileExceptionlessLog(string filePath, bool append = false) {
25
30
26
31
Initialize ( ) ;
27
32
33
+ _flushMutex = CreateSystemFileMutex ( FilePath ) ;
28
34
// flush the log every 3 seconds instead of on every write
29
35
_flushTimer = new Timer ( OnFlushTimer , null , TimeSpan . FromSeconds ( 3 ) , TimeSpan . FromSeconds ( 3 ) ) ;
30
36
}
@@ -57,7 +63,7 @@ protected internal virtual string GetFileContents() {
57
63
if ( File . Exists ( FilePath ) )
58
64
return File . ReadAllText ( FilePath ) ;
59
65
} 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 ( ) ) ;
61
67
}
62
68
63
69
return String . Empty ;
@@ -68,7 +74,7 @@ protected internal virtual long GetFileSize() {
68
74
if ( File . Exists ( FilePath ) )
69
75
return new FileInfo ( FilePath ) . Length ;
70
76
} 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 ( ) ) ;
72
78
}
73
79
74
80
return - 1 ;
@@ -132,7 +138,7 @@ public void Flush() {
132
138
_isFlushing = true ;
133
139
134
140
Run . WithRetries ( ( ) => {
135
- if ( ! _flushLock . WaitOne ( TimeSpan . FromSeconds ( 5 ) ) )
141
+ if ( ! _flushMutex . WaitOne ( TimeSpan . FromSeconds ( 5 ) ) )
136
142
return ;
137
143
138
144
hasFlushLock = true ;
@@ -155,10 +161,10 @@ public void Flush() {
155
161
}
156
162
} ) ;
157
163
} 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 ( ) ) ;
159
165
} finally {
160
166
if ( hasFlushLock )
161
- _flushLock . ReleaseMutex ( ) ;
167
+ _flushMutex . ReleaseMutex ( ) ;
162
168
_isFlushing = false ;
163
169
}
164
170
}
@@ -217,15 +223,14 @@ internal void CheckFileSize() {
217
223
string lastLines = String . Empty ;
218
224
try {
219
225
Run . WithRetries ( ( ) => {
220
- if ( ! _flushLock . WaitOne ( TimeSpan . FromSeconds ( 5 ) ) )
226
+ if ( ! _flushMutex . WaitOne ( TimeSpan . FromSeconds ( 5 ) ) )
221
227
return ;
222
228
223
- lastLines = GetLastLinesFromFile ( FilePath ) ;
224
-
225
- _flushLock . ReleaseMutex ( ) ;
229
+ lastLines = GetLastLinesFromFile ( ) ;
230
+ _flushMutex . ReleaseMutex ( ) ;
226
231
} ) ;
227
232
} 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 ( ) ) ;
229
234
}
230
235
231
236
if ( String . IsNullOrEmpty ( lastLines ) ) {
@@ -236,16 +241,16 @@ internal void CheckFileSize() {
236
241
// overwrite the log file and initialize it with the last X lines it had
237
242
try {
238
243
Run . WithRetries ( ( ) => {
239
- if ( ! _flushLock . WaitOne ( TimeSpan . FromSeconds ( 5 ) ) )
244
+ if ( ! _flushMutex . WaitOne ( TimeSpan . FromSeconds ( 5 ) ) )
240
245
return ;
241
246
242
247
using ( var writer = GetWriter ( true ) )
243
248
writer . Value . Write ( lastLines ) ;
244
249
245
- _flushLock . ReleaseMutex ( ) ;
250
+ _flushMutex . ReleaseMutex ( ) ;
246
251
} ) ;
247
252
} 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 ( ) ) ;
249
254
}
250
255
251
256
_isCheckingFileSize = false ;
@@ -263,9 +268,14 @@ public virtual void Dispose() {
263
268
}
264
269
265
270
Flush ( ) ;
271
+
272
+ if ( _flushMutex != null ) {
273
+ _flushMutex . Close ( ) ;
274
+ _flushMutex = null ;
275
+ }
266
276
}
267
277
268
- protected string GetLastLinesFromFile ( string path , int lines = 100 ) {
278
+ protected string GetLastLinesFromFile ( int lines = 100 ) {
269
279
byte [ ] buffer = Encoding . ASCII . GetBytes ( "\n " ) ;
270
280
271
281
using ( var fs = GetReader ( ) ) {
@@ -298,6 +308,49 @@ protected string GetLastLinesFromFile(string path, int lines = 100) {
298
308
}
299
309
}
300
310
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
+
301
354
private class LogEntry {
302
355
public LogEntry ( LogLevel level , string message ) {
303
356
LogLevel = level ;
0 commit comments