Compare commits

...

2 Commits

Author SHA1 Message Date
bed42a83bf Добавил кастомный форматер логов
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 2m31s
2025-11-21 23:43:08 +03:00
33814bb6f4 Обновил методы логирования 2025-11-21 23:37:15 +03:00
6 changed files with 192 additions and 46 deletions

View File

@@ -1,10 +1,7 @@
using System.Net;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using ModeusSchedule.Abstractions;
using ModeusSchedule.Abstractions.DTO;
using SfeduSchedule.Services;

View File

@@ -0,0 +1,124 @@
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging.Console;
using Microsoft.Extensions.Options;
namespace SfeduSchedule.Logging;
public sealed class ConsoleFormatter : Microsoft.Extensions.Logging.Console.ConsoleFormatter, IDisposable
{
private readonly IDisposable? _optionsReloadToken;
private ConsoleFormatterOptions _formatterOptions;
public ConsoleFormatter(IOptionsMonitor<ConsoleFormatterOptions> options)
: base("CustomConsoleFormatter")
{
_optionsReloadToken = options.OnChange(ReloadLoggerOptions);
_formatterOptions = options.CurrentValue;
}
private void ReloadLoggerOptions(ConsoleFormatterOptions options)
{
_formatterOptions = options;
}
public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeProvider? scopeProvider, TextWriter textWriter)
{
var message = logEntry.Formatter(logEntry.State, logEntry.Exception);
if (logEntry.Exception == null && message == null)
{
return;
}
// Timestamp
if (!string.IsNullOrEmpty(_formatterOptions.TimestampFormat))
{
textWriter.Write(DateTime.Now.ToString(_formatterOptions.TimestampFormat));
}
// Level
// Нужно для удаления цвета в логах при перенаправлении вывода
var useColor = _formatterOptions.ColorBehavior == LoggerColorBehavior.Enabled ||
(_formatterOptions.ColorBehavior == LoggerColorBehavior.Default && !System.Console.IsOutputRedirected);
textWriter.Write(GetLogLevelString(logEntry.LogLevel, useColor));
// Write :
textWriter.Write(":");
// TraceId
var traceIdHolder = new TraceIdHolder();
scopeProvider?.ForEachScope((scope, state) =>
{
if (scope is not IEnumerable<KeyValuePair<string, object>> props) return;
foreach (var pair in props)
{
if (pair.Key == "TraceId")
state.TraceId = pair.Value?.ToString();
}
}, traceIdHolder);
if (!string.IsNullOrEmpty(traceIdHolder.TraceId))
{
textWriter.Write($" [{traceIdHolder.TraceId}]");
}
// Category
textWriter.Write($" {logEntry.Category}: ");
// Message
textWriter.WriteLine(message);
if (logEntry.Exception != null)
{
textWriter.WriteLine(logEntry.Exception.ToString());
}
}
private static string GetLogLevelString(LogLevel logLevel, bool useColor)
{
var logLevelString = logLevel switch
{
LogLevel.Trace => "trce",
LogLevel.Debug => "dbug",
LogLevel.Information => "info",
LogLevel.Warning => "warn",
LogLevel.Error => "fail",
LogLevel.Critical => "crit",
_ => "unknown"
};
if (!useColor)
{
return logLevelString;
}
var color = logLevel switch
{
LogLevel.Trace => "\x1B[90m",
LogLevel.Debug => "\x1B[37m",
LogLevel.Information => "\x1B[32m",
LogLevel.Warning => "\x1B[33m",
LogLevel.Error => "\x1B[31m",
LogLevel.Critical => "\x1B[41m\x1B[37m",
_ => "\x1B[39m"
};
return $"{color}{logLevelString}\x1B[0m";
}
public void Dispose()
{
_optionsReloadToken?.Dispose();
}
}
public class ConsoleFormatterOptions : Microsoft.Extensions.Logging.Console.ConsoleFormatterOptions
{
public LoggerColorBehavior ColorBehavior { get; set; }
}
internal class TraceIdHolder
{
public string? TraceId { get; set; }
}

View File

