Skip to content

Commit 0f94455

Browse files
committed
fix: improve error handling and logging across modules
Fix DiscordApiException to inherit from DiscordException for unified exception hierarchy. Add structured logging to MemoryCacheProvider for cache operations, eviction, and health checks. Update DI registration to inject logger into cache provider. Replace all Debug.WriteLine calls in CommandsExtension with proper ILogger LogError/LogWarning calls.
1 parent a41619c commit 0f94455

5 files changed

Lines changed: 39 additions & 10 deletions

File tree

src/PawSharp.API/Exceptions/DiscordApiException.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#nullable enable
22
using System;
33
using System.Net;
4+
using PawSharp.Core.Exceptions;
45

56
namespace PawSharp.API.Exceptions;
67

@@ -44,7 +45,7 @@ namespace PawSharp.API.Exceptions;
4445
/// </remarks>
4546
/// </para>
4647
/// </summary>
47-
public sealed class DiscordApiException : Exception
48+
public sealed class DiscordApiException : DiscordException
4849
{
4950
/// <summary>
5051
/// Gets the HTTP status code returned by Discord, if available.

src/PawSharp.Cache/Providers/MemoryCacheProvider.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Linq;
77
using System.Threading;
88
using System.Threading.Tasks;
9+
using Microsoft.Extensions.Logging;
910
using PawSharp.Cache.Interfaces;
1011
using PawSharp.Cache.Telemetry;
1112
using PawSharp.Core.Entities;
@@ -34,6 +35,7 @@ public class MemoryCacheProvider : IEntityCache, ICacheProviderHealthCheckable
3435
private readonly CacheOptions _options;
3536
private readonly System.Timers.Timer _cleanupTimer;
3637
private readonly ICacheTelemetry? _telemetry;
38+
private readonly ILogger<MemoryCacheProvider>? _logger;
3739
private readonly object _lock = new();
3840
private readonly object _evictionLock = new();
3941

@@ -73,8 +75,9 @@ public ICacheTelemetry? Telemetry
7375
public int RoleCacheSize => _roles.Count;
7476
public int EmojiCacheSize => _emojis.Count;
7577

76-
public MemoryCacheProvider(CacheOptions? options = null, ICacheTelemetry? telemetry = null)
78+
public MemoryCacheProvider(CacheOptions? options = null, ICacheTelemetry? telemetry = null, ILogger<MemoryCacheProvider>? logger = null)
7779
{
80+
_logger = logger;
7881
var opts = options ?? new CacheOptions();
7982

8083
_maxGuilds = opts.MaxGuilds;
@@ -246,6 +249,7 @@ private void EnforceEntityCacheBounds<TKey, TValue>(ConcurrentDictionary<TKey, T
246249
{
247250
_lastAccess.TryRemove(entityId, out _);
248251
_telemetry?.RecordEviction(entityType, "LRU");
252+
_logger?.LogDebug(PawSharp.Core.Logging.PawSharpLogEvents.CacheEviction, entityType, 1);
249253
EntityEvicted?.Invoke(this, new CacheInvalidationEventArgs
250254
{
251255
EntityType = entityType,
@@ -264,20 +268,23 @@ public bool Exists(string key)
264268

265269
public void Clear()
266270
{
271+
var total = GetEntityCount();
267272
_guilds.Clear();
268273
_channels.Clear();
269274
_users.Clear();
270275
_messages.Clear();
271276
_members.Clear();
272277
_roles.Clear();
273278
_emojis.Clear();
279+
_logger?.LogInformation("Cache cleared. Removed {Count} entities.", total);
274280
CacheCleared?.Invoke(this, EventArgs.Empty);
275281
}
276282

277283
// Typed entity operations
278284
public void CacheUser(User user)
279285
{
280286
_users[user.Id] = user;
287+
_logger?.LogDebug("Cached user {UserId}", user.Id);
281288
EnforceEntityCacheBounds(_users, _maxUsers, "User");
282289
}
283290

@@ -290,17 +297,20 @@ public void CacheUser(User user)
290297
Interlocked.Increment(ref _hits);
291298
_telemetry?.RecordHit("User");
292299
_telemetry?.RecordOperation("Get", "User", stopwatch.Elapsed);
300+
_logger?.LogDebug(PawSharp.Core.Logging.PawSharpLogEvents.CacheHit, "User", userId);
293301
return user;
294302
}
295303
Interlocked.Increment(ref _misses);
296304
_telemetry?.RecordMiss("User");
297305
_telemetry?.RecordOperation("Get", "User", stopwatch.Elapsed);
306+
_logger?.LogDebug(PawSharp.Core.Logging.PawSharpLogEvents.CacheMiss, "User", userId);
298307
return null;
299308
}
300309

301310
public void CacheGuild(Guild guild)
302311
{
303312
_guilds[guild.Id] = guild;
313+
_logger?.LogDebug("Cached guild {GuildId} ({GuildName})", guild.Id, guild.Name);
304314
EnforceEntityCacheBounds(_guilds, _maxGuilds, "Guild");
305315
}
306316

@@ -313,11 +323,13 @@ public void CacheGuild(Guild guild)
313323
Interlocked.Increment(ref _hits);
314324
_telemetry?.RecordHit("Guild");
315325
_telemetry?.RecordOperation("Get", "Guild", stopwatch.Elapsed);
326+
_logger?.LogDebug(PawSharp.Core.Logging.PawSharpLogEvents.CacheHit, "Guild", guildId);
316327
return guild;
317328
}
318329
Interlocked.Increment(ref _misses);
319330
_telemetry?.RecordMiss("Guild");
320331
_telemetry?.RecordOperation("Get", "Guild", stopwatch.Elapsed);
332+
_logger?.LogDebug(PawSharp.Core.Logging.PawSharpLogEvents.CacheMiss, "Guild", guildId);
321333
return null;
322334
}
323335

@@ -329,6 +341,7 @@ public IEnumerable<Guild> GetAllGuilds()
329341
public void CacheChannel(Channel channel)
330342
{
331343
_channels[channel.Id] = channel;
344+
_logger?.LogDebug("Cached channel {ChannelId} ({ChannelName})", channel.Id, channel.Name);
332345
EnforceEntityCacheBounds(_channels, _maxChannels, "Channel");
333346
}
334347

@@ -341,11 +354,13 @@ public void CacheChannel(Channel channel)
341354
Interlocked.Increment(ref _hits);
342355
_telemetry?.RecordHit("Channel");
343356
_telemetry?.RecordOperation("Get", "Channel", stopwatch.Elapsed);
357+
_logger?.LogDebug(PawSharp.Core.Logging.PawSharpLogEvents.CacheHit, "Channel", channelId);
344358
return channel;
345359
}
346360
Interlocked.Increment(ref _misses);
347361
_telemetry?.RecordMiss("Channel");
348362
_telemetry?.RecordOperation("Get", "Channel", stopwatch.Elapsed);
363+
_logger?.LogDebug(PawSharp.Core.Logging.PawSharpLogEvents.CacheMiss, "Channel", channelId);
349364
return null;
350365
}
351366

