ILogger sink for Azure Serverless Functions - azure

I am injecting ILogger into my Azure functions, and in my host.json I have the following:
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"maxTelemetryItemsPerSecond": 20
}
}
}
}
I then log as below:
public class EnrollmentFunctions
{
private readonly ILogger<AttestationFunctions> logger;
public EnrollmentFunctions(ILogger<AttestationFunctions> log)
{
logger = log;
}
public async Task<IActionResult> Enroll(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "enroll")] HttpRequest req)
{
logger.LogInformation("Requesting attestation of device name {deviceName}.", req.Query["deviceName"]);
}
}
However no logs are logged as below - appreciate pointers as to what I am doing wrong and where I would see my logs please.

I've followed the below work around to achieve the desired results as described:
I've used generalized logging in my function project using Visual studio and included both ILogger and TelemetryClient in a class Genlog.cs
Genlog.cs
public class Genlog
{
public Genlog(ILogger<Genlog> logger, TelemetryClient telemetry)
{
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
Telemetry = telemetry ?? throw new ArgumentNullException(nameof(telemetry));
}
public void testlog(string mylog)
{
Logger.LogInformation("TestCustomInformationLog:" + mylog);
Telemetry.TrackTrace("TestCustomTraceLog: " + mylog, Microsoft.ApplicationInsights.DataContracts.SeverityLevel.Information);
Telemetry.TrackException(new Exception(""));
}
public ILogger<Genlog> Logger { get; }
public TelemetryClient Telemetry { get; }
}
Startup.cs
#Adding configuring service of Genlog in startup.cs
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddScoped<Genlog>();
}
Function.cs
public class Function1
{
private readonly Genlog genlog;
public Function1(Genlog genlog)
{
this.genlog = genlog ?? throw new ArgumentNullException(nameof(genlog));
}
[FunctionName("Function1")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
# Using Both Ilogger & Telemetry logger in same class.
genlog.testlog("C# HTTP trigger function processed a request.");
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
string responseMessage = string.IsNullOrEmpty(name)
? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
: $"Hello, {name}. This HTTP triggered function executed successfully.";
genlog.testlog(responseMessage);
return new OkObjectResult(responseMessage);
}
}
Result
Published to Azure to check & retrieve logs:

Related

Azure Function - ILogger is not logging?

