Log4Net config with .NET Core app - log4net

I need to use log4net on a new .NET Core app that references some just written assemblies which uses log4net.
I've searched around but all the examples passes just a FileInfo to the log4net.Config.XmlConfigurator.Configure but using the latest version asks for a first paremeter of type ILoggerRepository repository
What should I pass?
My code is
public static void Main(string[] args)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");
var configuration = builder.Build();
ContainerConfig.RegisterDependencies(configuration);
MappingConfig.RegisterMapping();
var fileInfo = new FileInfo(#"\config.log4net");
log4net.Config.XmlConfigurator.Configure(null, fileInfo);
var core = ContainerWrapper.Container.GetInstance<ICore>();
core.StartAsync().Wait();
Console.Write("Press a key to exit");
Console.ReadLine();
}
But with null it crashes
Any suggestions?

You can get the default repository via the statement here below,
and pass that one as argument of the Configure method.
ILoggerRepository repository = log4net.LogManager.GetRepository(Assembly.GetCallingAssembly());
Depending on your setup, you might need to use Assembly.GetExecutingAssembly() instead.

Related

how distinguish traces from different instances .net core application in Application Insights

I work on .NET Core 2.2 console application that uses Microsoft.Extensions.Logging and is configured to send logs to Azure Application Insights using Microsoft.ApplicationInsights.Extensibility by:
services.AddSingleton(x =>
new TelemetryClient(
new TelemetryConfiguration
{
InstrumentationKey = "xxxx"
}));
...
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
loggerFactory.AddApplicationInsights(serviceProvider, logLevel);
It works ok: I can read logs in Application Insights. But the application can be started simultanously in few instances (in different Docker containers). How can I distinguish traces from different instances? I can use source FileName, but I don't know how I should inject it.
I tried to use Scope:
var logger = loggerFactory.CreateLogger<Worker>();
logger.BeginScope(dto.FileName);
logger.LogInformation($"Start logging.");
It's interesting that my configuration is almost identical as in example: https://github.com/MicrosoftDocs/azure-docs/issues/12673
But in my case I can't see the property "FileName" in Application Insights.
For console project, if you want to use the custom ITelemetryInitializer, you should use this format: .TelemetryInitializers.Add(new CustomInitializer());
Official doc is here.
I test it at my side, and it works. The role name can be set.
Sample code is below:
static void Main(string[] args)
{
TelemetryConfiguration configuration = TelemetryConfiguration.CreateDefault();
configuration.InstrumentationKey = "xxxxx";
configuration.TelemetryInitializers.Add(new CustomInitializer());
var client = new TelemetryClient(configuration);
ServiceCollection services = new ServiceCollection();
services.AddSingleton(x => client);
var provider = services.BuildServiceProvider();
var loggerFactory = new LoggerFactory();
loggerFactory.AddApplicationInsights(provider, LogLevel.Information);
var logger = loggerFactory.CreateLogger<Program>();
logger.LogInformation("a test message 111...");
Console.WriteLine("Hello World!");
Console.ReadLine();
}
Check the role name in azure portal:
If you really have no way to distinguish them you can use a custom telemetry initializer like this:
public class CustomInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
telemetry.Context.Cloud.RoleName = Environment.MachineName;
}
}
and/or you can add a custom property:
public class CustomInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
if(telemetry is ISupportProperties)
{
((ISupportProperties)telemetry).Properties["MyIdentifier"] = Environment.MachineName;
}
}
}
In this example I used Environment.MachineName but you can of course use something else if needed. Like this work Id parameter of yours.
the wire it up using:
services.AddSingleton<ITelemetryInitializer, CustomInitializer>();

Using AddAzureKeyVault makes my application 10 seconds slower