@@ -357,6 +372,7 @@ public IEnumerable<Channel> GetGuildChannels(ulong guildId)
357372
public void CacheMessage(Message message)
358373
{
359374
_messages[message.Id] = message;
375+
_logger?.LogDebug("Cached message {MessageId} in channel {ChannelId}", message.Id, message.ChannelId);
360376
EnforceEntityCacheBounds(_messages, _maxMessages, "Message");
361377
}
362378

@@ -369,11 +385,13 @@ public void CacheMessage(Message message)
369385
Interlocked.Increment(ref _hits);
370386
_telemetry?.RecordHit("Message");
371387
_telemetry?.RecordOperation("Get", "Message", stopwatch.Elapsed);
388+
_logger?.LogDebug(PawSharp.Core.Logging.PawSharpLogEvents.CacheHit, "Message", messageId);
372389
return message;
373390
}
374391
Interlocked.Increment(ref _misses);
375392
_telemetry?.RecordMiss("Message");
376393
_telemetry?.RecordOperation("Get", "Message", stopwatch.Elapsed);
394+
_logger?.LogDebug(PawSharp.Core.Logging.PawSharpLogEvents.CacheMiss, "Message", messageId);
377395
return null;
378396
}
379397

@@ -389,6 +407,7 @@ public void CacheGuildMember(ulong guildId, GuildMember member)
389407
{
390408
var key = $"{guildId}:{member.User?.Id}";
391409
_members[key] = member;
410+
_logger?.LogDebug("Cached guild member {UserId} in guild {GuildId}", member.User?.Id, guildId);
392411
EnforceEntityCacheBounds(_members, _maxMembers, "Member");
393412

394413
// Also cache the user
@@ -408,11 +427,13 @@ public void CacheGuildMember(ulong guildId, GuildMember member)
408427
Interlocked.Increment(ref _hits);
409428
_telemetry?.RecordHit("Member");
410429
_telemetry?.RecordOperation("Get", "Member", stopwatch.Elapsed);
430+
_logger?.LogDebug(PawSharp.Core.Logging.PawSharpLogEvents.CacheHit, "Member", userId);
411431
return member;
412432
}
413433
Interlocked.Increment(ref _misses);
414434
_telemetry?.RecordMiss("Member");
415435
_telemetry?.RecordOperation("Get", "Member", stopwatch.Elapsed);
436+
_logger?.LogDebug(PawSharp.Core.Logging.PawSharpLogEvents.CacheMiss, "Member", userId);
416437
return null;
417438
}
418439