I have an azure function, it logs the information without any issues.
namespace aspnetcore_azurefun_blob
{
[StorageAccount("AzureWebJobsStorage")]
public class FileTrigger
{
#region Property
private readonly IFileProcessor fileProcessor;
#endregion
#region Constructor
public FileTrigger(IFileProcessor fileProcessor)
{
this.fileProcessor = fileProcessor;
}
#endregion
[FunctionName("FileTrigger")]
public void ProcessFilesFromSamplesContainer([BlobTrigger("samples-workitems/{name}")]Stream myBlob, string name, ILogger log, ExecutionContext context)
{
log.LogInformation("Function: ProcessFilesFromSamplesContainer is called");
log.LogInformation($"C# Blob trigger function Processed blob\n Name:{name} \n Size: {myBlob.Length} Bytes");
var result = fileProcessor.ProcessAsync(myBlob, name);
log.LogInformation($"Function Completed Successfully on {DateTime.Now.ToLongDateString()} # {DateTime.Now.ToShortTimeString()}.\n.");
}
However, I also have the business logic implemented using DI and below is the excerpt of the implementation.
ServiceBase.cs
namespace BusinessService.Services.Common
{
public abstract class ServiceBase<T>
{
public static AppDbContext AppDbContext;
public static ILogger<T> Logger { get; set; }
public static AppConfigurator Configurator { get; set; }
public ServiceBase(AppDbContext appDbContext, ILogger<T> logger, IOptions<AppConfigurator> configurator)
{
AppDbContext = appDbContext ?? throw new ArgumentNullException(nameof(appDbContext));
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
Configurator = configurator.Value ?? throw new ArgumentNullException(nameof(configurator));
}
}
}
FileProcessingService.cs
namespace BusinessService.Services
{
public interface IFileProcessingService
{
void Process(Stream myBlob, string name);
}
public class FileProcessingService : ServiceBase<FileProcessingService>, IFileProcessingService
{
#region Constructor
public FileProcessingService(AppDbContext appDbContext, ILogger<FileProcessingService> logger, IOptions<AppConfigurator> configurator)
: base(appDbContext, logger, configurator) { }
#endregion
#region Public Methods
public void Process(Stream myBlob, string name)
{
AppDbContext.FileRecords.Add(new FileRecords
{
FileName = name,
IsCompleted = DefaultValues.IsCompleted
});
AppDbContext.SaveChanges();
Logger.LogInformation("Reading configuration from the configuration settings file: {Configurator.AzureSQLServerConfigurator.ConnnectionString}");
Logger.LogInformation("Database is updated..!");
}
#endregion
}
}
Line#34 and #35 doesn't Log anything
Logger.LogInformation("Reading configuration from the configuration settings file: {Configurator.AzureSQLServerConfigurator.ConnnectionString}");
Logger.LogInformation("Database is updated..!");
DependencyRegistrar.cs
namespace CrossCutting.DependencyInjection
{
public static class DependencyRegistrar
{
public static void Intialize(this IServiceCollection services)
{
// Initialize App Settings from Configurator Settings Json file
services.AddOptions<AppConfigurator>()
.Configure<IConfiguration>((settings, configuration) =>
{
configuration.GetSection("AppConfigurator").Bind(settings);
})
.Validate((c) =>
{
return !new[] { c.AzureSQLServerConfigurator.ConnnectionString }.Any(s => String.IsNullOrWhiteSpace(s));
});
}
}
}
What am I missing so that FileProcessingService.cs will log the information ?
I have checked your code in our end I could get the logging information. In your code i have noticed in your code you are using the Logger instead of logger.
Because in your Region Constructor you are using ILogger<FileProcessingService> logger from here you have to call the logger to push your logging information into Application Insights/Function execution panel (Output console window)
#region Constructor
public FileProcessingService(AppDbContext appDbContext, ILogger<FileProcessingService> logger, IOptions<AppConfigurator> configurator)
: base(appDbContext, logger, configurator) { }
#endregion
#region Public Methods
public void Process(Stream myBlob, string name)
{
AppDbContext.FileRecords.Add(new FileRecords
{
FileName = name,
IsCompleted = DefaultValues.IsCompleted
});
AppDbContext.SaveChanges();
# changed Logger into logger
logger.LogInformation("Reading configuration from the configuration settings file: {Configurator.AzureSQLServerConfigurator.ConnnectionString}");
logger.LogInformation("Database is updated..!");
}
#endregion
Still if not able to push the logs you can add your namespace in your host.json file to avoid missing the logging
{
"version": "2.0",
"logging": {
"logLevel": {
// Here you can use your Project namespace like BusinessService.Services
"<namespace>": "Information"
}
}
}

How do I log results from an Azure Function App dependency?

I have an Azure Function App project with the following files:
Startup.cs: Registers a dependency
[assembly: FunctionsStartup(typeof(MyLoggingFunction.Startup))]
namespace MyLoggingFunction
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddScoped<MyService>();
}
}
}
MyService.cs: Writes a log message
namespace MyLoggingFunction
{
public class MyService
{
private readonly ILogger<MyService> logger;
public MyService(ILogger<MyService> logger)
{
this.logger = logger;
}
public void Go()
{
this.logger.LogInformation("MyService.Go");
}
}
}
MyFunction.cs: The actual function; uses MyService
namespace MyLoggingFunction
{
public class MyFunction
{
private readonly MyService myService;
public MyFunction(MyService myService)
{
this.myService = myService;
}
[FunctionName("MyFunction")]
public IActionResult Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
this.myService.Go();
log.LogInformation("All done");
return new OkObjectResult("Done.");
}
}
}
Here is the output visible in Azure after the function runs successfully. Note that the log message from the injected dependency is missing:
How do I get log messages from the dependency to show up as part of the built-in logging?
Add logging level entry to host.json :
{
"version": "2.0",
"logging": {
"logLevel": {
"default": "Information"
}
}
}
You must whitelist the classes and/or namespaces from which you want to allow logging. An example hosts.json file:
{
"logging": {
"logLevel": {
"MyLoggingFunction": "Information"
}
},
"version": "2.0"
}
This is documented as an issue on the azure-function-host GitHub repository.

