Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add a file target for desktop apps

  • Loading branch information...
commit e37d1a10eb9903d08a61dcceba3ab6c87beecdb0 1 parent 755de58
@onovotny onovotny authored
View
2  MetroLog.NetCore/WinRTFileTarget.cs
@@ -65,7 +65,7 @@ protected override Task EnsureInitialized()
return EnsureInitializedAsync();
}
- protected override async Task DoCleanup(Regex pattern, DateTime threshold)
+ sealed protected override async Task DoCleanup(Regex pattern, DateTime threshold)
{
var toDelete = new List<StorageFile>();
View
118 MetroLog.NetFx/FileTarget.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using MetroLog.Layouts;
+using MetroLog.Targets;
+
+namespace MetroLog
+{
+ public abstract class FileTarget : FileTargetBase
+ {
+ private static DirectoryInfo _logFolder;
+ private string _appDataPath;
+
+ public string PathUnderAppData
+ {
+ get { return _appDataPath; }
+ set
+ {
+ if (string.IsNullOrWhiteSpace(value))
+ throw new ArgumentNullException("value");
+
+ _appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), value);
+ }
+ }
+
+ protected FileTarget(Layout layout) : base(layout)
+ {
+ _appDataPath = GetUserAppDataPath();
+ }
+
+ protected override Task EnsureInitialized()
+ {
+ var tcs = new TaskCompletionSource<bool>();
+
+ try
+ {
+ if (_logFolder == null)
+ {
+ var root = new DirectoryInfo(PathUnderAppData);
+
+ var logFolder = root.CreateSubdirectory(LogFolderName);
+
+
+ Interlocked.CompareExchange(ref _logFolder, logFolder, null);
+ }
+
+ tcs.SetResult(true);
+ }
+ catch(Exception e)
+ {
+ tcs.SetException(e);
+ }
+
+ return tcs.Task;
+ }
+
+ protected sealed override async Task<LogWriteOperation> DoWriteAsync(string fileName, string contents, LogEventInfo entry)
+ {
+ // Create writer
+ using (var file = new StreamWriter(Path.Combine(_logFolder.FullName, fileName), FileNamingParameters.CreationMode == FileCreationMode.AppendIfExisting, Encoding.UTF8))
+ {
+ // Write contents
+ await WriteTextToFileCore(file, contents);
+ await file.FlushAsync();
+ }
+
+ // return...
+ return new LogWriteOperation(this, entry, true);
+ }
+
+ protected abstract Task WriteTextToFileCore(StreamWriter file, string contents);
+
+ sealed protected override Task DoCleanup(Regex pattern, DateTime threshold)
+ {
+ return Task.Run(() =>
+ {
+ var toDelete = new List<FileInfo>();
+ foreach (var file in _logFolder.EnumerateFiles())
+ {
+ if (pattern.Match(file.Name).Success && file.CreationTimeUtc <= threshold)
+ toDelete.Add(file);
+ }
+
+ // walk...
+ foreach (var file in toDelete)
+ {
+ try
+ {
+ file.Delete();
+ }
+ catch (Exception ex)
+ {
+ InternalLogger.Current.Warn(string.Format("Failed to delete '{0}'.", file.FullName), ex);
+ }
+ }
+ });
+ }
+
+ private static string GetUserAppDataPath()
+ {
+ var path = string.Empty;
+
+ // Get the .EXE assembly
+ var assm = Assembly.GetEntryAssembly();
+ // Build the User App Data Path
+ path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), assm.GetName().Name);
+
+ return path;
+ }
+
+ }
+}
View
2  MetroLog.NetFx/MetroLog.NetFx.csproj
@@ -48,9 +48,11 @@
<Compile Include="..\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
+ <Compile Include="FileTarget.cs" />
<Compile Include="LoggingEnvironment.cs" />
<Compile Include="LogConfigurator.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="StreamingFileTarget.cs" />
<Compile Include="Targets\TraceTarget.cs" />
</ItemGroup>
<ItemGroup>
View
35 MetroLog.NetFx/StreamingFileTarget.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MetroLog.Layouts;
+using MetroLog.Targets;
+
+namespace MetroLog
+{
+ public class StreamingFileTarget : FileTarget
+ {
+ public StreamingFileTarget()
+ : this(new SingleLineLayout())
+ {
+ }
+
+ public StreamingFileTarget(Layout layout) : base(layout)
+ {
+ FileNamingParameters.IncludeLevel = false;
+ FileNamingParameters.IncludeLogger = false;
+ FileNamingParameters.IncludeSequence = false;
+ FileNamingParameters.IncludeSession = false;
+ FileNamingParameters.IncludeTimestamp = FileTimestampMode.Date;
+ FileNamingParameters.CreationMode = FileCreationMode.AppendIfExisting;
+ }
+
+
+ protected override Task WriteTextToFileCore(StreamWriter file, string contents)
+ {
+ return file.WriteLineAsync(contents);
+ }
+ }
+}
View
51 MetroLog/Internal/AsyncLock.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MetroLog.Internal
+{
+ internal class AsyncLock
+ {
+ private readonly AsyncSemaphore m_semaphore;
+ private readonly Task<Releaser> m_releaser;
+
+ public AsyncLock()
+ {
+ m_semaphore = new AsyncSemaphore(1);
+ m_releaser = Task.FromResult(new Releaser(this));
+ }
+
+ public struct Releaser : IDisposable
+ {
+ private readonly AsyncLock m_toRelease;
+
+ internal Releaser(AsyncLock toRelease) { m_toRelease = toRelease; }
+
+ public void Dispose()
+ {
+ if (m_toRelease != null)
+ m_toRelease.m_semaphore.Release();
+ }
+ }
+
+ public Task<Releaser> LockAsync([CallerMemberName] string callingMethod = null, [CallerFilePath] string path = null, [CallerLineNumber] int line = 0)
+ {
+ Debug.WriteLine("AsyncLock.LockAsync called by: " + callingMethod + " in file: " + path + " : " + line);
+
+
+ var wait = m_semaphore.WaitAsync();
+
+ return wait.IsCompleted ?
+ m_releaser :
+ wait.ContinueWith((_, state) => new Releaser((AsyncLock)state),
+ this, CancellationToken.None,
+ TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
+ }
+
+ }
+}
View
57 MetroLog/Internal/AsyncSemaphore.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MetroLog.Internal
+{
+ internal class AsyncSemaphore
+ {
+ private readonly static Task s_completed = Task.FromResult(true);
+ private readonly Queue<TaskCompletionSource<bool>> m_waiters = new Queue<TaskCompletionSource<bool>>();
+ private int m_currentCount;
+
+ public AsyncSemaphore(int initialCount)
+ {
+ if (initialCount < 0) throw new ArgumentOutOfRangeException("initialCount");
+ m_currentCount = initialCount;
+ }
+
+ public Task WaitAsync()
+ {
+ //Debug.WriteLine("AsyncSemaphore.WaitAsync called by: " + callingMethod);
+ lock (m_waiters)
+ {
+ if (m_currentCount > 0)
+ {
+ --m_currentCount;
+ return s_completed;
+ }
+ else
+ {
+ var waiter = new TaskCompletionSource<bool>();
+ m_waiters.Enqueue(waiter);
+ return waiter.Task;
+ }
+ }
+ }
+
+ public void Release()
+ {
+ TaskCompletionSource<bool> toRelease = null;
+ lock (m_waiters)
+ {
+ if (m_waiters.Count > 0)
+ toRelease = m_waiters.Dequeue();
+ else
+ ++m_currentCount;
+ }
+ if (toRelease != null)
+ toRelease.SetResult(true);
+ }
+
+ }
+}
View
5 MetroLog/MetroLog.csproj
@@ -109,6 +109,8 @@
</Compile>
<Compile Include="ExceptionWrapper.cs" />
<Compile Include="ILogConfigurator.cs" />
+ <Compile Include="Internal\AsyncLock.cs" />
+ <Compile Include="Internal\AsyncSemaphore.cs" />
<Compile Include="LogConfigurator.cs" />
<Compile Include="LoggerEventArgs.cs" />
<Compile Include="ILoggerQuery.cs" />
@@ -149,9 +151,8 @@
<Compile Include="Targets\FileCreationMode.cs" />
<Compile Include="Targets\FileNamingParameters.cs" />
<Compile Include="Targets\FileTargetBase.cs" />
- <Compile Include="Targets\FileTimestampMoe.cs" />
+ <Compile Include="Targets\FileTimestampMode.cs" />
<Compile Include="Targets\HttpClientEventHandler.cs" />
- <Compile Include="Targets\IReadableTarget.cs" />
<Compile Include="Targets\JsonPostTarget.cs" />
<Compile Include="Targets\JsonPostWrapper.cs" />
<Compile Include="Targets\LogReadQuery.cs" />
View
16 MetroLog/Targets/FileTargetBase.cs
@@ -1,4 +1,5 @@
using System.Text.RegularExpressions;
+using MetroLog.Internal;
using MetroLog.Layouts;
using System;
using System.Collections.Generic;
@@ -32,6 +33,8 @@ public abstract class FileTargetBase : AsyncTarget
/// </summary>
protected DateTime NextCleanupUtc { get; set; }
+ private readonly AsyncLock _lock = new AsyncLock();
+
protected FileTargetBase(Layout layout)
: base(layout)
{
@@ -68,13 +71,16 @@ protected FileTargetBase(Layout layout)
sealed protected override async Task<LogWriteOperation> WriteAsyncCore(LogWriteContext context, LogEventInfo entry)
{
- await EnsureInitialized();
- await CheckCleanupAsync();
+ using (await _lock.LockAsync())
+ {
+ await EnsureInitialized();
+ await CheckCleanupAsync();
- var filename = FileNamingParameters.GetFilename(context, entry);
- var contents = Layout.GetFormattedString(context, entry);
+ var filename = FileNamingParameters.GetFilename(context, entry);
+ var contents = Layout.GetFormattedString(context, entry);
- return await DoWriteAsync(filename, contents, entry);
+ return await DoWriteAsync(filename, contents, entry);
+ }
}
protected abstract Task<LogWriteOperation> DoWriteAsync(string fileName, string contents, LogEventInfo entry);
View
0  MetroLog/Targets/FileTimestampMoe.cs → MetroLog/Targets/FileTimestampMode.cs
File renamed without changes
View
16 MetroLog/Targets/IReadableTarget.cs
@@ -1,16 +0,0 @@
-//using System;
-//using System.Collections.Generic;
-//using System.Linq;
-//using System.Text;
-//using System.Threading.Tasks;
-
-//namespace MetroLog.Targets
-//{
-// /// <summary>
-// /// Defines a target where entries can be read back in.
-// /// </summary>
-// public interface IReadableTarget
-// {
-// Task<IEnumerable<LogEventInfo>> ReadLogEntriesAsync(LogReadQuery query);
-// }
-//}
View
9 Samples/ConsoleSample/ConsoleSample.csproj
@@ -43,9 +43,18 @@
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Properties\Settings.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTimeSharedInput>True</DesignTimeSharedInput>
+ <DependentUpon>Settings.settings</DependentUpon>
+ </Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
+ <None Include="Properties\Settings.settings">
+ <Generator>SettingsSingleFileGenerator</Generator>
+ <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+ </None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\MetroLog.NetFx\MetroLog.NetFx.csproj">
View
13 Samples/ConsoleSample/Program.cs
@@ -16,7 +16,7 @@ static void Main(string[] args)
try
{
// Initialize MetroLog using the defaults
- //LogManagerFactory.DefaultConfiguration.AddTarget(LogLevel.Trace, LogLevel.Fatal, new DebugTarget());
+ LogManagerFactory.DefaultConfiguration.AddTarget(LogLevel.Trace, LogLevel.Fatal, new StreamingFileTarget());
ILogManager logManager = LogManagerFactory.DefaultLogManager;
// Inject the ILogManager manually
@@ -27,7 +27,7 @@ static void Main(string[] args)
{
// If we have a debugger, stop so you can see the output
if (Debugger.IsAttached)
- Console.ReadLine();
+ Console.ReadKey();
}
}
}
@@ -49,6 +49,15 @@ public void DoMagic()
_log.Trace("Trace some data.");
_log.Error("Something bad happened at {0}", DateTime.Now);
_log.Fatal("Danger Will Robinson!");
+
+ try
+ {
+ throw new InvalidOperationException("Test");
+ }
+ catch(Exception e)
+ {
+ _log.Error("Bad thing!", e);
+ }
}
}
View
26 Samples/ConsoleSample/Properties/Settings.Designer.cs
@@ -0,0 +1,26 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.17929
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace ConsoleSample.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+ }
+}
View
6 Samples/ConsoleSample/Properties/Settings.settings
@@ -0,0 +1,6 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
+ <Profiles>
+ <Profile Name="(Default)" />
+ </Profiles>
+</SettingsFile>
Please sign in to comment.
Something went wrong with that request. Please try again.