@@ -0,0 +1,46 @@
using System.Runtime.CompilerServices;
namespace SfeduSchedule.Logging;
public static class LoggerExtensions
{
public static void LogTraceHere(this ILogger logger, string message, [CallerMemberName] string memberName = "")
{
logger.LogTrace("[{Member}] {Message}", memberName, message);
}
public static void LogDebugHere(this ILogger logger, string message, [CallerMemberName] string memberName = "")
{
logger.LogDebug("[{Member}] {Message}", memberName, message);
}
public static void LogInformationHere(this ILogger logger, string message, [CallerMemberName] string memberName = "")
{
logger.LogInformation("[{Member}] {Message}", memberName, message);
}
public static void LogWarningHere(this ILogger logger, string message, [CallerMemberName] string memberName = "")
{
logger.LogWarning("[{Member}] {Message}", memberName, message);
}
public static void LogErrorHere(this ILogger logger, string message, [CallerMemberName] string memberName = "")
{
logger.LogError("[{Member}] {Message}", memberName, message);
}
public static void LogErrorHere(this ILogger logger, Exception exception, string message, [CallerMemberName] string memberName = "")
{
logger.LogError(exception, "[{Member}] {Message}", memberName, message);
}
public static void LogCriticalHere(this ILogger logger, string message, [CallerMemberName] string memberName = "")
{
logger.LogCritical("[{Member}] {Message}", memberName, message);
}
public static void LogCriticalHere(this ILogger logger, Exception exception, string message, [CallerMemberName] string memberName = "")
{
logger.LogCritical(exception, "[{Member}] {Message}", memberName, message);
}
}

View File

@@ -1,5 +1,4 @@
using System.Net;
using System.Diagnostics;
using System.Reflection;
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.Authentication;
@@ -14,11 +13,10 @@ using Quartz;
using SfeduSchedule;
using SfeduSchedule.Auth;
using SfeduSchedule.Jobs;
using SfeduSchedule.Logging;
using SfeduSchedule.Middleware;
using SfeduSchedule.Services;
using SfeduSchedule.Logging;
using X.Extensions.Logging.Telegram.Extensions;
using Microsoft.Extensions.Logging.Console;
using IPNetwork = Microsoft.AspNetCore.HttpOverrides.IPNetwork;
var builder = WebApplication.CreateBuilder(args);
@@ -53,12 +51,9 @@ var pluginsPath = Path.Combine(dataDirectory, "Plugins");
#region Работа с логированием
builder.Logging.ClearProviders();
// Configure the console logger to include logging scopes so TraceId from the CorrelationIdMiddleware is visible
builder.Logging.AddSimpleConsole(options =>
{
options.SingleLine = true;
options.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff ";
});
builder.Logging.AddConsole(options => options.FormatterName = "CustomConsoleFormatter")
.AddConsoleFormatter<ConsoleFormatter, ConsoleFormatterOptions>();
builder.Logging.AddFilter("Quartz", LogLevel.Warning);
if (!string.IsNullOrEmpty(tgChatId) && !string.IsNullOrEmpty(tgToken))
builder.Logging.AddTelegram(options =>

View File

@@ -3,6 +3,7 @@ using System.Text.Json;
using Microsoft.Net.Http.Headers;
using ModeusSchedule.Abstractions;
using ModeusSchedule.Abstractions.DTO;
using SfeduSchedule.Logging;
namespace SfeduSchedule.Services;
@@ -23,7 +24,7 @@ public class ModeusHttpClient
public void SetToken(string? token)
{
if (string.IsNullOrWhiteSpace(token)) {
_logger.LogError("SetToken: Предоставленный токен пустой.");
_logger.LogErrorHere("Предоставленный токен пустой.");
return;
}
@@ -40,8 +41,7 @@ public class ModeusHttpClient
var response = await _httpClient.SendAsync(request);
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
_logger.LogError("GetScheduleAsync: Неуспешный статус при получении расписания: {StatusCode}, Request: {msr}",
response.StatusCode, msr);
_logger.LogErrorHere($"Неуспешный статус при получении расписания: {response.StatusCode}, Request: {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}");
return null;
}
return await response.Content.ReadAsStringAsync();
@@ -54,8 +54,7 @@ public class ModeusHttpClient
var response = await _httpClient.SendAsync(request);
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
_logger.LogError("GetAttendeesAsync: Неуспешный статус при получении расписания: {StatusCode}, eventId: {eventId}",
response.StatusCode, eventId);
_logger.LogErrorHere($"Неуспешный статус при получении расписания: {response.StatusCode}, eventId: {eventId}");
}
List<Attendees>? attendees;
try
@@ -65,7 +64,7 @@ public class ModeusHttpClient
}
catch (Exception ex)
{
_logger.LogError(ex, "GetAttendeesAsync: Deserialization failed.");
_logger.LogErrorHere(ex, "Deserialization failed.");
}
return new List<Attendees>();
@@ -80,8 +79,7 @@ public class ModeusHttpClient
var response = await _httpClient.SendAsync(request);
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
_logger.LogError("GetScheduleAsync: Неуспешный статус при получении расписания: {StatusCode}, Request: {requestDto}",
response.StatusCode, requestDto);
_logger.LogErrorHere($"Неуспешный статус при получении расписания: {response.StatusCode}, Request: {JsonSerializer.Serialize(requestDto, GlobalConsts.JsonSerializerOptions)}");
return null;
}
return await response.Content.ReadAsStringAsync();
@@ -99,11 +97,10 @@ public class ModeusHttpClient
}), Encoding.UTF8, "application/json");
var response = await _httpClient.SendAsync(request);
_logger.LogInformation("GetGuidAsync: Ответ получен: {StatusCode}", response.StatusCode);
_logger.LogInformationHere($"Ответ получен: {response.StatusCode}");
if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
_logger.LogError("GetGuidAsync: Неуспешный статус при получении расписания: {StatusCode}, Name: {fullName}",
response.StatusCode, fullName);
_logger.LogErrorHere($"Неуспешный статус при получении расписания: {response.StatusCode}, Name: {fullName}");
return null;
}
@@ -120,8 +117,7 @@ public class ModeusHttpClient
}
catch
{
_logger.LogWarning(
"GetGuidAsync: не удалось получить идентификатор пользователя. FullName={FullName}", fullName);
_logger.LogWarningHere($"Не удалось получить идентификатор пользователя. FullName={fullName}");
return null;
}

