Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 90 additions & 2 deletions 92 src/NLog/Internal/FileAppenders/BaseFileAppender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,14 @@ namespace NLog.Internal.FileAppenders
using System.IO;
using System.Runtime.InteropServices;
using NLog.Common;
using NLog.Config;
using NLog.Internal;
using NLog.Time;
using System.Threading;
#if !SILVERLIGHT
using System.Security.AccessControl;
using System.Security.Principal;
using System.Security.Cryptography;
#endif
using System.Text;

/// <summary>
/// Base class for optimized file appenders.
Expand All @@ -63,6 +68,9 @@ public BaseFileAppender(string fileName, ICreateFileParameters createParameters)
this.OpenTime = DateTime.UtcNow; // to be consistent with timeToKill in FileTarget.AutoClosingTimerCallback
this.LastWriteTime = DateTime.MinValue;
this.CaptureLastWriteTime = createParameters.CaptureLastWriteTime;
#if !SILVERLIGHT
this.ArchiveMutex = CreateArchiveMutex();
#endif
}

protected bool CaptureLastWriteTime { get; private set; }
Expand Down Expand Up @@ -97,6 +105,14 @@ public BaseFileAppender(string fileName, ICreateFileParameters createParameters)
/// <value>The file creation parameters.</value>
public ICreateFileParameters CreateFileParameters { get; private set; }

#if !SILVERLIGHT
/// <summary>
/// Gets the mutually-exclusive lock for archiving files.
/// </summary>
/// <value>The mutex for archiving.</value>
public Mutex ArchiveMutex { get; private set; }
#endif

/// <summary>
/// Writes the specified bytes.
/// </summary>
Expand Down Expand Up @@ -158,6 +174,78 @@ protected void FileTouched(DateTime dateTime)
this.LastWriteTime = dateTime;
}

#if !SILVERLIGHT
/// <summary>
/// Creates a mutually-exclusive lock for archiving files.
/// </summary>
/// <returns>A <see cref="Mutex"/> object which can be used for controlling the archiving of files.</returns>
protected virtual Mutex CreateArchiveMutex()
{
return new Mutex();
}

/// <summary>
/// Creates a mutex for archiving that is sharable by more than one process.
/// </summary>
/// <returns>A <see cref="Mutex"/> object which can be used for controlling the archiving of files.</returns>
protected Mutex CreateSharableArchiveMutex()
{
return CreateSharableMutex("FileArchiveLock");
}

/// <summary>
/// Creates a mutex that is sharable by more than one process.
/// </summary>
/// <param name="mutexNamePrefix">The prefix to use for the name of the mutex.</param>
/// <returns>A <see cref="Mutex"/> object which is sharable by multiple processes.</returns>
protected Mutex CreateSharableMutex(string mutexNamePrefix)
{
// Creates a mutex sharable by more than one process
var mutexSecurity = new MutexSecurity();
var everyoneSid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
mutexSecurity.AddAccessRule(new MutexAccessRule(everyoneSid, MutexRights.FullControl, AccessControlType.Allow));

// The constructor will either create new mutex or open
// an existing one, in a thread-safe manner
bool createdNew;
return new Mutex(false, GetMutexName(mutexNamePrefix), out createdNew, mutexSecurity);
}

private string GetMutexName(string mutexNamePrefix)
{
const string mutexNameFormatString = @"Global\NLog-File{0}-{1}";
const int maxMutexNameLength = 260;

string canonicalName = Path.GetFullPath(FileName).ToLowerInvariant();

// Mutex names must not contain a backslash, it's the namespace separator,
// but all other are OK
canonicalName = canonicalName.Replace('\\', '/');
string mutexName = string.Format(mutexNameFormatString, mutexNamePrefix, canonicalName);

// A mutex name must not exceed MAX_PATH (260) characters
if (mutexName.Length <= maxMutexNameLength)
{
return mutexName;
}

// The unusual case of the path being too long; let's hash the canonical name,
// so it can be safely shortened and still remain unique
string hash;
using (MD5 md5 = MD5.Create())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if this is the best approach. We could also substring it and add a guid?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah well, it was already there?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this code came from MutexMultiProcessFileAppender. Adding a Guid won't work, because a different process will compute a different Guid for the same file. We need the same file to have the same mutex-name across processes.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok thanks! :)

{
byte[] bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(canonicalName));
hash = Convert.ToBase64String(bytes);
}

