feat: Add monitoring with prometheus and grafana

This commit is contained in:
Pc 2023-07-30 00:58:05 +03:30
parent 668991d006
commit ef72d64700
36 changed files with 272 additions and 62 deletions

View File

@ -144,7 +144,46 @@ services:
networks:
- booking
#######################################################
# prometheus
#######################################################
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
environment:
- TZ=UTC
volumes:
- ./monitoring/prom/prometheus.yml:/etc/prometheus/prometheus.yml
networks:
- booking
#######################################################
# grafana
#######################################################
grafana:
image: grafana/grafana
container_name: grafana
ports:
- "3000:3000"
volumes:
- ./monitoring/grafana-data/data:/var/lib/grafana
networks:
- booking
#######################################################
# node_exporter
#######################################################
node_exporter:
image: prom/node-exporter
container_name: node_exporter
restart: unless-stopped
ports:
- 9101:9100
networks:
- booking
######################################################
# Gateway
######################################################

View File

@ -158,6 +158,46 @@ services:
ports:
- 6379:6379
#######################################################
# prometheus
#######################################################
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
environment:
- TZ=UTC
volumes:
- ./monitoring/prom/prometheus.yml:/etc/prometheus/prometheus.yml
networks:
- booking
#######################################################
# grafana
#######################################################
grafana:
image: grafana/grafana
container_name: grafana
ports:
- "3000:3000"
volumes:
- ./monitoring/grafana-data/data:/var/lib/grafana
networks:
- booking
#######################################################
# node_exporter
#######################################################
node_exporter:
image: prom/node-exporter
container_name: node_exporter
restart: unless-stopped
ports:
- 9101:9100
networks:
- booking
networks:
booking:

View File

@ -0,0 +1,53 @@
{{ define "__subject" }}[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ if gt (.Alerts.Resolved | len) 0 }}, RESOLVED:{{ .Alerts.Resolved | len }}{{ end }}{{ end }}] {{ .GroupLabels.SortedPairs.Values | join " " }} {{ if gt (len .CommonLabels) (len .GroupLabels) }}({{ with .CommonLabels.Remove .GroupLabels.Names }}{{ .Values | join " " }}{{ end }}){{ end }}{{ end }}
{{ define "__text_values_list" }}{{ if len .Values }}{{ $first := true }}{{ range $refID, $value := .Values -}}
{{ if $first }}{{ $first = false }}{{ else }}, {{ end }}{{ $refID }}={{ $value }}{{ end -}}
{{ else }}[no value]{{ end }}{{ end }}
{{ define "__text_alert_list" }}{{ range . }}
Value: {{ template "__text_values_list" . }}
Labels:
{{ range .Labels.SortedPairs }} - {{ .Name }} = {{ .Value }}
{{ end }}Annotations:
{{ range .Annotations.SortedPairs }} - {{ .Name }} = {{ .Value }}
{{ end }}{{ if gt (len .GeneratorURL) 0 }}Source: {{ .GeneratorURL }}
{{ end }}{{ if gt (len .SilenceURL) 0 }}Silence: {{ .SilenceURL }}
{{ end }}{{ if gt (len .DashboardURL) 0 }}Dashboard: {{ .DashboardURL }}
{{ end }}{{ if gt (len .PanelURL) 0 }}Panel: {{ .PanelURL }}
{{ end }}{{ end }}{{ end }}
{{ define "default.title" }}{{ template "__subject" . }}{{ end }}
{{ define "default.message" }}{{ if gt (len .Alerts.Firing) 0 }}**Firing**
{{ template "__text_alert_list" .Alerts.Firing }}{{ if gt (len .Alerts.Resolved) 0 }}
{{ end }}{{ end }}{{ if gt (len .Alerts.Resolved) 0 }}**Resolved**
{{ template "__text_alert_list" .Alerts.Resolved }}{{ end }}{{ end }}
{{ define "__teams_text_alert_list" }}{{ range . }}
Value: {{ template "__text_values_list" . }}
Labels:
{{ range .Labels.SortedPairs }} - {{ .Name }} = {{ .Value }}
{{ end }}
Annotations:
{{ range .Annotations.SortedPairs }} - {{ .Name }} = {{ .Value }}
{{ end }}
{{ if gt (len .GeneratorURL) 0 }}Source: [{{ .GeneratorURL }}]({{ .GeneratorURL }})
{{ end }}{{ if gt (len .SilenceURL) 0 }}Silence: [{{ .SilenceURL }}]({{ .SilenceURL }})
{{ end }}{{ if gt (len .DashboardURL) 0 }}Dashboard: [{{ .DashboardURL }}]({{ .DashboardURL }})
{{ end }}{{ if gt (len .PanelURL) 0 }}Panel: [{{ .PanelURL }}]({{ .PanelURL }})
{{ end }}
{{ end }}{{ end }}
{{ define "teams.default.message" }}{{ if gt (len .Alerts.Firing) 0 }}**Firing**
{{ template "__teams_text_alert_list" .Alerts.Firing }}{{ if gt (len .Alerts.Resolved) 0 }}
{{ end }}{{ end }}{{ if gt (len .Alerts.Resolved) 0 }}**Resolved**
{{ template "__teams_text_alert_list" .Alerts.Resolved }}{{ end }}{{ end }}

