Adding Custom Dimension to Request Telemetry - Azure functions - azure

I am creating a new Function app using v2.x and I am integrating Application Insights for request logging that is automatically being done as Azure Function is now integrated with App Insights (as mentioned in the documentation link). What I would need to do is log few custom fields in the custom dimensions in Application Insights Request Telemetry. Is it possible without using Custom Request logging (using TrackRequest method)

About adding custom properties, you could refer to this tutorial:Add properties: ITelemetryInitializer. The below is my test a HTTP trigger function.
public static class Function1
{
private static string key = "Your InstrumentationKey";
private static TelemetryClient telemetry = new TelemetryClient() { InstrumentationKey = key };
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
if (!telemetry.Context.Properties.ContainsKey("Function_appName"))
{
telemetry.Context.Properties.Add("Function_appName", "testfunc");
}
else
{
telemetry.Context.Properties["Function_appName"] = "testfunc";
}
telemetry.TrackEvent("eventtest");
telemetry.TrackTrace("tracetest");
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");
}
}
After running this function, go to the Application Insights Search could check the data Or go to Logs(Analytics).
Update:

You should use ITelemetry Initializer(which can add custom dimension to a specified telemetry like only for request) in function app, please follow the steps below:
1.In Visual studio, create a function app(In my test, I create a blob triggerd function), and install the following nuget packages:
Microsoft.ApplicationInsights, version 2.10.0
Microsoft.NET.Sdk.Functions, version 1.0.29
2.Then in the Function1.cs, write code like below:
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.IO;
[assembly: WebJobsStartup(typeof(FunctionApp21.MyStartup))]
namespace FunctionApp21
{
public static class Function1
{
[FunctionName("Function1")]
public static 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");
}
}
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("my_custom_dimen22"))
{
telemetry.Context.GlobalProperties.Add("my_custom_dimen22", "Hello, this is custom dimension for request!!!");
}
}
}
public class MyStartup : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder)
{
builder.Services.AddSingleton<ITelemetryInitializer, MyTelemetryInitializer>();
}
}
}
3.Publish it to azure, then nav to azure portal -> the published function app -> Monitor -> Add an application insights.
4.Run the function from azure. And wait for a few minutes -> nav to the application insights portal, check the telemetry data, and you can see the custom dimension is only added to request telemetry:

The other solutions don't quite answer the question, how to add custom properties to the request telemetry. There is a very simple solution, add the following within your function's code:
Activity.Current?.AddTag("my_prop", "my_value");
You'll need:
using System.Diagnostics;
This then can be dynamic per function invocation / request, rather a fixed global property.

Related

Azure Function binding working locally but not in portal

I have this azure function v3:
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Microsoft.Azure.Cosmos.Table;
namespace FunctionApp4
{
public static class Function1
{
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
[Table("Items")] CloudTable table,
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;
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.";
return new OkObjectResult(responseMessage);
}
}
}
Runs perfectly locally but when published to the portal I get:
Error indexing method 'Function1' Cannot bind parameter 'table' to type CloudTable. Make sure the parameter Type is supported by the binding. If you're using binding extensions (e.g. Azure Storage, ServiceBus, Timers, etc.) make sure you've called the registration method for the extension(s) in your startup code (e.g. builder.AddAzureStorage(), builder.AddServiceBus(), builder.AddTimers(), etc.).
Does anyone know what could be causing this?
These are the dependencies for the function:

Error : Format of the initialization string does not conform to specification starting at index 0. when trying to invoke the function app

