sigma update
This commit is contained in:
@@ -19,6 +19,7 @@ public class FileDownloadManagerFactory
|
||||
private readonly TextureDownscaleService _textureDownscaleService;
|
||||
private readonly ModelDecimationService _modelDecimationService;
|
||||
private readonly TextureMetadataHelper _textureMetadataHelper;
|
||||
private readonly FileDownloadDeduplicator _downloadDeduplicator;
|
||||
|
||||
public FileDownloadManagerFactory(
|
||||
ILoggerFactory loggerFactory,
|
||||
@@ -29,7 +30,8 @@ public class FileDownloadManagerFactory
|
||||
LightlessConfigService configService,
|
||||
TextureDownscaleService textureDownscaleService,
|
||||
ModelDecimationService modelDecimationService,
|
||||
TextureMetadataHelper textureMetadataHelper)
|
||||
TextureMetadataHelper textureMetadataHelper,
|
||||
FileDownloadDeduplicator downloadDeduplicator)
|
||||
{
|
||||
_loggerFactory = loggerFactory;
|
||||
_lightlessMediator = lightlessMediator;
|
||||
@@ -40,6 +42,7 @@ public class FileDownloadManagerFactory
|
||||
_textureDownscaleService = textureDownscaleService;
|
||||
_modelDecimationService = modelDecimationService;
|
||||
_textureMetadataHelper = textureMetadataHelper;
|
||||
_downloadDeduplicator = downloadDeduplicator;
|
||||
}
|
||||
|
||||
public FileDownloadManager Create()
|
||||
@@ -53,6 +56,7 @@ public class FileDownloadManagerFactory
|
||||
_configService,
|
||||
_textureDownscaleService,
|
||||
_modelDecimationService,
|
||||
_textureMetadataHelper);
|
||||
_textureMetadataHelper,
|
||||
_downloadDeduplicator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,7 +476,7 @@ public class PlayerDataFactory
|
||||
if (transientPaths.Count == 0)
|
||||
return (new Dictionary<string, string[]>(StringComparer.Ordinal), clearedReplacements);
|
||||
|
||||
var resolved = await GetFileReplacementsFromPaths(obj, transientPaths, new HashSet<string>(StringComparer.Ordinal))
|
||||
var resolved = await GetFileReplacementsFromPaths(transientPaths, new HashSet<string>(StringComparer.Ordinal))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (_maxTransientResolvedEntries > 0 && resolved.Count > _maxTransientResolvedEntries)
|
||||
@@ -692,7 +692,6 @@ public class PlayerDataFactory
|
||||
|
||||
|
||||
private async Task<IReadOnlyDictionary<string, string[]>> GetFileReplacementsFromPaths(
|
||||
GameObjectHandler handler,
|
||||
HashSet<string> forwardResolve,
|
||||
HashSet<string> reverseResolve)
|
||||
{
|
||||
@@ -707,59 +706,6 @@ public class PlayerDataFactory
|
||||
var reversePathsLower = reversePaths.Length == 0 ? Array.Empty<string>() : reversePaths.Select(p => p.ToLowerInvariant()).ToArray();
|
||||
|
||||
Dictionary<string, List<string>> resolvedPaths = new(forwardPaths.Length + reversePaths.Length, StringComparer.Ordinal);
|
||||
if (handler.ObjectKind != ObjectKind.Player)
|
||||
{
|
||||
var (objectIndex, forwardResolved, reverseResolved) = await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
var idx = handler.GetGameObject()?.ObjectIndex;
|
||||
if (!idx.HasValue)
|
||||
return ((int?)null, Array.Empty<string>(), Array.Empty<string[]>());
|
||||
|
||||
var resolvedForward = new string[forwardPaths.Length];
|
||||
for (int i = 0; i < forwardPaths.Length; i++)
|
||||
resolvedForward[i] = _ipcManager.Penumbra.ResolveGameObjectPath(forwardPaths[i], idx.Value);
|
||||
|
||||
var resolvedReverse = new string[reversePaths.Length][];
|
||||
for (int i = 0; i < reversePaths.Length; i++)
|
||||
resolvedReverse[i] = _ipcManager.Penumbra.ReverseResolveGameObjectPath(reversePaths[i], idx.Value);
|
||||
|
||||
return (idx, resolvedForward, resolvedReverse);
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
if (objectIndex.HasValue)
|
||||
{
|
||||
for (int i = 0; i < forwardPaths.Length; i++)
|
||||
{
|
||||
var filePath = forwardResolved[i]?.ToLowerInvariant();
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
continue;
|
||||
|
||||
if (resolvedPaths.TryGetValue(filePath, out var list))
|
||||
list.Add(forwardPaths[i].ToLowerInvariant());
|
||||
else
|
||||
{
|
||||
resolvedPaths[filePath] = [forwardPathsLower[i]];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < reversePaths.Length; i++)
|
||||
{
|
||||
var filePath = reversePathsLower[i];
|
||||
var reverseResolvedLower = new string[reverseResolved[i].Length];
|
||||
for (var j = 0; j < reverseResolvedLower.Length; j++)
|
||||
{
|
||||
reverseResolvedLower[j] = reverseResolved[i][j].ToLowerInvariant();
|
||||
}
|
||||
if (resolvedPaths.TryGetValue(filePath, out var list))
|
||||
list.AddRange(reverseResolved[i].Select(c => c.ToLowerInvariant()));
|
||||
else
|
||||
resolvedPaths[filePath] = [.. reverseResolved[i].Select(c => c.ToLowerInvariant()).ToList()];
|
||||
}
|
||||
|
||||
return resolvedPaths.ToDictionary(k => k.Key, k => k.Value.ToArray(), StringComparer.OrdinalIgnoreCase).AsReadOnly();
|
||||
}
|
||||
}
|
||||
|
||||
var (forward, reverse) = await _ipcManager.Penumbra.ResolvePathsAsync(forwardPaths, reversePaths).ConfigureAwait(false);
|
||||
|
||||
for (int i = 0; i < forwardPaths.Length; i++)
|
||||
|
||||
@@ -1,43 +1,44 @@
|
||||
using LightlessSync.API.Data;
|
||||
using LightlessSync.API.Data;
|
||||
|
||||
namespace LightlessSync.PlayerData.Pairs;
|
||||
namespace LightlessSync.PlayerData.Pairs;
|
||||
|
||||
/// <summary>
|
||||
/// orchestrates the lifecycle of a paired character
|
||||
/// </summary>
|
||||
public interface IPairHandlerAdapter : IDisposable, IPairPerformanceSubject
|
||||
{
|
||||
new string Ident { get; }
|
||||
bool Initialized { get; }
|
||||
bool IsVisible { get; }
|
||||
bool ScheduledForDeletion { get; set; }
|
||||
CharacterData? LastReceivedCharacterData { get; }
|
||||
long LastAppliedDataBytes { get; }
|
||||
new string? PlayerName { get; }
|
||||
string PlayerNameHash { get; }
|
||||
uint PlayerCharacterId { get; }
|
||||
DateTime? LastDataReceivedAt { get; }
|
||||
DateTime? LastApplyAttemptAt { get; }
|
||||
DateTime? LastSuccessfulApplyAt { get; }
|
||||
string? LastFailureReason { get; }
|
||||
IReadOnlyList<string> LastBlockingConditions { get; }
|
||||
bool IsApplying { get; }
|
||||
bool IsDownloading { get; }
|
||||
int PendingDownloadCount { get; }
|
||||
int ForbiddenDownloadCount { get; }
|
||||
bool PendingModReapply { get; }
|
||||
bool ModApplyDeferred { get; }
|
||||
int MissingCriticalMods { get; }
|
||||
int MissingNonCriticalMods { get; }
|
||||
int MissingForbiddenMods { get; }
|
||||
DateTime? InvisibleSinceUtc { get; }
|
||||
DateTime? VisibilityEvictionDueAtUtc { get; }
|
||||
/// <summary>
|
||||
/// orchestrates the lifecycle of a paired character
|
||||
/// </summary>
|
||||
public interface IPairHandlerAdapter : IDisposable, IPairPerformanceSubject
|
||||
{
|
||||
new string Ident { get; }
|
||||
bool Initialized { get; }
|
||||
bool IsVisible { get; }
|
||||
bool ScheduledForDeletion { get; set; }
|
||||
CharacterData? LastReceivedCharacterData { get; }
|
||||
long LastAppliedDataBytes { get; }
|
||||
new string? PlayerName { get; }
|
||||
string PlayerNameHash { get; }
|
||||
uint PlayerCharacterId { get; }
|
||||
DateTime? LastDataReceivedAt { get; }
|
||||
DateTime? LastApplyAttemptAt { get; }
|
||||
DateTime? LastSuccessfulApplyAt { get; }
|
||||
string? LastFailureReason { get; }
|
||||
IReadOnlyList<string> LastBlockingConditions { get; }
|
||||
bool IsApplying { get; }
|
||||
bool IsDownloading { get; }
|
||||
int PendingDownloadCount { get; }
|
||||
int ForbiddenDownloadCount { get; }
|
||||
bool PendingModReapply { get; }
|
||||
bool ModApplyDeferred { get; }
|
||||
int MissingCriticalMods { get; }
|
||||
int MissingNonCriticalMods { get; }
|
||||
int MissingForbiddenMods { get; }
|
||||
DateTime? InvisibleSinceUtc { get; }
|
||||
DateTime? VisibilityEvictionDueAtUtc { get; }
|
||||
|
||||
void Initialize();
|
||||
void ApplyData(CharacterData data);
|
||||
void ApplyLastReceivedData(bool forced = false);
|
||||
bool FetchPerformanceMetricsFromCache();
|
||||
void LoadCachedCharacterData(CharacterData data);
|
||||
void SetUploading(bool uploading);
|
||||
void SetPaused(bool paused);
|
||||
}
|
||||
void ApplyData(CharacterData data);
|
||||
void ApplyLastReceivedData(bool forced = false);
|
||||
Task EnsurePerformanceMetricsAsync(CancellationToken cancellationToken);
|
||||
bool FetchPerformanceMetricsFromCache();
|
||||
void LoadCachedCharacterData(CharacterData data);
|
||||
void SetUploading(bool uploading);
|
||||
void SetPaused(bool paused);
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
private readonly XivDataAnalyzer _modelAnalyzer;
|
||||
private readonly PenumbraTempCollectionJanitor _tempCollectionJanitor;
|
||||
private readonly LightlessConfigService _configService;
|
||||
private readonly SemaphoreSlim _metricsComputeGate = new(1, 1);
|
||||
private readonly PairManager _pairManager;
|
||||
private readonly IFramework _framework;
|
||||
private CancellationTokenSource? _applicationCancellationTokenSource;
|
||||
@@ -193,8 +194,8 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
public string? LastFailureReason => _lastFailureReason;
|
||||
public IReadOnlyList<string> LastBlockingConditions => _lastBlockingConditions;
|
||||
public bool IsApplying => _applicationTask is { IsCompleted: false };
|
||||
public bool IsDownloading => _downloadManager.IsDownloading;
|
||||
public int PendingDownloadCount => _downloadManager.CurrentDownloads.Count;
|
||||
public bool IsDownloading => _downloadManager.IsDownloadingFor(_charaHandler);
|
||||
public int PendingDownloadCount => _downloadManager.GetPendingDownloadCount(_charaHandler);
|
||||
public int ForbiddenDownloadCount => _downloadManager.ForbiddenTransfers.Count;
|
||||
|
||||
public PairHandlerAdapter(
|
||||
@@ -721,6 +722,74 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task EnsurePerformanceMetricsAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
EnsureInitialized();
|
||||
|
||||
if (LastReceivedCharacterData is null || IsApplying)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (LastAppliedApproximateVRAMBytes >= 0
|
||||
&& LastAppliedDataTris >= 0
|
||||
&& LastAppliedApproximateEffectiveVRAMBytes >= 0
|
||||
&& LastAppliedApproximateEffectiveTris >= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _metricsComputeGate.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (LastReceivedCharacterData is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (LastAppliedApproximateVRAMBytes >= 0
|
||||
&& LastAppliedDataTris >= 0
|
||||
&& LastAppliedApproximateEffectiveVRAMBytes >= 0
|
||||
&& LastAppliedApproximateEffectiveTris >= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sanitized = CloneAndSanitizeLastReceived(out var dataHash);
|
||||
if (sanitized is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(dataHash) && TryApplyCachedMetrics(dataHash))
|
||||
{
|
||||
_cachedData = sanitized;
|
||||
_pairStateCache.Store(Ident, sanitized);
|
||||
return;
|
||||
}
|
||||
|
||||
if (LastAppliedApproximateVRAMBytes < 0 || LastAppliedApproximateEffectiveVRAMBytes < 0)
|
||||
{
|
||||
_playerPerformanceService.ComputeAndAutoPauseOnVRAMUsageThresholds(this, sanitized, []);
|
||||
}
|
||||
|
||||
if (LastAppliedDataTris < 0 || LastAppliedApproximateEffectiveTris < 0)
|
||||
{
|
||||
await _playerPerformanceService.CheckTriangleUsageThresholds(this, sanitized).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
StorePerformanceMetrics(sanitized);
|
||||
_cachedData = sanitized;
|
||||
_pairStateCache.Store(Ident, sanitized);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_metricsComputeGate.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private CharacterData? CloneAndSanitizeLastReceived(out string? dataHash)
|
||||
{
|
||||
dataHash = null;
|
||||
@@ -1090,6 +1159,19 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
Logger.LogDebug("[BASE-{appBase}] Setting data: {hash}, forceApplyMods: {force}", applicationBase, _cachedData.DataHash.Value, _forceApplyMods);
|
||||
}
|
||||
|
||||
var forceModsForMissing = _pendingModReapply;
|
||||
if (!forceModsForMissing && HasMissingCachedFiles(characterData))
|
||||
{
|
||||
forceModsForMissing = true;
|
||||
}
|
||||
|
||||
if (forceModsForMissing)
|
||||
{
|
||||
_forceApplyMods = true;
|
||||
}
|
||||
|
||||
var suppressForcedModRedrawOnForcedApply = suppressForcedModRedraw || forceModsForMissing;
|
||||
|
||||
SetUploading(false);
|
||||
|
||||
Logger.LogDebug("[BASE-{appbase}] Applying data for {player}, forceApplyCustomization: {forced}, forceApplyMods: {forceMods}", applicationBase, GetLogIdentifier(), forceApplyCustomization, _forceApplyMods);
|
||||
@@ -1106,7 +1188,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
"Applying Character Data")));
|
||||
|
||||
var charaDataToUpdate = characterData.CheckUpdatedData(applicationBase, _cachedData?.DeepClone() ?? new(), Logger, this,
|
||||
forceApplyCustomization, _forceApplyMods, suppressForcedModRedraw);
|
||||
forceApplyCustomization, _forceApplyMods, suppressForcedModRedrawOnForcedApply);
|
||||
|
||||
if (handlerReady && _forceApplyMods)
|
||||
{
|
||||
@@ -1921,7 +2003,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
}
|
||||
|
||||
var handlerForDownload = _charaHandler;
|
||||
_pairDownloadTask = Task.Run(async () => await _downloadManager.DownloadFiles(handlerForDownload, toDownloadReplacements, downloadToken, skipDownscaleForPair, skipDecimationForPair).ConfigureAwait(false));
|
||||
_pairDownloadTask = Task.Run(async () => await _downloadManager.DownloadFiles(handlerForDownload, toDownloadReplacements, toDownloadFiles, downloadToken, skipDownscaleForPair, skipDecimationForPair).ConfigureAwait(false));
|
||||
|
||||
await _pairDownloadTask.ConfigureAwait(false);
|
||||
|
||||
@@ -2136,6 +2218,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
}
|
||||
|
||||
SplitPapMappings(moddedPaths, out var withoutPap, out var papOnly);
|
||||
var hasPap = papOnly.Count > 0;
|
||||
|
||||
await _ipcManager.Penumbra.AssignTemporaryCollectionAsync(Logger, penumbraCollection, objIndex.Value).ConfigureAwait(false);
|
||||
|
||||
@@ -2148,22 +2231,29 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
if (handlerForApply.Address != nint.Zero)
|
||||
await _actorObjectService.WaitForFullyLoadedAsync(handlerForApply.Address, token).ConfigureAwait(false);
|
||||
|
||||
var removedPap = await StripIncompatiblePapAsync(handlerForApply, charaData, papOnly, token).ConfigureAwait(false);
|
||||
if (removedPap > 0)
|
||||
if (hasPap)
|
||||
{
|
||||
Logger.LogTrace("[{applicationId}] Removed {removedPap} incompatible PAP mappings found for {handler}", _applicationId, removedPap, GetLogIdentifier());
|
||||
var removedPap = await StripIncompatiblePapAsync(handlerForApply, charaData, papOnly, token).ConfigureAwait(false);
|
||||
if (removedPap > 0)
|
||||
{
|
||||
Logger.LogTrace("[{applicationId}] Removed {removedPap} incompatible PAP mappings found for {handler}", _applicationId, removedPap, GetLogIdentifier());
|
||||
}
|
||||
|
||||
var merged = new Dictionary<(string GamePath, string? Hash), string>(withoutPap, withoutPap.Comparer);
|
||||
foreach (var kv in papOnly)
|
||||
merged[kv.Key] = kv.Value;
|
||||
|
||||
await _ipcManager.Penumbra.SetTemporaryModsAsync(
|
||||
Logger, _applicationId, penumbraCollection,
|
||||
merged.ToDictionary(k => k.Key.GamePath, k => k.Value, StringComparer.Ordinal))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
_lastAppliedModdedPaths = new Dictionary<(string GamePath, string? Hash), string>(merged, merged.Comparer);
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastAppliedModdedPaths = new Dictionary<(string GamePath, string? Hash), string>(withoutPap, withoutPap.Comparer);
|
||||
}
|
||||
|
||||
var merged = new Dictionary<(string GamePath, string? Hash), string>(withoutPap, withoutPap.Comparer);
|
||||
foreach (var kv in papOnly)
|
||||
merged[kv.Key] = kv.Value;
|
||||
|
||||
await _ipcManager.Penumbra.SetTemporaryModsAsync(
|
||||
Logger, _applicationId, penumbraCollection,
|
||||
merged.ToDictionary(k => k.Key.GamePath, k => k.Value, StringComparer.Ordinal))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
_lastAppliedModdedPaths = new Dictionary<(string GamePath, string? Hash), string>(merged, merged.Comparer);
|
||||
|
||||
LastAppliedDataBytes = -1;
|
||||
foreach (var path in moddedPaths.Values.Distinct(StringComparer.OrdinalIgnoreCase).Select(v => new FileInfo(v)).Where(p => p.Exists))
|
||||
@@ -2218,20 +2308,20 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
_lastSuccessfulApplyAt = DateTime.UtcNow;
|
||||
ClearFailureState();
|
||||
Logger.LogDebug("[{applicationId}] Application finished", _applicationId);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Logger.LogDebug("[{applicationId}] Application cancelled for {handler}", _applicationId, GetLogIdentifier());
|
||||
_cachedData = charaData;
|
||||
_pairStateCache.Store(Ident, charaData);
|
||||
_forceFullReapply = true;
|
||||
RecordFailure("Application cancelled", "Cancellation");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is AggregateException aggr && aggr.InnerExceptions.Any(e => e is ArgumentNullException))
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
IsVisible = false;
|
||||
Logger.LogDebug("[{applicationId}] Application cancelled for {handler}", _applicationId, GetLogIdentifier());
|
||||
_cachedData = charaData;
|
||||
_pairStateCache.Store(Ident, charaData);
|
||||
_forceFullReapply = true;
|
||||
RecordFailure("Application cancelled", "Cancellation");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is AggregateException aggr && aggr.InnerExceptions.Any(e => e is ArgumentNullException))
|
||||
{
|
||||
IsVisible = false;
|
||||
_forceApplyMods = true;
|
||||
_cachedData = charaData;
|
||||
_pairStateCache.Store(Ident, charaData);
|
||||
@@ -2471,6 +2561,11 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
(item) =>
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
if (string.IsNullOrWhiteSpace(item.Hash))
|
||||
{
|
||||
Logger.LogTrace("[BASE-{appBase}] Skipping replacement with empty hash for paths: {paths}", applicationBase, string.Join(", ", item.GamePaths));
|
||||
return;
|
||||
}
|
||||
var fileCache = _fileDbManager.GetFileCacheByHash(item.Hash);
|
||||
if (fileCache is not null && !File.Exists(fileCache.ResolvedFilepath))
|
||||
{
|
||||
@@ -2698,10 +2793,10 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
ApplyCharacterData(pending.ApplicationId, pending.CharacterData, pending.Forced);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, "Failed applying queued data for {handler}", GetLogIdentifier());
|
||||
}
|
||||
});
|
||||
{
|
||||
Logger.LogError(ex, "Failed applying queued data for {handler}", GetLogIdentifier());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void HandleActorTracked(ActorObjectService.ActorDescriptor descriptor)
|
||||
|
||||
@@ -271,7 +271,20 @@ public sealed class PairLedger : DisposableMediatorSubscriberBase
|
||||
|
||||
try
|
||||
{
|
||||
handler.ApplyLastReceivedData(forced: true);
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await handler.EnsurePerformanceMetricsAsync(CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (_logger.IsEnabled(LogLevel.Debug))
|
||||
{
|
||||
_logger.LogDebug(ex, "Failed to ensure performance metrics for {Ident}", handler.Ident);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user