View File

@ -0,0 +1,61 @@
global:
scrape_interval: 15s
scrape_timeout: 10s
evaluation_interval: 15s
alerting:
alertmanagers:
- scheme: http
timeout: 10s
api_version: v1
static_configs:
- targets: []
scrape_configs:
- job_name: prometheus
honor_timestamps: true
scrape_interval: 15s
scrape_timeout: 10s
metrics_path: /metrics
scheme: http
static_configs:
- targets:
- localhost:9090
- job_name: flight
scrape_interval: 15s
scrape_timeout: 10s
metrics_path: /metrics
scheme: http
static_configs:
- targets:
- host.docker.internal:5004
- job_name: identity
scrape_interval: 15s
scrape_timeout: 10s
metrics_path: /metrics
scheme: http
static_configs:
- targets:
- host.docker.internal:6005
- job_name: passenger
scrape_interval: 15s
scrape_timeout: 10s
metrics_path: /metrics
scheme: http
static_configs:
- targets:
- host.docker.internal:6012
- job_name: booking
scrape_interval: 15s
scrape_timeout: 10s
metrics_path: /metrics
scheme: http
static_configs:
- targets:
- host.docker.internal:6010
- job_name: gateway
scrape_interval: 15s
scrape_timeout: 10s
metrics_path: /metrics
scheme: https
static_configs:
- targets:
- host.docker.internal:5000

View File

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS builder
FROM mcr.microsoft.com/dotnet/sdk:8.0-preview AS builder
WORKDIR /src
# Setup working directory for the project
@ -26,7 +26,7 @@ WORKDIR /src/ApiGateway/src
# and no build, as we did it already
RUN dotnet publish -c Release --no-build -o out
FROM mcr.microsoft.com/dotnet/aspnet:7.0
FROM mcr.microsoft.com/dotnet/aspnet:8.0-preview
# Setup working directory for the project
WORKDIR /app

View File

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS builder
FROM mcr.microsoft.com/dotnet/sdk:8.0-preview AS builder
WORKDIR /src
# Setup working directory for the project
@ -29,7 +29,7 @@ WORKDIR /src/ApiGateway/src
RUN --mount=type=cache,id=gateway_nuget,target=/root/.nuget/packages \
dotnet publish -c Release --no-build -o out
FROM mcr.microsoft.com/dotnet/aspnet:7.0
FROM mcr.microsoft.com/dotnet/aspnet:8.0-preview
# Setup working directory for the project
WORKDIR /app

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
@ -37,7 +37,8 @@
<PackageReference Include="Mongo2Go" Version="3.1.3" />
<PackageReference Include="Npgsql" Version="7.0.1" />
<PackageReference Include="NSubstitute" Version="4.4.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.GrpcNetClient" Version="1.0.0-rc9.7" />
<PackageReference Include="OpenTelemetry.Exporter.Prometheus.AspNetCore" Version="1.6.0-alpha.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.GrpcNetClient" Version="1.5.1-beta.1" />
<PackageReference Include="Polly" Version="7.2.3" />
<PackageReference Include="protobuf-net.BuildTools" Version="3.1.25">
<PrivateAssets>all</PrivateAssets>
@ -117,11 +118,11 @@
<PackageReference Include="prometheus-net" Version="7.0.0" />
<PackageReference Include="prometheus-net.AspNetCore" Version="7.0.0" />
<PackageReference Include="OpenTelemetry" Version="1.4.0-beta.1" />
<PackageReference Include="OpenTelemetry.Exporter.Jaeger" Version="1.4.0-beta.1" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.0.0-rc9.7" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.0.0-rc9.7" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.0.0-rc9.7" />
<PackageReference Include="OpenTelemetry" Version="1.6.0-alpha.1" />
<PackageReference Include="OpenTelemetry.Exporter.Jaeger" Version="1.6.0-alpha.1" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.6.0-alpha.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.5.1-beta.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.5.1-beta.1" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.0.64">
<PrivateAssets>all</PrivateAssets>

