RE:NODE

აპლიკაციები13 წუთის საკითხავი

ASP.NET Core-ის ლოგირება და health check-ები production-ში

ILogger და ლოგის დონეები, სტრუქტურირებული შეტყობინებები, Serilog გონივრული sink-ებით, მოთხოვნების ლოგირება და health check endpoint-ები, რომლებიც აპლიკაციაზე სიმართლეს ამბობენ.

0 მკითხველი

ASP.NET Core აპლიკაციას production-ში ორი რამ სჭირდება შენგან, სანამ რამე ჭკვიანური დასჭირდება: ლოგები, რომლებიც ამბობენ, რა მოხდა, შეგნებულად არჩეულ დონეზე, და endpoint, რომელიც ამბობს, შეუძლია თუ არა აპლიკაციას ახლა თავისი საქმის კეთება. ჩაშენებული ILogger პირველს ფარავს, თუ Logging:LogLevel-ს გააზრებულად დააყენებ და interpolated სტრიქონების ნაცვლად სტრუქტურირებულ შეტყობინებებს დაწერ. AddHealthChecks() და MapHealthChecks("/healthz") მეორეს ორ ხაზში ფარავს. Serilog-ის დამატება ღირს, როცა ფაილები, enrichment ან ლოგების სერვერი გინდა; კარგი ლოგირებისთვის ის აუცილებელი არ არის. ეს პოსტი ორივე ნახევარს იმ რიგით გადის, რომლითაც მათ შეხვდები, ნაგულისხმევი მნიშვნელობებით, კონფიგურაციის გასაღებებით და შეცდომებით, რომლებიც ლოგირებას სავსე დისკის ინციდენტად აქცევს.

როგორ მუშაობს ლოგირება ASP.NET Core-ში#

ლოგირება generic host-ის ნაწილია. როცა WebApplication.CreateBuilder(args)-ს იძახებ, builder არეგისტრირებს console, debug და event source პროვაიდერებს (Windows-ზე დამატებით Windows event log-საც) და კითხულობს კონფიგურაციის Logging სექციას. ყველაფერი, რაც dependency injection-ით ILogger<T>-ს ითხოვს, იღებს logger-ს, რომლის კატეგორია T-ს სრული სახელია, და ყოველი ლოგის გამოძახება ამ კატეგორიით იფილტრება, სანამ პროვაიდერამდე მიაღწევს.

csharp
public class OrderService(ILogger<OrderService> logger, AppDbContext db){    public async Task PlaceAsync(Order order)    {        db.Orders.Add(order);        await db.SaveChangesAsync();        logger.LogInformation("Order {OrderId} placed for {CustomerId}",            order.Id, order.CustomerId);    }}

კატეგორიას მნიშვნელობა აქვს, რადგან ფილტრი სწორედ მას ადარებს. Microsoft.EntityFrameworkCore.Database.Command არის კატეგორია, რომელიც ყოველ SQL ბრძანებას ლოგავს, Microsoft.AspNetCore.Hosting.Diagnostics მოთხოვნის დაწყებასა და დასრულებას ლოგავს, ხოლო MyApp.OrderService ზემოთ მოცემულია. ფილტრები პრეფიქსით ემთხვევა, ამიტომ წესი Microsoft.AspNetCore-ისთვის ყველაფერს ფარავს, რაც მის ქვეშაა.

დონე შვიდია, და ისინი დალაგებული შკალაა და არა ეტიკეტების ნაკრები:

დონემნიშვნელობაგამოიყენე
Trace0ნაბიჯ-ნაბიჯ დეტალებისთვის, შესაძლოა მგრძნობიარე მონაცემებით. production-ში არასოდეს ჩართო
Debug1დეველოპერის დიაგნოსტიკისთვის. production-ში გამორთულია, თუ რამეს არ ეძებ
Information2ჩვეულებრივი მოვლენებისთვის, რომლებიც ხაზად ღირს: გაეშვა, შეკვეთა გაფორმდა, ამოცანა დასრულდა
Warning3უცნაური რამისთვის, საიდანაც აპლიკაცია გამოვიდა
Error4ოპერაცია ჩავარდა; აპლიკაცია მუშაობას აგრძელებს
Critical5აპლიკაცია ან ძირითადი დამოკიდებულება გათიშულია
None6მხოლოდ ფილტრებში, კატეგორიის გამოსართავად

