Azure Functions and Dependency Injection - azure

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

Related

ILogger sink for Azure Serverless Functions

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:

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");
}
}
}

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.

Unable to run azure function getting error as connection string not set

I have this azure function which shouldread a message from service bus queue.
I have already given connection string into code still throws error while running func -
Set the connection string named 'Endpoint=sb://demoser
vicebus2019.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKe
y;[Hidden Credential]' in the connectionStrings section of the .config file in t
he following format <add name="Endpoint=sb://demoservicebus2019.servicebus.windo
ws.net/;SharedAccessKeyName=RootManageSharedAccessKey;[Hidden Credential]" conne
ctionString="DefaultEndpointsProtocol=http|https;AccountName=NAME;AccountKey=KEY
code-
Function1.cs
public static class Function1
{
[FunctionName("Function1")]
public static void Run([QueueTrigger("customer", Connection = "Endpoint=sb://demoservicebus2019.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=text")]string myQueueItem, TraceWriter log)
{
log.Info($"C# Queue trigger function processed: {myQueueItem}");
}
}
local.setting.json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "storageaccount-connectionstring",
"AzureWebJobsDashboard": "storageaccount-connectionstring"
}
}
So the connection property is the Key for the environment/configuration variable and not the actual connection string.
You can read about the bindings here
The name of an app setting that contains the Storage connection string to use for this binding
So change it to this:
public static class Function1
{
[FunctionName("Function1")]
public static void Run([QueueTrigger("customer", Connection = "MyConnection")]string myQueueItem, TraceWriter log)
{
log.Info($"C# Queue trigger function processed: {myQueueItem}");
}
}
Configuration
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "storageaccount-connectionstring",
"AzureWebJobsDashboard": "storageaccount-connectionstring"
"MyConnection":"Endpoint=sb://demoservicebus2019.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=text"
}
}

Resources