Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/StackExchange.Redis/LoggerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ internal static void LogWithThreadPoolStats(this ILogger? log, string message)
// awkwardly they read. If a name genuinely must change, pin the old one via the attribute's EventName.
[LoggerMessage(
Level = LogLevel.Error,
EventId = 127,
Message = "Connection failed: {EndPoint} ({ConnectionType}, {FailureType}): {ErrorMessage}")]
internal static partial void LogErrorConnectionFailed(this ILogger logger, Exception? exception, EndPointLogValue endPoint, ConnectionType connectionType, ConnectionFailureType failureType, string errorMessage);

Expand Down Expand Up @@ -768,7 +769,7 @@ internal static void LogWithThreadPoolStats(this ILogger? log, string message)
// established" arrived under one id on netfx and another on modern TFMs, for no gain to anyone.
[LoggerMessage(
Level = LogLevel.Information,
EventId = 99,
EventId = 126,
Message = "TLS connection established successfully using protocol: {SslProtocol}, cipher suite: {CipherSuite}")]
internal static partial void LogInformationTLSConnectionEstablished(this ILogger logger, System.Security.Authentication.SslProtocols sslProtocol, System.Net.Security.TlsCipherSuite cipherSuite);
#endif
Expand All @@ -781,13 +782,13 @@ internal static void LogWithThreadPoolStats(this ILogger? log, string message)

[LoggerMessage(
Level = LogLevel.Information,
EventId = 116,
EventId = 124,
Message = "{Server}: Requesting maintenance notifications ({Mode})")]
internal static partial void LogInformationRequestingMaintenanceNotifications(this ILogger logger, ServerEndPointLogValue server, MaintenanceNotificationMode mode);

[LoggerMessage(
Level = LogLevel.Information,
EventId = 117,
EventId = 125,
Message = "{Server}: Maintenance notifications accepted")]
internal static partial void LogInformationMaintenanceNotificationsAccepted(this ILogger logger, ServerEndPointLogValue server);

Expand Down
60 changes: 60 additions & 0 deletions tests/StackExchange.Redis.Tests/LoggerEventIdTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.Logging;
using Xunit;

namespace StackExchange.Redis.Tests;

/// <summary>
/// Guards the source-generated <see cref="LoggerMessageAttribute"/> events in <c>LoggerExtensions</c>:
/// every event must carry an explicit <c>EventId</c>, and no two events may share one. Event ids are
/// what consumers filter and alert on, so a clash silently merges unrelated events downstream; this
/// has happened when parallel branches both took "the next free number".
/// </summary>
public class LoggerEventIdTests(ITestOutputHelper output) : TestBase(output)
{
[Fact]
public void EventIdsAreExplicitAndUnique()
{
var events = (
from method in typeof(LoggerExtensions).GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
let attrib = method.GetCustomAttribute<LoggerMessageAttribute>()
where attrib is not null
orderby attrib.EventId, method.Name
select (Method: method, Attrib: attrib)).ToList();

Assert.NotEmpty(events); // if reflection stops finding them, the test is vacuous

var problems = new List<string>();

// LoggerMessageAttribute.EventId defaults to -1 when not specified; the generator then emits 0,
// which is indistinguishable from "unknown" to a consumer
foreach (var (method, attrib) in events.Where(e => e.Attrib.EventId < 0))
{
problems.Add($"{Describe(method)} has no explicit EventId");
}

foreach (var group in events.Where(e => e.Attrib.EventId >= 0).GroupBy(e => e.Attrib.EventId).Where(g => g.Count() > 1))
{
problems.Add($"EventId {group.Key} is used {group.Count()} times: {string.Join(", ", group.Select(e => Describe(e.Method)))}");
}

int max = events.Max(e => e.Attrib.EventId);
Log($"{events.Count} events; highest EventId {max}; next free EventId {max + 1}");
foreach (var problem in problems)
{
Log(problem);
}

Assert.True(problems.Count == 0, string.Join(Environment.NewLine, problems));
}

private static string Describe(MethodInfo method)
{
// skip the "this ILogger" parameter; show the rest so overloads are distinguishable
var args = method.GetParameters().Skip(1).Select(p => p.ParameterType.Name);
return $"{method.Name}({string.Join(", ", args)})";
}
}
Loading