Aller au contenu

Asp.net core 9

De Banane Atomic

Links

Changes

Log

By default, an app is logging into Console, Debug, EventSource, EventLog (only when running on Windows).
This include the linux journal for production and the VS code DEBUG CONSOLE for development.
public class MyClass(ILogger<MyClass> logger)
{
    public void MyMethod()
    {
        // to optimize the performances, test if the log level is enabled
        if (logger.IsEnabled(LogLevel.Debug)) logger.LogDebug("Debug");
        logger.LogTrace("Trace");
        logger.LogInformation("Info");
        logger.LogWarning("Warn");
        logger.LogError("Error");
        logger.LogCritical("Fatal");
    }
}
appsettings.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",       // log level par défaut
      "Microsoft": "Warning",         // pour les loggers commençant par Microsoft
      "MyNamespace.MyClass": "Debug"  // pour les loggers de MyClass
    }
  }
Program.cs
builder.Logging.ClearProviders();  // remove all the providers: no more logs
The Log methods are synchronous.
There is no file log provider.

Log in Program.cs

Program.cs
// with app
app.Logger.LogInformation("message");

// before app
var loggerFactory = LoggerFactory.Create(loggingBuilder =>
{
    loggingBuilder.AddConsole();
    loggingBuilder.AddConfiguration(builder.Configuration.GetSection("Logging"));
});
var logger = loggerFactory.CreateLogger("Program");

Built-in logging providers

Console
  • VS code "DEBUG CONSOLE" tab
  • In the console window when the app is run with dotnet run
Debug Debug window when debugging in Visual Studio
On Linux journal or /var/log/message or /var/log/syslog
EventSource Event Tracing for Windows (ETW)
EventLog Windows Event Log
TraceSource System.Diagnostics.TraceSource libraries and providers ()
Azure App Service text files in an Azure App Service app's file system and to blob storage in an Azure Storage account

Console

appsettings.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Error"  // default log level
    }
  },
  "Console": {
    "LogLevel": {
      "Default": "Information"  // overwrite log level
    },
    "FormatterName": "json"  // log in JSON format
  }
}

journal

mar 19 17:53:10 hostname logtest[29321]: fail: LogTest.Controllers.ItemController[0]
mar 19 17:53:10 hostname logtest[29321]:       Log message

mar 19 17:58:27 hostname logtest[29321]: fail: Microsoft.AspNetCore.Server.Kestrel[13]
mar 19 17:58:27 hostname logtest[29321]:       Connection id "...", Request id "...": An unhandled exception was thrown by the application.
mar 19 17:58:27 hostname logtest[29321]: System.Exception: exception message
mar 19 17:58:27 hostname logtest[29321]:    at LogTest.Controllers.ItemController.ThrowException() in /dev-path/LogTest/Controllers/ItemController.cs:line 41

apache log

  • access.log is well filled by each query
  • error.log is never filled

LoggerMessage

Use LoggerMessage over Logger extension methods to improve performances.

logger.LogInformation("Message: {Message}", message);

static partial class Log
{
    [LoggerMessage(Level = LogLevel.Information, Message = "Message: {Message}")]
    public static partial void LogRequest(ILogger logger, string Message);
}

Log.LogRequest(logger, message);

Configuration

Sources des settings par défaut dans l'ordre de lecture:

  1. appsettings.json
  2. appsettings.<EnvironmentName>.json (Development, Staging, Production)
  3. UserSecrets in Development environment
  4. Environment variables
  5. Command-line arguments
Les settings de la source suivante écrasent ceux déjà chargés.
ItemsController.cs
public class ItemsController(IConfiguration configuration) : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        // recherche Key, si Key n'a pas été trouvé affecte default value à la variable value
        var value = Configuration.GetValue<string>("Key", "default value");
        var value = Configuration["Key"];

       // pour les connection strings
       configuration.GetConnectionString("SqlServer1")

       // dotnet add package System.Data.SqlClient
       var builder = new SqlConnectionStringBuilder(Configuration.GetConnectionString("SqlServer4")); // from appsettings.json
       builder.Password = configuration["DbPassword"]; // from secrets storage in Dev and appsettings.json in Prod 
       var connectionString = builder.ConnectionString;

appsettings.json

appsettings.json
{
  "Key": "Value",
  "ConnectionStrings": {
    "SqlServer1": "Server=(localdb)\\MSSQLLocalDB;Database=MyDb;Integrated Security=True;MultipleActiveResultSets=True",
    "SqlServer2": "Server=(localdb)\\mssqllocaldb;Database=MyDb;Trusted_Connection=True;",
    "SqlServer3": "Server=localhost;Database=MyDb;User=sa;Password=pwd;",
    "SqlServer4": "server=localhost;database=MyDb;user=sa;",
    "MySql": "Server=localhost;Database=MyDb;User=root;Password=pwd;",
    "Sqlite": "Data Source=MyDb.db"
  },
  "DbPassword": "****"
}

