Commit 11c540d7 authored by Andy James's avatar Andy James

Initial commit - Migrated NChronicle.File

Migrated from the NChronicle (now NChronicle.Core) repository at https://git.ikimi.co/NChronicle/NChronicle.Core
parents
Pipeline #13 passed with stage
.vs/
.idea/
packages/
*.DotSettings.user
*.csproj.user
*.nupkg
*.log
**/bin/
**/obj/
stages:
- Build
- Package
Build Solution:
stage: Build
script: "powershell .\\Build.ps1 NChronicle.File.sln"
\ No newline at end of file
[submodule "NChronicle.File.Wiki"]
path = _Wiki
url = [email protected]:NChronicle/NChronicle.File.wiki.git
Param([string] $target, [string] $msbuild)
$PROGRAM_FILES_32 = ${env:ProgramFiles(x86)}
$DEFAULT_LOCATION_FOR_BUILDTOOLS = "$PROGRAM_FILES_32\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin\MSBuild.exe"
$DEFAULT_LOCATION_FOR_VS_COMMUNITY = "$PROGRAM_FILES_32\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe"
$DEFAULT_LOCATION_FOR_VS_PROFESSIONAL = "$PROGRAM_FILES_32\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\MSBuild.exe"
$DEFAULT_LOCATION_FOR_VS_ENTERPRISE = "$PROGRAM_FILES_32\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\MSBuild.exe"
if (![String]::IsNullOrEmpty($msbuild)) {
if (-not (Test-Path $msbuild)) {
Throw [System.IO.FileNotFoundException] "The path $msbuild was invalid or the file did not exist."
}
} else {
$msbuild = $DEFAULT_LOCATION_FOR_BUILDTOOLS
if (-not (Test-Path $msbuild)) {
$msbuild = $DEFAULT_LOCATION_FOR_VS_COMMUNITY
}
if (-not (Test-Path $msbuild)) {
$msbuild = $DEFAULT_LOCATION_FOR_VS_PROFESSIONAL
}
if (-not (Test-Path $msbuild)) {
$msbuild = $DEFAULT_LOCATION_FOR_VS_ENTERPRISE
}
if (-not (Test-Path $msbuild)) {
Throw [System.IO.FileNotFoundException] "Could not find a C# 7 MSBuild tool."
}
}
&$msbuild $target
\ No newline at end of file
Param([string] $sourcePath, [string] $buildPath)
$files = get-childitem "$sourcePath*.md";
$regex = [System.Text.RegularExpressions.Regex]::new("\<a href\=\`".*?\`"\>.*?\<\/a\>");
$targetRegex = [System.Text.RegularExpressions.Regex]::new("(?<=(href\=\`")).*?(?=\`")");
$titleRegex = [System.Text.RegularExpressions.Regex]::new("(?<=(\`"\>)).*?(?=(\<\/a\>))");
foreach ($file in $files) {
set-content $file $regex.Replace(([IO.File]::ReadAllText($file)), {
param($match);
$target = $targetRegex.Match($match).Value;
if ([String]::IsNullOrEmpty($target) -or $target.ToString().StartsWith('#') -or !(Test-Path "$sourcePath$target.md")) {
return $match;
}
Write-Host("Found link $match");
$title = $titleRegex.Match($match).Value;
if ([String]::IsNullOrEmpty($title)) {
$title = $target
}
Write-Host("Rewritten to [$title]($target)")
return "[$title]($target)"
});
}
Write-Host("Removing markdown files in $buildPath")
Remove-Item $buildPath\*.md
Write-Host("Removing media files in $buildPath")
Remove-Item -Force -Recurse $buildPath\media\
Write-Host("Copying markdown files from $sourcePath to $buildPath")
Copy-Item $sourcePath\*.md $buildPath\
Write-Host("Copying media files from $sourcePath to $buildPath")
Copy-Item -Force -Recurse $sourcePath\media $buildPath\
\ No newline at end of file
This diff is collapsed.
using System;
using System.Xml;
using System.Xml.Schema;
namespace NChronicle.File.Configuration {
/// <summary>
/// Container for <see cref="RetentionPolicy"/> configuration.
/// </summary>
public class RetentionPolicyConfiguration {
internal TimeSpan? AgeLimit = new TimeSpan(1, 0, 0, 0);
internal long FileSizeLimit = 104857600; // 100 MB
internal long RetentionLimit = 20;
/// <summary>
/// Set the age limit for the output file before it will
/// be archived. The default age limit is 1 day.
/// </summary>
/// <param name="timeSpan">The maximum age for the output file as a <see cref="TimeSpan"/>.</param>
public void WithAgeLimit (TimeSpan timeSpan) {
if (timeSpan < TimeSpan.FromMinutes(1))
throw new ArgumentException($"Specified {nameof(timeSpan)} is less than the minimum of 1 minute.");
this.AgeLimit = timeSpan;
}
/// <summary>
/// Remove the age limit for the output file so as not to
/// archive it - regardless of it's age - unless it extends
/// over the set file size limit.
/// </summary>
public void WithNoAgeLimit () => this.AgeLimit = null;
/// <summary>
/// Set the file size limit for the output file before it
/// will be archived. The file size limit must be above 50KB.
/// The default file size limit is 100MB;.
/// </summary>
/// <param name="bytes">The maximum file size for the output file in Kilobytes.</param>
public void WithFileSizeLimitInKilobytes (long bytes) => this.WithFileSizeLimitInBytes(bytes*1024);
/// <summary>
/// Set the file size limit for the output file before it
/// will be archived. The file size limit must be above 50KB.
/// The default file size limit is 100MB;.
/// </summary>
/// <param name="bytes">The maximum file size for the output file in Bytes.</param>
public void WithFileSizeLimitInBytes (long bytes) {
if (bytes < 50*1024) throw new ArgumentException("File size limit must be 50 or more kilobytes.");
this.FileSizeLimit = bytes;
}
/// <summary>
/// Remove the file size limit for the output file so as not
/// to archive it - regardless of it's file size - unless it
/// extends over the set age limit.
/// </summary>
public void WithNoFileSizeLimit () {
this.FileSizeLimit = 0;
}
/// <summary>
/// Set the file size limit for the output file before it
/// will be archived. The file size limit must be above 50KB.
/// The default file size limit is 100MB;.
/// </summary>
/// <param name="bytes">The maximum file size for the output file in Megabytes.</param>
public void WithFileSizeLimitInMegabytes (long bytes) => this.WithFileSizeLimitInBytes(bytes*1024*1024);
/// <summary>
/// Remove the retention limit so as not to delete
/// any archived log regardless of the quantity.
/// </summary>
public void WithNoRetentionLimit () => this.RetentionLimit = 0;
/// <summary>
/// Set a retention <paramref name="limit"/>, defining how many of
/// the newest archived logs are kept, the elder archived logs are deleted.
/// the default retention limit is 20.
/// </summary>
/// <param name="limit">The maximum number of archived log files to keep.</param>
public void WithRetentionLimit (long limit) => this.RetentionLimit = limit;
/// <summary>
/// Required for XML serialization, this method offers no functionality.
/// </summary>
/// <returns>A null <see cref="XmlSchema"/>.</returns>
public XmlSchema GetSchema() => null;
/// <summary>
/// Populate configuration from XML via the specified <see cref="XmlReader" />.
/// </summary>
/// <param name="reader"><see cref="XmlReader" /> stream from the configuration file.</param>
/// <seealso cref="Core.NChronicle.ConfigureFrom(string, bool, int)"/>
public void ReadXml(XmlReader reader) {
while (reader.Read()) {
if (reader.NodeType == XmlNodeType.Element) {
switch (reader.Name) {
case nameof(this.AgeLimit):
if (reader.IsEmptyElement) break;
var agelimitStr = reader.ReadElementContentAsString();
if (string.IsNullOrWhiteSpace(agelimitStr))
throw new XmlException($"Unexpected library configuration for {nameof(FileChronicleLibrary)}, empty {nameof(this.AgeLimit)}.");
TimeSpan ageLimit;
if (!TimeSpan.TryParse(agelimitStr, out ageLimit))
throw new XmlException($"Unexpected library configuration for {nameof(FileChronicleLibrary)}, value '{agelimitStr}' for {nameof(this.AgeLimit)} is not a valid {nameof(TimeSpan)}.");
if (ageLimit <= TimeSpan.Zero)
this.WithNoAgeLimit();
else
this.WithAgeLimit(ageLimit);
break;
case nameof(this.FileSizeLimit):
if (reader.IsEmptyElement) break;
var fileSizeLimitStr = reader.ReadElementContentAsString();
if (string.IsNullOrWhiteSpace(fileSizeLimitStr))
throw new XmlException($"Unexpected library configuration for {nameof(FileChronicleLibrary)}, empty {nameof(this.FileSizeLimit)}.");
Int64 fileSizeLimit;
if (!Int64.TryParse(fileSizeLimitStr, out fileSizeLimit))
throw new XmlException($"Unexpected library configuration for {nameof(FileChronicleLibrary)}, value '{fileSizeLimitStr}' for {nameof(this.FileSizeLimit)} is not a valid {nameof(Int64)}.");
if (fileSizeLimit <= 0)
this.WithNoFileSizeLimit();
else
this.WithFileSizeLimitInBytes(fileSizeLimit);
break;
case nameof(this.RetentionLimit):
if (reader.IsEmptyElement) break;
var retentionLimitStr = reader.ReadElementContentAsString();
if (string.IsNullOrWhiteSpace(retentionLimitStr))
throw new XmlException($"Unexpected library configuration for {nameof(FileChronicleLibrary)}, empty {nameof(this.FileSizeLimit)}.");
Int64 retentionLimit;
if (!Int64.TryParse(retentionLimitStr, out retentionLimit))
throw new XmlException($"Unexpected library configuration for {nameof(FileChronicleLibrary)}, value '{retentionLimitStr}' for {nameof(this.RetentionLimit)} is not a valid {nameof(Int64)}.");
if (retentionLimit <= 0)
this.WithNoRetentionLimit();
else
this.WithRetentionLimit(retentionLimit);
break;
default:
reader.Skip();
break;
}
}
else if (reader.NodeType == XmlNodeType.EndElement)
{
return;
}
}
}
/// <summary>
/// Write configuration to XML via the specified <see cref="XmlWriter" />.
/// </summary>
/// <param name="writer"><see cref="XmlWriter" /> stream to the configuration file.</param>
/// <seealso cref="Core.NChronicle.SaveConfigurationTo(string)"/>
public void WriteXml(XmlWriter writer) {
if (this.AgeLimit.HasValue) {
writer.WriteElementString(nameof(this.AgeLimit), this.AgeLimit.Value.ToString());
}
writer.WriteElementString(nameof(this.FileSizeLimit), this.FileSizeLimit.ToString());
writer.WriteElementString(nameof(this.RetentionLimit), this.RetentionLimit.ToString());
}
}
}
\ No newline at end of file
using NChronicle.File.Configuration;
namespace NChronicle.File.Delegates {
/// <summary>
/// A function to configure a <see cref="FileChronicleLibrary"/>.
/// </summary>
/// <param name="configuration">The <see cref="FileChronicleLibrary"/> configuration.</param>
public delegate void FileChronicleLibraryConfigurationDelegate (FileChronicleLibraryConfiguration configuration);
}
\ No newline at end of file
using NChronicle.File.Configuration;
namespace NChronicle.File.Delegates {
/// <summary>
/// A function to configure a <see cref="RetentionPolicy"/>.
/// </summary>
/// <param name="configuration">The <see cref="RetentionPolicy"/> configuration.</param>
public delegate void RetentionPolicyConfigurationDelegate (RetentionPolicyConfiguration configuration);
}
\ No newline at end of file
using System;
namespace NChronicle.File.Exceptions {
/// <summary>
/// The exception that is thrown when the specified file path is invalid.
/// </summary>
public class InvalidFilePathException : Exception {
/// <summary>
/// Create a new <see cref="InvalidFilePathException"/> instance with
/// the specified <paramref name="message"/>.
/// </summary>
/// <param name="message">A message describing the error.</param>
public InvalidFilePathException (string message) : base(message) {}
/// <summary>
/// Create a new <see cref="InvalidFilePathException"/> instance with
/// the specified <paramref name="message"/> and <paramref name="innerException"/>.
/// </summary>
/// <param name="message">A message describing the error.</param>
/// <param name="innerException">The exception that caused this exception.</param>
public InvalidFilePathException (string message, Exception innerException) : base(message, innerException) {}
}
}
\ No newline at end of file
using NChronicle.Core.Model;
namespace NChronicle.File.Extensions {
/// <summary>
/// Container for configuration extension methods.
/// </summary>
public static class ConfigurationExtensions {
/// <summary>
/// Create and add a <see cref="FileChronicleLibrary"/> to the specified <see cref="ChronicleConfiguration"/>.
/// </summary>
/// <param name="config">The <see cref="ChronicleConfiguration"/> for which to add the <see cref="FileChronicleLibrary"/>.</param>
/// <returns>The created <see cref="FileChronicleLibrary"/>.</returns>
public static FileChronicleLibrary WithFileLibrary (this ChronicleConfiguration config) {
var lib = new FileChronicleLibrary();
config.WithLibrary(lib);
return lib;
}
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Xml;
using System.Xml.Schema;
using NChronicle.Core.Interfaces;
using NChronicle.Core.Model;
using NChronicle.File.Configuration;
using NChronicle.File.Delegates;
namespace NChronicle.File {
/// <summary>
/// A <see cref="IChronicleLibrary"/> writing <see cref="ChronicleRecord"/>s to a file.
/// </summary>
public class FileChronicleLibrary : IChronicleLibrary, IDisposable {
private readonly FileChronicleLibraryConfiguration _configuration;
private volatile FileStream _fileStream;
private string _fileStreamLockKey => string.Intern ($"{nameof (FileChronicleLibrary)}.{nameof (this._fileStream)}.{this._configuration.OutputPath}");
private readonly Dictionary<string, MethodHandler> _methods;
private readonly Dictionary<string, KeyHandler> _keys;
/// <summary>
/// Create a new <see cref="FileChronicleLibrary"/> instance with the default configuration.
/// </summary>
public FileChronicleLibrary () {
this._configuration = new FileChronicleLibraryConfiguration ();
this._methods = new Dictionary<string, MethodHandler> {
{"TAGS", this.TagsMethodHandler}
};
this._keys = new Dictionary<string, KeyHandler> {
{"MSG", this.MessageKeyHandler},
{"EXC", this.ExceptionKeyHandler},
{"EMSG", this.ExceptionMessageKeyHandler},
{"TH", this.ThreadKeyHandler},
{"TAGS", this.TagsKeyHandler},
{"LVL", this.LevelKeyHandler}
};
this._fileStream = null;
}
/// <summary>
/// Render the record to the file (if not filtered by <see cref="ChronicleLevel"/> or tag ignorance).
/// </summary>
/// <param name="record">The <see cref="ChronicleRecord"/> to render.</param>
public void Store (ChronicleRecord record) {
if (!this.ListenTo (record))
return;
var pattern = this._configuration.OutputPattern;
var output = this.FormulateOutput (record, pattern);
this.SendToFile (output);
}
private bool ListenTo (ChronicleRecord record) {
return (this._configuration.Levels.Any () && this._configuration.Levels.ContainsKey (record.Level))
&& (!this._configuration.Tags.Any () || this._configuration.Tags.Keys.Any (record.Tags.Contains))
&& !this._configuration.IgnoredTags.Keys.Any (record.Tags.Contains);
}
private string FormulateOutput (ChronicleRecord record, string pattern) {
var output = pattern;
var currentTime = TimeZoneInfo.ConvertTimeFromUtc (DateTime.UtcNow, this._configuration.TimeZone);
foreach (var token in this.FindTokens (pattern)) {
var tokenBody = token.Substring (1, token.Length - 2);
var tokenIsDate = tokenBody.StartsWith ("%");
if (tokenIsDate) {
var dateFormatting = tokenBody.Remove (0, 1);
output = output.Replace (token, currentTime.ToString (dateFormatting));
continue;
}
var tokenIsQuery = tokenBody.Contains ("?");
if (tokenIsQuery) {
var queryKey = tokenBody.Split ('?') [0];
var tokenIsInverseQuery = false;
if (queryKey.EndsWith ("!")) {
queryKey = queryKey.Remove (queryKey.Length - 1);
tokenIsInverseQuery = true;
}
var hasMeaning = this._keys.ContainsKey (queryKey)
&& !string.IsNullOrEmpty (this._keys [queryKey] (record));
if (tokenIsInverseQuery == hasMeaning) {
output = output.Replace (token, string.Empty);
continue;
}
var queryBody = tokenBody.Substring (queryKey.Length + (tokenIsInverseQuery ? 2 : 1));
var queryOutput = this.FormulateOutput (record, queryBody);
output = output.Replace (token, queryOutput);
continue;
}
var tokenIsMethodInvokation = tokenBody.Contains ("|");
if (tokenIsMethodInvokation) {
var methodKey = tokenBody.Split ('|') [0];
var invokationArguments = tokenBody.Substring (methodKey.Length + 1).Split ('|');
if (this._methods.ContainsKey (methodKey)) {
output = output.Replace (token, this._methods [methodKey] (record, invokationArguments));
continue;
}
}
if (this._keys.ContainsKey (tokenBody)) {
output = output.Replace (token, this._keys [tokenBody] (record));
}
}
return output;
}
private IEnumerable<string> FindTokens (string input) {
var output = new List<string> ();
var nest = 0;
var position = -1;
var token = new StringBuilder ();
while (++position < input.Length) {
if (input [position] == '{') {
nest++;
}
if (nest > 0) {
token.Append (input [position]);
}
if (input [position] == '}') {
nest--;
if (nest == 0) {
output.Add (token.ToString ());
token.Clear ();
}
}
}
return output;
}
private string TagsMethodHandler (ChronicleRecord record, params string [] parameters) {
return parameters.Length < 1 ? string.Empty : string.Join (parameters [0], record.Tags);
}
private string MessageKeyHandler (ChronicleRecord record) {
return record.Message != record.Exception?.Message ? record.Message : string.Empty;
}
private string ExceptionKeyHandler (ChronicleRecord record) {
return record.Exception?.ToString ();
}
private string ExceptionMessageKeyHandler (ChronicleRecord record) {
return record.Exception?.Message;
}
private string ThreadKeyHandler (ChronicleRecord record) {
return Thread.CurrentThread.ManagedThreadId.ToString ();
}
private string TagsKeyHandler (ChronicleRecord record) {
return this.TagsMethodHandler (record, ", ");
}
private string LevelKeyHandler (ChronicleRecord record) {
return record.Level.ToString ();
}
private void SendToFile (string output) {
//if (this._disposed) return;
if (this._fileStream == null) {
lock (this._fileStreamLockKey) {
if (this._fileStream == null) {
this._fileStream = new FileStream (this._configuration.OutputPath, FileMode.Append, FileAccess.Write, FileShare.Read);
}
}
}
var bytes = Encoding.UTF8.GetBytes ($"{output}\r\n");
lock (this._fileStreamLockKey) {
if (this._configuration.RetentionPolicy != null) {
if (this._configuration.RetentionPolicy.CheckPolicy (this._configuration.OutputPath, bytes)) {
this._fileStream.Close ();
this._configuration.RetentionPolicy.InvokePolicy (this._configuration.OutputPath);
this._fileStream = new FileStream (this._configuration.OutputPath, FileMode.Append, FileAccess.Write, FileShare.Read);
}
}
this._fileStream.Write (bytes, 0, bytes.Length);
this._fileStream.Flush ();
}
}
/// <summary>
/// Configure this <see cref="FileChronicleLibrary"/> with the specified options.
/// </summary>
/// <param name="configurationDelegate">A function to set <see cref="FileChronicleLibrary"/> configuration.</param>
/// <returns>This <see cref="FileChronicleLibrary"/> instance.</returns>
public FileChronicleLibrary Configure (FileChronicleLibraryConfigurationDelegate configurationDelegate) {
configurationDelegate.Invoke (this._configuration);
return this;
}
private delegate string MethodHandler (ChronicleRecord record, params string [] parameters);
private delegate string KeyHandler (ChronicleRecord record);
#region Xml Serialization
/// <summary>
/// Required for XML serialization, this method offers no functionality.
/// </summary>
/// <returns>A null <see cref="XmlSchema"/>.</returns>
public XmlSchema GetSchema () => null;
/// <summary>
/// Populate configuration from XML via the specified <see cref="XmlReader" />.
/// </summary>
/// <param name="reader"><see cref="XmlReader" /> stream from the configuration file.</param>
/// <seealso cref="Core.NChronicle.ConfigureFrom(string, bool, int)"/>
public void ReadXml (XmlReader reader) => this._configuration.ReadXml (reader);
/// <summary>
/// Write configuration to XML via the specified <see cref="XmlWriter" />.
/// </summary>
/// <param name="writer"><see cref="XmlWriter" /> stream to the configuration file.</param>
/// <seealso cref="Core.NChronicle.SaveConfigurationTo(string)"/>
public void WriteXml (XmlWriter writer) => this._configuration.WriteXml (writer);
#endregion
/// <summary>
/// Conclude and close this <see cref="FileChronicleLibrary"/>.
/// </summary>
public void Dispose () {
if (this._fileStream != null) {
lock (this._fileStreamLockKey) {
this._fileStream.Flush ();
this._fileStream.Close ();
}
}
GC.SuppressFinalize (this);
}
/// <summary>
/// The destructor for this <see cref="FileChronicleLibrary"/>.
/// Calls <see cref="FileChronicleLibrary.Dispose"/>.
/// </summary>
~FileChronicleLibrary () {
this.Dispose ();
}
}
}
\ No newline at end of file
using System.Xml.Serialization;
namespace NChronicle.File.Interfaces {
/// <summary>
/// A file retention policy for controlling the archiving of a
/// <see cref="FileChronicleLibrary"/>'s output file.
/// </summary>
public interface IRetentionPolicy : IXmlSerializable {
/// <summary>
/// Check whether the output file at the given <paramref name="path"/> should
/// have the file retention policy invoked upon it.
/// </summary>
/// <param name="path">The path to the output file.</param>
/// <param name="pendingBytes">Any pending bytes that are to be written to the output file.</param>
/// <returns>A <see cref="bool"/> indicating if the policy should be invoked.</returns>
bool CheckPolicy (string path, byte[] pendingBytes);
/// <summary>
/// Invoke the file retention policy on the output file at
/// the given <paramref name="path"/>.
/// </summary>
/// <param name="path">The path to the output file.</param>
void InvokePolicy (string path);
}
}
\ No newline at end of file
This diff is collapsed.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{5CCFB68D-D230-48AA-9DDC-BCE65BA8EC87}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>NChronicle.File</RootNamespace>
<AssemblyName>NChronicle.File</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\NChronicle.File.XML</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\NChronicle.File.XML</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="NChronicle.Core, Version=1.0.3.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>packages\NChronicle.Core.1.0.3\lib\net40\NChronicle.Core.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Configuration\FileChronicleLibraryConfiguration.cs" />
<Compile Include="Configuration\RetentionPolicyConfiguration.cs" />
<Compile Include="RetentionPolicy.cs" />
<Compile Include="Delegates\RetentionPolicyConfigurationDelegate.cs" />
<Compile Include="Interfaces\IRetentionPolicy.cs" />
<Compile Include="Delegates\FileChronicleLibraryConfigurationDelegate.cs" />
<Compile Include="Exceptions\InvalidFilePathException.cs" />
<Compile Include="Extensions\ConfigurationExtensions.cs" />
<Compile Include="FileChronicleLibrary.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
\ No newline at end of file
<?xml version="1.0"?>
<package >
<metadata>
<id>NChronicle.File</id>
<title>NChronicle.File</title>
<version>1.0.1</version>
<authors>Andy James</authors>
<owners>Andy James</owners>
<licenseUrl>https://git.ikimi.co/andrewj/NChronicle/raw/master/LICENSE</licenseUrl>
<projectUrl>https://git.ikimi.co/andrewj/NChronicle</projectUrl>
<iconUrl>https://git.ikimi.co/uploads/project/avatar/2/NChronicle_simple_square.png</iconUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<summary>
NChronicle is an extensible logging and output library allowing centralized management and distribution of log messages to a number of logging destinations (known as 'Libraries'). NChronicle.File is an extensive NChronicle Library writing log message to a file with flexible retention controls.
</summary>
<description>
NChronicle is an extensible logging and output library allowing centralized management and distribution of log messages to a number of logging destinations (known as 'Libraries').
NChronicle.File is an extensive file logging Library for the NChronicle logging framework. NChronicle.File includes options to ignore and/or listen solely to certain log messages via level and/or tags, completely customize the output format (date/time format specification, conditionals, and arguments), specify time zone, and apply retention policies for the output files.
Further Libraries planned for the NChronicle logging framework include NChronicle.API and NChronicle.Owin.
</description>
<dependencies>
<group targetFramework="net40">
<dependency id="NChronicle.Core" version="1.0.3" />
</group>