Skip to content

Commit

Permalink
integration with .Net Core logging infrastructure
Browse files Browse the repository at this point in the history
  • Loading branch information
Vadim Hatsura committed Jul 11, 2019
1 parent f9a9a8b commit bde79cc
Show file tree
Hide file tree
Showing 11 changed files with 231 additions and 7 deletions.
29 changes: 28 additions & 1 deletion docs/configuration.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,34 @@ With this you can use any configuration source that you configured on the `IConf

NOTE: By simply calling `app.UseElasticApm()` without the overload, the agent will read configurations only from environment variables.

This is a typical `appsettings.json` file that contains some sample configuration. The part below `ElasticApm` is fetched by the agent if the corresponding `IConfiguration` is passed to the agent.
[float]
[[sample-config]]
==== Sample configuration file

Below is a sample `appsettings.json` configuration file for a typical ASP.NET Core application. There are two important takeaways:
1. The part below `ElasticApm` is fetched by the agent if the corresponding `IConfiguration` is passed to the agent.
2. With ASP.NET Core, you must set `LogLevel for the internal APM logger in the standard `Logging` section with the `ElasticApm` category name.

[source,js]
----
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"Elastic.Apm": "Debug"
}
},
"AllowedHosts": "*",
"ElasticApm":
{
"ServerUrls": "http://myapmserver:8200",
"TransactionSampleRate": 1.0
}
}
----

In certain scenarios -- like when you're not using ASP.NET Core -- you wont activate the agent with the `UseElasticApm()` method.
In this case, you can set the log level of the agent with `ElasticApm:LogLevel`, as shown in the following `appsettings.json` file:

[source,js]
----
Expand Down
4 changes: 2 additions & 2 deletions sample/SampleAspNetCoreApp/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning"
"Default": "Warning",
"Elastic.Apm": "Error"
}
},
"AllowedHosts": "*",
"ElasticApm": {
"LogLevel": "Error",
"ServerUrls": "http://localhost:8200",
"TransactionSampleRate": 1.0
}
Expand Down
4 changes: 2 additions & 2 deletions sample/WebApiSample/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning"
"Default": "Warning",
"Elastic.Apm": "Error"
}
},
"AllowedHosts": "*",
"ElasticApm": {
"LogLevel": "Error",
"ServerUrls": "http://localhost:8200"
}
}
11 changes: 9 additions & 2 deletions src/Elastic.Apm.AspNetCore/ApmMiddlewareExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Elastic.Apm.Logging;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

