Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

${AssemblyVersion}: add type (File, Assembly, Informational) option #2487

Merged
merged 16 commits into from
Jan 4, 2018
Merged
10 changes: 10 additions & 0 deletions src/NLog/Internal/ReflectionHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,16 @@ public static TAttr GetCustomAttribute<TAttr>(this PropertyInfo info)
#endif
}

public static TAttr GetCustomAttribute<TAttr>(this Assembly assembly)
where TAttr : Attribute
{
#if NETSTANDARD1_0
return assembly.GetCustomAttributes(typeof(TAttr)).FirstOrDefault() as TAttr;
#else
return (TAttr)Attribute.GetCustomAttribute(assembly, typeof(TAttr));
#endif
}

public static IEnumerable<TAttr> GetCustomAttributes<TAttr>(this Type type, bool inherit) where TAttr : Attribute
{
#if NETSTANDARD1_0
Expand Down
124 changes: 104 additions & 20 deletions src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,60 +31,144 @@
// THE POSSIBILITY OF SUCH DAMAGE.
//


namespace NLog.LayoutRenderers
{
using System.Reflection;
using System.ComponentModel;
using System.Text;
using NLog.Config;
using NLog.Internal;

/// <summary>
/// Assembly version.
/// Renders the assembly version information for the entry assembly or a named assembly.
/// </summary>
/// <remarks>The entry assembly can't be found in some cases e.g. ASP.NET, Unit tests etc.</remarks>
/// <remarks>
/// As this layout renderer uses reflection and version information is unlikely to change during application execution,
/// it is recommended to use it in conjunction with the <see cref="NLog.LayoutRenderers.Wrappers.CachedLayoutRendererWrapper"/>.
/// </remarks>
/// <remarks>
/// The entry assembly can't be found in some cases e.g. ASP.NET, unit tests, etc.
/// </remarks>
[LayoutRenderer("assembly-version")]
public class AssemblyVersionLayoutRenderer : LayoutRenderer
{
/// <summary>
/// Initializes a new instance of the <see cref="AssemblyVersionLayoutRenderer" /> class.
/// </summary>
public AssemblyVersionLayoutRenderer()
{
Type = AssemblyVersionType.Assembly;
}

/// <summary>
/// The (full) name of the assembly. If <c>null</c>, using the entry assembly.
/// </summary>
[DefaultParameter]
public string Name { get; set; }

/// <summary>
/// Renders assembly version and appends it to the specified <see cref="StringBuilder" />.
/// Gets or sets the type of assembly version to retrieve.
/// </summary>
/// <remarks>
/// Some version type and platform combinations are not fully supported.
/// - UWP earlier than .NET Standard 1.5: Value for <see cref="AssemblyVersionType.Assembly"/> is always returned unless the <see cref="Name"/> parameter is specified.
/// - Silverlight: Value for <see cref="AssemblyVersionType.Assembly"/> is always returned.
/// </remarks>
/// <docgen category='Rendering Options' order='10' />
[DefaultValue(nameof(AssemblyVersionType.Assembly))]
public AssemblyVersionType Type { get; set; }

/// <summary>
/// Renders an assembly version and appends it to the specified <see cref="StringBuilder" />.
/// </summary>
/// <param name="builder">The <see cref="StringBuilder"/> to append the rendered data to.</param>
/// <param name="logEvent">Logging event.</param>
protected override void Append(StringBuilder builder, LogEventInfo logEvent)
{
string assemblyVersion = GetAssemblyApplicationVersion(Name);
if (string.IsNullOrEmpty(assemblyVersion))
var version = GetVersion();

if (string.IsNullOrEmpty(version))
{
assemblyVersion = $"Could not find {(string.IsNullOrEmpty(Name) ? "entry" : Name)} assembly";
version = $"Could not find value for {(string.IsNullOrEmpty(Name) ? "entry" : Name)} assembly and version type {Type}";
}
builder.Append(assemblyVersion);

builder.Append(version);
}

string GetAssemblyApplicationVersion(string assemblyName)
#if SILVERLIGHT

private string GetVersion()
{
if (!string.IsNullOrEmpty(assemblyName))
var assemblyName = GetAssemblyName();
assemblyName.Version.ToString();
Copy link
Member

Choose a reason for hiding this comment

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

missing return ;)

}

private System.Reflection.AssemblyName GetAssemblyName()
{
if (string.IsNullOrEmpty(Name))
{
#if SILVERLIGHT
return new AssemblyName(assemblyName).Version.ToString();
#else
return Assembly.Load(new AssemblyName(assemblyName))?.GetName().Version.ToString();
#endif
return new System.Reflection.AssemblyName(System.Windows.Application.Current.GetType().Assembly.FullName);
}
else
{
#if SILVERLIGHT
return new AssemblyName(System.Windows.Application.Current.GetType().Assembly.FullName).Version.ToString();
return new System.Reflection.AssemblyName(Name);
}
}

#elif WINDOWS_UWP

private string GetVersion()
{
if (string.IsNullOrEmpty(Name))
{
return Microsoft.Extensions.PlatformAbstractions.PlatformServices.Default.Application.ApplicationVersion;
}
else
{
var assembly = GetAssembly();
return assembly?.GetName().Version.ToString();
}
}

private System.Reflection.Assembly GetAssembly()
{
return System.Reflection.Assembly.Load(new System.Reflection.AssemblyName(Name));
}

#else
return Assembly.GetEntryAssembly()?.GetName().Version.ToString();
#endif

private string GetVersion()
{
var assembly = GetAssembly();
return GetVersion(assembly);
}

private System.Reflection.Assembly GetAssembly()
{
if (string.IsNullOrEmpty(Name))
{
return System.Reflection.Assembly.GetEntryAssembly();
}
else
{
return System.Reflection.Assembly.Load(new System.Reflection.AssemblyName(Name));
}
}
}

private string GetVersion(System.Reflection.Assembly assembly)
{
switch (Type)
{
case AssemblyVersionType.File:
return assembly?.GetCustomAttribute<System.Reflection.AssemblyFileVersionAttribute>()?.Version;

case AssemblyVersionType.Informational:
return assembly?.GetCustomAttribute<System.Reflection.AssemblyInformationalVersionAttribute>()?.InformationalVersion;

default:
return assembly?.GetName().Version?.ToString();
}
}
#endif
}
}
56 changes: 56 additions & 0 deletions src/NLog/LayoutRenderers/AssemblyVersionType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// Copyright (c) 2004-2017 Jaroslaw Kowalski <jaak@jkowalski.net>, Kim Christensen, Julian Verdurmen
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of Jaroslaw Kowalski nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//