// The hash makes the name unique, but also add the end of the path,
// so the end of the name tells us which file it is (for debugging)
mutexName = string.Format(mutexNameFormatString, mutexNamePrefix, hash);
int cutOffIndex = canonicalName.Length - (maxMutexNameLength - mutexName.Length);
return mutexName + canonicalName.Substring(cutOffIndex);
}
#endif

/// <summary>
/// Creates the file stream.
/// </summary>
Expand Down
12 changes: 9 additions & 3 deletions 12 src/NLog/Internal/FileAppenders/FileAppenderCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,14 @@ private BaseFileAppender GetAppender(string fileName)
return null;
}

#if !SILVERLIGHT
public Mutex GetArchiveMutex(string fileName)
{
var appender = GetAppender(fileName);
return appender == null ? null : appender.ArchiveMutex;
}
#endif

public DateTime? GetFileCreationTimeUtc(string filePath, bool fallback)
{
var appender = GetAppender(filePath);
Expand Down Expand Up @@ -354,9 +362,7 @@ private BaseFileAppender GetAppender(string fileName)

return result;
}




/// <summary>
/// Closes the specified appender and removes it from the list.
/// </summary>
Expand Down
58 changes: 8 additions & 50 deletions 58 src/NLog/Internal/FileAppenders/MutexMultiProcessFileAppender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,11 @@