namespace Elastic.Apm.AspNetCore
{
Expand Down Expand Up @@ -42,12 +43,13 @@ public static IApplicationBuilder UseElasticApm(
params IDiagnosticsSubscriber[] subscribers
)
{
var logger = ConsoleLogger.Instance;
var logger = builder.ApplicationServices.GetApmLogger();

var configReader = configuration == null
? new EnvironmentConfigurationReader(logger)
: new MicrosoftExtensionsConfig(configuration, logger) as IConfigurationReader;

var config = new AgentComponents(configurationReader: configReader);
var config = new AgentComponents(configurationReader: configReader, logger: logger);
UpdateServiceInformation(config.Service);

Agent.Setup(config);
Expand All @@ -69,6 +71,11 @@ params IDiagnosticsSubscriber[] subscribers
return builder.UseMiddleware<ApmMiddleware>(agent.Tracer, agent);
}

internal static IApmLogger GetApmLogger(this IServiceProvider serviceProvider) =>
serviceProvider.GetService(typeof(ILoggerFactory)) is ILoggerFactory loggerFactory
? (IApmLogger)new AspNetCoreLogger(loggerFactory)
: ConsoleLogger.Instance;

internal static void UpdateServiceInformation(Service service)
{
string version;
Expand Down
39 changes: 39 additions & 0 deletions src/Elastic.Apm.AspNetCore/AspNetCoreLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using Elastic.Apm.AspNetCore.Extensions;
using Elastic.Apm.Logging;
using Microsoft.Extensions.Logging;
using LogLevel = Elastic.Apm.Logging.LogLevel;

namespace Elastic.Apm.AspNetCore
{
internal class AspNetCoreLogger : IApmLogger
{
private readonly ILogger _logger;

public AspNetCoreLogger(ILoggerFactory loggerFactory)
{
_logger = loggerFactory?.CreateLogger("Elastic.Apm") ?? throw new ArgumentNullException(nameof(loggerFactory));
Level = _logger.GetMinLogLevel();
}

public LogLevel Level { get; }

public void Log<TState>(LogLevel level, TState state, Exception e, Func<TState, Exception, string> formatter) =>
_logger.Log(Convert(level), new EventId(), state, e, formatter);

private static Microsoft.Extensions.Logging.LogLevel Convert(LogLevel logLevel)
{
switch (logLevel)
{
case LogLevel.Trace: return Microsoft.Extensions.Logging.LogLevel.Trace;
case LogLevel.Debug: return Microsoft.Extensions.Logging.LogLevel.Debug;
case LogLevel.Information: return Microsoft.Extensions.Logging.LogLevel.Information;
case LogLevel.Warning: return Microsoft.Extensions.Logging.LogLevel.Warning;
case LogLevel.Error: return Microsoft.Extensions.Logging.LogLevel.Error;
case LogLevel.Critical: return Microsoft.Extensions.Logging.LogLevel.Critical;
case LogLevel.None:
default: return Microsoft.Extensions.Logging.LogLevel.None;
}
}
}
}
1 change: 1 addition & 0 deletions src/Elastic.Apm.AspNetCore/Elastic.Apm.AspNetCore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Routing.Abstractions" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Config\" />
Expand Down
26 changes: 26 additions & 0 deletions src/Elastic.Apm.AspNetCore/Extensions/LoggerExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using Microsoft.Extensions.Logging;
using LogLevel = Elastic.Apm.Logging.LogLevel;

namespace Elastic.Apm.AspNetCore.Extensions
{
internal static class LoggerExtensions
{
internal static LogLevel GetMinLogLevel(this ILogger logger)
{
if (logger == null) throw new ArgumentNullException(nameof(logger));

if (logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Trace)) return LogLevel.Trace;

if (logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug)) return LogLevel.Debug;

if (logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Information)) return LogLevel.Information;

if (logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Warning)) return LogLevel.Warning;

if (logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Error)) return LogLevel.Error;

return logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Critical) ? LogLevel.Critical : LogLevel.None;
}
}
}
33 changes: 33 additions & 0 deletions test/Elastic.Apm.AspNetCore.Tests/ApmMiddlewareExtensionTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace Elastic.Apm.AspNetCore.Tests
{
/// <summary>
/// Tests the <see cref="Elastic.Apm.AspNetCore.ApmMiddlewareExtension"/> type.
/// </summary>
public class ApmMiddlewareExtensionTest
{
[Fact]
public void UseElasticApmShouldUseAspNetLoggerWhenLoggingIsConfigured()
{
var services = new ServiceCollection()
.AddLogging();

var logger = services.BuildServiceProvider().GetApmLogger();

Assert.IsType<AspNetCoreLogger>(logger);
}

[Fact]
public void UseElasticApmShouldUseConsoleLoggerInstanceWhenLoggingIsNotConfigured()
{
var services = new ServiceCollection();

var logger = services.BuildServiceProvider().GetApmLogger();

Assert.IsType<Logging.ConsoleLogger>(logger);
Assert.Same(Logging.ConsoleLogger.Instance, logger);
}
}
}
48 changes: 48 additions & 0 deletions test/Elastic.Apm.AspNetCore.Tests/AspNetCoreLoggerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;

