Protokolování HTTP v ASP.NET Core

Poznámka:

Toto není nejnovější verze tohoto článku. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.

Upozorňující

Tato verze ASP.NET Core se už nepodporuje. Další informace najdete v tématu .NET a .NET Core Zásady podpory. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.

Důležité

Tyto informace se týkají předběžného vydání produktu, který může být podstatně změněn před komerčním vydáním. Microsoft neposkytuje žádné záruky, výslovné ani předpokládané, týkající se zde uváděných informací.

Aktuální verzi najdete ve verzi .NET 8 tohoto článku.

Protokolování HTTP je middleware, který protokoluje informace o příchozích požadavcích HTTP a odpovědích HTTP. Protokolování HTTP poskytuje protokoly obsahující:

  • Informace o požadavku HTTP
  • Obecné vlastnosti
  • Hlavičky
  • Text
  • Informace o odpovědi HTTP

Protokolování HTTP může:

  • Protokolujte všechny požadavky a odpovědi nebo pouze žádosti a odpovědi, které splňují určitá kritéria.
  • Vyberte, které části požadavku a odpovědi se protokolují.
  • Umožňuje redigovat citlivé informace z protokolů.

Protokolování HTTP může snížit výkon aplikace, zejména při protokolování těla požadavků a odpovědí. Při výběru polí, která se mají protokolovat, zvažte dopad na výkon. Otestujte dopad na výkon vybraných vlastností protokolování.

Upozorňující

Protokolování HTTP může potenciálně protokolovat identifikovatelné osobní údaje (PII). Vezměte riziko do úvahy a vyhněte se protokolování citlivých informací.

Povolení protokolování HTTP

Protokolování HTTP je povoleno voláním AddHttpLogging a UseHttpLogging, jak je znázorněno v následujícím příkladu:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(o => { });

var app = builder.Build();

app.UseHttpLogging();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();

app.MapGet("/", () => "Hello World!");

app.Run();

Prázdný lambda v předchozím příkladu volání AddHttpLogging přidá middleware s výchozí konfigurací. Protokolování HTTP ve výchozím nastavení protokoluje běžné vlastnosti, jako je cesta, stavový kód a hlavičky pro požadavky a odpovědi.

Aby se zobrazily protokoly HTTP, přidejte do souboru appsettings.Development.json na úrovni "LogLevel": { následující řádek:

 "Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"

Ve výchozí konfiguraci se požadavek a odpověď zaprotokolují jako dvojice zpráv podobných následujícímu příkladu:

info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[1]
      Request:
      Protocol: HTTP/2
      Method: GET
      Scheme: https
      PathBase:
      Path: /
      Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
      Host: localhost:52941
      User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.61
      Accept-Encoding: gzip, deflate, br
      Accept-Language: en-US,en;q=0.9
      Upgrade-Insecure-Requests: [Redacted]
      sec-ch-ua: [Redacted]
      sec-ch-ua-mobile: [Redacted]
      sec-ch-ua-platform: [Redacted]
      sec-fetch-site: [Redacted]
      sec-fetch-mode: [Redacted]
      sec-fetch-user: [Redacted]
      sec-fetch-dest: [Redacted]
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
      Response:
      StatusCode: 200
      Content-Type: text/plain; charset=utf-8
      Date: Tue, 24 Oct 2023 02:03:53 GMT
      Server: Kestrel

Možnosti protokolování HTTP

Pokud chcete nakonfigurovat globální možnosti middlewaru protokolování HTTP, zavolejte ho AddHttpLogging Program.cspomocí lambda ke konfiguraci HttpLoggingOptions.

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;
    logging.CombineLogs = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

Poznámka:

V předchozí ukázce a následujících ukázkách se volá po UseStaticFiles, UseHttpLogging takže protokolování HTTP není povolené pro statické soubory. Chcete-li povolit protokolování HTTP statického souboru, zavolejte UseHttpLogging před UseStaticFiles.

LoggingFields

HttpLoggingOptions.LoggingFields je příznak výčtu, který konfiguruje konkrétní protokolované části požadavku a odpovědi. Výchozí hodnotou HttpLoggingOptions.LoggingFields je RequestPropertiesAndHeaders | ResponsePropertiesAndHeaders.

RequestHeaders a ResponseHeaders

RequestHeaders a ResponseHeaders jsou sady hlaviček HTTP, které jsou protokolovány. Hodnoty hlaviček se protokolují jenom pro názvy hlaviček, které jsou v těchto kolekcích. Následující kód se přidá sec-ch-ua do RequestHeaders, takže hodnota sec-ch-ua hlavičky je zaznamenána. A přidá MyResponseHeader do něj ResponseHeadershodnotu hlavičky, takže MyResponseHeader se zaprotokoluje. Pokud jsou tyto řádky odebrány, hodnoty těchto záhlaví jsou [Redacted].

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;
    logging.CombineLogs = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

MediaTypeOptions

MediaTypeOptions poskytuje konfiguraci pro výběr kódování, které se má použít pro konkrétní typ média.

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;
    logging.CombineLogs = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

Tento přístup lze použít také k povolení protokolování dat, která nejsou ve výchozím nastavení protokolována (například data formuláře, která můžou mít typ média, například application/x-www-form-urlencoded ).multipart/form-data

Metody MediaTypeOptions

RequestBodyLogLimit a ResponseBodyLogLimit

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;
    logging.CombineLogs = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

CombineLogs

true Nastavení CombineLogs pro konfiguraci middlewaru ke konsolidaci všech jeho povolených protokolů pro požadavek a odpověď do jednoho protokolu na konci. To zahrnuje požadavek, text požadavku, odpověď, text odpovědi a dobu trvání.

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;
    logging.CombineLogs = true;
});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

