Logging from ASP.NET 5 application hosted as Azure Web App - c#

I have an ASP.NET 5 Web API that I host in Azure as a Web App. I want to log messages from my code using Azure Diagnostics. There are multiple article including Azure docs that suggest that it should be as easy as System.Diagnostics.Trace.WriteLine once enabled. The logs should show up under LogsFiles/Application and in log stream in Azure.
I enabled application logging for the web app:
But the following calls produces no logs:
System.Diagnostics.Trace.TraceError("TEST");
System.Diagnostics.Trace.TraceInformation("TEST");
System.Diagnostics.Trace.TraceWarning("TEST");
System.Diagnostics.Trace.WriteLine("TEST");
I tried to manually define TRACE symbol, but with no luck:
I also tried to use the new Microsoft.Extensions.Logging framework and use ILogger.Log API, but without any results again:
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
loggerFactory.MinimumLevel = LogLevel.Debug;
var sourceSwitch = new SourceSwitch("Sandbox.AspNet5.ApiApp-Demo");
sourceSwitch.Level = SourceLevels.All;
loggerFactory.AddTraceSource(sourceSwitch,
new ConsoleTraceListener(false));
loggerFactory.AddTraceSource(sourceSwitch,
new EventLogTraceListener("Application"));
}
Any ideas on what am I doing wrong?

If you look at your web.config, it probably has stdoutLogEnabled="false". If you set that to true, then anything that is written to standard output will be written to a file. And the stdoutLogFile determines where it goes, which by default is under d:\home\logfiles.
Now you need to make sure that your logging actually goes to stdout. Doing Console.WriteLine would definitely work. I think it's probably possible to also configure things via ILoggerFactory in your startup.cs to make logs go to stdout.

I've found an simple trick for azure app ,see https://github.com/aspnet/Logging/tree/dev/src/Microsoft.Extensions.Logging.AzureAppServices,
please add the package "Microsoft.Extensions.Logging.AzureAppServices": "1.0.0-preview1-final" and update related dependencies,
add the azure diagnostics in startup.cs like this :
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
loggerFactory.AddAzureWebAppDiagnostics(); // for default setting.
or for custom setting:
loggerFactory.AddAzureWebAppDiagnostics(new AzureAppServicesDiagnosticsSettings( ...)); // add custom setting.
// see here for detailed member properties: https://github.com/aspnet/Logging/blob/dev/src/Microsoft.Extensions.Logging.AzureAppServices/AzureAppServicesDiagnosticsSettings.cs
And enable the diagnostics log on azure, both logging on blob and file are work well. No need for any
extra configuration. :)

