getsentry / sentry-dotnet

Sentry SDK for .NET
https://docs.sentry.io/platforms/dotnet
MIT License
599 stars 206 forks source link

Dotnet 9 Functions IHostApplicationBuilder #3773

Open cliedeman opened 2 days ago

cliedeman commented 2 days ago

Problem Statement

Dotnet 9 has included support for using the IHostApplicationBuilder style for app creation

https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-process-guide?tabs=ihostapplicationbuilder%2Cwindows#start-up-and-configuration

With the new style we dont have access to the HostBuilderContext

var builder = FunctionsApplication.CreateBuilder(args);

builder.UseSentry(<missing>, options =>
{
    ...
});

Temporary workaround:

var builder = FunctionsApplication.CreateBuilder(args);

var context = new HostBuilderContext(new Dictionary<object, object>())
{
    Configuration = builder.Configuration,
};

builder.UseSentry(context, options =>
{
    ...
});

I see 2 obvious solutions:

Either add a builder extension with using FunctionsApplicationBuilder

or do a cast

    public static IHostApplicationBuilder UseSentry(
        this IFunctionsWorkerApplicationBuilder builder,
        Action<SentryAzureFunctionsOptions>? optionsConfiguration)
    {
        builder.UseMiddleware<SentryFunctionsWorkerMiddleware>();
        IServiceCollection services = builder.Services;
        IConfigurationSection section;
        if (builder is IHostApplicationBuilder applicationBuilder)
        {
            section = applicationBuilder.Configuration.GetSection("Sentry");
        }
        else
        {
            throw new ArgumentException("builder is not a IHostApplicationBuilder");
        }

        services.AddSingleton<IConfigureOptions<SentryAzureFunctionsOptions>>((Func<IServiceProvider, IConfigureOptions<SentryAzureFunctionsOptions>>) (_ => (IConfigureOptions<SentryAzureFunctionsOptions>) new SentryAzureFunctionsOptionsSetup((IConfiguration) section)));
        if (optionsConfiguration != null)
            services.Configure<SentryAzureFunctionsOptions>(optionsConfiguration);
        services.AddLogging();
        services.AddSingleton<ILoggerProvider, SentryAzureFunctionsLoggerProvider>();
        services.AddSingleton<IConfigureOptions<SentryAzureFunctionsOptions>, SentryAzureFunctionsOptionsSetup>();
        services.AddSentry<SentryAzureFunctionsOptions>();
        return builder;
    }

Solution Brainstorm

No response

bruno-garcia commented 2 days ago

Thanks for raising this. We're working on improved .NET 9.0 support now on main branch as part of our 5.0.0 release.

Would you consider opening a PR?

cliedeman commented 1 day ago

Sure. which approach should I use?

bruno-garcia commented 1 day ago

Not sure tbqh. @jamescrosswell opinions?

If the type cast is safe, maybe that's the simplest as no new API needed

jamescrosswell commented 23 hours ago

It looks like it'd be easy to break with the cast option if someone called this incorrectly from an app that used the older IHostBuilder:

        else
        {
            throw new ArgumentException("builder is not a IHostApplicationBuilder");
        }

Ideally we wouldn't find out at runtime what we could prevent with static types.

SentryFunctionsWorkerApplicationBuilderExtensions doesn't actually need HostBuilderContext or an IHostApplicationBuilder... ultimately the dependency it's after is IConfiguration. I think it makes sense to have one overload (where the real work happens) that takes an IConfiguration parameter... and then wrap this with some other overloads that are built to work specifically with either HostBuilderContext or IHostApplicationBuilder.

GetoXs commented 20 hours ago

Hi guys, How about change extension "base" interface from IFunctionsWorkerApplicationBuilder to IHostApplicationBuilder? It would simplify code, and it could be reusable in other sentry integrations (not only azure function).


    public static IHostApplicationBuilder UseSentry(
        this IHostApplicationBuilder builder,
        Action<SentryAzureFunctionsOptions>? optionsConfiguration)
    {

        var services = builder.Services;
        var section = builder.Configuration.GetSection("Sentry");
#if NET8_0_OR_GREATER
        services.AddSingleton<IConfigureOptions<SentryAzureFunctionsOptions>>(_ =>
            new SentryAzureFunctionsOptionsSetup(section)
        );
#else
        services.Configure<SentryAzureFunctionsOptions>(options =>
            section.Bind(options));
#endif
        ...
    }

PS @cliedeman many thanks for workaround.