What is LoggerExtension Args Parameter and how to integrate with Application insights? - azure

I thought using args parameter i will see a new custom dimension under customDimensions in Azure Application insights but it is not working for me. I cannot find any good information about how to use this parameter. What is it for and where in App insights is the information can be found?
I simply passed an array of strings but no where this object can be found in AI.
//
// Summary:
// Formats and writes an error log message.
//
// Parameters:
// logger:
// The Microsoft.Extensions.Logging.ILogger to write to.
//
// exception:
// The exception to log.
//
// message:
// Format string of the log message in message template format. Example:
// "User {User} logged in from {Address}"
//
// args:
// An object array that contains zero or more objects to format.
public static void LogError(this ILogger logger, Exception exception, string message, params object[] args)
{
logger.Log(LogLevel.Error, exception, message, args);
}

I thought using args parameter i will see a new custom dimension under customDimensions in Azure Application insights
It does, but only if you supply a message template. For example, the following won't work:
logger.LogError(ex, "Error occured", "a", "simple", "string");
but this will:
logger.LogError(ex, "Error occured {PropA} {PropB} {PropC}", "a", "simple", "string");
The last line will result in three properties (named PropA, PropB and PropC) in the customDimensions field of the generated telemetry.

Related

Application Insights telemetry correlation using log4net

I'm looking to have our distributed event logging have proper correlation. For our Web Applications this seems to be automatic. Example of correlated logs from one of our App Services API:
However, for our other (non ASP, non WebApp) services were we use Log4Net and the App Insights appender our logs are not correlated. I tried following instructions here: https://learn.microsoft.com/en-us/azure/azure-monitor/app/correlation
Even after adding unique operation_Id attributes to each operation, we're not seeing log correlation (I also tried "Operation Id"). Example of none correlated log entry:
Any help on how to achieve this using log4net would be appreciated.
Cheers!
Across services correlation IDs are mostly propagated through headers. When AI is enabled for Web Application, it reads IDs/Context from the incoming headers and then updates outgoing headers with the appropriate IDs/Context. Within service, operation is tracked with Activity object and every telemetry emitted will be associated with this Activity, thus sharing necessary correlation IDs.
In case of Service Bus / Event Hub communication, the propagation is also supported in the recent versions (IDs/Context propagate as metadata).
If service is not web-based and AI automated correlation propagation is not working, you may need to manually get incoming ID information from some metadata if any exists, restore/initiate Activity, start AI operation with this Activity. When telemetry item is generated in scope of that Activity, it will get proper IDs and will be part of the overarching trace. With that, if telemetry is generated from Log4net trace that was executed in the scope of AI operation context then that telemetry should get right IDs.
Code sample to access correlation from headers:
public class ApplicationInsightsMiddleware : OwinMiddleware
{
// you may create a new TelemetryConfiguration instance, reuse one you already have
// or fetch the instance created by Application Insights SDK.
private readonly TelemetryConfiguration telemetryConfiguration = TelemetryConfiguration.CreateDefault();
private readonly TelemetryClient telemetryClient = new TelemetryClient(telemetryConfiguration);
public ApplicationInsightsMiddleware(OwinMiddleware next) : base(next) {}
public override async Task Invoke(IOwinContext context)
{
// Let's create and start RequestTelemetry.
var requestTelemetry = new RequestTelemetry
{
Name = $"{context.Request.Method} {context.Request.Uri.GetLeftPart(UriPartial.Path)}"
};
// If there is a Request-Id received from the upstream service, set the telemetry context accordingly.
if (context.Request.Headers.ContainsKey("Request-Id"))
{
var requestId = context.Request.Headers.Get("Request-Id");
// Get the operation ID from the Request-Id (if you follow the HTTP Protocol for Correlation).
requestTelemetry.Context.Operation.Id = GetOperationId(requestId);
requestTelemetry.Context.Operation.ParentId = requestId;
}
// StartOperation is a helper method that allows correlation of
// current operations with nested operations/telemetry
// and initializes start time and duration on telemetry items.
var operation = telemetryClient.StartOperation(requestTelemetry);
// Process the request.
try
{
await Next.Invoke(context);
}
catch (Exception e)
{
requestTelemetry.Success = false;
telemetryClient.TrackException(e);
throw;
}
finally
{
// Update status code and success as appropriate.
if (context.Response != null)
{
requestTelemetry.ResponseCode = context.Response.StatusCode.ToString();
requestTelemetry.Success = context.Response.StatusCode >= 200 && context.Response.StatusCode <= 299;
}
else
{
requestTelemetry.Success = false;
}
// Now it's time to stop the operation (and track telemetry).
telemetryClient.StopOperation(operation);
}
}
public static string GetOperationId(string id)
{
// Returns the root ID from the '|' to the first '.' if any.
int rootEnd = id.IndexOf('.');
if (rootEnd < 0)
rootEnd = id.Length;
int rootStart = id[0] == '|' ? 1 : 0;
return id.Substring(rootStart, rootEnd - rootStart);
}
}
Code sample for manual correlated operation tracking in isolation:
async Task BackgroundTask()
{
var operation = telemetryClient.StartOperation<DependencyTelemetry>(taskName);
operation.Telemetry.Type = "Background";
try
{
int progress = 0;
while (progress < 100)
{
// Process the task.
telemetryClient.TrackTrace($"done {progress++}%");
}
// Update status code and success as appropriate.
}
catch (Exception e)
{
telemetryClient.TrackException(e);
// Update status code and success as appropriate.
throw;
}
finally
{
telemetryClient.StopOperation(operation);
}
}
Please note that the most recent version of Application Insights SDK is switching to W3C correlation standard, so header names and expected format would be different as per W3C specification.