@@ -425,6 +446,7 @@ public void CacheRole(ulong guildId, Role role)
425446
{
426447
var key = $"{guildId}:{role.Id}";
427448
_roles[key] = role;
449+
_logger?.LogDebug("Cached role {RoleId} ({RoleName}) in guild {GuildId}", role.Id, role.Name, guildId);
428450
EnforceEntityCacheBounds(_roles, _maxRoles, "Role");
429451
}
430452

@@ -438,11 +460,13 @@ public void CacheRole(ulong guildId, Role role)
438460
Interlocked.Increment(ref _hits);
439461
_telemetry?.RecordHit("Role");
440462
_telemetry?.RecordOperation("Get", "Role", stopwatch.Elapsed);
463+
_logger?.LogDebug(PawSharp.Core.Logging.PawSharpLogEvents.CacheHit, "Role", roleId);
441464
return role;
442465
}
443466
Interlocked.Increment(ref _misses);
444467
_telemetry?.RecordMiss("Role");
445468
_telemetry?.RecordOperation("Get", "Role", stopwatch.Elapsed);
469+
_logger?.LogDebug(PawSharp.Core.Logging.PawSharpLogEvents.CacheMiss, "Role", roleId);
446470
return null;
447471
}
448472

@@ -457,6 +481,7 @@ public void CacheEmoji(ulong guildId, Emoji emoji)
457481
{
458482
var key = $"{guildId}:{emoji.Id.Value}";
459483
_emojis[key] = emoji;
484+
_logger?.LogDebug("Cached emoji {EmojiId} in guild {GuildId}", emoji.Id.Value, guildId);
460485
EnforceEntityCacheBounds(_emojis, _maxEmojis, "Emoji");
461486
}
462487
}
@@ -474,11 +499,13 @@ public void CacheEmoji(ulong guildId, Emoji emoji)
474499
Interlocked.Increment(ref _hits);
475500
_telemetry?.RecordHit("Emoji");
476501
_telemetry?.RecordOperation("Get", "Emoji", stopwatch.Elapsed);
502+
_logger?.LogDebug(PawSharp.Core.Logging.PawSharpLogEvents.CacheHit, "Emoji", emojiId);
477503
return emoji;
478504
}
479505
Interlocked.Increment(ref _misses);
480506
_telemetry?.RecordMiss("Emoji");
481507
_telemetry?.RecordOperation("Get", "Emoji", stopwatch.Elapsed);
508+
_logger?.LogDebug(PawSharp.Core.Logging.PawSharpLogEvents.CacheMiss, "Emoji", emojiId);
482509
return null;
483510
}
484511