View File

@ -6,17 +6,38 @@ using OpenTelemetry.Trace;
namespace BuildingBlocks.OpenTelemetry;
using global::OpenTelemetry.Metrics;
public static class Extensions
{
public static IServiceCollection AddCustomOpenTelemetry(this IServiceCollection services)
{
services.AddOpenTelemetryTracing(builder => builder
.AddGrpcClientInstrumentation()
.AddMassTransitInstrumentation()
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(services.GetOptions<AppOptions>("AppOptions").Name))
.AddJaegerExporter());
services.AddOpenTelemetry()
.WithTracing(builder => builder
.AddGrpcClientInstrumentation()
.AddMassTransitInstrumentation()
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.SetResourceBuilder(ResourceBuilder.CreateDefault()
.AddService(services.GetOptions<AppOptions>("AppOptions").Name))
.AddJaegerExporter())
.WithMetrics(builder =>
{
builder.AddPrometheusExporter();
builder.AddMeter(
"Microsoft.AspNetCore.Hosting",
"Microsoft.AspNetCore.Server.Kestrel"
);
builder.AddView("request-duration",
new ExplicitBucketHistogramConfiguration
{
Boundaries = new[]
{
0, 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5, 7.5, 10
}
});
}
);
return services;
}

View File

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS builder
FROM mcr.microsoft.com/dotnet/sdk:8.0-preview AS builder
WORKDIR /
# Setup working directory for the project
@ -27,7 +27,7 @@ WORKDIR /Services/Booking/src/Booking.Api
# and no build, as we did it already
RUN dotnet publish -c Release --no-build -o out
FROM mcr.microsoft.com/dotnet/aspnet:7.0
FROM mcr.microsoft.com/dotnet/aspnet:8.0-preview
# Setup working directory for the project
WORKDIR /

View File

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS builder
FROM mcr.microsoft.com/dotnet/sdk:8.0-preview AS builder
WORKDIR /
# Setup working directory for the project
@ -30,7 +30,7 @@ WORKDIR /Services/Booking/src/Booking.Api
RUN --mount=type=cache,id=booking_nuget,target=/root/.nuget/packages\
dotnet publish -c Release --no-build -o out
FROM mcr.microsoft.com/dotnet/aspnet:7.0
FROM mcr.microsoft.com/dotnet/aspnet:8.0-preview
# Setup working directory for the project
WORKDIR /

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -88,15 +88,15 @@ public static class InfrastructureExtensions
var env = app.Environment;
var appOptions = app.GetOptions<AppOptions>(nameof(AppOptions));
app.MapPrometheusScrapingEndpoint();
app.UseCustomProblemDetails();
app.UseSerilogRequestLogging(options =>
{
options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest;
});
app.UseCorrelationId();
app.UseHttpMetrics();
app.UseCustomHealthCheck();
app.MapMetrics();
app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name));
if (env.IsDevelopment())

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS builder
FROM mcr.microsoft.com/dotnet/sdk:8.0-preview AS builder
WORKDIR /
# Setup working directory for the project
@ -27,7 +27,7 @@ WORKDIR /Services/Flight/src/Flight.Api
# and no build, as we did it already
RUN dotnet publish -c Release --no-build -o out
FROM mcr.microsoft.com/dotnet/aspnet:7.0
FROM mcr.microsoft.com/dotnet/aspnet:8.0-preview
# Setup working directory for the project
WORKDIR /

View File

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS builder
FROM mcr.microsoft.com/dotnet/sdk:8.0-preview AS builder
WORKDIR /
# Setup working directory for the project
@ -30,7 +30,7 @@ WORKDIR /Services/Flight/src/Flight.Api
RUN --mount=type=cache,id=flight_nuget,target=/root/.nuget/packages \
dotnet publish -c Release --no-build -o out
FROM mcr.microsoft.com/dotnet/aspnet:7.0
FROM mcr.microsoft.com/dotnet/aspnet:8.0-preview
# Setup working directory for the project
WORKDIR /

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

View File