View File

@@ -5,6 +5,7 @@ using Ical.Net.DataTypes;
using Ical.Net.Serialization;
using ModeusSchedule.Abstractions;
using ModeusSchedule.Abstractions.DTO;
using SfeduSchedule.Logging;
namespace SfeduSchedule.Services;
@@ -30,7 +31,7 @@ public class ModeusService
var schedule = await GetScheduleAsync(msr);
if (schedule == null)
{
_logger.LogError("GetScheduleJsonAsync: schedule is null. {@Request}", msr);
_logger.LogErrorHere($"schedule is null. {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}");
throw new Exception("Schedule is null");
}
@@ -41,25 +42,16 @@ public class ModeusService
switch (scheduleJson)
{
case null:
_logger.LogError(
"GetScheduleJsonAsync: scheduleJson is null. Schedule: {Schedule}\n Request: {msr}",
schedule, JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions));
_logger.LogErrorHere($"scheduleJson is null. Schedule: {schedule}\n Request: {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}");
break;
case { Embedded: null }:
_logger.LogError(
"GetScheduleJsonAsync: scheduleJson.Embedded is null. Response: {@response}\nscheduleJson: {@scheduleJson}\n Request: {msr}",
schedule, scheduleJson, JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions));
_logger.LogErrorHere($"scheduleJson.Embedded is null. Response: {schedule}\nscheduleJson: {scheduleJson}\n Request: {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}");
break;
case { Embedded.Events: null }:
_logger.LogError(
"GetScheduleJsonAsync: scheduleJson.Embedded.Events is null. Response: {@response}\nEmbedded: {@Embedded}\n Request: {msr}",
schedule, scheduleJson.Embedded,
JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions));
_logger.LogErrorHere($"scheduleJson.Embedded.Events is null. Response: {schedule}\nscheduleJson: {scheduleJson}\n Request: {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}");
break;
case { Embedded.Events.Length: 0 }:
_logger.LogWarning(
"GetScheduleJsonAsync: scheduleJson.Embedded.Events is empty. Embedded: {@Embedded}\n Request: {msr}",
scheduleJson.Embedded, JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions));
_logger.LogWarningHere($"scheduleJson.Embedded.Events is empty. Embedded: {scheduleJson.Embedded}\n Request: {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}");
break;
default:
return scheduleJson;
@@ -67,9 +59,7 @@ public class ModeusService
}
catch (Exception ex)
{
_logger.LogError(ex,
"GetScheduleJsonAsync: Deserialization failed. Schedule: {Schedule}\n Request: {msr}", schedule,
JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions));
_logger.LogErrorHere($"Deserialization failed. Schedule: {schedule}\n Request: {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}\n Exception: {ex}");
}
return null;
@@ -80,8 +70,7 @@ public class ModeusService
var scheduleJson = await GetScheduleJsonAsync(msr);
if (scheduleJson == null)
{
_logger.LogError("GetIcsAsync: scheduleJson is null after deserialization. Request: " +
JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions));
_logger.LogErrorHere($"scheduleJson is null after deserialization. Request: {JsonSerializer.Serialize(msr, GlobalConsts.JsonSerializerOptions)}");
return null;
}
@@ -189,8 +178,7 @@ public class ModeusService
var serializer = new CalendarSerializer();
var serializedCalendar = serializer.SerializeToString(calendar);
_logger.LogInformation("GetIcsAsync: serialized calendar created. Length: {Length}",
serializedCalendar?.Length ?? 0);
_logger.LogInformationHere($"serialized calendar created. Length: {serializedCalendar?.Length ?? 0}");
return serializedCalendar;
}