In Azure function that returns ServiceBus message, how do I conditionally returning message?

I have an azure function which returns a service bus message. However, I want to conditionally return a service bus message, instead of being forced to return the message every time.
here is an example
[FunctionName("ServiceBusOutput")]
[return: ServiceBus("myqueue", Connection = "ServiceBusConnection")]
public static string ServiceBusOutput([HttpTrigger] dynamic input, ILogger log)
{
log.LogInformation($"C# function processed: {input.Text}");
// check condition here, abort return completely
// Otherwise, return
return input.Text;
}
Said another way, I want to return a message on a service bus when certain conditions apply within the function code block. Is this possible?
One idea that does not work is to throw an exception. However, this just results in the message being placed into the DL queue. I want to completely abort the operation of returning the message on the service bus, and avoid DL.
Another idea that does not work is to simply execute
return;
But this results in compile-time error, which is sort of expected
"An object of a type convertible to 'MyReturnType1' is required"
I can think of a hack which I dont like, which is to return null, and handle the null later in the chain. But this is sort of dirty to me.
You could just bind ServiceBus as MessageSender type, then use the SendAsync() method to send the message.
The below is my test code, if the request name equals "george", it will send the name to the message queue.
public static class Function1
{
[FunctionName("Function1")]
public static async System.Threading.Tasks.Task RunAsync(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
[ServiceBus("myqueue", Connection = "ServiceBusConnection")] MessageSender messagesQueue,
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;
if (name.Equals("george")) {
byte[] bytes = Encoding.ASCII.GetBytes(name);
Message m1 = new Message();
m1.Body = bytes;
await messagesQueue.SendAsync(m1);
}
}
}
Suppose this is what you want, hope this could help you, if you still have other problem please feel free to let me know.
Instead of using the output binding available in Azure Function, you can send a message to the queue from a custom queue client created inside the function.
Posting the message based on a condition is not possible with the bindings.

Azure Functions dynamic input / output binding based on Service Bus trigger message content

I'm trying to create Azure Function 2.0 with multiple bindings. The function gets triggered by Azure Service Bus Queue message and I would like to read Blob based on this message content. I've already tried below code:
public static class Functions
{
[FunctionName(nameof(MyFunctionName))]
public static async Task MyFunctionName(
[ServiceBusTrigger(Consts.QueueName, Connection = Consts.ServiceBusConnection)] string message,
[Blob("container/{message}-xyz.txt", FileAccess.Read, Connection = "StorageConnName")] string blobContent
)
{
// processing the blob content
}
}
but I'm getting following error:
Microsoft.Azure.WebJobs.Host: Error indexing method 'MyFunctionName'. Microsoft.Azure.WebJobs.Host: Unable to resolve binding parameter 'message'. Binding expressions must map to either a value provided by the trigger or a property of the value the trigger is bound to, or must be a system binding expression (e.g. sys.randguid, sys.utcnow, etc.).
I saw somewhere that dynamic bindings can be used but perhaps it's not possible to create input binding based on another input binding. Any ideas?
I'm actually surprise that did not work. There are lots of quirks with bindings. Please give this a shot:
public static class Functions
{
[FunctionName(nameof(MyFunctionName))]
public static async Task MyFunctionName(
[ServiceBusTrigger(Consts.QueueName, Connection = Consts.ServiceBusConnection)] MyConcreteMessage message,
[Blob("container/{message}-xyz.txt", FileAccess.Read, Connection = "StorageConnName")] string blobContent
)
{
// processing the blob content
}
}
Create a DTO:
public class MyConcreteMessage
{
public string message {get;set;}
}
Ensure that the message that you are using in the servicebus is something like this:
{
"message": "MyAwesomeFile"
}
It should now be able to read your binding container/{message}-xyz.txt and recognize that message

