DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • Android Cloud Apps with Azure
  • Delivering Your Code to the Cloud With JFrog Artifactory and GitHub Actions
  • Migrating Spring Java Applications to Azure App Service (Part 1: DataSources and Credentials)
  • Configuring Anypoint Platform as an Azure AD Service Provider SSO

Trending

  • Designing a Java Connector for Software Integrations
  • AI-Driven Root Cause Analysis in SRE: Enhancing Incident Resolution
  • Vibe Coding With GitHub Copilot: Optimizing API Performance in Fintech Microservices
  • Integration Isn’t a Task — It’s an Architectural Discipline
  1. DZone
  2. Software Design and Architecture
  3. Cloud Architecture
  4. A Comprehensive Guide to Azure Functions Error Monitoring

A Comprehensive Guide to Azure Functions Error Monitoring

In order to monitor for errors, you need to be able to log them!

By 
Phil Vuollet user avatar
Phil Vuollet
·
Feb. 19, 19 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
10.6K Views

Join the DZone community and get the full member experience.

Join For Free

Azure Functions is Microsoft's solution to serverless computing. While it actually does run on servers, the key difference here is that you aren't responsible for maintaining the function hosting environment. This is both a blessing and a curse.

On the one hand, you don't need to burden yourself with the details of the OS and web server. This frees you up to focus on what's important: developing the core functionality of your application. On the other hand, you have to relinquish some control over the execution environment. When it comes to logging application errors, this presents a challenge.

This post will focus on how to monitor your Azure Functions apps for errors. You need to be able to tell when your Azure Functions apps cause problems. And in order to monitor for errors, you need to be able to log them!

Error Monitoring With a Traditional Web App

In a web-server-hosted process, you would normally connect some kind of global logger to catch and log exceptions. You would use that same logger to log from your code. This is fairly straightforward and has become almost automatic for many developers.

For example, it's effortless to hook up the Raygun's error monitoring tool in an ASP.NET Core app. Just import the Mindscape.Raygun4Net.AspNetCore package, add the following three lines to the Startup.cs file, and add some minimal configuration to your appsettings.json file.

using Mindscape.Raygun4Net.AspNetCore; // within ConfigureServices services.AddRaygun(Configuration); // within Configure app.UseRaygun();


This code will set Raygun Crash Reporting to log all unhandled exceptions. Of course, you may want to configure the middleware to add additional information per request, as explained in this guide.

When you set up a logger this way, you get the benefit of reuse. All request handlers (controllers) will use the same error logging. This is desirable because it saves us from having the same boilerplate code in every controller method.

Unfortunately, you can't inject a logger into the Azure Functions host the same way. But don't worry, we still have options.

Error Logging With Azure Functions

By default, each call to an Azure Function handler receives a logger instance that logs to Azure file storage. The logger instance is passed to the function along with the invocation.

In C# .NET Core, an Azure Function method signature looks like this:

[FunctionName("HttpTriggerCSharpThrowError")] public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log)


An instance of ILogger is passed as an argument to the function invocation. You can use extension methods from Microsoft.Extensions.Logging to log events using the log instance.

log.LogError(...); log.LogWarning(...); log.LogInformation(...); log.LogDebug(...); // etc... 


And the invocation function in JavaScript looks like this (read the docs for more info):

module.exports = async function(context, myTrigger, myInput, myOtherInput) 


The context passed into the handler function has a log() function and a log object. For logging at different levels, you call the appropriate log functions as follows:

context.log.error( "That's an error!" ) context.log.warn( "I'm warning you!" ) context.log.info( "For your information." ) context.log.verbose( "To whom it may concern..." ) context.log( "Just saying.") 


And that's how you log in Azure Functions. But where does the logged event go? And how do you use your own logger instance? That's the real trouble!

Default Azure Functions Log Location

When you create a new Azure Functions app, you'll configure its storage location. You can actually go to the "file service" in the storage account and see the directories where the logs are kept. There are actually quite a few locations with logs. The "/LogFiles/Application/Functions" directory has the host logs and the function logs.

Theoretically, since you can get to the file, you should be able to send the logs to Raygun, which is what you really want to do here. You could periodically poll the logs using a timed Azure Function, but then, you'd have to rely on file reading and parsing. But this wouldn't be ideal since you would have unnecessary complexity.

It would be best to have a queue or a stream to tap into directly. There isn't an easy way to do this in Azure Functions just yet, so let's look at the next best thing.

Explicitly Logging to Raygun

There are a number of techniques you can use to send errors to Raygun, which you want to do so Raygun can work its magic. The simplest way is to explicitly log the exceptions in a catch block. Here are the steps to get this done:

1) Add the Raygun client package to your function code.

dotnet add package Mindscape.Raygun4Net.NetCore


2) Add the code to create a client.

var raygunClient = new Mindscape.Raygun4Net.RaygunClient("YOUR_TOP_SECRET_KEY");


3) Send the exception.

This is the most basic way to send exceptions to Raygun from your Azure Functions apps. Of course, you can take things up a few levels from here. Let's take a look at some things you can do to improve on this approach.

1. Store Your Key as a Config

Azure Functions apps can run multiple functions on a single host. The host is an app service that has its own configuration, and the host config is available to your functions.