Konfigurace specifická pro koncový bod

Pro konfiguraci specifickou pro koncový bod v minimálních aplikacích WithHttpLogging API je k dispozici metoda rozšíření. Následující příklad ukazuje, jak nakonfigurovat protokolování HTTP pro jeden koncový bod:

app.MapGet("/response", () => "Hello World! (logging response)")
    .WithHttpLogging(HttpLoggingFields.ResponsePropertiesAndHeaders);

Pro konfiguraci specifickou pro koncový bod v aplikacích, které používají kontrolery, [HttpLogging] je atribut k dispozici. Atribut lze použít také v minimálních aplikacích API, jak je znázorněno v následujícím příkladu:

app.MapGet("/duration", [HttpLogging(loggingFields: HttpLoggingFields.Duration)]
    () => "Hello World! (logging duration)");

IHttpLoggingInterceptor

IHttpLoggingInterceptor je rozhraní pro službu, která se dá implementovat za účelem zpracování zpětného volání na požadavek a odpověď pro přizpůsobení podrobností, které se zaprotokolují. Nejprve se použijí všechna nastavení protokolu specifická pro koncový bod a pak je možné je v těchto zpětných voláních přepsat. Implementace může:

  • Zkontrolujte požadavek nebo odpověď.
  • Povolte nebo zakažte libovolnou HttpLoggingFieldsmožnost .
  • Upravte, kolik textu požadavku nebo odpovědi se protokoluje.
  • Přidejte do protokolů vlastní pole.

Zaregistrujte implementaci IHttpLoggingInterceptor voláním AddHttpLoggingInterceptor<T> .Program.cs Pokud je zaregistrovaných více IHttpLoggingInterceptor instancí, spustí se v zaregistrované objednávce.

Následující příklad ukazuje, jak zaregistrovat implementaci IHttpLoggingInterceptor :

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.Duration;
});
builder.Services.AddHttpLoggingInterceptor<SampleHttpLoggingInterceptor>();

Následující příklad je IHttpLoggingInterceptor implementace, která:

  • Zkontroluje metodu požadavku a zakáže protokolování požadavků POST.
  • Pro požadavky jiného než POST:
    • Redacts request path, request headers, and response headers.
    • Přidá do protokolů požadavků a odpovědí vlastní pole a hodnoty polí.
using Microsoft.AspNetCore.HttpLogging;

namespace HttpLoggingSample;