src/PawSharp.Client/Extensions/PawSharpServiceCollectionExtensions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ public static IServiceCollection AddPawSharp(
7878
sp.GetRequiredService<ILogger<GatewayClient>>()));
7979

8080
// Cache defaults to the in-memory provider unless a custom cache is supplied.
81-
services.AddSingleton<IEntityCache>(sp => cacheFactory?.Invoke(sp) ?? new MemoryCacheProvider());
81+
services.AddSingleton<IEntityCache>(sp => cacheFactory?.Invoke(sp) ?? new MemoryCacheProvider(
82+
logger: sp.GetService<ILogger<MemoryCacheProvider>>()));
8283

8384
// Interaction handler
8485
services.AddSingleton<InteractionHandler>(sp =>

src/PawSharp.Client/PawSharpClientBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ public DiscordClient Build()
260260
};
261261

262262
var logFactory = _loggerFactory ?? NullLoggerFactory.Instance;
263-
var cache = _cache ?? new MemoryCacheProvider();
263+
var cache = _cache ?? new MemoryCacheProvider(logger: logFactory.CreateLogger<MemoryCacheProvider>());
264264
var http = _httpClient ?? new HttpClient(new SocketsHttpHandler
265265
{
266266
EnableMultipleHttp2Connections = true

src/PawSharp.Commands/CommandsExtension.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,7 +1114,7 @@ await CommandErrored(new CommandErrorEventArgs(
11141114
}
11151115
catch (Exception handlerEx)
11161116
{
1117-
System.Diagnostics.Debug.WriteLine($"Error in slash command error handler: {handlerEx.Message}");
1117+
_logger.LogError(handlerEx, "Error in slash command error handler for /{CommandName}", commandName);
11181118
}
11191119
}
11201120
}
@@ -1382,7 +1382,7 @@ private static bool IsOptionalType(Type type)
13821382
try { return Convert.ChangeType(option.Value, inner, CultureInfo.InvariantCulture); }
13831383
catch (Exception ex)
13841384
{
1385-
System.Diagnostics.Debug.WriteLine($"Type conversion failed for {targetType.Name}: {ex.Message}");
1385+
_logger.LogWarning(ex, "Type conversion failed for {TargetTypeName}", targetType.Name);
13861386
return GetDefault(targetType);
13871387
}
13881388
}
@@ -1604,7 +1604,7 @@ await CommandErrored(new CommandErrorEventArgs(
16041604
}
16051605
catch (Exception handlerEx)
16061606
{
1607-
System.Diagnostics.Debug.WriteLine($"Error in context menu error handler: {handlerEx.Message}");
1607+
_logger.LogError(handlerEx, "Error in context menu error handler for {CommandName}", capturedName);
16081608
}
16091609
}
16101610
}
@@ -1631,7 +1631,7 @@ await CommandErrored(new CommandErrorEventArgs(
16311631
}
16321632
catch (Exception handlerEx)
16331633
{
1634-
System.Diagnostics.Debug.WriteLine($"Error in context menu error handler: {handlerEx.Message}");
1634+
_logger.LogError(handlerEx, "Error in context menu error handler for {CommandName}", capturedName);
16351635
}
16361636
}
16371637
}
@@ -1708,7 +1708,7 @@ await CommandErrored(new CommandErrorEventArgs(
17081708
}
17091709
catch (Exception handlerEx)
17101710
{
1711-
System.Diagnostics.Debug.WriteLine($"Error in context menu error handler: {handlerEx.Message}");
1711+
_logger.LogError(handlerEx, "Error in context menu error handler for {CommandName}", capturedName);
17121712
}
17131713
}
17141714
}
@@ -1735,7 +1735,7 @@ await CommandErrored(new CommandErrorEventArgs(
17351735
}
17361736
catch (Exception handlerEx)
17371737
{
1738-
System.Diagnostics.Debug.WriteLine($"Error in context menu error handler: {handlerEx.Message}");
1738+
_logger.LogError(handlerEx, "Error in context menu error handler for {CommandName}", capturedName);
17391739
}
17401740
}
17411741
}

0 commit comments

Comments
 (0)