sentry-dotnet
sentry-dotnet copied to clipboard
Improve story for Azure Functions DI
Right now the entry point is on IWebHost. This is not expose on serverless environments. Which means users need to Init and Dispose the SDK on each execution or some other work around that at Init once but await SentrySdk.FlushAsync by hand.
This task is to improve the story so that it's easy to plug Sentry's SDK with Azure Functions via DI (i.e: single Hook into IFunctionsHostBuilder).
Related to #730 #529
You've mentioned AWS Lambda, that doesn't make any sense.
Fixed
I've at least got the ILogger stuff wired up correctly doing the following:
[assembly: FunctionsStartup(typeof(FunctionsTest.Startup))]
namespace FunctionsTest
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
ConfigureServices(builder.Services);
}
private void ConfigureServices(IServiceCollection services)
{
services.AddLogging(l =>
{
l.AddSentry(o =>
{
o.Dsn = "https://[email protected]/1234567";
o.Environment = "Test";
o.BeforeSend = sentryEvent =>
{
sentryEvent.Logger = "Functions";
return sentryEvent;
};
});
});
}
}
}
@jachin84 I'm following your example but I'm not getting events into Sentry. Is there something else needed to get it working?
I'm expecting my function's injected ILogger to send events to Sentry on log.Error() just like it does in a normal ASP.NET Core application.
Setting a breakpoint on the BeforeSend function shows that Sentry is indeed being attached to the ILogger. I see all the breadcrumbs and everything on the event. But it never reaches Sentry.
Maybe we need to do some flushing on the finally clause as shown in the Node JS Azure Function example?
@eparizzi can you try to call await SentrySdk.FlushAsync() at the end of your function execution?
@bruno-garcia I'm not using the SentrySdk directly. I'm using the ILogger integration.
Anyway, an await Task.Delay(2000) in the finally clause after the catch where the error logging is done seems to do the trick.
@eparizzi Thanks for confirming the delay helps.
It's worth noting that your approach will delay responding to the caller by 2 seconds.
Calling await SentrySdk.FlushAsync() will only delay the time needed to send it to Sentry (usually a few milliseconds).
In worst case up to the timeout parameter in case there's latency for can't reach Sentry.
You can access the SentrySdk class just by importing Sentry like: using Sentry; (it's a transient dependency to the logging integration).
If you prefer not to depend on a static class (to allow testability), in case you're using the ASP.NET Core integration, you can just take ISentryClient in the constructor, or in the controller action:
public IActionResult Action([FromServices] ISentryClient sentry) {
try {
throw null;
} catch (Exception e) {
logger.LogError(e); // or sentry.CaptureException(e);
await sentry.FlushAsync(TimeSpan.FromSeconds(2));
}
You can call Flush on the client: https://github.com/getsentry/sentry-dotnet/blob/15d1d7677f9bbbbe7ec2f307cc4a07b916d50e8e/src/Sentry/ISentryClient.cs#L30
@bruno-garcia
Oh ok. I thought that accessing the static SentrySdk when using the ILogger implementation wouldn't have any effect on its behaviour.
Thanks!
The injected ISentryClient will be pointing to the same instance that's behind the SentrySdk static class.
@bruno-garcia We've started using Azure Functions more often and are running into the same issue. The cleanest setup I've found so far is this
[FunctionName("FunctionName")]
public async Task Run([TimerTrigger("0 0 * * 0")]TimerInfo myTimer)
{
using (SentrySdk.Init())
{
try
{
await ExecuteAsync();
}
catch (Exception ex)
{
SentrySdk.CaptureException(ex);
throw;
}
}
}
private async static Task ExecuteAsync() {
// Real code
}
Another problem I see is Azure Functions don't have any concept of middlewares. Even if Sentry is hooked into IFunctionsHostBuilder it doesn't seem like it can automatically capture exceptions like with .NET Core web applications and SentryMiddleware. Either way a user will have to call CaptureException or LogError manually.
Maybe you could steal some ideas from https://github.com/umamimolecule/azure-functions-http-middleware?
Have you found anything else on this issue?
Oh it's a bummer that there's still no better API to deal with this. I took a stab at Google Cloud Function here #529 and it's such a nice API.
You approach looks bullet proof given that you're catching all exceptions before you rethrow and Dispose will make sure things get flushed out. But ideally we would hide all of that in some Sentry.Azure.Functions. Thanks for the pointer, I didn't know that repo. So far we haven't been able to prioritize this but would accept some helps if someone wants to contribute.
Could we use the same approach for GCP mentioned in #1085 and apply it to Azure functions running dotnet core?
Could we use the same approach for GCP mentioned in #1085 and apply it to Azure functions running dotnet core?
Yeah if it's possible, would be nice. We'd love to get a PR adding support.
+1 in hoping that Azure Functions get support
The isolated (out of process) azure functions actually do support middleware (https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-process-guide#middleware) and apparently are the future anyway (https://techcommunity.microsoft.com/t5/apps-on-azure-blog/net-on-azure-functions-roadmap-update/ba-p/3619066) - any chance the asp.net core sentry integration could be used there?
The approach would be similar, but it looks like we would need new middleware that implements IFunctionsWorkerMiddleware. Also, we'd hopefully want a solution that would with with both in-process and isolated process models. Or at least, we'd want to offer both solutions together. I'll keep this in mind when we can carve out some time to work on it. Thanks!
Using this as an example, I think I have it working.
public class SentryMiddleware : IFunctionsWorkerMiddleware
{
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
try
{
await next(context);
}
catch (Exception ex)
{
var logger = context.GetLogger(context.FunctionDefinition.Name);
logger.LogError("Unexpected Error in {name}: {message}", context.FunctionDefinition.Name, ex.Message);
throw;
}
}
}
In my Program.cs host builder
.ConfigureFunctionsWorkerDefaults(builder =>
{
builder.UseMiddleware<SentryMiddleware>();
})
.ConfigureLogging((context, builder) =>
{
builder.AddSentry(options =>
{
options.Dsn = Environment.GetEnvironmentVariable("SENTRY_DSN");
});
})
Just a note for anyone using Azure Functions in the meantime. You can use either the SentrySdk.Init function or the logging integration with AddSentry, but either way you should initialize once during startup rather than on each function invocation. Don't try to init and dispose the Sentry SDK for each function invocation.
Also be sure to call await SentrySdk.FlushAsync() at the end of the function invocation, so that any queued messages are sent to Sentry before the Functions runtime freezes the process. (In ASP.NET Core, we have a FlushOnCompletedRequest option that does this automatically - but we don't have a similar option for a non ASP.NET Core Azure Function. Yet.)
Note, the example shown by @hostr in https://github.com/getsentry/sentry-dotnet/issues/373#issuecomment-708147239 is not advised.
The better approach is to use the Sentry.Extensions.Logging package, and call AddSentry in ConfigureLogging as @Hackmodford showed in https://github.com/getsentry/sentry-dotnet/issues/373#issuecomment-1361622217.
@Hackmodford - If you're setting the SENTRY_DSN environment variable, you don't need to assign it to options.Dsn. That will happen automatically, so that line is redundant. Also, in the middleware, you might want to call SentrySdk.CaptureException instead of throwing. You could also call SentrySdk.FlushAsync there instead of in the function body.
These approaches all need to be tested though, and that's mostly what this open issue is about.
@bruno-garcia - Regarding:
... You approach looks bullet proof given that you're catching all exceptions before you rethrow and
Disposewill make sure things get flushed out. ...
That may have been ok at the time originally written, but not since 3.13.0 due to #1354. Doing that now will leak memory due to having multiple instances of BackgroundWorker. I'll open a new issue to think about how to address that.
High-level plan
Azure Functions has undergone a significant change in the last few years. A big investment was made into Isolated Worker SDK (Out of Process). The Isolated worker SDK is the recommended way to build Azure Functions. In-Process SDK is still supported but not for long. More about the Azure Functions roadmap here.
The plan for Sentry support is to build first-class support for the Isolated Worker SDK and introduce an equivalent, if possible, for In-Process SDK. High-level objectives are:
- [x]
.UseSentry()on the Functions specific host builder API to be consistent with other .NET technologies such as ASP.NET Core. - [x] Setting optimal defaults
- [x] Automatically track errors, sessions (release health), and performance
- [x] Manage transaction names according to the trigger type in a way that makes sense (HTTP route for HTTP triggered functions vs alternative for non-HTTP triggers) --> reflection workaround. Tracking issue with Functions SDK here.
- ~~Out-of-the-box exception processor that cleans up the data that's getting logged~~
- [x] Functions cancellation token to mark Sentry transaction as Cancelled
- [x] Support for triggers
- [x] HTTP
- [x] Timer
- Blob, Storage queue, Azure Service Bus, etc will follow the same pattern
- [x] Add a sample
- [x] Deduplication of Sentry tags (AzureFunctions_FunctionName vs functionName)
- [x] Replace with tag filters --> PR #2360
- [ ] Review support for In-Proc SDK support
- [ ] Usage Documentation
Our initial support for Azure Functions (isolated worker) has been released. (Thanks @SeanFeldman!)
You'll find it on Nuget as Sentry.AzureFunctions.Worker
Documentation is pending. For now, see the sample at https://github.com/getsentry/sentry-dotnet/blob/main/samples/Sentry.Samples.AzureFunctions.Worker/Program.cs.
Please leave any general feedback here on this issue, but open a new issue for any bugs or significant oversights.
Note, we do not have a version for in-process functions ready at this time, but may in the future. Thanks.
in-process functions
I thought in-process will be redundant, in the not too distant future?
meaning .. no need to spend cycles on that?
@PureKrome, the blog post you're referring to was revised.
While the goal for Microsoft remains the same (eliminate In-Proc SDK), it is no longer as aggressive as it was earlier. Meaning there will be customers on the older SDK that might need support. Or at least some guidance.