internal sealed class SampleHttpLoggingInterceptor : IHttpLoggingInterceptor
{
    public ValueTask OnRequestAsync(HttpLoggingInterceptorContext logContext)
    {
        if (logContext.HttpContext.Request.Method == "POST")
        {
            // Don't log anything if the request is a POST.
            logContext.LoggingFields = HttpLoggingFields.None;
        }

        // Don't enrich if we're not going to log any part of the request.
        if (!logContext.IsAnyEnabled(HttpLoggingFields.Request))
        {
            return default;
        }

        if (logContext.TryDisable(HttpLoggingFields.RequestPath))
        {
            RedactPath(logContext);
        }

        if (logContext.TryDisable(HttpLoggingFields.RequestHeaders))
        {
            RedactRequestHeaders(logContext);
        }

        EnrichRequest(logContext);

        return default;
    }

    public ValueTask OnResponseAsync(HttpLoggingInterceptorContext logContext)
    {
        // Don't enrich if we're not going to log any part of the response
        if (!logContext.IsAnyEnabled(HttpLoggingFields.Response))
        {
            return default;
        }

        if (logContext.TryDisable(HttpLoggingFields.ResponseHeaders))
        {
            RedactResponseHeaders(logContext);
        }

        EnrichResponse(logContext);

        return default;
    }

    private void RedactPath(HttpLoggingInterceptorContext logContext)
    {
        logContext.AddParameter(nameof(logContext.HttpContext.Request.Path), "RedactedPath");
    }

    private void RedactRequestHeaders(HttpLoggingInterceptorContext logContext)
    {
        foreach (var header in logContext.HttpContext.Request.Headers)
        {
            logContext.AddParameter(header.Key, "RedactedHeader");
        }
    }

    private void EnrichRequest(HttpLoggingInterceptorContext logContext)
    {
        logContext.AddParameter("RequestEnrichment", "Stuff");
    }

    private void RedactResponseHeaders(HttpLoggingInterceptorContext logContext)
    {
        foreach (var header in logContext.HttpContext.Response.Headers)
        {
            logContext.AddParameter(header.Key, "RedactedHeader");
        }
    }

    private void EnrichResponse(HttpLoggingInterceptorContext logContext)
    {
        logContext.AddParameter("ResponseEnrichment", "Stuff");
    }
}

Při použití tohoto průsečíku požadavek POST negeneruje žádné protokoly, ani když je protokolování HTTP nakonfigurované pro protokolování HttpLoggingFields.All. Požadavek GET generuje protokoly podobné následujícímu příkladu:

info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[1]
      Request:
      Path: RedactedPath
      Accept: RedactedHeader
      Host: RedactedHeader
      User-Agent: RedactedHeader
      Accept-Encoding: RedactedHeader
      Accept-Language: RedactedHeader
      Upgrade-Insecure-Requests: RedactedHeader
      sec-ch-ua: RedactedHeader
      sec-ch-ua-mobile: RedactedHeader
      sec-ch-ua-platform: RedactedHeader
      sec-fetch-site: RedactedHeader
      sec-fetch-mode: RedactedHeader
      sec-fetch-user: RedactedHeader
      sec-fetch-dest: RedactedHeader
      RequestEnrichment: Stuff
      Protocol: HTTP/2
      Method: GET
      Scheme: https
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[2]
      Response:
      Content-Type: RedactedHeader
      MyResponseHeader: RedactedHeader
      ResponseEnrichment: Stuff
      StatusCode: 200
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[4]
      ResponseBody: Hello World!
info: Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware[8]
      Duration: 2.2778ms

Pořadí konfigurace protokolování priority

Následující seznam ukazuje pořadí priorit konfigurace protokolování:

  1. Globální konfigurace ze HttpLoggingOptionssady , nastavená voláním AddHttpLogging.
  2. Konfigurace specifická pro koncový bod z atributu [HttpLogging] WithHttpLogging nebo metody rozšíření přepíše globální konfiguraci.
  3. IHttpLoggingInterceptor se volá s výsledky a může dále upravit konfiguraci na požadavek.

