Azure Function 3.1 + NLog + Structured Logging, Issues with LoggerName - azure

NLog + Azure Functions 3.1
Azure function Startup class
public override void Configure(IFunctionsHostBuilder builder)
{
var logger = LogManager.Setup()
.SetupExtensions(e => e.AutoLoadAssemblies(false))
.LoadConfigurationFromFile(currentDirectory + Path.DirectorySeparatorChar + 'NLog.config')
.GetLogger(configuration.GetSection('logging:nlog:defaultloggername')?.Value);
builder.Services.AddLogging((logger) =>
{
//logger.ClearProviders();
logger.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
logger.AddNLog();
}).BuildServiceProvider();
}
//NLog.config
<variable name='commonLayout' value='${longdate}|${logger}|${uppercase:${level}}|${message}, ${all-event-properties:format=[key]=[value]:separator=, } ${exception}' />
Azure Function
public FunctionA(ILogger<FunctionA> logger){}
Structured logging does not work with Azure function 3.1. The loggername is spitting out FunctionA, how can I change this to use NLog object in the Azure function.
Note: I'm using Azure Function 3.1, I can inject NLog in .net core 2.1 though.

I'm guessing from your question that you want to control the name of the Logger, so you can make better use of NLog-Filtering-Rules.
Instead of doing this:
public FunctionA(ILogger<FunctionA> logger){}
Have you tried to ask for ILoggerFactory like this:
public FunctionA(ILoggerFactory logFactory)
{
var logger = logFactory.CreateLogger("MyFavoriteLoggerName");
logger.LogError("Test");
}
You can also make use of BeginScope or LogEvent properties like EventId_Id to signal origin.

Related

Azure Function v3 - "Azure Functions runtime is unreachable" when add Identity in Startup

I have an Azure Function (v3) that must interact with DB and also with user management.
It has as a dependency a project that also contains the DAL so all the context configuration.
When in the Startup of the function I add the dependency to the DbContext and deploy it on Azure I have no problems.
When in addition to the DbContext I also add Identity and re-deploy, the Portal says "Azure Functions runtime is unreachable".
This is the function Startup.cs:
[assembly: FunctionsStartup(typeof(Directio.PeopleSee.OrderSynchronizer.Startup))]
namespace Directio.PeopleSee.OrderSynchronizer
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddHttpClient();
builder.Services.AddDbContext<DBContext>(options1 =>
{
options1.UseLazyLoadingProxies(false);
options1.UseSqlServer(Environment.GetEnvironmentVariable("DBConnectionString"), builder =>
{
builder.CommandTimeout(10);
}
);
})
.AddIdentity<AspNetUser, AspNetRole>(opt =>
{
opt.Password.RequireDigit = false;
opt.Password.RequireLowercase = false;
opt.Password.RequireNonAlphanumeric = false;
opt.Password.RequireUppercase = false;
opt.Password.RequiredLength = 0;
opt.Password.RequiredUniqueChars = 0;
opt.User.RequireUniqueEmail = false;
opt.SignIn.RequireConfirmedEmail = false;
opt.SignIn.RequireConfirmedAccount = false;
opt.SignIn.RequireConfirmedPhoneNumber = false;
})
.AddEntityFrameworkStores<DBContext>()
.AddDefaultTokenProviders();
builder.Services.AddOptions<FunctionAppSettings>().Configure<IConfiguration>((settings, configuration) => configuration.GetSection("FunctionAppSettings").Bind(settings));
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
builder.Services.AddScoped<IUserService, UserServiceImpl>();
builder.Services.AddScoped<IRoleService, RoleServiceImpl>();
builder.Services.AddScoped<ISubscribersService, SubscriberServiceImpl>();
builder.Services.AddScoped<IOrdersService, OrdersService>();
}
}
}
All services registrations come from the project the dependency is linked to.
The function is under netcore3.1 as a framework, and is deployed in an Azure Func App with a pay-as-you-go plan, Windows server and it's not a docker container.
What could be the problem?
When you add Identity you are also adding the AspNetCore Authentication dependencies. For some reason Functions host checks if these dependencies exist in your applications and stops the Host module from adding the built-in authentication schemes that allows the portal to have access to the functions in your project.
If you still want to load Identity components at startup time I encourage you to look at https://www.nuget.org/packages/DarkLoop.Azure.Functions.Authorize
Here is a post describing how it works: https://blog.darkloop.com/post/functionauthorize-for-azure-functions-v3
you can call
builder.AddAuthentication();
builder.AddAuthorization();
// or
builder.Services.AddFunctionsAuthentication();
builder.Services.AddFunctionsAuthorization();
This call forces the functions built-in authentication to be loaded and allows you to add more authentication dependencies and schemes.
After this call you can make your calls AddDbContext<... and AddIdentity<...
Azure portal should be able to get all the information it needs about your functions.