I’m having this very simple .NET Core application:
static void Main(string[] args)
{
var builder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true);
builder.AddAzureKeyVault("https://MyKeyVault.vault.azure.net");
var stopwatch = new Stopwatch();
stopwatch.Start();
var configuration = builder.Build();
var elapsed = stopwatch.Elapsed;
Console.WriteLine($"Elapsed time: {elapsed.TotalSeconds}");
}
The csproj-file looks like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.AzureKeyVault" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.1.1" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.1" />
</ItemGroup>
</Project>
My problem is that the application takes about 10 seconds to execute with a debugger attached (about 5 seconds without a debugger). If I remove the line with AddAzureKeyVault the application is executed in less than a second. I know that AddAzureKeyVault will make the application connect to Azure and read values from a key vault but I expected this to be a lot faster.
Is this an expected behaviour? Is there anything I could do to make this faster?
For the Microsoft.Azure.Services.AppAuthentication library, see the original answer. For the newer Azure.Identity library, see Update 2021-03-22.
Original Answer:
Yes, configure the AzureServiceTokenProvider explicitly to use the az cli for authentication. You can do this by setting an environment variable named AzureServicesAuthConnectionString.
Bash:
export AzureServicesAuthConnectionString="RunAs=Developer; DeveloperTool=AzureCli"
PowerShell:
$Env:AzureServicesAuthConnectionString = "RunAs=Developer; DeveloperTool=AzureCli"
Note that the environment variable needs to be set in whatever session you're running your app.
Explanation
The root of the problem is hinted at in MS docs on authentication, which state, "By default, AzureServiceTokenProvider uses multiple methods to retrieve a token."
Of the multiple methods used, az cli authentication is a ways down the list. So the AzureServiceTokenProvider takes some time to try other auth methods higher in the pecking order before finally using the az cli as the token source. Setting the connection string in the environment variable removes the time you spend waiting for other auth methods to fail.
This solution is preferable to hardcoding a clientId and clientSecret not only for convenience, but also because az cli auth doesn't require hardcoding your clientSecret or storing it in plaintext.
UPDATE (2021-03-22)
The Azure.Identity auth provider, compatible with newer Azure client SDKs (like Azure.Security.KeyVault.Secrets) has code-based options (instead of a connection string) to skip certain authentication methods. You can:
set exclusions in the DefaultAzureCredential constructor, or
generate a TokenCredential using more specific class type constructors (see also the auth provider migration chart here).
You could try to get azure keyvault with clientId and clientSecret and it may run faster.
builder.AddAzureKeyVault("https://yourkeyvaultname.vault.azure.net", clientId,clinetSecret);
And I test with it and it costs 3 seconds.
For more details, you could refer to this article.
The previously suggested solutions with clientId and AzureServiceTokenProvider do have an affect in the deprecated packet Microsoft.Azure.KeyVault. But with the new packet Azure.Security.KeyVault.Secrets these solutions are no longer necessary in my measurements.
My solution is to cache the configuration from Azure KeyVault and store that configuration locally. With this solution you will be able to use Azure KeyVault during development and still have a great performance. This following code shows how to do this:
using Azure.Extensions.AspNetCore.Configuration.Secrets;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.Json;
namespace ConfigurationCache
{
public class Program
{
private static readonly Stopwatch Stopwatch = new Stopwatch();
public static void Main(string[] args)
{
Stopwatch.Start();
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((ctx, builder) =>
{
builder.AddAzureConfigurationServices();
})
.ConfigureServices((hostContext, services) =>
{
Stopwatch.Stop();
Console.WriteLine($"Start time: {Stopwatch.Elapsed}");
Console.WriteLine($"Config: {hostContext.Configuration.GetSection("ConnectionStrings:MyContext").Value}");
services.AddHostedService<Worker>();
});
}
public static class AzureExtensions
{
public static IConfigurationBuilder AddAzureConfigurationServices(this IConfigurationBuilder builder)
{
// Build current configuration. This is later used to get environment variables.
IConfiguration config = builder.Build();
#if DEBUG
if (Debugger.IsAttached)
{
// If the debugger is attached, we use cached configuration instead of
// configurations from Azure.
AddCachedConfiguration(builder, config);
return builder;
}
#endif
// Add the standard configuration services
return AddAzureConfigurationServicesInternal(builder, config);
}
private static IConfigurationBuilder AddAzureConfigurationServicesInternal(IConfigurationBuilder builder, IConfiguration currentConfig)
{
// Get keyvault endpoint. This is normally an environment variable.
string keyVaultEndpoint = currentConfig["KEYVAULT_ENDPOINT"];
// Setup keyvault services
SecretClient secretClient = new SecretClient(new Uri(keyVaultEndpoint), new DefaultAzureCredential());
builder.AddAzureKeyVault(secretClient, new AzureKeyVaultConfigurationOptions());
return builder;
}
private static void AddCachedConfiguration(IConfigurationBuilder builder, IConfiguration currentConfig)
{
//Setup full path to cached configuration file.
string path = System.IO.Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"MyApplication");
string filename = System.IO.Path.Combine(path, $"configcache.dat");
// If the file does not exists, or is more than 12 hours, update the cached configuration.
if (!System.IO.File.Exists(filename) || System.IO.File.GetLastAccessTimeUtc(filename).AddHours(12) < DateTime.UtcNow)
{
System.IO.Directory.CreateDirectory(path);
UpdateCacheConfiguration(filename, currentConfig);
}
// Read the file
string encryptedFile = System.IO.File.ReadAllText(filename);
// Decrypt the content
string jsonString = Decrypt(encryptedFile);
// Create key-value pairs
var keyVaultPairs = JsonSerializer.Deserialize<Dictionary<string, string>>(jsonString);
// Use the key-value pairs as configuration
builder.AddInMemoryCollection(keyVaultPairs);
}
private static void UpdateCacheConfiguration(string filename, IConfiguration currentConfig)
{
// Create a configuration builder. We will just use this to get the
// configuration from Azure.
ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
// Add the services we want to use.
AddAzureConfigurationServicesInternal(configurationBuilder, currentConfig);
// Build the configuration
IConfigurationRoot azureConfig = configurationBuilder.Build();
// Serialize the configuration to a JSON-string.
string jsonString = JsonSerializer.Serialize(
azureConfig.AsEnumerable().ToDictionary(a => a.Key, a => a.Value),
options: new JsonSerializerOptions()
{
WriteIndented = true
}
);
//Encrypt the string
string encryptedString = Encrypt(jsonString);
// Save the encrypted string.
System.IO.File.WriteAllText(filename, encryptedString);
}
// Replace the following with your favorite encryption code.
private static string Encrypt(string str)
{
return Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(str));
}
private static string Decrypt(string str)
{
return System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(str));
}
}
}