Protokolování HTTP je middleware, který protokoluje informace o příchozích požadavcích HTTP a odpovědích HTTP. Protokolování HTTP poskytuje protokoly obsahující:

  • Informace o požadavku HTTP
  • Obecné vlastnosti
  • Hlavičky
  • Text
  • Informace o odpovědi HTTP

Protokolování HTTP je velmi účinné v několika scénářích, kdy provádí následující:

  • Záznam informací o příchozích požadavcích a odpovědích
  • Filtrování, které části požadavku a odpovědi se protokolují
  • Filtrování záhlaví, která se mají protokolovat

Protokolování HTTP může snížit výkon aplikace, zejména při protokolování těl požadavků a odpovědí. Při výběru polí, která se mají protokolovat, zvažte dopad na výkon. Otestujte dopad na výkon vybraných vlastností protokolování.

Upozorňující

Protokolování HTTP může potenciálně protokolovat identifikovatelné osobní údaje (PII). Vezměte riziko do úvahy a vyhněte se protokolování citlivých informací.

Povolení protokolování HTTP

Protokolování HTTP se povoluje pomocí UseHttpLogging, čímž se přidá middleware protokolování HTTP.

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.UseHttpLogging();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();

app.MapGet("/", () => "Hello World!");

app.Run();

Protokolování HTTP ve výchozím nastavení protokoluje běžné vlastnosti, jako je cesta, stavový kód a hlavičky požadavků a odpovědí. Aby se zobrazily protokoly HTTP, přidejte do souboru appsettings.Development.json na úrovni "LogLevel": { následující řádek:

 "Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"

Výstup se protokoluje jako jedna zpráva v umístění LogLevel.Information.

Ukázkový výstup žádosti

Možnosti protokolování HTTP

Chcete-li nakonfigurovat middleware protokolování HTTP, zavolejte AddHttpLogging v souboru Program.cs.

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;

});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

Poznámka:

V předchozí ukázce a následujících ukázkách se volá po UseStaticFiles, UseHttpLogging takže protokolování HTTP není povolené pro statický soubor. Chcete-li povolit protokolování HTTP statického souboru, zavolejte UseHttpLogging před UseStaticFiles.

LoggingFields

HttpLoggingOptions.LoggingFields je příznak výčtu, který konfiguruje konkrétní protokolované části požadavku a odpovědi. Výchozí hodnotou HttpLoggingOptions.LoggingFields je RequestPropertiesAndHeaders | ResponsePropertiesAndHeaders.

RequestHeaders

Headers je sada hlaviček požadavků HTTP, které je možné protokolovat. Hodnoty hlaviček se protokolují pouze pro názvy hlaviček, které jsou v této kolekci. Následující kód zaznamená hlavičku požadavku "sec-ch-ua". Pokud se logging.RequestHeaders.Add("sec-ch-ua"); odebere, hodnota hlavičky požadavku "sec-ch-ua" se rediguje. Následující zvýrazněný kód volá HttpLoggingOptions.RequestHeaders a HttpLoggingOptions.ResponseHeaders:

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;

});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

MediaTypeOptions

MediaTypeOptions poskytuje konfiguraci pro výběr kódování, které se má použít pro konkrétní typ média.

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;

});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();

Tento přístup lze použít také k povolení protokolování dat, která nejsou ve výchozím nastavení protokolována. Například data formuláře, která mohou mít typ média, například application/x-www-form-urlencoded nebo multipart/form-data.

Metody MediaTypeOptions

RequestBodyLogLimit a ResponseBodyLogLimit

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(logging =>
{
    logging.LoggingFields = HttpLoggingFields.All;
    logging.RequestHeaders.Add("sec-ch-ua");
    logging.ResponseHeaders.Add("MyResponseHeader");
    logging.MediaTypeOptions.AddText("application/javascript");
    logging.RequestBodyLogLimit = 4096;
    logging.ResponseBodyLogLimit = 4096;

});

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
}

app.UseStaticFiles();

app.UseHttpLogging(); 

app.Use(async (context, next) =>
{
    context.Response.Headers["MyResponseHeader"] =
        new string[] { "My Response Header Value" };

    await next();
});

app.MapGet("/", () => "Hello World!");

app.Run();