Azure Durable Functions: Can I use a startup.cs?

I have a couple of functions that I want to refactor into a Azure Durable Function, where the orchestrator calls different ActivityFunctions (the previously seperate Azure Functions). The seperate functions use a startup where I configure dependency injection and some other configurations.
Is it possible to use a startup class in a Durable Function scheme? I cannot find anything that seems to suggest this in the documentation.
If it is not possible, what other alternatives are there, for example, to define database connection string and dependency injection?
Thnx
Dependency Injection is possible in Durable Functions too.
Install and add reference to the following package from Nuget:
Microsoft.Azure.Functions.Extensions.DependencyInjection
Here's a working sample of Startup file with Dependency Injection done:
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Azure.WebJobs.Host.Bindings;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System;
[assembly: FunctionsStartup(typeof(MyDurableFunction.Startup))]
namespace MyDurableFunction
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
var executioncontextoptions = builder.Services.BuildServiceProvider()
.GetService<IOptions<ExecutionContextOptions>>().Value;
var currentDirectory = executioncontextoptions.AppDirectory;
var environment = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT");
var config = new ConfigurationBuilder()
.SetBasePath(currentDirectory)
.AddJsonFile($"settings.{environment}.json", optional: false, reloadOnChange: false)
.AddEnvironmentVariables()
.Build();
builder.Services.AddSingleton<IMyBusinessService1>(sp =>
{
var secretFromAppSettings = config["secretKey"];
var passwordFromAppSettings = config["secretPassword"];
return new MyBusinessService1(secretFromAppSettings, passwordFromAppSettings);
});
builder.Services.AddSingleton<IDatabaseHelper, DatabaseHelper>();
builder.Services.AddLogging();
}
}
}
Hope this helps!
Yes you can use Dependency Injection similar to normal functions.
A complete example which I found in this blog

Application Insights for WebAPI application