I am testing my deployed Azure function and getting the following error. My function runs locally connecting to Azure database but fails when its deployed and run. I have configured the application settings to read the secret url to the connection string.
This is how my connectionstring looks like
Server=tcp:ranjitazuredb.database.windows.net,1433;Initial Catalog=Srl;Persist Security Info=False;User ID=usr;Password=pwd;MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;
Application setting - Url to the secret
https://srlcustomermanagervault.vault.azure.net/secrets/ConnectionString
Function
public class GetCustomersOrders
{
private readonly ICustomerOrdersRepository _repo;
private readonly IMapper _mapper;
private readonly TelemetryClient _telemetryClient;
public GetCustomersOrders(ICustomerOrdersRepository repo, IMapper mapper, TelemetryConfiguration configuration)
{
_repo = repo;
_mapper = mapper;
_telemetryClient = new TelemetryClient(configuration);
}
[FunctionName("GetCustomersOrders")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "customer-orders")] HttpRequest req,
ILogger log)
{
this._telemetryClient.TrackTrace("C# HTTP trigger function processed a request.");
var customersOrders = _repo.GetCustomerOrders();
return new OkObjectResult(_mapper.Map<List<CustomerOrdersViewModel>>(customersOrders));
}
}
This is how I have assigned the policy
Function start up
[assembly: FunctionsStartup(typeof(Startup))]
namespace SRL.CustomerOrder
{
internal class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
var connectionString = Environment.GetEnvironmentVariable("ConnectionString");
builder.Services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
builder.Services.AddScoped<ISrlContext, CustomerManagerContext>();
builder.Services.AddAutoMapper(typeof(Startup));
builder.Services.AddDbContext<CustomerManagerContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddTransient<ICustomerDetailsRepository, CustomerDetailsRepository>();
builder.Services.AddTransient<ICustomerOrdersRepository, CustomerOrdersRepository>();
builder.Services.AddTransient<IOrderDetailsRepository, OrderDetailsRepository>();
}
}
}
Presuming the connection string worked when you used it directly in the app settings I would check out this link
https://learn.microsoft.com/en-us/azure/app-service/app-service-key-vault-references
So in your example you would use
#Microsoft.KeyVault(SecretUri=https://srlcustomermanagervault.vault.azure.net/secrets/ConnectionString
)
The documentation says you need the version id but you do not, (it is a bug that it works). Azure is working on a release so that it works without a version which should probably be out in preview by now and if not shortly. I have talked with several people and have it working for a client without the version.

Application Insights with multiple applications

I have an Application Insights which logs traces from an App Service and an App Function (one resource for 2 functions).
I need to filter traces according to the resource (App Service or App Function) and, if possible, for the App Function which function is actually logging.
Looking at the traces I see the following list of properties:
I thought to find the resource name in the appName property, instead there is the Application Insights resource name, which is useless for me, since all those traces are from that resource.
Note: I don't like the workaround to set a prefix in the message to filter the traces.
UPDATE
I followed Peter Bons suggestions and I created a brand new Function V3 project. The basic version of the project worked also without the Telemetry Initializer, I mean that the Cloud_RoleName property was correctly populated.
Then, I added my changes to adapt the sample code and I found that the problem comes up when I inject a new Telemetry Client. I know, it is not recommended to manually inject TelemetryClient in App Function, but I absolutely need to send Custom Event to Application Insights and, as far as I know, it is not possible with ILogger interface used by default in App Function.
Startup.cs
public class Startup : FunctionsStartup
{
private TelemetryConfiguration telemetryConfiguration;
public override void Configure(IFunctionsHostBuilder builder)
{
var localRoot = Environment.GetEnvironmentVariable("AzureWebJobsScriptRoot");
var azureRoot = $"{Environment.GetEnvironmentVariable("HOME")}/site/wwwroot";
var configBuilder = new ConfigurationBuilder()
.SetBasePath(localRoot ?? azureRoot)
.AddEnvironmentVariables()
.AddJsonFile("local.settings.json", optional: true, reloadOnChange: true);
var configuration = configBuilder.Build();
if (builder != null)
{
this.ConfigureServices(builder.Services, configuration);
}
}
private void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
services.AddSingleton<ITelemetryInitializer>(x => new CustomTelemetryInitializer(configuration["appFunctionName"]));
telemetryConfiguration = new TelemetryConfiguration(configuration["APPINSIGHTS_INSTRUMENTATIONKEY"]);
telemetryConfiguration.TelemetryInitializers.Add(new OperationCorrelationTelemetryInitializer());
var telemetryClient = new TelemetryClient(telemetryConfiguration);
services.AddSingleton(telemetryClient);
services.AddSingleton<ISampleInterface, SampleService>();
}
}
CustomTelemetryInitializer.cs
public class CustomTelemetryInitializer : ITelemetryInitializer
{
private readonly string roleName;
public CustomTelemetryInitializer(string roleName)
{
this.roleName = roleName;
}
public void Initialize(ITelemetry telemetry)
{
if (string.IsNullOrEmpty(telemetry?.Context?.Cloud?.RoleName))
{
telemetry.Context.Cloud.RoleName = roleName;
}
}
}
SampleService.cs
public class SampleService : ISampleInterface
{
private TelemetryClient telemetryClient;
public SampleService(TelemetryClient telemetryClient)
{
this.telemetryClient = telemetryClient;
}
public void TestAppInsights()
{
telemetryClient.TrackEvent("Sample Custom Event with init");
telemetryClient.TrackTrace("Sample Custom Trace with init");
}
}
Function.cs
public class Function1
{
private ISampleInterface service;
public Function1(ISampleInterface service)
{
this.service = service;
}
[FunctionName("Function1")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request with init.");
this.service.TestAppInsights();
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.";
return new OkObjectResult(responseMessage);
}
}
How about inspecting the cloud_RoleName property, available to all telemetry? By default it will have the name of the webapp or function (including slot names) as the value.
Otherwise, if you want to add custom properties or modify properties for all telemetry at one place you can make use of a telemetry initializer as demonstrated here:
using Microsoft.ApplicationInsights.Channel;
using Microsoft.ApplicationInsights.Extensibility;
namespace CustomInitializer.Telemetry
{
public class MyTelemetryInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
telemetry.Context.Cloud.RoleName = "HttpTriggered";
}
}
}
This avoids having to prefix all traces as you mentioned as a work around by having a single piece of code all telemetry passes through:
Another thing
[...] but I absolutely need to send Custom Event to Application Insights and, as far as I know, it is not possible with ILogger interface used by default in App Function.
Do note that you can redirect the output emitted by using the ILogger interface to Application Insights. It will show up as a trace.