Warning-ზე დაყენებული ფილტრი ატარებს Warning-ს, Error-ს და Critical-ს და დანარჩენს აგდებს. გადაგდებული შეტყობინების ფასი ძალიან მცირეა - logger დონეს რამის დაფორმატებამდე ამოწმებს - ამიტომაც შეგიძლია LogDebug გამოძახებები კოდში დატოვო ისე, რომ მათში არ გადაიხადო.

ლოგის დონეების დაყენება appsettings-სა და გარემოს ცვლადებში#

შაბლონის appsettings.json production-ისთვის თითქმის სწორია:

appsettings.json
{  "Logging": {    "LogLevel": {      "Default": "Information",      "Microsoft.AspNetCore": "Warning",      "Microsoft.EntityFrameworkCore.Database.Command": "Warning"    }  }}

Default ვრცელდება ყველა კატეგორიაზე, რომელსაც უფრო კონკრეტული წესი არ აქვს. Microsoft.AspNetCore Warning-ზე ადუმებს framework-ის ყოველ მოთხოვნაზე ლაყბობას, რომელიც სხვაგვარად ორ ან მეტ ხაზს წერს ყოველი მოთხოვნისთვის, ყოველი სტატიკური ფაილის ჩათვლით. Entity Framework-ის ხაზი ის არის, რომელიც ხალხს ავიწყდება: Information-ზე EF Core ყოველ შესრულებულ SQL ბრძანებას მისი ხანგრძლივობით ლოგავს, რაც შენს ლეპტოპზე სასარგებლოა, ხოლო დატვირთვისას - წყალდიდობა.

კონფიგურაცია ფენებადაა, ამიტომ მომუშავე deploy-ზე დონის შესაცვლელად ფაილის რედაქტირება იშვიათად გჭირდება. გარემოს ცვლადები JSON-ს ფარავს, ხოლო ჩადგმული გასაღებების გამყოფი ორმაგი ქვედა ხაზია:

env
Logging__LogLevel__Default=WarningLogging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Information

წერტილებიანი კატეგორიის სახელი Linux-ზე გარემოს ცვლადის გასაღებად მუშაობს. appsettings.Production.json იტვირთება appsettings.json-ის თავზე, როცა ASPNETCORE_ENVIRONMENT (ან DOTNET_ENVIRONMENT) არის Production, რაც ნაგულისხმევიცაა, როცა არცერთი არ არის დაყენებული. ASP.NET Core-ის კონფიგურაცია და საიდუმლოებები მთელ ფენების რიგს ხსნის, და მისი წაკითხვა ღირს, სანამ connection string-ს სადმე ჩადებ.

დისკზე appsettings.json-ის ცვლილებები აპლიკაციის მუშაობისას აიღება, რადგან ნაგულისხმევ JSON წყაროს reloadOnChange აქვს ჩართული. გარემოს ცვლადები ერთხელ იკითხება, გაშვებისას, ამიტომ ერთის შეცვლა restart-ს ნიშნავს.

ლოგის შეტყობინებები, რომელთა წაკითხვაც ღირს#

.NET-ში ლოგირების ყველაზე გავრცელებული შეცდომა სტრიქონის interpolation-ია:

csharp
// Wrong: formatted every time, and the values are lost as fieldslogger.LogInformation($"Order {order.Id} placed for {order.CustomerId}");// Right: a message template with named placeholderslogger.LogInformation("Order {OrderId} placed for {CustomerId}",    order.Id, order.CustomerId);

მეორე ფორმა message template-ია. placeholder-ები არგუმენტებს პოზიციით ემთხვევა და არა სახელით, ხოლო სახელები ლოგის მოვლენის თვისებებად იქცევა. უბრალო console logger-ით ორივე შემთხვევაში ერთსა და იმავე წინადადებას ხედავ. სტრუქტურირებული პროვაიდერით - JSON console formatter, Serilog ან ნებისმიერი რამ, რაც ლოგების სერვერზე აგზავნის - OrderId და CustomerId ველებად გაქვს, რომლებითაც ფილტრავ, ხოლო თავად შაბლონი უცვლელი რჩება, ამიტომ კითხვა "რამდენჯერ მოხდა ეს შეტყობინება" უბრალო დათვლად იქცევა. interpolated ვერსია ფორმატდება მაშინაც, როცა დონე გაფილტრულია, რადგან სტრიქონი მეთოდის გამოძახებამდე იწყობა.