@ -23,13 +23,10 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Prometheus;
using Serilog;
namespace Flight.Extensions.Infrastructure;
using BuildingBlocks.PersistMessageProcessor.Data;
public static class InfrastructureExtensions
{
public static WebApplicationBuilder AddInfrastructure(this WebApplicationBuilder builder)
@ -95,15 +92,15 @@ public static class InfrastructureExtensions
var env = app.Environment;
var appOptions = app.GetOptions<AppOptions>(nameof(AppOptions));
app.MapPrometheusScrapingEndpoint();
app.UseCustomProblemDetails();
app.UseSerilogRequestLogging(options =>
{
options.EnrichDiagnosticContext = LogEnrichHelper.EnrichFromRequest;
});
app.UseCorrelationId();
app.UseHttpMetrics();
app.UseMigration<FlightDbContext>(env);
app.MapMetrics();
app.UseCustomHealthCheck();
app.MapGrpcService<FlightGrpcServices>();
app.UseRateLimiter();

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS builder
FROM mcr.microsoft.com/dotnet/sdk:8.0-preview AS builder
# Setup working directory for the project
WORKDIR /
@ -26,7 +26,7 @@ WORKDIR /Services/Identity/src/Identity.Api
# and no build, as we did it already
RUN dotnet publish -c Release --no-build -o out
FROM mcr.microsoft.com/dotnet/aspnet:7.0
FROM mcr.microsoft.com/dotnet/aspnet:8.0-preview
# Setup working directory for the project
WORKDIR /

View File

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS builder
FROM mcr.microsoft.com/dotnet/sdk:8.0-preview AS builder
# Setup working directory for the project
WORKDIR /
@ -29,7 +29,7 @@ WORKDIR /Services/Identity/src/Identity.Api
RUN --mount=type=cache,id=identity_nuget,target=/root/.nuget/packages \
dotnet publish -c Release --no-build -o out
FROM mcr.microsoft.com/dotnet/aspnet:7.0
FROM mcr.microsoft.com/dotnet/aspnet:8.0-preview
# Setup working directory for the project
WORKDIR /

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

View File

@ -92,6 +92,8 @@ public static class InfrastructureExtensions
var env = app.Environment;
var appOptions = app.GetOptions<AppOptions>(nameof(AppOptions));
app.MapPrometheusScrapingEndpoint();
app.UseForwardedHeaders();
app.UseCustomProblemDetails();
@ -101,11 +103,8 @@ public static class InfrastructureExtensions
});
app.UseMigration<IdentityContext>(env);
app.UseCorrelationId();
app.UseHttpMetrics();
app.UseCustomHealthCheck();
app.UseIdentityServer();
app.MapMetrics();
app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name));

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS builder
FROM mcr.microsoft.com/dotnet/sdk:8.0-preview AS builder
WORKDIR /
# Setup working directory for the project
@ -27,7 +27,7 @@ WORKDIR /Services/Passenger/src/Passenger.Api
# and no build, as we did it already
RUN dotnet publish -c Release --no-build -o out
FROM mcr.microsoft.com/dotnet/aspnet:7.0
FROM mcr.microsoft.com/dotnet/aspnet:8.0-preview
# Setup working directory for the project
WORKDIR /

View File

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS builder
FROM mcr.microsoft.com/dotnet/sdk:8.0-preview AS builder
WORKDIR /
# Setup working directory for the project
@ -30,7 +30,7 @@ WORKDIR /Services/Passenger/src/Passenger.Api
RUN --mount=type=cache,id=passenger_nuget,target=/root/.nuget/packages \
dotnet publish -c Release --no-build -o out
FROM mcr.microsoft.com/dotnet/aspnet:7.0
FROM mcr.microsoft.com/dotnet/aspnet:8.0-preview
# Setup working directory for the project
WORKDIR /

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -21,7 +21,6 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Passenger.Data;
using Passenger.GrpcServer.Services;
using Prometheus;
using Serilog;
namespace Passenger.Extensions.Infrastructure;
@ -87,6 +86,8 @@ public static class InfrastructureExtensions
var env = app.Environment;
var appOptions = app.GetOptions<AppOptions>(nameof(AppOptions));
app.MapPrometheusScrapingEndpoint();
app.UseCustomProblemDetails();
app.UseSerilogRequestLogging(options =>
{
@ -94,9 +95,7 @@ public static class InfrastructureExtensions
});
app.UseMigration<PassengerDbContext>(env);
app.UseCorrelationId();
app.UseHttpMetrics();
app.UseCustomHealthCheck();
app.MapMetrics();
app.MapGrpcService<PassengerGrpcServices>();
app.MapGet("/", x => x.Response.WriteAsync(appOptions.Name));

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
</PropertyGroup>