Azure Event Grid - Event Delivery Security at Azure Function

I have been working on a POC related to Azure Event Grid integration with Azure Function. I am stuck on the Event delivery Security as mentioned here.
I am using Event Grid Trigger which is sent by built-in Event grid Subscription in Azure Blob Storage. I have added an access token as a query parameter in WebHook endpoint as mentioned in the above URL.
But I cannot access that parameter in the Function code. Can someone share a sample for doing this?
FYI - Below is the function definition in my code.
[FunctionName("EventGridFunc")]
public static void Run([EventGridTrigger]EventGridEvent eventGridEvent,
TraceWriter log)
{
log.Info("Received a trigger.");
log.Info(eventGridEvent.Data.ToString());
}
The full subscriberUrl for EventGridTrigger function has the following format:
https://{FunctionAppName}.azurewebsites.net/admin/extensions/EventGridExtensionConfig?functionName={EventGridTriggerFunctionName}&code={masterKey}
As you can see, the EventGridTrigger is basically a special HttpTrigger (push) function with a "hidden pre-processing" of the event message for its validation.
Update:
I didn't see a way how to obtain a query string in the EventGridTrigger. However, there are few workarounds for your solution such as:
using an Application settings
using an Azure Key Vault for storing a secret
using a HttpTrigger instead of EventGridTrigger
The following code snippet shows an example of the HttpTrigger function for EventGrid (version 2018-05-01-preview) subscriber:
#r "Newtonsoft.Json"
using System.Net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Linq;
using System.Threading.Tasks;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, IDictionary<string, string> query, TraceWriter log)
{
log.Info("C# HTTP trigger function processed an EventGrid request.");
log.Info($"\nHeaders:\n\t{string.Join("\n\t", req.Headers.Where(i => i.Key.StartsWith("aeg-")).Select(i => $"{i.Key}={i.Value.First()}"))}");
log.Info($"\nQuery:\n\t{string.Join("\n\t", query.Select(i => $"{i.Key}={i.Value}"))}");
string eventGridValidationHeader = req.Headers.FirstOrDefault( x => string.Compare(x.Key,"Aeg-Event-Type", true) == 0).Value?.FirstOrDefault().Trim();
// media type = application/json or application/cloudevents+json
string jsontext = null;
var jtoken = JToken.Parse(await req.Content.ReadAsStringAsync());
log.Info($"\n{jtoken.ToString(Newtonsoft.Json.Formatting.Indented)}");
if (jtoken is JArray)
jsontext = jtoken.SingleOrDefault<JToken>().ToString();
else if (jtoken is JObject)
jsontext = jtoken.ToString();
var eventGridEvent = JsonConvert.DeserializeAnonymousType(jsontext, new { EventType = "", Data = new JObject()});
if (string.IsNullOrEmpty(eventGridValidationHeader) || string.IsNullOrEmpty(eventGridEvent?.EventType) || eventGridEvent?.Data == null)
{
return req.CreateErrorResponse(HttpStatusCode.BadRequest, "No EventGrid message.");
}
if (eventGridValidationHeader == "SubscriptionValidation" && eventGridEvent.EventType == "Microsoft.EventGrid.SubscriptionValidationEvent")
{
log.Verbose(#"Event Grid Validation event received.");
return req.CreateResponse(HttpStatusCode.OK, JsonConvert.DeserializeObject(JsonConvert.SerializeObject(new { validationResponse = ((dynamic)eventGridEvent.Data).validationCode })));
}
#region Event Processing
// for testing a retry delivery policy
//return req.CreateResponse(HttpStatusCode.BadRequest, "Testing");
#endregion
return req.CreateResponse(HttpStatusCode.NoContent);
}

Azure Functions and DocumentDB triggers

Is it possible to specify the DocumentDB is to fire triggers when writing to DocumentDB?
I have an Azure function that pulls JSON messages off a Service Bus Queue and puts them into DocumentDB like so:
using System;
using System.Threading.Tasks;
public static string Run(string myQueueItem, TraceWriter log)
{
log.Info($"C# ServiceBus queue trigger function processed message: {myQueueItem}");
return myQueueItem;
}
This inserts new documents into the database as they are added to the service bus queue, however I need DocumentDB to process these as they are added and add attachments. This cannot be done in the present setup and I would like to tell DocumentDB to fire a trigger.
I have tried something like this:
using System;
using System.Threading.Tasks;
public static string Run(string myQueueItem, TraceWriter log)
{
log.Info($"C# ServiceBus queue trigger function processed message: {myQueueItem}");
return "x-ms-documentdb-post-trigger-include: addDocument\n" + myQueueItem;
}
It doesn't work and gives me errors like this:
Exception while executing function:
Functions.ServiceBusQueueTriggerCSharp1. Microsoft.Azure.WebJobs.Host:
Error while handling parameter _return after function returned:.
Newtonsoft.Json: Unexpected character encountered while parsing value:
x. Path '', line 0, position 0.
I like this setup because I can saturate the queue with requests to add records and they just buffer until the database can deal with it, which deals with spikes in demand, but it allows data offload from the client machine as fast as the network can carry it and then the queue/database combination gets caught up when demand drops again.
You could refer to the following code sample to create document with the trigger enabled in Azure Functions.
using System;
using System.Threading.Tasks;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
public static void Run(string myQueueItem, TraceWriter log)
{
string EndpointUri = "https://{documentdb account name}.documents.azure.com:443/";
string PrimaryKey = "{PrimaryKey}";
DocumentClient client = new DocumentClient(new Uri(EndpointUri), PrimaryKey);
client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri("{databaseid}", "{collenctionid}"), new MyChunk { MyProperty = "hello" },
new RequestOptions
{
PreTriggerInclude = new List<string> { "YourTriggerName" },
}).Wait();
log.Info($"C# ServiceBus queue trigger function processed message: {myQueueItem}");
}
public class MyChunk
{
public string MyProperty { get; set; }
}
Note: for using Microsoft.Azure.DocumentDB NuGet package in a C# function, please upload a project.json file to the function's folder in the function app's file system.
project.json
{
"frameworks": {
"net46":{
"dependencies": {
"Microsoft.Azure.DocumentDB": "1.13.1"
}
}
}
}
Besides, please make sure you have created triggers in your DocumentDB, for details about creating triggers, please refer to this article.

Resources