Safe storage of app secrets in development in ASP.NET Core

Useful in dev environment to not store password in source code.

# aller dans le dossier contenant le *.csproj
cd MyProject

# adds a UserSecretsId element within a PropertyGroup of the .csproj file
dotnet user-secrets init

# set a secret
# put a space as first character so the command line is not saved in the history
 dotnet user-secrets set "key" "secret"
# the whole connection string
 dotnet user-secrets set "ConnectionStrings:SqlServer" "server=localhost;database=test;user=test;password=****"
# only the connection string password
 dotnet user-secrets set "DbPassword" "****"

# the secrets are stored in a JSON configuration file in a system-protected user profile folder on the local machine
# Windows: %APPDATA%\microsoft\UserSecrets\<userSecretsId>\secrets.json
# Linux:   ~/.microsoft/usersecrets/<userSecretsId>/secrets.json


# list all secrets of the current project
dotnet user-secrets list

# remove a secret in the current project
dotnet user-secrets remove "key"

# remove all secrets of the current project
dotnet user-secrets clear
The user secrets configuration source is automatically added in development mode for Web API project when WebApplication.CreateBuilder(args) is called.
CreateBuilder calls AddUserSecrets when the EnvironmentName is Development
Console/Program.cs
public static IConfigurationRoot Configuration { get; set; }

private static void Main()
{
    var builder = new ConfigurationBuilder();
    // dotnet add package Microsoft.Extensions.Configuration.UserSecrets
    builder.AddUserSecrets<Program>();
    Configuration = builder.Build();

Non-prefixed environment variables

/etc/systemd/system/kestrel-myapp.service
Environment=ConnectionStrings__MyApp=server\x3dlocalhost\x3bdatabase\x3dMyDb\x3buser\x3dMyUser\x3bpassword\x3dMyPwd
Environment=SqlServerVersion=10.10.5-MariaDB

Command-line

dotnet MyApp MyKey="value"

Publish

Framework-dependent executable

Creates a platform-specific executable with only the application binaries and its dependencies.

  • lighter deployment: only the application binaries and its dependencies are deployed
  • the .NET runtime security updates are managed by the system
  • the required .NET runtime of the application has to match the installed .NET runtime
dotnet publish -c Release -r linux-arm64 --self-contained false
# -c : configuration : Debug (default), Release
# -r <RUNTIME_IDENTIFIER> : publishes the application for a given runtime
# --self-contained [true|false] : publishes the .NET Core runtime with the application (default true if a runtime identifier is specified and the project is an executable project)
# -o /path/folder : output directory (default: bin/[configuration]/[framework]/[runtime]/publish )

RID Catalog (list): linux-arm64, linux-x64, win10-x64

Self-contained deployment

Creates a platform-specific executable with the application binaries, its dependencies and the required .NET runtime.

  • runs on hosts which doesn't have the .NET runtime installed
  • bigger deployment
  • doesn't follow the .NET security updates
dotnet publish -c Release -r linux-arm64 --self-contained true -p:PublishTrimmed=true
# -c : configuration : Debug (default), Release
# -r <RUNTIME_IDENTIFIER> : publishes the application for a given runtime
# -o /path/folder : output directory (default: bin/[configuration]/[framework]/[runtime]/publish )

Linux

Service file

/etc/systemd/system/kestrel-myproject.service
[Unit]
Description=My Project description

[Service]
WorkingDirectory=/var/www/myproject
ExecStart=/var/www/myproject/MyProject
Restart=always
RestartSec=10  # Restart service after 10 seconds if dotnet service crashes
KillSignal=SIGINT
SyslogIdentifier=myproject
User=http
Environment=DOTNET_ROOT=/usr/share/dotnet8.0.4 ; to force a specific version of dotnet
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false
Environment=ASPNETCORE_URLS=http://localhost:5001
Environment=ConnectionStrings__MyConnectionString=server\x3dlocalhost\x3bdatabase\x3dMyDb\x3buser\x3dMyUser\x3bpassword\x3dMyPwd
Environment=SqlServerVersion=10.10.5-MariaDB
Environment=MyWebapiUrl=http://localhost:5002

[Install]
WantedBy=multi-user.target
# escape value as connection string
systemd-escape "<value-to-escape>"