namespace Elastic.Apm.AspNetCore.Tests
{
/// <summary>
/// Tests the <see cref="Elastic.Apm.AspNetCore.AspNetCoreLogger"/> type.
/// </summary>
public class AspNetCoreLoggerTests
{
[Fact]
public void AspNetCoreLoggerShouldThrowExceptionWhenLoggerFactoryIsNull()
=> Assert.Throws<ArgumentNullException>(() => new AspNetCoreLogger(null));

[Fact]
public void AspNetCoreLoggerShouldGetLoggerFromFactoryWithProperCategoryName()
{
var loggerFactoryMock = new Mock<ILoggerFactory>();
loggerFactoryMock.Setup(x => x.CreateLogger(It.IsAny<string>()))
.Returns(() => Mock.Of<ILogger>());

// ReSharper disable UnusedVariable
var logger = new AspNetCoreLogger(loggerFactoryMock.Object);
// ReSharper restore UnusedVariable

loggerFactoryMock.Verify(x => x.CreateLogger(It.Is<string>(s => s.Equals("Elastic.Apm"))), Times.Once);
}

[Fact]
public void AspNetCoreLoggerShouldCalculateMinLogLevelOnCreation()
{
var loggerFactoryMock = new Mock<ILoggerFactory>();
var loggerMock = new Mock<ILogger>();
loggerFactoryMock.Setup(x => x.CreateLogger(It.IsAny<string>()))
.Returns(() => loggerMock.Object);

var logger = new AspNetCoreLogger(loggerFactoryMock.Object);

loggerMock.Verify(x => x.IsEnabled(It.Is<LogLevel>(l => l == LogLevel.Trace)), Times.Once);
// ReSharper disable UnusedVariable
var level = logger.Level;
// ReSharper restore UnusedVariable
loggerMock.Verify(x => x.IsEnabled(It.Is<LogLevel>(l => l == LogLevel.Trace)), Times.Once);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<PackageReference Include="FluentAssertions" Version="5.6.0" />
<PackageReference Include="FluentAssertions.Analyzers" Version="0.11.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
<PackageReference Include="Moq" Version="4.12.0" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
Expand Down
42 changes: 42 additions & 0 deletions test/Elastic.Apm.AspNetCore.Tests/LoggerExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System.Collections.Generic;
using Elastic.Apm.AspNetCore.Extensions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Xunit;
using LogLevel = Elastic.Apm.Logging.LogLevel;

namespace Elastic.Apm.AspNetCore.Tests
{
/// <summary>
/// Tests the <see cref="Elastic.Apm.AspNetCore.Extensions.LoggerExtensions"/> type.
/// </summary>
public class LoggerExtensionsTests
{
[Theory]
[InlineData(Microsoft.Extensions.Logging.LogLevel.Trace, LogLevel.Trace)]
[InlineData(Microsoft.Extensions.Logging.LogLevel.Debug, LogLevel.Debug)]
[InlineData(Microsoft.Extensions.Logging.LogLevel.Information, LogLevel.Information)]
[InlineData(Microsoft.Extensions.Logging.LogLevel.Warning, LogLevel.Warning)]
[InlineData(Microsoft.Extensions.Logging.LogLevel.Error, LogLevel.Error)]
[InlineData(Microsoft.Extensions.Logging.LogLevel.Critical, LogLevel.Critical)]
[InlineData(Microsoft.Extensions.Logging.LogLevel.None, LogLevel.None)]
public void GetMinLogLevelTest(Microsoft.Extensions.Logging.LogLevel level, LogLevel expected)
{
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new[]{new KeyValuePair<string, string>("Logging:LogLevel:Default", level.ToString()), })
.Build();

var serviceProvider = new ServiceCollection()
.AddLogging(builder => builder.AddConfiguration(configuration.GetSection("Logging")).AddConsole())
.AddOptions()
.BuildServiceProvider();

var logger = serviceProvider.GetService<ILoggerFactory>().CreateLogger<LoggerExtensionsTests>();

var minLogLevel = logger.GetMinLogLevel();

Assert.Equal(expected, minLogLevel);
}
}
}

0 comments on commit bde79cc

Please sign in to comment.