ASP.NET Core How to read the content of an AzureTable

I develop a ASP.NET Core application working with Azure Tables.
So, I created a tables storage account in Azure Portal, created a table, filled it with some test data, and now I would like to display the content of that table to test the reading.
my appsettings.json is
{
"ConnectionStrings": {
"MyTables":"DefaultEndpointsProtocol=https;AccountName=yyy;AccountKey=xxx;EndpointSuffix=core.windows.net"
},
"Logging": {
"IncludeScopes": false,
[etc etc...]
}
}
And my Startup.cs:
public class Startup
{
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
.AddEnvironmentVariables();
Configuration = builder.Build();
// here in debug we can see the connection string, that is OK
Console.WriteLine($"conn string:{Configuration["ConnectionStrings:MyTables"]}");
}
public IConfigurationRoot Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
}
And here is my controller I try to Display the values:
using Microsoft.AspNetCore.Mvc;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;
using NextMove.Models;
using System.Text;
[...]
public class HelloWorldController : Controller
{
public string ReadTables() {
// ????? Code does not work, as Startup not a reference
string myConnString = Startup.Configuration["ConnectionStrings:MyTables"];
//////////////////////////////////
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(myConnString);
CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
CloudTable table = tableClient.GetTableReference("themes");
TableQuery<ProjectThemeEntity> query = new TableQuery<ProjectThemeEntity>().Where(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "fr"));
StringBuilder response = new StringBuilder("Here is your test table:");
foreach (ProjectThemeEntity item in table.ExecuteQuery(query)) {
response.AppendLine($"Key: {item.RowKey}; Value: {item.Description}");
}
return response.ToString();
}
//
// GET: /HelloWorld/
public IActionResult Index() {
return View();
}
Questions:
a) How to fix this code in order to get the connection string?
b) There should be a "Table.ExecuteQuery(query)" as per this MSDN article in the controller's foreach, but it does not find such a method in CloudTable class, I however added the necessary references, as shown in the controller's code above, only two "Async" methods are available:
PS.
-For the (b) question several people has the same issue here, hope the situation changed now...
You can't access Startup.Configuration from the controller because it's not a static property. Even though you've made it public (generally not a good idea) it still requires you to have an instance of Startup to get access to it.
Generally to get access to settings in ASP.NET Core it's best to create a class with the properties you want and use the IOptions pattern to get them with Dependency Injection. In your startup where you configure your services (add services to the dependency injection container) you would use the helper methods to add your configuration object to the container and then in your controller you would specify you wanted an IOptions or IOptionsSnapshot to get access to it.
I'd suggest you don't put your data access in your controller though. It makes your controller harder to read and harder to maintain if you need to change your strategy later. Move your ReadTables method to its own class and add it to the DI container in Startup taking whatever settings you need to create the service. Use constructor injection in your controller to get the service and execute calls from your controller actions where you need them.