This means you can store the key in the host rather than keeping it in code. That's a better practice for security purposes.

First, you'll need to add the value to the application settings. In the Azure portal, go to Function Apps and select one. You should see Application settings in the main pane for the selected app.

Image title

Once you've clicked that link, you should be in the tab for application settings. Here, you can control various aspects of the Azure Functions app hosting environment. There's an application settings section that looks like this:

This is where you'll add an app setting. Finally, you'll need to modify your code to get the value. Modify the code that created the client to this:

var key = System.Environment.GetEnvironmentVariable(
   "Raygun_ApiKey",
    EnvironmentVariableTarget.Process
);
var raygunClient = new Mindscape.Raygun4Net.AspNetCore.RaygunClient(key);


Microsoft recommends using GetEnvironmentVariable rather than the ConfigurationManager for this purpose. Now, with the variable in your hosting environment, you're using a better approach. Of course, once you have more than one function, you'll be repeating the above code again and again. This brings up an important issue: refactoring.

2. Refactor the Log Client for Reuse

Perhaps, someday, it'll be possible to connect to the log stream or inject your own logger. Until that becomes a reality, you either need to use fragile hacks or build a wrapper to use on every function call. I suggest using the following pattern to do just that. The logging construct can be wrapped up in a reusable package if you do just a little bit of leg work.

// usual usings here using Mindscape.Raygun4Net.AspNetCore; namespace YourNameSpace { public static class YourFunctionAppClass { public static Lazy<RaygunClient> RaygunClient = new Lazy<RaygunClient>(() => MyRaygunLogger.GetRaygunClient()); [FunctionName("YourFunctionName")] public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest reqest, ILogger logger) { var myRaygunLogger = new MyRaygunLogger(logger, RaygunClient); return await myRaygunLogger.WithRaygunClient(async (req, log) => { string name = req.Query["name"]; string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); dynamic data = JsonConvert.DeserializeObject(requestBody); name = name ?? data?.name; if (name == "Throw") throw new ApplicationException("Thrown on purpose"); return name != null ? (ActionResult)new OkObjectResult($"Hello, {name}") : new BadRequestObjectResult("Oops, send a 'name' please!"); }, reqest); } } }


 MyRaygunLogger is a decorator that sends exceptions to Raygun, so it can do the error tracking and alerting. Here's the code for it:

public class MyRaygunLogger : ILogger { private readonly ILogger _log; private readonly Lazy<RaygunClient> _raygunClient; public MyRaygunLogger(ILogger log, Lazy<RaygunClient> raygunClient) { _log = log; _raygunClient = raygunClient; } // factory public static RaygunClient GetRaygunClient() { var key = System.Environment.GetEnvironmentVariable( "Raygun_ApiKey", EnvironmentVariableTarget.Process ); return new RaygunClient(key); } // function wrapper public async Task<IActionResult> WithRaygunClient( Func<HttpRequest, ILogger, Task<IActionResult>> func, HttpRequest req) { try { this.LogInformation("Request started"); return await func(req, this); } catch(Exception ex) { this.LogError(0, ex, "Error"); return new StatusCodeResult(500); } finally { this.LogInformation("Request complete"); } } // ILogger public IDisposable BeginScope<TState>(TState state) { return _log.BeginScope(state); } public bool IsEnabled(LogLevel logLevel) { return _log.IsEnabled(logLevel); } public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { if(logLevel == LogLevel.Error || logLevel == LogLevel.Critical) { _raygunClient.Value.Send(exception); } _log.Log<TState>(logLevel, eventId, state, exception, formatter); } }


The decorator does a few things. It wraps the original logger so that you can add functionality without altering the existing behavior. I'm only logging events with "Error" or "Critical" to Raygun in this sample, but you can send all events to take full advantage of your subscription.

The main business of this class is to offer a way to wrap every function call in the code that handles boilerplate logging-before the call, after the call, and exception handling.

You'll notice I'm using Lazy to create the Raygun client. This way, the client is only instantiated when there's an exception. You'll also notice it's being created outside the function code as a public member. Since the Azure Functions app code has to be static, this is how we can unit test. Just replace the initializer with a mock and you'll be set for unit testing.

Closing Thoughts

Using the examples in this post as a starting point, you can take things in any number of directions, depending on how sophisticated you want to be. For example, you could apply standard formatting to your messages sent to Raygun. You could send all the messages to a separate Azure Function for pre-processing before they get logged. Perhaps, you'll want to send only log levels LogLevel.Error or higher. If you want to add timing, you can do so within the wrapper method.

The point is: now, you can send messages to Raygun, so it can report on errors in your Azure Functions along with everything else. From there, Raygun can track errors and alert your response team right away.

If you're using Azure App Services, there's an extension that does this automatically. For the time being, it doesn't work the same way with Azure Functions.

By using these methods, you can still send the errors along and keep your error reporting as seamless as ever!

You can try Raygun for free today.

azure app Host (Unix) application

Published at DZone with permission of Phil Vuollet, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Android Cloud Apps with Azure
  • Delivering Your Code to the Cloud With JFrog Artifactory and GitHub Actions
  • Migrating Spring Java Applications to Azure App Service (Part 1: DataSources and Credentials)
  • Configuring Anypoint Platform as an Azure AD Service Provider SSO

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!