namespace NLog.Internal.FileAppenders
{
using NLog.Common;
using System;
using System.IO;
using System.Security;
using System.Security.AccessControl;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
using System.Threading;
using NLog.Common;

/// <summary>
/// Provides a multiprocess-safe atomic file appends while
Expand Down Expand Up @@ -73,7 +69,7 @@ public MutexMultiProcessFileAppender(string fileName, ICreateFileParameters para
{
try
{
this.mutex = CreateSharableMutex(GetMutexName(fileName));
this.mutex = CreateSharableMutex("FileLock");
this.fileStream = CreateFileStream(true);
}
catch
Expand Down Expand Up @@ -187,51 +183,13 @@ private FileCharacteristics GetFileCharacteristics()
return FileCharacteristicsHelper.Helper.GetFileCharacteristics(FileName, this.fileStream.SafeFileHandle.DangerousGetHandle());
}

private static Mutex CreateSharableMutex(string name)
{
// Creates a mutex sharable by more than one process
var mutexSecurity = new MutexSecurity();
var everyoneSid = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
mutexSecurity.AddAccessRule(new MutexAccessRule(everyoneSid, MutexRights.FullControl, AccessControlType.Allow));

// The constructor will either create new mutex or open
// an existing one, in a thread-safe manner
bool createdNew;
return new Mutex(false, name, out createdNew, mutexSecurity);
}

private static string GetMutexName(string fileName)
/// <summary>
/// Creates a mutually-exclusive lock for archiving files.
/// </summary>
/// <returns>A <see cref="Mutex"/> object which can be used for controlling the archiving of files.</returns>
protected override Mutex CreateArchiveMutex()
{
// The global kernel object namespace is used so the mutex
// can be shared among processes in all sessions
const string mutexNamePrefix = @"Global\NLog-FileLock-";
const int maxMutexNameLength = 260;

string canonicalName = Path.GetFullPath(fileName).ToLowerInvariant();

// Mutex names must not contain a backslash, it's the namespace separator,
// but all other are OK
canonicalName = canonicalName.Replace('\\', '/');

// A mutex name must not exceed MAX_PATH (260) characters
if (mutexNamePrefix.Length + canonicalName.Length <= maxMutexNameLength)
{
return mutexNamePrefix + canonicalName;
}

// The unusual case of the path being too long; let's hash the canonical name,
// so it can be safely shortened and still remain unique
string hash;
using (MD5 md5 = MD5.Create())
{
byte[] bytes = md5.ComputeHash(Encoding.UTF8.GetBytes(canonicalName));
hash = Convert.ToBase64String(bytes);
}

// The hash makes the name unique, but also add the end of the path,
// so the end of the name tells us which file it is (for debugging)
int cutOffIndex = canonicalName.Length - (maxMutexNameLength - mutexNamePrefix.Length - hash.Length);
return mutexNamePrefix + hash + canonicalName.Substring(cutOffIndex);
return CreateSharableArchiveMutex();
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ namespace NLog.Internal.FileAppenders
{
using System;
using System.IO;
using System.Threading;

/// <summary>
/// Multi-process and multi-host file appender which attempts
Expand Down Expand Up @@ -120,6 +121,17 @@ public override void Close()
return null;
}

#if !SILVERLIGHT
/// <summary>
/// Creates a mutually-exclusive lock for archiving files.
/// </summary>
/// <returns>A <see cref="Mutex"/> object which can be used for controlling the archiving of files.</returns>
protected override Mutex CreateArchiveMutex()
{
return CreateSharableArchiveMutex();
}
#endif

/// <summary>
/// Factory class.
/// </summary>
Expand Down
74 changes: 47 additions & 27 deletions 74 src/NLog/Targets/FileTarget.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1035,11 +1035,7 @@ private void ProcessLogEvent(LogEventInfo logEvent, string fileName, byte[] byte
#if !SILVERLIGHT && !__IOS__ && !__ANDROID__
this.fileAppenderCache.InvalidateAppendersForInvalidFiles();
#endif
var archiveFile = this.GetArchiveFileName(fileName, logEvent, bytesToWrite.Length);
if (!string.IsNullOrEmpty(archiveFile))
{
this.DoAutoArchive(archiveFile, logEvent);
}
TryArchiveFile(fileName, logEvent, bytesToWrite.Length);

// Clean up old archives if this is the first time a log record is being written to
// this log file and the archiving system is date/time based.
Expand Down Expand Up @@ -1195,26 +1191,7 @@ private void RollArchivesForward(string fileName, string pattern, int archiveNum
else
{
InternalLogger.Info("Roll archive {0} to {1}", fileName, newFileName);
try
{
File.Move(fileName, newFileName);
}
catch (Exception ex)
{
InternalLogger.Warn(ex, "Roll archive {0} to {1} failed", fileName, newFileName);
//if failed due to a race condition (file exists suddenly), then retry with a new number.
if (File.Exists(newFileName))
{
InternalLogger.Debug(ex, "Roll archive {0} to {1} failed because of a race condition. Retry with new name", fileName, newFileName);
RollArchivesForward(newFileName, pattern, archiveNumber + 1);
}
else
{
throw;
}
}


File.Move(fileName, newFileName);
}
}

Expand Down Expand Up @@ -1791,7 +1768,50 @@ private bool ShouldDeleteOldArchives()
}

/// <summary>
/// Gets the file name for archiving, or null if archiving should not occur.
/// Archives the file if it should be archived.
/// </summary>
/// <param name="fileName">The file name to check for.</param>
/// <param name="ev">Log event that the <see cref="FileTarget"/> instance is currently processing.</param>
/// <param name="upcomingWriteSize">The size in bytes of the next chunk of data to be written in the file.</param>
private void TryArchiveFile(string fileName, LogEventInfo ev, int upcomingWriteSize)
{
var archiveFile = this.GetArchiveFileName(fileName, ev, upcomingWriteSize);
if (!string.IsNullOrEmpty(archiveFile))
{
#if !SILVERLIGHT
Mutex archiveMutex = this.fileAppenderCache.GetArchiveMutex(fileName);
try
{
if (archiveMutex != null)
archiveMutex.WaitOne();
}
catch (AbandonedMutexException)
{
// ignore the exception, another process was killed without properly releasing the mutex
// the mutex has been acquired, so proceed to writing
// See: http://msdn.microsoft.com/en-us/library/system.threading.abandonedmutexexception.aspx
}
#endif
try
{
archiveFile = this.GetArchiveFileName(fileName, ev, upcomingWriteSize);
if (!string.IsNullOrEmpty(archiveFile))
{
this.DoAutoArchive(archiveFile, ev);
}
}
finally
{
#if !SILVERLIGHT
if (archiveMutex != null)
archiveMutex.ReleaseMutex();
#endif
}
}
}

/// <summary>
/// Indicates if the automatic archiving process should be executed.
/// </summary>
/// <param name="fileName">File name to be written.</param>
/// <param name="ev">Log event that the <see cref="FileTarget"/> instance is currently processing.</param>
Expand All @@ -1804,7 +1824,7 @@ private string GetArchiveFileName(string fileName, LogEventInfo ev, int upcoming
{
return GetArchiveFileNameBasedOnFileSize(fileName, upcomingWriteSize) ??
GetArchiveFileNameBasedOnTime(fileName, ev);
}
}

return null;
}
Expand Down
Morty Proxy This is a proxified and sanitized view of the page, visit original site.