How to troubleshoot Flaky Azure function app behaviour?

I am trying out Azure Function Apps.
The first one following the example in a tutorial with Open Weather map, stopped working after I used log.WriteLine(), which correctly threw a compiler error. I changed to log.Info() and it kept complaining about TraceWriter not containing a definition for WriteLine.
After a lengthy troubleshooting session, I created a new function, copying all the content of the broken one, and it worked immediately.
Created a new function, as before, and began making changes to the Run() method, and running this function yields:
"The resource you are looking for has been removed, had its name
changed, or is temporarily unavailable."
Bearing in mind, the function URL is based on the default key Azure generates when the function is created: https://.azurewebsites.net/api/WeatherWhereYouAre?code=my1really2RAndom3defauLT4Key5from6Azure==
Created yet another function, with no changes from the default "Hello Azure" sample, and it yields a 500 error with:
"Exception while executing function: Functions.HttpTriggerCSharp2 ->
One or more errors occurred. -> Exception binding parameter 'req' ->
Input string was not in a correct format."
This is the content of the project.json file:
{
"frameworks": {
"net46": {
"dependencies": {
"Microsoft.IdentityModel.Clients.ActiveDirectory": "3.16.0",
"Microsoft.Azure.KeyVault": "2.3.2",
"Microsoft.AspNet.WebApi.Client": "5.2.3"
}
}
}
}
And the run.csx:
using System.Net;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
// parse query parameter
string name = req.GetQueryNameValuePairs()
.FirstOrDefault(q => string.Compare(q.Key, "name", true) == 0)
.Value;
// Get request body
dynamic data = await req.Content.ReadAsAsync<object>();
// Set name to query string or body data
name = name ?? data?.name;
return name == null
? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")
: req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
}
EDIT
In the above image, note that this is httpTriggerFSharp1, but the exception is HttpTriggerCSharp2 (which is the only one that works!)
Is there a way I can properly troubleshoot these?
For the default HttpTrigger template for C#, you could call it as follows:
Get https://brucefunapp.azurewebsites.net/api/HttpTriggerCSharp3?name=bruce&code=ItDhLMxwDYmTvMTYzVbbALtL5GEcmaL5DlzSaD4FRIuFdh17ZkY71g==
Or
Post https://brucefunapp.azurewebsites.net/api/HttpTriggerCSharp3?code=ItDhLMxwDYmTvMTYzVbbALtL5GEcmaL5DlzSaD4FRIuFdh17ZkY71g==
Content-type: application/json
{"name": "bruce"}
For more details about Azure Functions C# script, you could refer to here.
Is there a way I can properly troubleshoot these?
Per my understanding, you could leverage Precompiled functions and use Visual Studio 2017 Tools for Azure Functions for creating, local debugging, and publishing to Azure.

log.Info and log.Verbose - where can I see the output in Azure portal?

Following is some code extracted from my Azure Function which is being called from a Logic App:
public static async Task<object> Run(HttpRequestMessage req, TraceWriter log)
{
log.Verbose($"Function Run Called");
var jsonContent = await req.Content.ReadAsStringAsync();
log.Info($"jsonContent var assigned {jsonContent}");
dynamic data = JsonConvert.DeserializeObject(jsonContent.ToString());
log.Verbose($"data var assigned");
log.Verbose($"JsonContent: {data.FileContent}!");
bool result = true;
return req.CreateResponse(HttpStatusCode.OK, new {
result = $"Hello {result}!"
});
}
Once executed, I can see the function executed successfully without any errors, but I am unable to see what "log.Verbose" has printed. I have also tried log.Info but I don't see any output.
Any idea from where I can check the output of log.Info and log.Verbose?
If in your scenario you're not actually using our Functions portal for the invocations, then to see the logs, you can go to the "Monitor" page for your function. From the invocation log you can select individual functions and see their output under the invocation details section.
When running functions from our Functions portal, you'll see the logs in the log stream window. Note that the default TraceLevel configured for a Function app is Info. So you won't see Verbose logs. You can configure the TraceLevel in your host.json file by setting the tracing.consoleLevel property. See here for more information.

Resources