Add metadata to traces in Azure functions

I have an Azure function(.NET core 2.0) that runs on each PR in an ADO repo.
I would like to add the PR-ID as metadata to each trace logged by the Azure function.
(I am viewing the logs using the traces table in Azure application insights instance)
The Azure function logs traces via:
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]HttpRequestMessage req, ILogger log, ExecutionContext context)
{
logger.LogInformation("Trace Message");
}
How can I add additional metadata to each trace?
Yes this is possible.
Option 1: Using a Telemetry Initializer with the help of AsyncLocal:
public class CustomTelemetryInitializer : ITelemetryInitializer
{
public static AsyncLocal<string> MyValue = new AsyncLocal<string>();
public void Initialize(ITelemetry telemetry)
{
if (telemetry is ISupportProperties propertyItem)
{
propertyItem.Properties["myProp"] = MyValue.Value;
}
}
}
You can set the value of the AsyncLocal in the function like this:
[FunctionName("Function")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]
HttpRequest req,
ILogger log)
{
var name = req.Query["name"];
CustomTelemetryInitializer.MyValue.Value = name;
log.LogInformation("C# HTTP trigger function processed a request.");
return new OkObjectResult($"Hello, {name}");
}
When you run the function you can see the information in the portal:
You can find the whole code at this repo
Now, addressing your comments. Yes you need something extra. The ITelemetryInitializer instance needs to be registered using dependency injection. That is done in the Startup class as outlined in the documentation:
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
[assembly: FunctionsStartup(typeof(FunctionApp.Startup))]
namespace FunctionApp
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddSingleton<ITelemetryInitializer, CustomTelemetryInitializer>();
builder.Services.AddLogging();
}
}
}
Once registered the Application Insights SDK will use the CustomTelemetryInitializer.
Option 2
The other option does not involve any TelemetryInitializer, but you can only add properties to the generated RequestTelemetry that is added by the Azure Function App Insights integration. This is done by making using of the fact that the current TelemetryRequest is stored in the HttpContext:
[FunctionName("Function")]
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]
HttpRequest req,
ILogger log)
{
var name = req.Query["name"];
CustomTelemetryInitializer.MyValue.Value = name;
log.LogInformation("C# HTTP trigger function processed a request.");
var telemetryItem = req.HttpContext.Features.Get<RequestTelemetry>();
telemetryItem.Properties["SetInFunc"] = name;
return new OkObjectResult($"Hello, {name}");
}
This will show up in the portal as well:
Is it a problem that the context is added only to the Request? It might, but be aware you can query all related telemetry and know what context is involved, for example:
union (traces), (requests), (dependencies), (customEvents), (exceptions)
| extend itemType = iif(itemType == 'availabilityResult',itemType,iif(itemType == 'customEvent',itemType,iif(itemType == 'dependency',itemType,iif(itemType == 'pageView',itemType,iif(itemType == 'request',itemType,iif(itemType == 'trace',itemType,iif(itemType == 'exception',itemType,"")))))))
| extend prop = customDimensions.SetInFunc
| where ((itemType == 'trace' or (itemType == 'request' or (itemType == 'pageView' or (itemType == 'customEvent' or (itemType == 'exception' or (itemType == 'dependency' or itemType == 'availabilityResult')))))))
| top 101 by timestamp desc
will show:
All telemetry that comes from the same invocation will have the same operation_Id.
This could be implemented with ITelemetry Initializer, you could add custom dimension to a specified telemetry like only for request.
1.Install the following nuget packages:
Microsoft.ApplicationInsights, version 2.11.0
Microsoft.NET.Sdk.Functions, version 1.0.29
2.The below is my test code.
[assembly: WebJobsStartup(typeof(FunctionApp54.MyStartup))]
namespace FunctionApp54
{
internal class MyTelemetryInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
//use telemetry is RequestTelemetry to make sure only add to request
if (telemetry != null && telemetry is RequestTelemetry && !telemetry.Context.GlobalProperties.ContainsKey("testpro"))
{
telemetry.Context.GlobalProperties.Add("testpro", "testvalue");
}
}
}
public class MyStartup : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
builder.Services.AddSingleton<ITelemetryInitializer, MyTelemetryInitializer>();
}
}
public static class Function2
{
[FunctionName("Function2")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
return name != null
? (ActionResult)new OkObjectResult($"Hello, {name}")
: new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}
}
}