Self-hosting MVC6 app

I'm trying to get an MVC6 app to be self-hosted for testing. I can do in-memory testing using TestServer, but for testing integration of multiple web apps, one of which includes a middleware that I have no control over that connects to the other app, I need at least one of the apps to be accessible over TCP.
I have tried using WebApp.Start, but it works with an IAppBuilder rather than IApplicationBuilder, so I can't get it to work with my Startup.
Is there any way to get an MVC6 app to be self-hosted in an xUnit test, via OWIN or any other way?
UPDATE:
FWIW, based on Pinpoint's answer and some additional research, I was able to come up with the following base class that works in xUnit, at least when the tests are in the same project as the MVC project:
public class WebTestBase : IDisposable
{
private IDisposable webHost;
public WebTestBase()
{
var env = CallContextServiceLocator.Locator.ServiceProvider.GetRequiredService<IApplicationEnvironment>();
var builder = new ConfigurationBuilder(env.ApplicationBasePath)
.AddIniFile("hosting.ini");
var config = builder.Build();
webHost = new WebHostBuilder(CallContextServiceLocator.Locator.ServiceProvider, config)
.UseEnvironment("Development")
.UseServer("Microsoft.AspNet.Server.WebListener")
.Build()
.Start();
}
public void Dispose()
{
webHost.Dispose();
}
}
Katana's WebApp static class has been replaced by WebHostBuilder, that offers a much more flexible approach: https://github.com/aspnet/Hosting/blob/dev/src/Microsoft.AspNet.Hosting/WebHostBuilder.cs.
You've probably already used this API without realizing it, as it's the component used by the hosting block when you register a new web command in your project.json (e.g Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:54540) and run it using dnx (e.g dnx . web):
namespace Microsoft.AspNet.Hosting
{
public class Program
{
private const string HostingIniFile = "Microsoft.AspNet.Hosting.ini";
private const string ConfigFileKey = "config";
private readonly IServiceProvider _serviceProvider;
public Program(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public void Main(string[] args)
{
// Allow the location of the ini file to be specified via a --config command line arg
var tempBuilder = new ConfigurationBuilder().AddCommandLine(args);
var tempConfig = tempBuilder.Build();
var configFilePath = tempConfig[ConfigFileKey] ?? HostingIniFile;
var appBasePath = _serviceProvider.GetRequiredService<IApplicationEnvironment>().ApplicationBasePath;
var builder = new ConfigurationBuilder(appBasePath);
builder.AddIniFile(configFilePath, optional: true);
builder.AddEnvironmentVariables();
builder.AddCommandLine(args);
var config = builder.Build();
var host = new WebHostBuilder(_serviceProvider, config).Build();
using (host.Start())
{
Console.WriteLine("Started");
var appShutdownService = host.ApplicationServices.GetRequiredService<IApplicationShutdown>();
Console.CancelKeyPress += (sender, eventArgs) =>
{
appShutdownService.RequestShutdown();
// Don't terminate the process immediately, wait for the Main thread to exit gracefully.
eventArgs.Cancel = true;
};
appShutdownService.ShutdownRequested.WaitHandle.WaitOne();
}
}
}
}
https://github.com/aspnet/Hosting/blob/dev/src/Microsoft.AspNet.Hosting/Program.cs
You can use Microsoft.AspNet.TestHost
See http://www.strathweb.com/2015/05/integration-testing-asp-net-5-asp-net-mvc-6-applications/ for details on use.
TestHost can work with your startup using a line like
TestServer dataServer = new TestServer(TestServer.CreateBuilder().UseStartup<WebData.Startup>());
where is the name of the application. The application has to be referenced in the test harness

NHibernate configuration issue: "entry point was not found"

I'm configuring NHibernate on SharePoint 2010 web application.
Previously it works fine when mappings and domain were in one project. But during refactoring process I splitted solution on several projects. I've also implemented my custom IHttpModule where I want to initialize nhibernate configuration
protected void context_BeginRequest(object sender, EventArgs e)
{
var httpApplication = sender as HttpApplication;
lock (httpApplication )
{
if (!httpApplication.Context.Items.Contains(ApplicationConstants.IsApplicationInitialized))
{
httpApplication.Context.Items.Add(ApplicationConstants.IsApplicationInitialized, true);
InitInRequest(httpApplication);
}
}
httpApplication.Context.Items.Add(ApplicationConstants.SESSION, NhibernateManager.GetSession());
}
private void InitInRequest(HttpApplication httpApplication)
{
NhibernateManager.Init(ApplicationVariables.ApplicationSettingsPath);
}
And NHibernateManager.Init():
public static void Init(string configurationFilePath)
{
specifiedConfigurationFilePath = configurationFilePath;
Configure();
InitSessionFactory();
}
private static void Configure()
{
if (config == null)
{
if (string.IsNullOrEmpty(specifiedConfigurationFilePath) == false)
{
config = new Configuration();
config = config.Configure(specifiedConfigurationFilePath);
config = Fluently.Configure(config)
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<ItemMap>())
.BuildConfiguration();
}
else
{
config = Fluently.Configure()
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<ItemMap>())
.BuildConfiguration();
}
}
}
And in BuildConfiguration() I have very strange error (InnerException): "Entry point was not found." Stack trace shows that getting mapping information is cause of error:
at System.Collections.Generic.IDictionary`2.TryGetValue(TKey key, TValue& value)
at NHibernate.Cfg.Configuration.GetClassMapping(String entityName)
at NHibernate.Cfg.Configuration.GetClassMapping(Type persistentClass)
at FluentNHibernate.PersistenceModel.Configure(Configuration cfg)
at FluentNHibernate.Cfg.MappingConfiguration.Apply(Configuration cfg)
at FluentNHibernate.Cfg.FluentConfiguration.BuildConfiguration()
All assemblies are in the GAC. I tried to copy them in _app_bin or bin but without success.
UPDATE
Please, help me! I'm stuck with this weird problem :(
I found solution.
Take a look at my configuration lines:
config = new Configuration();
config = config.Configure(specifiedConfigurationFilePath);
config = Fluently.Configure(config)
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<ItemMap>())
.BuildConfiguration();
I'm creating new nhibernate configuration object passing it to fluent-nhibernate static initializer method. This is a point where things happen. Fluent-nhibernate can't take configuration from specified file. Instead it can take nhibernate configuration object with file path specified which it then uses to build configuration.
Previous version of my application was in one assembly and this way of configuration seems to work fine. But when I split up application the issue appeared.
So to solve the problem I should to take nhibernate configuration info from web.config file. I can't merge settings of timer job and web-application in one file as I could in single-assembly project. So I had to have several configuration files and always use classic lines like that:
Fluently
.Configure()
.Mappings(p => p.FluentMappings
.AddFromAssemblyOf<ItemMap>())
.BuildConfiguration()
It's appear to be some sort of fluent-nhibernate bug or something...

Resources