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");
}
}
}
Related
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:
I'm trying to call two rest APIs (Azure Web Service APIs) in a Azure Function. I have written the below code but When I try to run it, the first API is only getting executed but the second one isn't.
The Logic should be like if the first API gets a response status as 200 Ok then only, it should proceed to call the next API.
namespace FunctionChainingApp
{
public static class FunctionChaining
{
[FunctionName("FunctionChaining")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)]HttpRequestMessage req, ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
try
{
// Call Your API
HttpClient abcdeClient = new HttpClient();
HttpRequestMessage abcdeRequest = new HttpRequestMessage(HttpMethod.Get, string.Format("https://abcde.azurewebsites.net/api/abcde/GetabcdeDetails"));
//Read Server Response
HttpResponseMessage abcdeResponse = await abcdeClient.SendAsync(abcdeRequest);
bool abcdeStatusCode200 = await abcdeResponse.Content.ReadAsAsync<bool>();
if (abcdeStatusCode200 == true)
{
// Call Your API
HttpClient vwxyzClient = new HttpClient();
HttpRequestMessage vwxyzRequest = new HttpRequestMessage(HttpMethod.Get, string.Format("https://vwxyz.azurewebsites.net/api/vwxyz/GetvwxyzDefaultDetails"));
//Read Server Response
HttpResponseMessage vwxyzResponse = await vwxyzClient.SendAsync(vwxyzRequest);
bool vwxyzStatusCode200 = await vwxyzResponse.Content.ReadAsAsync<bool>();
}
else
{
// Call Your API
abcdeRequest = new HttpRequestMessage(HttpMethod.Get, string.Format("https://abcde.azurewebsites.net/api/abcde/GetabcdeDetails"));
}
return req.CreateResponse(HttpStatusCode.OK);
}
catch (Exception ex)
{
return req.CreateResponse(HttpStatusCode.OK, "The Called Scheduler Failed : {0}", string.Format(ex.Message));
}
}
}
public class ResponseModel
{
public bool abcdeStatusCode200 { get; set; }
public bool vwxyzStatusCode200 { get; set; }
}
}
You need to separate these two functions and call them within DurableOrchestrationClient
var output = new List<string>();
output.Add(await context.CallActivityAsync<string>("CallAPI1", "test2019"));
output.Add(await context.CallActivityAsync<string>("CallAPI2", "test2"));
EXAMPLE
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
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.
I have HTTP trigger which invokes orchestra function. In HTTP trigger function I receive json string which I deserialize into object . However I have no idea how to pass this object to orchestra function or activity functions.
I have tried to include it as parameter of orchestra function but it gives me error
HTTP trigger
#load "../Shared/jsonObject.csx"
public static async Task<HttpResponseMessage> Run(
HttpRequestMessage req,
DurableOrchestrationClient starter,
string functionName,
ILogger log)
{
HttpContent requestContent = req.Content;
string jsonContent = requestContent.ReadAsStringAsync().Result;
jsonObject bsObj = JsonConvert.DeserializeObject<jsonObject> (jsonContent);
// Pass the function name as part of the route
string instanceId = await starter.StartNewAsync("Orchestra", bsObj);
log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
return starter.CreateCheckStatusResponse(req, instanceId);
}
Orchestra function
#load "../Shared/jsonObject.csx"
[FunctionName("Orchestra")]
public static async void Run(DurableOrchestrationContext context)
{
var output = await context.CallActivityAsync<int>("checkConditions", bsObj);
}
Activity functionn not implemented yet
#load "../Shared/jsonObject.csx"
public static string Run(jsonObject bsObj)
{
return "done";
}
[Error] run.csx(19,74): error CS0103: The name 'bsObj' does not exist in the current context
Please try running the below code
HTTP Trigger function.
[FunctionName("HttpTriggerCSharp")]
public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Function, "post")]HttpRequestMessage req,
[OrchestrationClient]DurableOrchestrationClient starter,
ILogger log)`
{
HttpContent requestContent = req.Content;
string jsonContent = requestContent.ReadAsStringAsync().Result;
JObject bsObj = JsonConvert.DeserializeObject<JObject>(jsonContent);
// Pass the function name as part of the route
string instanceId = await starter.StartNewAsync("Orchestra", bsObj);
log.LogInformation($"Started orchestration with ID = '{instanceId}'.");
return starter.CreateCheckStatusResponse(req, instanceId);
}
Orchestration Function
[FunctionName("Orchestra")]
public static async Task RunOrchestrator(
[OrchestrationTrigger] DurableOrchestrationContext context)
{
var bsObj = context.GetInput<JObject>();
var output = await context.CallActivityAsync<JObject>("checkConditions", bsObj);
}
Activity Function
[FunctionName("checkConditions")]
public static string SayHello([ActivityTrigger] JObject bsObj, ILogger log)
{
//Assign input value to any variable
var json = bsObj;
//Logic of the code
return "done";
}
I have referenced this doc for durable functions extention and this doc for HTTP Triggered functions.