ცხელი გზებისთვის source-generated LoggerMessage ატრიბუტი დარჩენილ ხარჯსაც აშორებს: არანაირი value type-ების boxing, არანაირი შაბლონის parsing გაშვების დროს და დონის შემოწმება ყველაფრის შეფასებამდე.

csharp
public static partial class Log{    [LoggerMessage(Level = LogLevel.Warning,        Message = "Payment provider slow: {ElapsedMs} ms for order {OrderId}")]    public static partial void PaymentSlow(ILogger logger, long elapsedMs, int orderId);}

სამი ჩვევა ნებისმიერ პროვაიდერის არჩევანზე მეტი ღირს:

  • გამონაკლისები გამონაკლისებად დალოგე. logger.LogError(ex, "Charging order {OrderId} failed", id) stack trace-ს სტრუქტურირებულ ველად ინახავს. ex.Message-ის შაბლონში ჩასმა იმ ნაწილს გადააგდებს, რომელიც გჭირდება.
  • ერთი ხაზი მოვლენაზე და არა ნაბიჯზე. მოთხოვნა, რომელიც ბედნიერ გზაზე ათ Information ხაზს წერს, დამარხავს იმ ერთ Warning-ს, რომელსაც მნიშვნელობა აქვს.
  • არასოდეს დალოგო საიდუმლოებები ან მოთხოვნების სრული body. პაროლები, ტოკენები, connection string-ები და ბარათის ნომრები ლოგ ფაილებში ხვდება, რომლებიც კოპირდება, ჩამოიტვირთება და ბევრად უფრო დიდხანს ინახება, ვიდრე მონაცემები, რომლებსაც აღწერს.

Scope-ები კონტექსტს ამაგრებს ყოველ შეტყობინებას, რომელიც მათ შიგნით იწერება, და ასე ხვდება შეკვეთის ნომერი იმ ხაზებზე, რომლებსაც კოდი ლოგავს, რომელმაც შეკვეთების შესახებ არაფერი იცის. using (logger.BeginScope("Order {OrderId}", id)) { ... } ამას აკეთებს; console პროვაიდერი scope-ებს მხოლოდ მაშინ აჩვენებს, თუ მის formatter-ზე IncludeScopes ჩართულია, ხოლო სტრუქტურირებული პროვაიდერები მათ თვისებებად წერენ.

Console-ის გამოტანა კონტეინერში#

კონტეინერის ჰოსტზე console თავად არის ლოგი. რასაც პროცესი სტანდარტულ გამოტანაში წერს, იმას გაჩვენებს პანელი, ხოლო რა ინახება restart-ებს შორის, ჰოსტზეა დამოკიდებული, ამიტომ console ცოცხალ ხედად მიიჩნიე და არა არქივად. ნაგულისხმევი simple formatter თითო შეტყობინებაზე ორ ხაზს წერს (კატეგორია ერთზე, ტექსტი მეორეზე), რაც ცოცხალ console-ში ცუდად იკითხება. ორი ცვლილება გეხმარება:

csharp
builder.Logging.ClearProviders();builder.Logging.AddSimpleConsole(options =>{    options.SingleLine = true;    options.TimestampFormat = "yyyy-MM-dd HH:mm:ss ";    options.UseUtcTimestamp = true;});

თუ შენს ლოგებს რაღაც ქვემოთ parse-ს უკეთებს, ამის ნაცვლად გამოიყენე builder.Logging.AddJsonConsole(), რომელიც თითო ხაზზე ერთ JSON ობიექტს წერს შაბლონით, დარენდერებული შეტყობინებით და ყოველი დასახელებული თვისებით.

RE:NODE-ზე console ჩანართი პროცესის გამოტანას უფილტროდ და ცოცხლად აჩვენებს, მეხსიერების, CPU-სა და დისკის გრაფიკებთან ერთად გეგმის ლიმიტებთან შედარებით, ამიტომ ერთხაზიანი, დროის ნიშნულებიანი console არის ის, რასაც რეალურად წაიკითხავ, როცა რაღაც აირევა. ეს ლოგების არქივი არ არის: თუ გასული სამშაბათის შეცდომები მომავალ თვეში დაგჭირდება, ჩაწერე ისინი სადმე, სადაც შეინახება.

Serilog: როდის ღირს დამატება#