Azure Functions and Dependency Injection

I have the following Startup class in my Azure Function v2 project:
[assembly: FunctionsStartup(typeof(AzureAppDomainRegistration.Startup))]
namespace AzureAppDomainRegistration
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
var connString = System.Configuration.ConfigurationManager.AppSettings["ConnectionStrings:DataContext"];
//var connString = config["ConnectionStrings:DataContext"];
builder.Services.AddDbContext<DataContext>(options => options
.UseLazyLoadingProxies()
.UseSqlServer(connString));
builder.Services.AddTransient<IActionsRegistrationInfo, EfActionsRegistrationInfo>();
}
}
}
and Function:
public class Function100_CheckEmail
{
readonly IActionsRegistrationInfo _actionsRegistrationInfo;
public Function100_CheckEmail(IActionsRegistrationInfo actionsRegistrationInfo)
{
_actionsRegistrationInfo = actionsRegistrationInfo;
}
[FunctionName("Function100_CheckEmail")]
//public static IActionResult Run(
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
[Queue("email-message-admin-confirmation", Connection = "StorageConnectionString")]CloudQueue outputQueue,
ExecutionContext context,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
but when this function is being executed, I get the following errors on Azure Portal:
without DI it works fine. What is wrong?
.NET Core 2.2
ADDED:
I tried to remote debugger and I see, that Configure method of Startup file has exceptions (logged by App Insights) with ArgumentNullException, but no details. What can be it?
So in order to get a Environment Variable in Azure functions, you need to use
var connStr = Environment.GetEnvironmentVariable("ConnectionStrings:SQLConnectionString", EnvironmentVariableTarget.Process);
Also check if you have the value in local.settings.json which looks something like this
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "<connection string>",
"AzureWebJobsDashboard": "<connection string>"
},
"Host": {
"LocalHttpPort": 7071,
"CORS": "*"
},
"ConnectionStrings": {
"SQLConnectionString": "Value"
}
}
Refereces :
MSFT Docs
A good article

How to use dependency inject for TelemetryConfiguration in Azure Function