Is it possible to tell Application Insights to use a different InstrumentationKey depending on the request URL?
Our application works with different clients and we want to separate the logs for them in different instances of Application Insights.
Url format: https://webapi.com/v1/{client_name}/bla/bla
It would be great to setup configuration to select InstrumentationKey by client_name from request.
If the goal is to send different telemetry items to different instrumentation key, the correct way to achieve that is by modifying the individual item with a TelemetryInitializer to have the correct ikey.
An initializer like the following:
item.Context.InstrumentationKey = ikey.
This initializer should access HttpContext and decide the ikey dynamically from request route/other params.
Modifying TC.Active is not recommended for this purpose as its a global shared setting.
(This is not a very common use case - but there are teams inside Microsoft who does this for PROD scale apps)
You can do that. If you have a logger, have the ApplicationInsightsKey parameter-ized and pass the Key for the client on every call, or inject it on load if your application is tenant based.
Checkout the Docs here: Separating telemetry from Development, Test, and Production
Microsoft.ApplicationInsights.Extensibility.
TelemetryConfiguration.Active.InstrumentationKey = <App-Insights-Key-for-the-client>
Just change the Application Insights key before logging and it will do the job.
It would be great to setup configuration to select InstrumentationKey
by client_name from request.
You can dynamically select the ikey as per the client_name from the request. First, you need to get the request url, then check the client_name.
To do that, you can add the following code to the Global.asax file:
void Application_BeginRequest(Object source, EventArgs e)
{
var app = (HttpApplication)source;
//get the request url
var uriObject = app.Context.Request.Url.ToString();
if (uriObject.Contains("/client_name_1"))
{
Microsoft.ApplicationInsights.Extensibility.
TelemetryConfiguration.Active.InstrumentationKey = "ikey_1";
}
else if (uriObject.Contains("/client_name_2"))
{
Microsoft.ApplicationInsights.Extensibility.
TelemetryConfiguration.Active.InstrumentationKey = "ikey_2";
}
else
{
Microsoft.ApplicationInsights.Extensibility.
TelemetryConfiguration.Active.InstrumentationKey = "ikey_3";
}
}
The test result:
But I want to say we rarely use 1 more ikeys in one environment. If your goal is to make the data not being cluttered, I suggest you can use only 1 ikey, and then use Kusto query for your purpose.
Thanks to the answers from #cijothomas and #danpop (link) I was able to understand the whole picture.
Step 1: Create custom ITelemetryInitializer (Microsoft Documentation):
public class MyTelemetryInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
var appKey = CallContext.LogicalGetData("ApplicationKey")?.ToString();
switch (appKey)
{
case "App1":
telemetry.Context.InstrumentationKey = "d223527b-f34e-4c47-8aa8-1f21eb0fc349";
return;
default:
telemetry.Context.InstrumentationKey = "f8ceb6cf-4357-4776-a2b6-5bbed8d2561c";
return;
}
}
}
Step 2: Register custom initializer:
<ApplicationInsights xmlns="http://schemas.microsoft.com/ApplicationInsights/2013/Settings">
<TelemetryInitializers>
<Add Type="Application.WebAPI.MyTelemetryInitializer, Application.WebAPI"/>
</TelemetryInitializers>
<!--<InstrumentationKey>f8ceb6cf-4357-4776-a2b6-5bbed8d2561c</InstrumentationKey>-->
</ApplicationInsights>
OR
protected void Application_Start()
{
// ...
TelemetryConfiguration.Active.TelemetryInitializers.Add(new MyTelemetryInitializer());
}
Step 3: Make some adjustments to the logger (source code taken from #danpop answer Logger target configuration):
var config = new LoggingConfiguration();
ConfigurationItemFactory.Default.Targets.RegisterDefinition("ai", typeof());
ApplicationInsightsTarget aiTarget = new ApplicationInsightsTarget();
aiTarget.InstrumentationKey = "your_key";
aiTarget.Name = "ai";
config.AddTarget("ai", aiTarget);
LogManager.Configuration = config;
ILogger configuration exmples: Log4Net, NLog, System.Diagnostics

How can I view console or trace output in an azure app service? Console.WriteLine or Trace.TraceError

I thought this would be simple but after several hours have realized that it is not. I deployed an "App Service" to Azure that is a C# .Net Core.
I am trying to add some crude monitoring of by app by using Console.WriteLine("my message") or Trace.TraceError("my message") but I can't find that output anywhere in Azure.
I tried enabling Application Insights but didn't find the output there either.
I just want the simplest way to get a little idea that a line of code in my app is getting hit. What is that simplest way?
Things I have tried:
1) I went in to the console in Azure and browsed to every file that might have this sort of output but did not find any. Certainly nothing in any of the files and folders under LogFiles.
2) I DID go to "App Service Logs" and enabled "Application Logging (Filesystem)".
3) I also enabled "Detailed error messages" in "App Service Logs".
4) I tried "Diagnostic settings (preview)" but could not find the output there.
5) I watch the "Log Stream" for Application Logs but nothing shows up there
6) Under "Logs" I just have this message: "We didn’t find any logs"
7) Neither "metrics" nor "Alerts" has this log output
I am beginning to suspect this is not support in Azure. Do I need to add a logging framework like serilog just to one time add a statement to may app like "You hit line 20"? I really just want something quick and dirty, but after spending a few hours on this the "quick" part of it isn't happening.
What I did was just to enable the App Service Logs in the Azure Portal.
Specifically I turned on the "Application Logging (Filesystem)" at the "Verbose" Level and then I selected "File System" in the "Web server logging" option
Then you can use the "Log stream" in the left bar to display the application logs.
In your code you just have to call
System.Diagnostics.Trace.TraceInformation("My message!")
Or
System.Diagnostics.Trace.WriteLine("My message!")
And that's it!
Here I include some screenshots:
I finally figured it out myself. I needed to add configuring AzureFileLoggerOptions. Without those options, nothing showed up in the Azure output.
.ConfigureLogging(logging => logging.AddAzureWebAppDiagnostics())
.ConfigureServices(serviceCollection => serviceCollection
.Configure<AzureFileLoggerOptions>(options =>
{
options.FileName = "azure-diagnostics-";
options.FileSizeLimit = 50 * 1024;
options.RetainedFileCountLimit = 5;
}).Configure<AzureBlobLoggerOptions>(options =>
{
options.BlobName = "log.txt";
})
)
In summary, for .Net Core 3.1, to get your messages to show up in the "Log Stream", you need to enable "Application Logging" in the "App Service Logs" section of Azure. In your code you need to reference:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.AzureAppServices;
Also, in my case, I am using Blazor/SignalR and don't have an MVC Controller, so I couldn't figure out how to access the Logging framework in my class. I am sure there is a better way, but by exposing the ILogger as in the code below I was able to reference it from anywhere within my code base.
I have a static method I call to write to the log (console when I am actively debugging on my pc, or Azure "Log Stream" when running in Azure:
public static void WriteToLog(string message)
{
Program.Logger.LogError(message);
}
Code in my Program.cs looks like this:
public class Program
{
public static ILogger Logger;
public static void Main(string[] args)
{
//CreateHostBuilder(args).Build().Run();
var host = CreateHostBuilder(args).Build();
var LoggerF = LoggerFactory.Create(builder =>
{
builder.AddFilter("BlazorDiceRoller", LogLevel.Warning)
.AddConsole().AddAzureWebAppDiagnostics()
.AddFilter("Microsoft", LogLevel.Warning)
.AddFilter("System", LogLevel.Warning);
});
Logger = LoggerF.CreateLogger<Program>();
host.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging => logging.AddAzureWebAppDiagnostics())
.ConfigureServices(serviceCollection => serviceCollection
.Configure<AzureFileLoggerOptions>(options =>
{
options.FileName = "azure-diagnostics-";
options.FileSizeLimit = 50 * 1024;
options.RetainedFileCountLimit = 5;
}).Configure<AzureBlobLoggerOptions>(options =>
{
options.BlobName = "log.txt";
})
)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
You can open Kudu and view Console.WritLine output. Open it from portal or:
https://your_app_name_on_azure.scm.azurewebsites.net/api/logstream
Don't forget to enable logs in azure:
Yes, this(console or trace output) is not supported in .NET core only. You should take use of ILogger as per steps below.
In Startup.cs -> Configure method, re-write Configure method like below:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
//your other code
//add the following 2 lines of code.
loggerFactory.AddConsole();
loggerFactory.AddDebug();
app.UseStaticFiles();
//your other code
}
Then in HomeController.cs(for example), add the following code:
public class HomeController : Controller
{
private readonly ILogger _logger;
public HomeController(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<HomeController>();
}
public IActionResult Index()
{
_logger.LogInformation("this is a information from ILogger...");
return View();
}
//other code
}
Please let me know if you still have more issues.
Try to use the namespace System.Diagnostics.Trace to register info about diagnostic
eg:
System.Diagnostics.Trace.TraceError("If you're seeing this, something bad happened");
By Default, ASP.NET Core use the provider for logs this namespace: Microsoft.Extensions.Logging.AzureAppServices.

Disable ColoredConsole Logging provider from Azure Functions V2

I am using Serilog for structured logging in asp.net core and would like to use the same library with Azure Functions.
My goal is to write the log messages in JSON format to Console Sink through Serilog. It is working, but the Functions Framework is also writing the text based logs using ColoredConsole provider. I want to disable that.
As you can see from the following code I tried to replace the LoggerFactory and clearing the providers. It still didn't work.
[assembly: WebJobsStartup(typeof(FuncStartup))]
namespace Function.Test
{
public class FuncStartup : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
var loggerConfiguration = new LoggerConfiguration()
.Enrich.FromLogContext()
.Enrich.WithExceptionDetails()
.Enrich.WithEnvironmentUserName()
.Enrich.WithMachineName()
.Enrich.WithProcessId()
.Enrich.WithProcessName()
.Enrich.WithThreadId()
.Enrich.With<ServiceVersionEnricher>()
.WriteTo.Console(new JsonFormatter());
Log.Logger = loggerConfiguration.CreateLogger();
//return serviceCollection.Replace(new ServiceDescriptor(typeof(ILoggerFactory), s => new SerilogLoggerFactory(null, true), ServiceLifetime.Singleton));
builder.Services.AddLogging(b =>
{
b.ClearProviders();
b.AddSerilog(Log.Logger);
});
}
}
}
Output:

Resources