namespace NLog.LayoutRenderers
{
/// <summary>
/// Type of assembly version to retrieve.
/// </summary>
public enum AssemblyVersionType
{
/// <summary>
/// Gets the assembly version.
/// </summary>
Assembly,

/// <summary>
/// Gets the file version.
/// </summary>
File,

/// <summary>
/// Gets additional version information.
/// </summary>
Informational
}
}
95 changes: 92 additions & 3 deletions tests/NLog.UnitTests/LayoutRenderers/AssemblyVersionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@
// THE POSSIBILITY OF SUCH DAMAGE.
//

using System.Reflection;

namespace NLog.UnitTests.LayoutRenderers
{
using System;
using System.Reflection;
using NLog.LayoutRenderers;
using Xunit;

public class AssemblyVersionTests : NLogTestBase
Expand All @@ -52,5 +53,93 @@ public void AssemblyNameVersionTest()
{
AssertLayoutRendererOutput("${assembly-version:NLogAutoLoadExtension}", "2.0.0.0");
}

[Fact]
public void AssemblyNameVersionTypeTest()
{
AssertLayoutRendererOutput("${assembly-version:name=NLogAutoLoadExtension:type=assembly}", "2.0.0.0");
AssertLayoutRendererOutput("${assembly-version:name=NLogAutoLoadExtension:type=file}", "2.0.0.0");
AssertLayoutRendererOutput("${assembly-version:name=NLogAutoLoadExtension:type=informational}", "1.0.0");
}

#if !NETSTANDARD
private const string AssemblyVersionTest = "1.1.1.1";
private const string AssemblyFileVersionTest = "1.1.1.2";
private const string AssemblyInformationalVersionTest = "Version 1";

[Theory]
[InlineData(AssemblyVersionType.Assembly, AssemblyVersionTest)]
[InlineData(AssemblyVersionType.File, AssemblyFileVersionTest)]
[InlineData(AssemblyVersionType.Informational, AssemblyInformationalVersionTest)]
public void AssemblyVersionTypeTest(AssemblyVersionType type, string expected)
{
LogManager.Configuration = CreateConfigurationFromString(@"
<nlog>
<targets><target name='debug' type='Debug' layout='${assembly-version:type=" + type.ToString().ToLower() + @"}' /></targets>
<rules><logger name='*' minlevel='Debug' writeTo='debug' /></rules>
</nlog>");
var logger = LogManager.GetLogger("SomeLogger");
var compiledAssembly = GenerateTestAssembly();
var testLoggerType = compiledAssembly.GetType("LogTester.LoggerTest");
var logMethod = testLoggerType.GetMethod("TestLog");
var testLoggerInstance = Activator.CreateInstance(testLoggerType);
logMethod.Invoke(testLoggerInstance, new object[] { logger, compiledAssembly });
AssertDebugLastMessage("debug", expected);
}

private static Assembly GenerateTestAssembly()
{
const string code = @"
Copy link
Member

Choose a reason for hiding this comment

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

nice!

using System;
using System.Reflection;

[assembly: AssemblyVersion(""" + AssemblyVersionTest + @""")]
[assembly: AssemblyFileVersion(""" + AssemblyFileVersionTest + @""")]
[assembly: AssemblyInformationalVersion(""" + AssemblyInformationalVersionTest + @""")]

namespace LogTester
{
public class LoggerTest
{
public void TestLog(NLog.Logger logger, Assembly assembly)
{
if (System.Reflection.Assembly.GetEntryAssembly() == null)
SetEntryAssembly(assembly); // Required in some unit testing scenarios, see https://github.com/Microsoft/vstest/issues/649
logger.Debug(""msg"");
}

private static void SetEntryAssembly(Assembly assembly)
{
var manager = new AppDomainManager();

var domain = AppDomain.CurrentDomain;
if (domain == null)
throw new InvalidOperationException(""Current app domain is null"");

var entryAssemblyField = manager.GetType().GetField(""m_entryAssembly"", BindingFlags.Instance | BindingFlags.NonPublic);
if (entryAssemblyField == null)
throw new InvalidOperationException(""Unable to find field m_entryAssembly"");
entryAssemblyField.SetValue(manager, assembly);

var domainManagerField = domain.GetType().GetField(""_domainManager"", BindingFlags.Instance | BindingFlags.NonPublic);
if (domainManagerField == null)
throw new InvalidOperationException(""Unable to find field _domainManager"");
domainManagerField.SetValue(domain, manager);
}
}
}";

var provider = new Microsoft.CSharp.CSharpCodeProvider();
var parameters = new System.CodeDom.Compiler.CompilerParameters
{
GenerateInMemory = true,
GenerateExecutable = false,
ReferencedAssemblies = {"NLog.dll"}
};
System.CodeDom.Compiler.CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
var compiledAssembly = results.CompiledAssembly;
return compiledAssembly;
}
#endif
}
}
}