I try to use Dependency injection in Azure Functions for TelemetryConfiguration. In my function I will have it resolved when I inject TelemetryConfiguration in the functions constructor. I suppose I don't really understand how I will do with TelemetryConfiguration in StartUp, thats why I get an exception. How will I add the TelemetryConfiguration I already configured.
I have did an easy example here what I'm doing so far.
[assembly: FunctionsStartup(typeof(StartUp))]
public class StartUp : FunctionsStartup
{
private string OmsModule { get; } = "OMS.VA";
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.Configure<TelemetryConfiguration>(
(o) =>
{
o.InstrumentationKey = Environment.GetEnvironmentVariable("APPINSIGHTS_INSTRUMENTATIONKEY");
o.TelemetryInitializers.Add(new OperationCorrelationTelemetryInitializer());
});
}
}
public class StopPlaceUpdateTimerTrigger
{
private TelemetryClient _telemetryClient;
private string _azureWebJobsStorage;
public StopPlaceUpdateTimerTrigger(TelemetryConfiguration telemetryConfiguration)
{
_telemetryClient = new TelemetryClient(telemetryConfiguration);
}
[FunctionName("StopPlaceLoader")]
public async Task StopPlaceLoaderMain([TimerTrigger("%CRON_EXPRESSION%", RunOnStartup = true)]TimerInfo myTimerInfo, ILogger log, ExecutionContext context)
{
SetConfig(context);
var cloudTable = await GetCloudTableAsync();
if (cloudTable == null)
{
//Do nothing
}
//Do nothing
}
private async Task<CloudTable> GetCloudTableAsync()
{
var storageAccount = CloudStorageAccount.Parse(_azureWebJobsStorage);
var tableClient = storageAccount.CreateCloudTableClient();
var table = tableClient.GetTableReference(nameof(StopPlaceLoaderCacheRecord));
if (!await table.ExistsAsync())
{
await table.CreateIfNotExistsAsync();
}
return table;
}
private void SetConfig(ExecutionContext context)
{
var config = new ConfigurationBuilder()
.SetBasePath(context.FunctionAppDirectory)
.AddJsonFile("local.settings.json", optional: true)
.AddEnvironmentVariables()
.Build();
_azureWebJobsStorage = config["AzureWebJobsStorage"];
}
}
//local.settings.json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "DefaultEndpointsProtocol...",
"FUNCTIONS_WORKER_RUNTIME": "dotnet",
"EnableMSDeployAppOffline": "True",
"CRON_EXPRESSION": "0 */5 22-3 * * *",
"APPINSIGHTS_INSTRUMENTATIONKEY": "..."
}
}
I get the following Exception;
Microsoft.Extensions.DependencyInjection.Abstractions: Unable to resolve service for type 'Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration' while attempting to activate 'OMS.VA.RealTime.StopPlaceLoader.StopPlaceUpdateTimerTrigger'.
Update:
We can change this line of code var newConfig = TelemetryConfiguration.Active; to var newConfig = TelemetryConfiguration.CreateDefault(); , since TelemetryConfiguration.Active is deprecated.
Please use the code below for TelemetryConfiguration DI, I test it with a blob trigger function and works well:
using System.IO;
using System.Linq;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
[assembly: WebJobsStartup(typeof(FunctionApp17.MyStartup))]
namespace FunctionApp17
{
public class MyStartup : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
var configDescriptor = builder.Services.SingleOrDefault(tc => tc.ServiceType == typeof(TelemetryConfiguration));
if (configDescriptor?.ImplementationFactory != null)
{
var implFactory = configDescriptor.ImplementationFactory;
builder.Services.Remove(configDescriptor);
builder.Services.AddSingleton(provider =>
{
if (implFactory.Invoke(provider) is TelemetryConfiguration config)
{
var newConfig = TelemetryConfiguration.Active;
newConfig.ApplicationIdProvider = config.ApplicationIdProvider;
newConfig.InstrumentationKey = config.InstrumentationKey;
return newConfig;
}
return null;
});
}
}
}
public class Function1
{
private TelemetryClient _telemetryClient;
public Function1(TelemetryConfiguration telemetryConfiguration)
{
_telemetryClient = new TelemetryClient(telemetryConfiguration);
}
[FunctionName("Function1")]
public void Run([BlobTrigger("samples-workitems/{name}", Connection = "AzureWebJobsStorage")]Stream myBlob, string name, ILogger log)
{
log.LogInformation($"!!!!!!!!!! C# Blob trigger function Processed blob\n Name:{name} \n Size: {myBlob.Length} Bytes");
_telemetryClient.TrackTrace("this is a test message from DI of telemetry client !!!!!!!!!!!!!!");
}
}
}
the test result as below, I can see the logs in the application insights in azure portal:
And one more thing, I see you try to use ITelemetry Initializer in your code. You can follow this GitHub issue for your ITelemetry Initializer or Itelemetry Processor
If you use builder.Services.Configure<TelemetryConfiguration>() to configure, you are using Options pattern in ASP.NET Core.
To access the option, you need to do as following:
public StopPlaceUpdateTimerTrigger(IOptionsMonitor<TelemetryConfiguration> telemetryConfiguration)
{
_telemetryClient = new TelemetryClient(telemetryConfiguration.CurrentValue);
}
If you just want to directly use TelemetryConfiguration object, you need to add it in service collection:
builder.Services.AddSingleton<TelemetryConfiguration >(sp =>
{
var telemetryConfiguration = new TelemetryConfiguration();
telemetryConfiguration.InstrumentationKey = Environment.GetEnvironmentVariable("APPINSIGHTS_INSTRUMENTATIONKEY");
telemetryConfiguration.TelemetryInitializers.Add(new OperationCorrelationTelemetryInitializer());
return telemetryConfiguration;
}
Then you can :
public StopPlaceUpdateTimerTrigger(TelemetryConfiguration telemetryConfiguration)
{
_telemetryClient = new TelemetryClient(telemetryConfiguration);
}
Hope it helps.

Resources