I got Streaming Log output working for an ASP.NET 5 web app by writing the following class (based on David Ebbo's example):
public class AzureApplicationLogTraceListener : TraceListener
{
private readonly string _logPath;
private readonly object _lock = new object();
public AzureApplicationLogTraceListener()
{
string instanceId = Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID");
if (instanceId != null)
{
string logFolder = Environment.ExpandEnvironmentVariables(#"%HOME%\LogFiles\application");
Directory.CreateDirectory(logFolder);
instanceId = instanceId.Substring(0, 6);
_logPath = Path.Combine(logFolder, $"logs_{instanceId}.txt");
}
}
public override void Write(string message)
{
if (_logPath != null)
{
lock (this)
{
File.AppendAllText(_logPath, message);
}
}
}
public override void WriteLine(string message)
{
Write(message + Environment.NewLine);
}
}
and then putting this in my Startup.Configure:
Trace.Listeners.Add(new AzureApplicationLogTraceListener());
This only supports filesystem-based logging (which is sufficient for live log streams).

Related

How to use NLog in xUnit tests from .NET Core?

I am trying to output some log messages from within .NET Core 3.1-based xUnit test cases by means of the NLog logging library. Logging by means of NLog works fine in my main application, but the test cases just won't output any messages.
I think I am doing everything suggested in this related question: NLog works in ASP.NET Core App but not in .NET Core xUnit Test Project
Somehow, though, I cannot figure out what is missing. I have reduced my code into a minimal sample that seems very straightforward, but still does not output anything:
using NLog;
using NLog.Config;
using NLog.Targets;
using System;
using Xunit;
using Xunit.Abstractions;
namespace UnitTests
{
public class LoggingTest
{
public LoggingTest(ITestOutputHelper output)
{
this.output = output;
}
private readonly ITestOutputHelper output;
[Fact]
public void TestLog()
{
var target = new MemoryTarget {Layout = "${message}"};
LogManager.Configuration ??= new LoggingConfiguration();
LogManager.Configuration.AddRuleForAllLevels(target);
LogManager.GetCurrentClassLogger().Info("Hello, World!");
output.WriteLine("{0} line(s) logged:\n{1}", target.Logs.Count, String.Join("\n", target.Logs));
}
}
}
Expected output:
1 line(s) logged:
Hello, World!
Actual output:
0 line(s) logged:
As one further trace, I have read in various places that NLog will only write something in .NET Core 3.1 projects if certain settings are present in a Logging section of the appsettings.json file. I think this section also had to be added to our main application's appsettings.json file.
I am not sure how to transfer this knowledge to the unit tests, though, as they do not appear to come with an appsettings.json file. I have tried copying the main appsettings.json file to the output directory of the unit tests (which is, I think, their execution directory when run from within ReSharper), but to no avail.
What am I missing?
To apply the config, you need to assign LogManager.Configuration, like
LogManager.Configuration = config;
Working example:
[Fact]
public void TestLog()
{
var target = new MemoryTarget { Layout = "${message}" };
var config = new LoggingConfiguration();
config.AddRuleForAllLevels(target);
LogManager.Configuration = config; // <-- assign here
LogManager.GetCurrentClassLogger().Info("Hello, World!");
output.WriteLine("{0} line(s) logged:\n{1}", target.Logs.Count, String.Join("\n", target.Logs));
Assert.Equal(1, target.Logs.Count);
}
Bonus: tests in parallel
Bonus, if you like tests in parallel (who doesn't ;)) - create a new LogFactory instead of assigning the global LogManager.
Like this:
[Fact]
public void TestLogParallelSafe()
{
var logFactory = new LogFactory();
var target = new MemoryTarget { Layout = "${message}" };
var config = new LoggingConfiguration();
config.AddRuleForAllLevels(target);
logFactory.Configuration = config;
logFactory.GetCurrentClassLogger().Info("Hello, World!");
output.WriteLine("{0} line(s) logged:\n{1}", target.Logs.Count, String.Join("\n", target.Logs));
Assert.Equal(1, target.Logs.Count);
}
Of course if other code is using the LogManager, you can't assert those logs.
.NET Core integration
As one further trace, I have read in various places that NLog will only write something in .NET Core 3.1 projects if certain settings are present in a Logging section of the appsettings.json file. I think this section also had to be added to our main application's appsettings.json file.
This is only needed when integrating with ASP.NET Core - e.g. when injection the ILogger<T> from Microsoft. That's is not needed here. For further reference, see Getting started with ASP.NET Core 3 ยท NLog/NLog Wiki
The approach proposed by Julian can be improved by implementing a specific target which writes the log entries to the output directly, without delay and without buffering all log entries in memory.
internal class TestOutputTarget: TargetWithLayout {
private readonly ITestOutputHelper output;
public TestOutputTarget(ITestOutputHelper output) {
this.output = output;
}
protected override void Write(LogEventInfo logEvent) {
output.WriteLine(RenderLogEvent(Layout, logEvent));
}
}
...
// In the test code
var target = new TestOutputTarget(output) { Layout = "${message}" };

C# Azure: How to set Azure timeout and retry policy when running locally?

I am writing C# code that runs against an Azure cloud. My application is an ASP.NET Core web service that exposes methods but no UI.
Sometimes I want to run my code locally using Microsoft Azure Storage Emulator. When my code starts up, one of the first things that happens is this:
var container = new BlobContainerClient(_connectionString, s);
bool exists = await container.ExistsAsync(ct);
if (!exists)
await container.CreateAsync(cancellationToken: ct);
When running locally, I sometimes forget to start Azure Storage Emulator. When that happens, it takes my code like a minute to time out and tell me it can't reach the "cloud".
What I want to achieve is: Make the program give me good error messages quickly when running locally, but use more lenient timeout strategies when actually running in the cloud.
I can reduce the above timeout by doing something like this:
var blobClientOptions = new BlobClientOptions();
blobClientOptions.Retry.MaxRetries = 0;
var container = new BlobContainerClient(_connectionString, s, blobClientOptions);
... but when running against the real cloud I don't want that; I want it to retry. One option might be to set the retries to zero like above, but only when running locally.
I have a development-specific configuration file (appsettings.Development.json). Is it possible to configure such timeout/retry settings in the config file?
Or is there some other best-practice way to accomplish the "fail quickly in development" behaviour that I seek?
Thanks in advance!
create a class that will contain you blobstorage configuration:
public class BlobStorageConfiguration
{
public string ConnectionString {get; set;}
public int MaxRetries {get; set;}
}
in your appsettings.Development.json
{
...
"BlobStorageConfiguration": {
"ConnectionString " : "<your_connection_string>",
"MaxRetries ":0
}
...
}
in your Startup.cs in the ConfigureServices method
..
var blobConfig = new BlobStorageConfiguration ();
Configuration.Bind(nameof(BlobStorageConfiguration ), blobConfig);
services.AddSingleton(blobConfig );
..
now you can inject your config and it will take values from the appsettings.Development.json if you are running it locally:
some controller:
[Route("api/somthing")]
[ApiController]
public class SomethingController : ControllerBase
private readonly ILogger<SomethingController > logger;
public SomethingController (
ILogger<SomethingController > logger,
BlobStorageConfiguration blobConfig)
{
this.logger = logger;
// use your blobConfig (connectionstring and maxRetries)
}

How can I get a custom exception handler written in ASP.Net Core 2.0 to work in ASP.Net Core 3.1?

Having worked through a course on Pluralsight (.NET Logging Done Right: An Opinionated Approach Using Serilog by Erik Dahl) I began implementing a similar solution in my own ASP.Net Core 3.1 MVC project. As an initial proof of concept I downloaded his complete sample code from the course and integrated his logger class library into my project to see if it worked.
Unfortunately, everything seems to work apart from one critical element. In the Configure method of my project's Startup.cs file rather than app.UseExceptionHandler("/Home/Error"); I now have app.UseCustomExceptionHandler("MyAppName", "Core MVC", "/Home/Error"); - in theory this is meant to hit some custom middleware, passing in some additional data for error logging, then behave like the normal exception handler and hit the error handling path. In practice, it doesn't hit the error handling path and users are shown the browser's error page.
The comments on the middleware code say this code is:
// based on Microsoft's standard exception middleware found here:
// https://github.com/aspnet/Diagnostics/tree/dev/src/
// Microsoft.AspNetCore.Diagnostics/ExceptionHandler
This link no longer works. I found the new link but it's in a GitHub archive and it didn't tell me anything useful.
I am aware that there were changes to the way routing works between .Net Core 2.0 and 3.1, but I'm not sure whether these would cause the issue I am experiencing. I do not think the issue is in the code below that gets called from Startup.cs.
public static class CustomExceptionMiddlewareExtensions
{
public static IApplicationBuilder UseCustomExceptionHandler(
this IApplicationBuilder builder, string product, string layer,
string errorHandlingPath)
{
return builder.UseMiddleware<CustomExceptionHandlerMiddleware>
(product, layer, Options.Create(new ExceptionHandlerOptions
{
ExceptionHandlingPath = new PathString(errorHandlingPath)
}));
}
}
I believe the issue is likely to be in the Invoke method in the actual CustomExceptionMiddleware.cs below:
public sealed class CustomExceptionHandlerMiddleware
{
private readonly RequestDelegate _next;
private readonly ExceptionHandlerOptions _options;
private readonly Func<object, Task> _clearCacheHeadersDelegate;
private string _product, _layer;
public CustomExceptionHandlerMiddleware(string product, string layer,
RequestDelegate next,
ILoggerFactory loggerFactory,
IOptions<ExceptionHandlerOptions> options,
DiagnosticSource diagSource)
{
_product = product;
_layer = layer;
_next = next;
_options = options.Value;
_clearCacheHeadersDelegate = ClearCacheHeaders;
if (_options.ExceptionHandler == null)
{
_options.ExceptionHandler = _next;
}
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
WebHelper.LogWebError(_product, _layer, ex, context);
PathString originalPath = context.Request.Path;
if (_options.ExceptionHandlingPath.HasValue)
{
context.Request.Path = _options.ExceptionHandlingPath;
}
context.Response.Clear();
var exceptionHandlerFeature = new ExceptionHandlerFeature()
{
Error = ex,
Path = originalPath.Value,
};
context.Features.Set<IExceptionHandlerFeature>(exceptionHandlerFeature);
context.Features.Set<IExceptionHandlerPathFeature>(exceptionHandlerFeature);
context.Response.StatusCode = 500;
context.Response.OnStarting(_clearCacheHeadersDelegate, context.Response);
await _options.ExceptionHandler(context);
return;
}
}
private Task ClearCacheHeaders(object state)
{
var response = (HttpResponse)state;
response.Headers[HeaderNames.CacheControl] = "no-cache";
response.Headers[HeaderNames.Pragma] = "no-cache";
response.Headers[HeaderNames.Expires] = "-1";
response.Headers.Remove(HeaderNames.ETag);
return Task.CompletedTask;
}
}
Any suggestions would be really appreciated, I've been down so many rabbit holes trying to get this working over the last few days to no avail, and quite aside from my own project, I'd love to be able to leave a comment on the Pluralsight course for anyone else trying to do this to save them having to go through the same struggles as I have.
I actually recommend an easier approach than what I had originally showed in the .NET Logging Done Right course (that course was built around .NET Framework for the most part and the ASP.NET Core module was added after original publication). A better course for doing ASP.NET Core logging is the newer Effective Logging in ASP.NET Core. But rather than simply send you off to another course to watch, allow me to answer your question.
I think you should use the UseExceptionHandler(string path) middleware. You are free to log the exception in the error code (controller or razor page code). You can see this concretely in this code repo:
https://github.com/dahlsailrunner/aspnetcore-effective-logging
Specifically look at these files in the BookClub.UI project:
Startup.cs (Configure method)
Pages/Error.cshtml
Pages/Error.cshtml.cs
That will keep your custom code to a minimum (always a good thing).
HTH
You can try to reset the endpoint and route values in your custom exception handler middleware, like below.
try
{
context.Response.Clear();
context.SetEndpoint(endpoint: null);
var routeValuesFeature = context.Features.Get<IRouteValuesFeature>();
routeValuesFeature?.RouteValues?.Clear();
var exceptionHandlerFeature = new ExceptionHandlerFeature()
{
Error = ex,
Path = originalPath.Value,
};
//...
For more information, please check the source code of ExceptionHandlerMiddleware in github:
https://github.com/dotnet/aspnetcore/blob/5e575a3e64254932c1fd4937041a4e7426afcde4/src/Middleware/Diagnostics/src/ExceptionHandler/ExceptionHandlerMiddleware.cs#L107

Logging in .Net core console application not working

I am following this tutorial: https://andrewlock.net/using-dependency-injection-in-a-net-core-console-application/
and accordingly installed the packages but log is not getting printed anywhere.
This is my code:
var serviceProvider = new ServiceCollection()
.AddLogging()
.AddTransient<IFoo, Foo>(s =>
{
return new Foo()})
.BuildServiceProvider();
//configure console logging
serviceProvider
.GetService<ILoggerFactory>()
.AddConsole(LogLevel.Debug);
var logger = serviceProvider.GetService<ILoggerFactory>().CreateLogger<Program>();
logger.LogError("Starting application");
Turns out the console logging provider doesn't immediately flush the messages to the console like it did in the net-core-1.x versions. It appears to run on a different thread. See this web page for info: https://github.com/aspnet/Logging/issues/631
You can add at the end of the Main function.
serviceProvider.Dispose();
or you can add .AddDebug()
serviceProvider
.GetService<ILoggerFactory>()
.AddConsole(LogLevel.Debug)
.AddDebug();
Creating a new ServiceProvider and HostBuilder may not be worth it if we just want a Logging in Console Application because it's a bit of extra caution to clean it up or dispose of.
Rather, I would suggest just have Logging Factory to use logger and that will solve the logging if that is only what we want.
public static class ApplicationLogging
{
public static ILoggerFactory LogFactory { get; } = LoggerFactory.Create(builder =>
builder.ClearProviders();
// Clear Microsoft's default providers (like eventlogs and others)
builder.AddSimpleConsole(options =>
{
options.IncludeScopes = true;
options.SingleLine = true;
options.TimestampFormat = "hh:mm:ss ";
});
builder.AddApplicationInsights("instrument-key");
});
public static ILogger<T> CreateLogger<T>() => LogFactory.CreateLogger<T>();
}
static void Main(string[] args)
{
var logger = ApplicationLogging.CreateLogger<Program>();
logger.LogInformation("Let's do some work");
logger.LogWarning("I am going Crazy now!!!");
logger.LogInformation("Seems like we are finished our work!");
Console.ReadLine();
}
I landed on this thread trying to troubleshoot why console logging didn't work and this answer documents what I found.
Packages used:
Microsoft.Extensions.Logging
Microsoft.Extensions.Logging.Console
Microsoft.Extensions.Logging.Debug
Application:
.NET Core 2.2 Console (Microsoft.NET.Sdk, netcoreapp2.2)
Using Microsoft.Extensions.Hosting.IHost, this is how I added console logging:
var hostBuilder = new HostBuilder()
// Other Configuration omitted for brevity
.ConfigureLogging((hostBuilderContext, loggingBuilder) =>
{
loggingBuilder.AddConfiguration(hostBuilderContext.Configuration.GetSection("Logging"));
loggingBuilder.AddConsole(options =>
{
options.IncludeScopes = true;
});
loggingBuilder.AddDebug();
});
// Start the application
await hostBuilder.RunConsoleAsync();
Interestingly, if I remove the options parameter in the call to AddConsole, I do not see any logging. I believe this is so because I use an ILogger in my code that emits log statements:
public class ClassThatLogs
{
private readonly ILogger<ClassThatLogs> _logger;
public ClassThatLogs(ILogger<ClassThatLogs> logger)
{
_logger = logger;
}
public void DoWork()
{
_logger.LogInformation("Working");
}
}
If I publish the console app as:
single file _log.LogDebug provide no output
when I publish it as not a single file (so I only uncheck the single file option) _log.LogDebug provides outputs on the console all my debugging statements. No other changes than unchecking single file.
So by only unticking "produce single file" my _logging.LogDebug starts to write stuff to the console.
Seems to be this: https://github.com/serilog/serilog-settings-configuration/issues/244

Logging to NLog in Hangfire.io

I am currently using Hangfire to run some jobs in a windows service. The jobs are fired from a WebAPI.
My system is currently working fine when all jobs succeed, but I am getting no logging for when exceptions occur. Has anyone got any experience in using a custom logger to receive messages from Hangfire?
My logger is a basic NLog interface:
public class Logger : ILogger
{
public readonly NLog.Logger logger;
public Logger(string name)
{
if (LogManager.Configuration == null)
{
FallbackInitialisation();
}
logger = LogManager.GetLogger(name);
}
public Trace(string message)
//etc.
}
I am configuring my Hangfire job server in my windows service like so:
SqlServerStorage storage = new SqlServerStorage("myConnectionString");
BackgroundJobServerOptions options = new BackgroundJobServerOptions();
m_server = new BackgroundJobServer(options, storage);
GlobalJobFilters.Filters.Add(new AutomaticRetryAttribute { Attempts = 0 });
Based on the hangfire documentation, I simply tried following it by adding the required references, and adding the following after my job server setup, however there were still no logs produced:
var properties = new NameValueCollection();
properties["configType"] = "INLINE";
LogManager.Adapter = new NLogLoggerFactoryAdapter(properties);
I am simply simulating an exception by throw new Exception() in the method called by the job. The job is created in the webApi call like so:
[HttpGet]
public void TestStartJob()
{
m_logger.Trace("TestStartJob");
BackgroundJob.Enqueue(() => m_service.TestStartJob());
}
What I am looking for, is if anyone has any experience in getting logging properly configured for use with Hangfire.
I have managed to solve this issue by using the following versions of nuget packages:
NLog - 3.1
Common.Logging - 2.2.0
JVW.Logging.CommonLoggingNLogAdapter - 1.0.0.1
and then configuring the logging as such
var properties = new NameValueCollection();
properties["configType"] = "INLINE";
Common.Logging.LogManager.Adapter = new NLogFactoryAdapter(properties);

Categories