1
votes

We have an Azure Function that runs every few seconds. We want to use New Relic to let us know if this code throws exceptions or starts misbehaving. I've gotten the New Relic agent installed on the VM, and brought in the NewRelic.Agent.Api DLL through NuGet. I've set the NewRelic.AppName to AzureFunctionAppTest and set the NEWRELIC_LICENSEKEY environment variable. The log files indicate I'm connecting to NewRelic. Now, I'm running the following code:

#r "Microsoft.windowsazure.storage"

using System;
using System.Threading.Tasks;
using Microsoft.WindowsAzure.Storage.Table;

public static void Run(TimerInfo myTimer, TraceWriter log, CloudTable syncTimesTable)
{
    NewRelic.Api.Agent.NewRelic.SetTransactionName("SyncEvents", "GetRoutineData");

    var rnd = new Random(DateTime.Now.Millisecond).Next(30);
    log.Info($"Logging {rnd}");

    var eventAttributes = new Dictionary<string, object>() {{"result", "Success!"}, {"Count", rnd.ToString()}};
    NewRelic.Api.Agent.NewRelic.RecordCustomEvent("DevicesSynced", eventAttributes);

    NewRelic.Api.Agent.NewRelic.NoticeError(new SyncException("This is another custom error"));
}

public class SyncException : Exception
{
    public SyncException(string msg) : base(msg)
    {
    }
}

So, basically I'm trying to set the transaction name, then I generate a random number and log it as a custom event, and then I try to log a custom error.

I let this run for a bit and then check in with the New Relic dashboard. Here's what I get:

  • I see my new application show up on the APM dashboard.
  • On the Insights Dashboard, I can query SELECT * FROM DevicesSynced SINCE 30 MINUTES AGO and I see a table with a bunch of records that say "Success!" with a random number in the count column.
  • In APM->Events->Errors, I see a "URL and type" of "NewRelic.Api.Agent.NoticeError API Call Submission#0+SyncException" with the Message "This is another custom error" and a count.
  • In Error analytics, I see "Great! You have no errors for this time window!" (Last 30 minutes)
  • In Monitoring->Transactions, I only see transactions for HTTP requests such as /Home/Get, /Admin/GetHostStatus, /Keys/Get and /Admin/GetFunctionStatus. I think these HTTP requests are from inner workings of Azure Functions and maybe occur when I click around in the Azure console. I do not see any sort of transaction called GetRoutineData. If I run SELECT * FROM Transactions I also don't see this.

I have a feeling that I'm not actually in a transaction in my Azure Function code, so New Relic isn't letting me set a name. I also have a feeling that even though I log an error using NoticeError, it doesn't get linked to any transaction thus it doesn't show up in Error analytics.

My questions:

  • Is there a way to get New Relic to recognize this code path as a transaction?
  • If not, is there at least a way to set up New Relic Application Monitoring to alert me if a certain exception type (such as my SyncException class) occurs or reaches a certain thresh hold?

Thanks!

UPDATE:

I tried adding a file called CustomInstrumentation.xml in the Extensions directory, as per the New Relic instructions with the following contents:

<?xml version="1.0" encoding="utf-8"?>
<extension
    xmlns="urn:newrelic-extension">
    <instrumentation>
        <!-- Define the method which triggers the creation of a transaction. -->
        <tracerFactory name="NewRelic.Agent.Core.Tracer.Factories.BackgroundThreadTracerFactory" metricName="Background/AzureDeviceSync">
            <match assemblyName="Microsoft.Azure.WebJobs.Script" className="Microsoft.Azure.WebJobs.Script.Description.FunctionInvokerBase">
                <exactMethodMatcher methodName="Invoke" />
            </match>
        </tracerFactory>
    </instrumentation>
</extension>

However, I don't see any difference in behavior. I've tried a few different method name matches (such as InvokeCore and Invoke) however it doesn't appear to make a difference.

Few questions:

The New Relic Docs says:

To view the transaction: From the New Relic menu bar, select APM > Applications > (selected app) > Monitoring > Transactions > Type > (selected type). (The type is defined by Category/Name.) You can then view all custom transactions of the selected type.

It shows a picture of a dropdown menu with a Custom option:

enter image description here

However, my New Relic interface doesn't have any sort of "Type" dropdown:

enter image description here

Also, if I instrument my code to show a stack trace from within the Function app, here's what I get. Maybe it'll help:

Stack:

   at System.Environment.GetStackTrace(Exception e, Boolean needFileInfo)
   at System.Environment.get_StackTrace()
   at Submission#0.Run(TimerInfo myTimer, TraceWriter log, CloudTable syncTimesTable) in :line 9
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
   at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Microsoft.Azure.WebJobs.Script.Description.DotNetFunctionInvoker.<InvokeCore>d__23.MoveNext()
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[TStateMachine](TStateMachine& stateMachine)
   at Microsoft.Azure.WebJobs.Script.Description.DotNetFunctionInvoker.InvokeCore(Object[] parameters, FunctionInvocationContext context)
   at Microsoft.Azure.WebJobs.Script.Description.FunctionInvokerBase.<Invoke>d__29.MoveNext()
   at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[TStateMachine](TStateMachine& stateMachine)
   at Microsoft.Azure.WebJobs.Script.Description.FunctionInvokerBase.Invoke(Object[] parameters)
   at Host.Functions.LogNewRelicAlert(TimerInfo myTimer, TraceWriter log, CloudTable syncTimesTable, ExecutionContext _context)
   at lambda_method(Closure , Functions , Object[] )
   at Microsoft.Azure.WebJobs.Host.Executors.TaskMethodInvoker`1.InvokeAsync(TReflected instance, Object[] arguments)
   at Microsoft.Azure.WebJobs.Host.Executors.FunctionInvoker`1.<InvokeAsync>d__8.MoveNext()
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.InvokeMoveNext(Object stateMachine)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.Run()
   at System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter.RunAction(Object state)
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()
   at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

I'm wondering if there's any sort of log file that shows if custom transaction settings are indeed loaded and when a new transaction is created. Right now I'm guessing my XML is matching the wrong thing.

One more thing. If I add the line:

NewRelic.Api.Agent.NewRelic.SetTransactionName("Custom", "Testing");

In my Function app, I'll see the following warning in the New Relic logs:

2017-02-01 21:45:25,005 NewRelic WARN: Agent API Error: An error occurred invoking API method "SetTransactionName" - "System.InvalidOperationException: The API method called is only valid from within a transaction. This error can occur if you call the API method from a thread other than the one the transaction started on.

I think that makes it clear we're not actually in a transaction at this point, unless it's happening on another thread (which I wouldn't be surprised if this were the case).

Other ideas?

2
This seems to be the logging you're looking for: discuss.newrelic.com/t/… You can access the file system for your function at <sitename>.scm.azurewebsites.net/DebugConsoleMatt Mason
@MattMason-MSFT - Right now it's looking like the instrumentation I added to the XML file works and the metrics are being sent to New Relic. However, it doesn't show up as a Transaction in the New Relic dashboard. I'm not sure if it's supposed to. I think I need a New Relic engineer to help me, since I think everything is set up correctly on my end.Mike Christensen
@MattMason-MSFT - Got it working! Well, 90% anyway. I'd love to get exception tracing to work as well, but I can do this by hand using NoticeError. I've added what I've found out below. Thanks again for your help!Mike Christensen

2 Answers

2
votes

You should give custom transactions a shot:

https://docs.newrelic.com/docs/agents/net-agent/instrumentation/net-custom-transactions

Here are the values that I think would be applicable:

  1. AssemblyName: Microsoft.Azure.WebJobs.Script
  2. ClassName: Microsoft.Azure.WebJobs.Script.Description.FunctionInvokerBase
  3. MethodName: InvokeCore

I believe that will add a transaction on this method call, which is the generic wrapper around all user function executions.

2
votes

Finally got this working! Well, at least the best I think it'll work. Here's my .XML file in the /Extensions directory:

<?xml version="1.0" encoding="utf-8"?>
<extension
    xmlns="urn:newrelic-extension">
    <instrumentation>
        <!-- Define the method which triggers the creation of a transaction. -->
        <tracerFactory
           name="NewRelic.Agent.Core.Tracer.Factories.BackgroundThreadTracerFactory"
           metricName="Limeade/AzureDeviceSync">
            <match assemblyName="Microsoft.Azure.WebJobs.Script" className="Microsoft.Azure.WebJobs.Script.Description.DotNetFunctionInvoker">
                <exactMethodMatcher methodName="InvokeCore" />
            </match>
        </tracerFactory>
    </instrumentation>
</extension>

It doesn't matter what you call this file, as New Relic will load every XML file in the Extensions directory.

Here's a few things I learned:

  1. When you make changes to any New Relic configuration, including Extensions XML files, you have to reset IIS. However, the "Restart Site" button on SCM does not actually kill the IIS process. You have to go into Process Explorer in Kudu and shut down the w3wp.exe process. If you can't figure out which process is the one to kill, you can add log.Info($"Currently running in PID: {System.Diagnostics.Process.GetCurrentProcess().Id}"); to your Function App to find out.

  2. New Relic will generate confusing transaction names such as Microsoft.Azure.WebJobs.Script.Description.DotNetFunctionInvoker/InvokeCore by default. I couldn't find a way to set the Transaction name in the XML file. However, you can set the new transaction name in your Azure Function code like so:

    NewRelic.Api.Agent.NewRelic.SetTransactionName("SyncEvents", "GetRoutineData");

  3. It would be super nice if throwing an Exception from your Run method would log the exception as an error in New Relic, but after quite a bit of trying I was unable to get this to work. I think these exceptions are getting eaten somewhere and I can't figure out the right combination of tracer configuration to intercept these before they get lost. However, you can use the NoticeError() call: NewRelic.Api.Agent.NewRelic.NoticeError(new SyncException("This is a custom error")); This appears to work just the same and those errors will show up in Errors and Error analytics in New Relic. The downside is you'll probably have to refactor a bunch of your Azure Function code to catch all errors, notice them, and then rethrow them if necessary.

I spent two days getting this to work, so I hope this helps someone!!