Serilog თავის ადგილს იმსახურებს, როცა სამიდან ერთ-ერთი რამ გინდა, რასაც ჩაშენებული პროვაიდერები კარგად ვერ აკეთებს: rolling ლოგ ფაილები retention-ით, enrichment (მანქანის სახელი, მოთხოვნის ID, მომხმარებლის ID ყოველ მოვლენაზე), ან sink-ით გაგზავნა ლოგების სერვერზე, როგორიცაა Seq, Elasticsearch ან Grafana Loki. დაყენება ერთი პაკეტი და რამდენიმე ხაზია:

bash
$ dotnet add package Serilog.AspNetCore$ dotnet add package Serilog.Sinks.File
csharp
Log.Logger = new LoggerConfiguration()    .WriteTo.Console()    .CreateBootstrapLogger();try{    var builder = WebApplication.CreateBuilder(args);    builder.Services.AddSerilog((services, lc) => lc        .ReadFrom.Configuration(builder.Configuration)        .ReadFrom.Services(services)        .Enrich.FromLogContext());    var app = builder.Build();    app.UseSerilogRequestLogging();    // ... endpoints    app.Run();}catch (Exception ex){    Log.Fatal(ex, "Host terminated unexpectedly");}finally{    Log.CloseAndFlush();}

bootstrap logger იჭერს შეცდომებს გაშვებისას, სანამ კონფიგურაცია ჩაიტვირთება - ზუსტად მაშინ, როცა გამოტოვებული გარემოს ცვლადი აპლიკაციას აგდებს და ნაგულისხმევი დაყენება ძალიან ცოტას ბეჭდავს. Log.CloseAndFlush() მნიშვნელოვანია ასინქრონული და batching sink-ებისთვის: მის გარეშე crash-ის წინა ბოლო შეტყობინებები ჯერ კიდევ buffer-შია, როცა პროცესი გადის.

UseSerilogRequestLogging() framework-ის თითო მოთხოვნაზე რამდენიმე ხაზს ერთი ხაზით ცვლის, რომელიც მეთოდს, გზას, სტატუს კოდს და გასულ დროს შეიცავს, და ეს ის მოთხოვნების ლოგია, რომელიც ადამიანების უმეტესობას რეალურად უნდა. შეუთავსე მას Microsoft.AspNetCore-ის override Warning-ზე, რომ framework-ის საკუთარი ხაზებიც არ ჩაიწეროს.

კონფიგურაცია საკუთარ სექციაში ცხოვრობს, რომელიც Logging-ს ცვლის, როგორც კი Serilog იღებს მართვას:

appsettings.json
{  "Serilog": {    "MinimumLevel": {      "Default": "Information",      "Override": {        "Microsoft.AspNetCore": "Warning",        "Microsoft.EntityFrameworkCore": "Warning"      }    },    "WriteTo": [      { "Name": "Console" },      {        "Name": "File",        "Args": {          "path": "logs/app-.log",          "rollingInterval": "Day",          "retainedFileCountLimit": 14,          "fileSizeLimitBytes": 52428800,          "rollOnFileSizeLimit": true        }      }    ]  }}

თუ ფაილები არ გჭირდება, ნუ წერ მათ. მხოლოდ console-ზე მომუშავე აპლიკაციას, რომელიც ლოგებს sink-ით სერვერზე აგზავნის, დისკის შესავსები არაფერი აქვს. ლოგები, რომლებიც ღირს შენახვად განიხილავს, რა შეინახო და რამდენ ხანს.

Health check-ები: liveness, readiness და დამოკიდებულებები#

health check endpoint მანქანისთვის ერთ კითხვას პასუხობს: შეუძლია ამ ინსტანსს ახლა მოთხოვნების მომსახურება? ASP.NET Core საჭირო მილსადენს ყუთშივე მოყვება.

csharp
builder.Services.AddHealthChecks()    .AddCheck("self", () => HealthCheckResult.Healthy(), tags: ["live"])    .AddDbContextCheck<AppDbContext>(tags: ["ready"]);var app = builder.Build();app.MapHealthChecks("/healthz/live", new HealthCheckOptions{    Predicate = check => check.Tags.Contains("live")});app.MapHealthChecks("/healthz/ready", new HealthCheckOptions{    Predicate = check => check.Tags.Contains("ready")});

AddDbContextCheck მოდის Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore პაკეტიდან და უბრალოდ ეკითხება EF Core-ს, შეუძლია თუ არა დაკავშირება. EF Core-ის გარეშე მონაცემთა ბაზის შესამოწმებლად საზოგადოების AspNetCore.HealthChecks.* პაკეტები გთავაზობს AddSqlServer, AddNpgSql, AddMySql, AddRedis და ბევრ სხვას; ისინი ფართოდ გამოიყენება, მაგრამ framework-ის ნაწილი არ არის, ამიტომ მათი ვერსიები ისევე დააფიქსირე, როგორც ნებისმიერი სხვა დამოკიდებულების.

პასუხი უბრალო ტექსტია - Healthy, Degraded ან Unhealthy - ხოლო სტატუს კოდები ნაგულისხმევად ასეა მიბმული:

შედეგიHTTP სტატუსიმნიშვნელობა
Healthy200ყველაფერი შემოწმებული წესრიგშია
Degraded200მუშაობს, მაგრამ რაღაც ნელია ან ნაწილობრივ ვარდება
Unhealthy503აქ ტრაფიკს ნუ გამოგზავნი

liveness-სა და readiness-ს შორის გაყოფა ის ნაწილია, რომელიც გათიშვებს თავიდან აგარიდებს და არ იწვევს მათ. Liveness კითხულობს "ცოცხალია და არ არის გაჭედილი პროცესი" და გარეთ არაფერი უნდა შეამოწმოს. Readiness კითხულობს "შეუძლია რეალური მოთხოვნების მომსახურება", და მონაცემთა ბაზა აქ ეკუთვნის. თუ შენი ერთადერთი endpoint მონაცემთა ბაზას ამოწმებს და რაღაც აპლიკაციას ყოველთვის რესტარტავს, როცა ეს endpoint ვარდება, მონაცემთა ბაზის ხუთწამიანი შეფერხება ყველა ინსტანსს ერთდროულად რესტარტავს და ყველა ერთად ხელახლა უკავშირდება. liveness check, რომელიც არცერთ დამოკიდებულებას არ ეხება, ამას ვერ გამოიწვევს.

საკუთარი check არის კლასი, რომელიც IHealthCheck-ს ახორციელებს:

csharp
public class QueueDepthCheck(IJobQueue queue) : IHealthCheck{    public async Task<HealthCheckResult> CheckHealthAsync(        HealthCheckContext context, CancellationToken cancellationToken = default)    {        var depth = await queue.CountAsync(cancellationToken);        return depth switch        {            < 1_000 => HealthCheckResult.Healthy($"{depth} jobs queued"),            < 10_000 => HealthCheckResult.Degraded($"{depth} jobs queued"),            _ => HealthCheckResult.Unhealthy($"{depth} jobs queued")        };    }}builder.Services.AddHealthChecks()    .AddCheck<QueueDepthCheck>("queue", timeout: TimeSpan.FromSeconds(3), tags: ["ready"]);

ყოველ check-ს, რომელიც ქსელს ეხება, timeout მიეცი. health endpoint, რომელიც ოცდაათი წამით კიდია, უარესია, ვიდრე ის, რომელიც 503-ს აბრუნებს, რადგან ის, რაც მას ამოწმებს, შეიძლება კავშირებს ღიად ინახავდეს და ისინი დაგროვდეს.

Health endpoint-ის დაცვა და გამოჩენა#

ნაგულისხმევი პასუხი ერთი სიტყვაა, რისი გამოჩენაც უსაფრთხოა. როგორც კი დეტალურ writer-ს დაამატებ - პოპულარულ UIResponseWriter.WriteHealthCheckUIResponse-ს AspNetCore.HealthChecks.UI.Client-იდან, ან საკუთარ JSON-ს ყოველი check-ის აღწერითა და გამონაკლისით - შენი დამოკიდებულებების სახელებს და ზოგჯერ მათ შეცდომის შეტყობინებებს აქვეყნებ. საჯარო endpoint მოკლე დატოვე, ხოლო დეტალური ავტორიზაციის ან ჰოსტის შეზღუდვის უკან დამალე:

csharp
app.MapHealthChecks("/healthz/ready");                 // public, one wordapp.MapHealthChecks("/healthz/detail", new HealthCheckOptions{    ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse}).RequireAuthorization("Ops");

health endpoint-ები მოთხოვნების ლოგირებიდან ამოიღე, თორემ მონიტორი, რომელიც ყოველ ოცდაათ წამში ამოწმებს, დღეში თითქმის სამი ათას ხაზს წერს არაფრის შესახებ. Serilog-ით UseSerilogRequestLogging-ის GetLevel ოფცია მათ Verbose-მდე ჩამოწევს.

იცოდე, რა გამოიძახებს endpoint-ს რეალურად. RE:NODE-ზე პლატფორმის crash watcher ყოველ ორ წუთში ამოწმებს სერვერს, რომლის uptime უკან წავიდა ან რომელიც offline გავიდა, და საათში სამი მოულოდნელი restart-ის შემდეგ ticket-ს ხსნის. ის პროცესს უყურებს; შენს /healthz-ს არ იძახებს. გაჭედილ აპლიკაციას, რომლის პროცესი ჯერ კიდევ ცოცხალია და 503-ს აბრუნებს, ის არ დაარესტარტებს, ამიტომ გარე uptime მონიტორი readiness URL-ზე მიმართე შენი დომენის გავლით. მონიტორინგი, რომელიც რამეს გეუბნება განიხილავს, რაზე გააგზავნო გაფრთხილება, ხოლო graceful shutdown და health check-ები მეორე ნახევარს ფარავს - readiness-ის unhealthy-ზე გადართვას, როცა აპლიკაცია ჩერდება, რომ proxy-მ მისთვის ტრაფიკის გაგზავნა შეწყვიტოს, სანამ ის გავა.

მოთხოვნების ლოგირება, HTTP ლოგირება და კორელაცია#

მოთხოვნის დონის სამი ინსტრუმენტი არსებობს და ისინი ურთიერთშემცვლელი არ არის:

  • Serilog request logging - ერთი შემაჯამებელი ხაზი თითო მოთხოვნაზე. სწორი ნაგულისხმევი არჩევანი.
  • `AddHttpLogging` / `UseHttpLogging` - framework-ის HTTP ლოგირების middleware, რომელსაც header-ებისა და body-ების ჩართვა შეუძლია. ის Information-ზე ლოგავს Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware კატეგორიაში, ამიტომ არაფერს აჩვენებს, სანამ ეს კატეგორია ფილტრში არ გაატარე. სასარგებლოა მოკლე debugging სესიისთვის; ჩართული დატოვება საშიშია, რადგან header-ები cookie-ებსა და ავტორიზაციის ტოკენებს შეიცავს, თუ RequestHeaders და ResponseHeaders არ შეზღუდე.
  • `AddW3CLogging` - access log W3C extended ფორმატში, რომელიც ფაილებში იწერება. მოსახერხებელია, თუ უკვე გაქვს ინსტრუმენტები, რომლებიც IIS-ის სტილის ლოგებს კითხულობს.

კორელაციისთვის ყოველ მოთხოვნას უკვე აქვს HttpContext.TraceIdentifier, ხოლო ნაგულისხმევი Activity tracking-ით მოთხოვნის შიგნით ყოველი ლოგის მოვლენა TraceId-სა და SpanId-ს ატარებს, როცა პროვაიდერი მათ წერს (JSON console formatter-საც და Serilog-საც შეუძლია). დააბრუნე trace ID შეცდომის პასუხებში - ProblemDetails ნაგულისხმევად შეიცავს traceId გაფართოებას, როცა AddProblemDetails()-ს იყენებ - და მომხმარებლის screenshot-ი შეცდომის გვერდიდან შენს ლოგებში პირდაპირ ძიებად იქცევა.

reverse proxy-ს უკან შენი მოთხოვნების ლოგებში კლიენტის მისამართი proxy-ისაა, სანამ forwarded header-ებს არ ჩართავ. კლიენტის რეალური IP X-Forwarded-For-ში მოდის, ხოლო ASP.NET Core reverse proxy-ს უკან აჩვენებს ForwardedHeadersOptions-ს, რომელიც HttpContext.Connection.RemoteIpAddress-ს სწორს ხდის, და სწორედ ამას კითხულობს შენი ლოგებიც და ნებისმიერი rate limiter-იც.

პრობლემების მოგვარება#

საერთოდ არაფერი ილოგება. ჩვეულებრივ ClearProviders() გამოიძახეს და უკან არაფერი დაამატეს, ან Serilog დააკონფიგურირეს, მაგრამ მისი WriteTo სექცია ცარიელია ან არასწორადაა დაწერილი. Serilog-ის SelfLog.Enable(Console.Error) მის საკუთარ კონფიგურაციის შეცდომებს ბეჭდავს.

Console SQL-ით ივსება. Microsoft.EntityFrameworkCore.Database.Command არის Information-ზე, ან აშკარად, ან Default-ის გავლით. დაამატე override Warning-ზე.

გაშვება სასარგებლო გამოტანის გარეშე ვარდება. გამონაკლისი ლოგირების დაკონფიგურირებამდე მოხდა. გამოიყენე bootstrap logger (Serilog) ან შეფუთე builder.Build() და app.Run() try/catch-ში, რომელიც Console.Error-ში წერს, შემდეგ კი console თავიდან წაიკითხე.

Health endpoint 503-ს აბრუნებს, მაგრამ აპლიკაცია მუშაობს. ამ endpoint-ზე მონიშნული ერთ-ერთი check ვარდება ან timeout-ს იღებს. დროებით მიაბი დეტალური writer ავტორიზაციის უკან და წაიკითხე, რომელია; ჩვეულებრივ ეს დამოკიდებულებაა, რომელიც endpoint-ს საერთოდ არ უნდა შეემოწმებინა.

დისკი გაივსო. ჯერ ლოგების დირექტორია იპოვე. rolling file sink-ები ნაგულისხმევი retention-ით, ჩართული დატოვებული Debug და HTTP ლოგირება body-ებით სამი ჩვეულებრივი მიზეზია, ამ რიგით.

ლოგის ხაზები ყველა კლიენტისთვის proxy-ის მისამართს აჩვენებს. forwarded header-ები არ არის დაკონფიგურირებული, ან KnownProxies proxy-ის მისამართს არ შეიცავს, ამიტომ middleware header-ს უგულებელყოფს.

FAQ#

მჭირდება Serilog, თუ ჩაშენებული ლოგირება საკმარისია?

ჩაშენებული ILogger JSON console formatter-ით საკმარისია აპლიკაციისთვის, რომლის ლოგებიც console-იდან იკითხება ან სტანდარტული გამოტანიდან გროვდება. Serilog-ის დამატება ღირს rolling ფაილებისთვის retention-ით, ყოველ მოვლენაზე enrichment-ისთვის ან ლოგების სერვერზე sink-ისთვის. შენი კოდი ორივე შემთხვევაში ILogger-ს იძახებს, ამიტომ მოგვიანებით გადართვა ერთი დაყენების ცვლილება ღირს.

რომელ ლოგის დონეზე უნდა მუშაობდეს production?

Information შენი საკუთარი კატეგორიებისთვის, Warning Microsoft.AspNetCore-ისა და EF Core-ისთვის. როცა პრობლემას დასდევ, ერთი კატეგორია დროებით აწიე Debug-მდე გარემოს ცვლადით, შემდეგ კი უკან დააბრუნე.

უნდა ამოწმებდეს health check მონაცემთა ბაზას?

readiness check-მა უნდა შეამოწმოს, რადგან აპლიკაცია, რომელიც მონაცემთა ბაზას ვერ წვდება, მოთხოვნების უმეტესობას ვერ მოემსახურება. liveness check-მა არ უნდა, რადგან აპლიკაციის restart მონაცემთა ბაზას არ ასწორებს, ხოლო ყველა ინსტანსის ერთდროული restart აღდგენას ანელებს.

რატომ აკლია ჩემს გამოტანას სტრუქტურირებული თვისებები?

ან შეტყობინება სტრიქონის interpolation-ით აიწყო და დასაჭერი placeholder-ები არ არის, ან პროვაიდერი უბრალო simple console formatter-ია, რომელიც წინადადებას არენდერებს და ველებს აგდებს. გამოიყენე message template და სტრუქტურირებული formatter ან sink.

რამდენად ხშირად უნდა იძახებდეს მონიტორი health endpoint-ს?

პატარა აპლიკაციისთვის ყოველი 30-დან 60 წამამდე საკმარისია. უფრო ხშირად დატვირთვასა და ლოგის ხმაურს ამატებს ისე, რომ ბევრად ადრე არაფერს გეუბნება, ხოლო ზედიზედ ორი ჩავარდნის შემდეგ გაფრთხილება ერთი ნელი პასუხის გამო საკუთარი თავის გაღვიძებას აგარიდებს.


კომენტარები

სრულიად ანონიმურად: ანგარიშის, ელფოსტის და cookie-ის გარეშე. ინახება მხოლოდ სახელი, ტექსტი და დრო - სხვა არაფერი. ბმულების რაოდენობა ლიმიტირებულია.

0/2000