diff --git a/FreeRedis.sln b/FreeRedis.sln index 1db522a..d96968d 100644 --- a/FreeRedis.sln +++ b/FreeRedis.sln @@ -47,6 +47,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeRedis.DistributedCache" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "console_net8_cluster_client_side_caching", "examples\console_net8_cluster_client_side_caching\console_net8_cluster_client_side_caching.csproj", "{1C4387AA-C4BD-4388-97D1-3A769439705D}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FreeRedis.OpenTelemetry", "src\FreeRedis.OpenTelemetry\FreeRedis.OpenTelemetry.csproj", "{CA71A937-D939-4922-B1DB-0380833B7270}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetryTest", "examples\OpenTelemetryTest\OpenTelemetryTest.csproj", "{37FAC646-809D-4BD4-8B74-1A9BEE02E963}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -225,6 +229,30 @@ Global {1C4387AA-C4BD-4388-97D1-3A769439705D}.Release|x64.Build.0 = Release|Any CPU {1C4387AA-C4BD-4388-97D1-3A769439705D}.Release|x86.ActiveCfg = Release|Any CPU {1C4387AA-C4BD-4388-97D1-3A769439705D}.Release|x86.Build.0 = Release|Any CPU + {CA71A937-D939-4922-B1DB-0380833B7270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA71A937-D939-4922-B1DB-0380833B7270}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA71A937-D939-4922-B1DB-0380833B7270}.Debug|x64.ActiveCfg = Debug|Any CPU + {CA71A937-D939-4922-B1DB-0380833B7270}.Debug|x64.Build.0 = Debug|Any CPU + {CA71A937-D939-4922-B1DB-0380833B7270}.Debug|x86.ActiveCfg = Debug|Any CPU + {CA71A937-D939-4922-B1DB-0380833B7270}.Debug|x86.Build.0 = Debug|Any CPU + {CA71A937-D939-4922-B1DB-0380833B7270}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA71A937-D939-4922-B1DB-0380833B7270}.Release|Any CPU.Build.0 = Release|Any CPU + {CA71A937-D939-4922-B1DB-0380833B7270}.Release|x64.ActiveCfg = Release|Any CPU + {CA71A937-D939-4922-B1DB-0380833B7270}.Release|x64.Build.0 = Release|Any CPU + {CA71A937-D939-4922-B1DB-0380833B7270}.Release|x86.ActiveCfg = Release|Any CPU + {CA71A937-D939-4922-B1DB-0380833B7270}.Release|x86.Build.0 = Release|Any CPU + {37FAC646-809D-4BD4-8B74-1A9BEE02E963}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {37FAC646-809D-4BD4-8B74-1A9BEE02E963}.Debug|Any CPU.Build.0 = Debug|Any CPU + {37FAC646-809D-4BD4-8B74-1A9BEE02E963}.Debug|x64.ActiveCfg = Debug|Any CPU + {37FAC646-809D-4BD4-8B74-1A9BEE02E963}.Debug|x64.Build.0 = Debug|Any CPU + {37FAC646-809D-4BD4-8B74-1A9BEE02E963}.Debug|x86.ActiveCfg = Debug|Any CPU + {37FAC646-809D-4BD4-8B74-1A9BEE02E963}.Debug|x86.Build.0 = Debug|Any CPU + {37FAC646-809D-4BD4-8B74-1A9BEE02E963}.Release|Any CPU.ActiveCfg = Release|Any CPU + {37FAC646-809D-4BD4-8B74-1A9BEE02E963}.Release|Any CPU.Build.0 = Release|Any CPU + {37FAC646-809D-4BD4-8B74-1A9BEE02E963}.Release|x64.ActiveCfg = Release|Any CPU + {37FAC646-809D-4BD4-8B74-1A9BEE02E963}.Release|x64.Build.0 = Release|Any CPU + {37FAC646-809D-4BD4-8B74-1A9BEE02E963}.Release|x86.ActiveCfg = Release|Any CPU + {37FAC646-809D-4BD4-8B74-1A9BEE02E963}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -246,6 +274,8 @@ Global {7AC68251-83D4-423F-849C-04BD89F4BDFB} = {DD1426BF-F56F-49A8-9D4E-2BA0E3BEFC72} {B0B0B55E-4D41-4419-83FE-309F72699A3A} = {D8045351-59E4-45B9-81F5-D21CC0996344} {1C4387AA-C4BD-4388-97D1-3A769439705D} = {DD1426BF-F56F-49A8-9D4E-2BA0E3BEFC72} + {CA71A937-D939-4922-B1DB-0380833B7270} = {D8045351-59E4-45B9-81F5-D21CC0996344} + {37FAC646-809D-4BD4-8B74-1A9BEE02E963} = {DD1426BF-F56F-49A8-9D4E-2BA0E3BEFC72} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B36AD060-F6AE-49C7-A310-B58B8467CE69} diff --git a/examples/OpenTelemetryTest/Controllers/WeatherForecastController.cs b/examples/OpenTelemetryTest/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000..7440075 --- /dev/null +++ b/examples/OpenTelemetryTest/Controllers/WeatherForecastController.cs @@ -0,0 +1,45 @@ +using FreeRedis; +using Microsoft.AspNetCore.Mvc; + +namespace OpenTelemetryTest.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private readonly IRedisClient _redisClient; + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger, IRedisClient redisClient) + { + _logger = logger; + _redisClient = redisClient; + } + + [HttpGet(Name = "GetWeatherForecast")] + public async Task> Get() + { + var response = Enumerable.Range(1, 20).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }).ToArray(); + + await _redisClient.SetAsync("test01", response, 60 * 5); + await _redisClient.GetAsync("test01"); + + var cache = response.GroupBy(x => x.Summary).ToDictionary(g => g.Key!, g => g.ToList()); + await _redisClient.MSetAsync(cache); + + var cache1 = await _redisClient.GetAsync>(Summaries[Random.Shared.Next(Summaries.Length)]); + + return response; + } + } +} diff --git a/examples/OpenTelemetryTest/OpenTelemetryTest.csproj b/examples/OpenTelemetryTest/OpenTelemetryTest.csproj new file mode 100644 index 0000000..30a54bc --- /dev/null +++ b/examples/OpenTelemetryTest/OpenTelemetryTest.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + diff --git a/examples/OpenTelemetryTest/OpenTelemetryTest.http b/examples/OpenTelemetryTest/OpenTelemetryTest.http new file mode 100644 index 0000000..4e7e6c3 --- /dev/null +++ b/examples/OpenTelemetryTest/OpenTelemetryTest.http @@ -0,0 +1,6 @@ +@OpenTelemetryTest_HostAddress = http://localhost:5252 + +GET {{OpenTelemetryTest_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/examples/OpenTelemetryTest/Program.cs b/examples/OpenTelemetryTest/Program.cs new file mode 100644 index 0000000..68200ce --- /dev/null +++ b/examples/OpenTelemetryTest/Program.cs @@ -0,0 +1,62 @@ +using FreeRedis; +using FreeRedis.OpenTelemetry; +using Microsoft.Extensions.DependencyInjection.Extensions; +using OpenTelemetry.Exporter; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +var services = builder.Services; +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +//redis +var redisClient = new RedisClient(builder.Configuration.GetConnectionString("Redis")); +redisClient.Serialize = obj => System.Text.Json.JsonSerializer.Serialize(obj); +redisClient.Deserialize = (json, type) => System.Text.Json.JsonSerializer.Deserialize(json, type); +//redisClient.Notice += (s, e) => +//{ +// Console.WriteLine(e.Log); +//}; +services.TryAddSingleton(redisClient); + +//OpenTelemetry +var otel = services.AddOpenTelemetry(); +otel.ConfigureResource(resource => +{ + resource.AddTelemetrySdk(); + resource.AddEnvironmentVariableDetector(); + resource.AddService("FreeRedisTest"); +}); +var otlpUrl = builder.Configuration["OpenTelemetry:OtlpHttpUrl"]; +otel.WithTracing(tracing => tracing.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddFreeRedisInstrumentation(redisClient) + .SetSampler(new AlwaysOnSampler()) + //.AddConsoleExporter() + .AddOtlpExporter(otlpOptions => + { + otlpOptions.Endpoint = new Uri($"http://{otlpUrl}/v1/traces"); + otlpOptions.Protocol = OtlpExportProtocol.HttpProtobuf; + otlpOptions.Headers = "Authorization=Basic YWRtaW46dGVzdEAxMjM="; + }) +); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/examples/OpenTelemetryTest/Properties/launchSettings.json b/examples/OpenTelemetryTest/Properties/launchSettings.json new file mode 100644 index 0000000..297d93b --- /dev/null +++ b/examples/OpenTelemetryTest/Properties/launchSettings.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "weatherforecast", + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/examples/OpenTelemetryTest/WeatherForecast.cs b/examples/OpenTelemetryTest/WeatherForecast.cs new file mode 100644 index 0000000..f931adb --- /dev/null +++ b/examples/OpenTelemetryTest/WeatherForecast.cs @@ -0,0 +1,13 @@ +namespace OpenTelemetryTest +{ + public class WeatherForecast + { + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } + } +} diff --git a/examples/OpenTelemetryTest/appsettings.Development.json b/examples/OpenTelemetryTest/appsettings.Development.json new file mode 100644 index 0000000..3240ccb --- /dev/null +++ b/examples/OpenTelemetryTest/appsettings.Development.json @@ -0,0 +1,14 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "Redis": "127.0.0.1:6379,password=test@123,defaultDatabase=0" + }, + "OpenTelemetry": { + "OtlpHttpUrl": "127.0.0.1:4318" + } +} diff --git a/examples/OpenTelemetryTest/appsettings.json b/examples/OpenTelemetryTest/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/examples/OpenTelemetryTest/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src/FreeRedis.OpenTelemetry/DiagnosticListener.cs b/src/FreeRedis.OpenTelemetry/DiagnosticListener.cs new file mode 100644 index 0000000..3d7468d --- /dev/null +++ b/src/FreeRedis.OpenTelemetry/DiagnosticListener.cs @@ -0,0 +1,77 @@ +using OpenTelemetry.Trace; +using System.Diagnostics; + +namespace FreeRedis.OpenTelemetry; + +public class DiagnosticListener : IObserver> +{ + public const string SourceName = "FreeRedis.OpenTelemetry"; + + private static readonly ActivitySource ActivitySource = new(SourceName, "1.0.0"); + + /// Notifies the observer that the provider has finished sending push-based notifications. + public void OnCompleted() + { + } + + /// Notifies the observer that the provider has experienced an error condition. + /// An object that provides additional information about the error. + public void OnError(Exception error) + { + } + + /// Provides the observer with new data. + /// The current notification information. + public void OnNext(KeyValuePair evt) + { + //https://opentelemetry.io/docs/specs/semconv/database/redis/ + switch (evt.Key) + { + case FreeRedisDiagnosticListenerNames.NoticeCallBefore: + { + var eventData = (InterceptorBeforeEventArgs)evt.Value!; + var activity = ActivitySource.StartActivity("redis command execute: " + eventData.Command); + if (activity != null) + { + activity.SetTag("db.system", "redis"); + activity.SetTag("db.operation.name", eventData.Command._command); + activity.SetTag("db.query.text", eventData.Command); + //Activity.Current?.SetTag("network.peer.address", ip); + //Activity.Current?.SetTag("network.peer.port", port); + + activity.AddEvent(new ActivityEvent("redis command execute start", + DateTimeOffset.FromUnixTimeMilliseconds(eventData.OperationTimestamp!.Value))); + } + } + break; + case FreeRedisDiagnosticListenerNames.NoticeCallAfter: + { + var eventData = (InterceptorAfterEventArgs)evt.Value!; + var writeTarget = eventData.Command.WriteTarget; + if (!string.IsNullOrEmpty(writeTarget)) + { + var parts = writeTarget.Split([':', '/'], StringSplitOptions.RemoveEmptyEntries); + var ip = parts[0]; + var port = int.Parse(parts[1]); + var dbIndex = int.Parse(parts[2]); + + Activity.Current?.SetTag("server.address", ip); + Activity.Current?.SetTag("server.port", port); + Activity.Current?.SetTag("db.namespace", dbIndex); + } + var tags = new ActivityTagsCollection { new("free_redis.duration", eventData.ElapsedMilliseconds) }; + if (eventData.Exception != null) + { + Activity.Current?.SetStatus(Status.Error.WithDescription(eventData.Exception.Message)); + tags.Add(new("error.type", eventData.Exception.Message)); + } + Activity.Current?.AddEvent(new ActivityEvent("redis command executed", + DateTimeOffset.FromUnixTimeMilliseconds(eventData.OperationTimestamp!.Value), tags) + ); + + Activity.Current?.Stop(); + } + break; + } + } +} \ No newline at end of file diff --git a/src/FreeRedis.OpenTelemetry/DiagnosticSourceSubscriber.cs b/src/FreeRedis.OpenTelemetry/DiagnosticSourceSubscriber.cs new file mode 100644 index 0000000..3e09f35 --- /dev/null +++ b/src/FreeRedis.OpenTelemetry/DiagnosticSourceSubscriber.cs @@ -0,0 +1,85 @@ +namespace FreeRedis.OpenTelemetry; + +public class DiagnosticSourceSubscriber : IDisposable, IObserver +{ + private readonly Func _diagnosticSourceFilter; + private readonly Func _handlerFactory; + private readonly Func? _isEnabledFilter; + private readonly List _listenerSubscriptions; + private IDisposable? _allSourcesSubscription; + private long _disposed; + + public DiagnosticSourceSubscriber( + DiagnosticListener handler, + Func? isEnabledFilter) + : this(_ => handler, + value => FreeRedisDiagnosticListenerNames.DiagnosticListenerName == value.Name, + isEnabledFilter) + { + } + + public DiagnosticSourceSubscriber( + Func handlerFactory, + Func diagnosticSourceFilter, + Func? isEnabledFilter) + { + _listenerSubscriptions = new List(); + _handlerFactory = handlerFactory ?? throw new ArgumentNullException(nameof(handlerFactory)); + _diagnosticSourceFilter = diagnosticSourceFilter; + _isEnabledFilter = isEnabledFilter; + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public void OnNext(System.Diagnostics.DiagnosticListener value) + { + if (Interlocked.Read(ref _disposed) == 0 && _diagnosticSourceFilter(value)) + { + var handler = _handlerFactory(value.Name); + var subscription = _isEnabledFilter == null + ? value.Subscribe(handler) + : value.Subscribe(handler, _isEnabledFilter); + + lock (_listenerSubscriptions) + { + _listenerSubscriptions.Add(subscription); + } + } + } + + public void OnCompleted() + { + } + + public void OnError(Exception error) + { + } + + public void Subscribe() + { + _allSourcesSubscription ??= System.Diagnostics.DiagnosticListener.AllListeners.Subscribe(this); + } + + protected virtual void Dispose(bool disposing) + { + if (Interlocked.CompareExchange(ref _disposed, 1, 0) == 1) return; + + lock (_listenerSubscriptions) + { + foreach (var listenerSubscription in _listenerSubscriptions) + { + listenerSubscription?.Dispose(); + } + + _listenerSubscriptions.Clear(); + } + + _allSourcesSubscription?.Dispose(); + _allSourcesSubscription = null; + } +} \ No newline at end of file diff --git a/src/FreeRedis.OpenTelemetry/FreeRedis.OpenTelemetry.csproj b/src/FreeRedis.OpenTelemetry/FreeRedis.OpenTelemetry.csproj new file mode 100644 index 0000000..3bcd684 --- /dev/null +++ b/src/FreeRedis.OpenTelemetry/FreeRedis.OpenTelemetry.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + diff --git a/src/FreeRedis.OpenTelemetry/FreeRedisInstrumentation.cs b/src/FreeRedis.OpenTelemetry/FreeRedisInstrumentation.cs new file mode 100644 index 0000000..e83fdd3 --- /dev/null +++ b/src/FreeRedis.OpenTelemetry/FreeRedisInstrumentation.cs @@ -0,0 +1,18 @@ +namespace FreeRedis.OpenTelemetry; + +public class FreeRedisInstrumentation : IDisposable +{ + private readonly DiagnosticSourceSubscriber? _diagnosticSourceSubscriber; + + public FreeRedisInstrumentation(DiagnosticListener diagnosticListener) + { + _diagnosticSourceSubscriber = new DiagnosticSourceSubscriber(diagnosticListener, null); + _diagnosticSourceSubscriber.Subscribe(); + } + + /// + public void Dispose() + { + _diagnosticSourceSubscriber?.Dispose(); + } +} \ No newline at end of file diff --git a/src/FreeRedis.OpenTelemetry/TracerProviderBuilderExtensions.cs b/src/FreeRedis.OpenTelemetry/TracerProviderBuilderExtensions.cs new file mode 100644 index 0000000..7a18f95 --- /dev/null +++ b/src/FreeRedis.OpenTelemetry/TracerProviderBuilderExtensions.cs @@ -0,0 +1,25 @@ +using OpenTelemetry.Trace; +using System.Diagnostics; + +namespace FreeRedis.OpenTelemetry; + +public static class TracerProviderBuilderExtensions +{ + public static TracerProviderBuilder AddFreeRedisInstrumentation(this TracerProviderBuilder builder, RedisClient redisClient) + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + if (redisClient == null) throw new ArgumentNullException(nameof(redisClient)); + + redisClient.Notice += (s, e) => + { + if (Debugger.IsAttached) + Debug.WriteLine(e.Log); + }; + + builder.AddSource(DiagnosticListener.SourceName); + + var instrumentation = new FreeRedisInstrumentation(new DiagnosticListener()); + + return builder.AddInstrumentation(() => instrumentation); + } +} \ No newline at end of file diff --git a/src/FreeRedis/FreeRedis.csproj b/src/FreeRedis/FreeRedis.csproj index 8623600..3d092ec 100644 --- a/src/FreeRedis/FreeRedis.csproj +++ b/src/FreeRedis/FreeRedis.csproj @@ -1,43 +1,49 @@  - - netstandard2.0;net451;net40 - FreeRedis - FreeRedis - FreeRedis - 1.3.1 - true - https://github.com/2881099/FreeRedis - FreeRedis is .NET redis client, supports cluster, sentinel, master-slave, pipeline, transaction and connection pool. - https://github.com/2881099/FreeRedis - FreeRedis redis-trib cluster rediscluster sentinel - git - MIT - $(AssemblyName) - true - true - 3 - true - key.snk - false - readme.md - + + netstandard2.0;net451;net40 + FreeRedis + FreeRedis + FreeRedis + 1.3.1 + true + https://github.com/2881099/FreeRedis + FreeRedis is .NET redis client, supports cluster, sentinel, master-slave, pipeline, transaction and connection pool. + https://github.com/2881099/FreeRedis + FreeRedis redis-trib cluster rediscluster sentinel + git + MIT + $(AssemblyName) + true + true + 3 + true + key.snk + false + readme.md + - - - - - - FreeRedis.xml - 3 - 1701;1702;1591 - + + + - - net40 - - - isasync - + + + 8.0.1 + + + + + FreeRedis.xml + 3 + 1701;1702;1591 + + + + net40 + + + isasync + diff --git a/src/FreeRedis/FreeRedisDiagnosticListenerNames.cs b/src/FreeRedis/FreeRedisDiagnosticListenerNames.cs new file mode 100644 index 0000000..9aa13a2 --- /dev/null +++ b/src/FreeRedis/FreeRedisDiagnosticListenerNames.cs @@ -0,0 +1,13 @@ +namespace FreeRedis +{ + public static class FreeRedisDiagnosticListenerNames + { + private const string FreeRedisPrefix = "FreeRedis."; + + //Tracing + public const string DiagnosticListenerName = "FreeRedisDiagnosticListener"; + + public const string NoticeCallAfter = FreeRedisPrefix + "NoticeCallAfter"; + public const string NoticeCallBefore = FreeRedisPrefix + "NoticeCallBefore"; + } +} \ No newline at end of file diff --git a/src/FreeRedis/RedisClientEvents.cs b/src/FreeRedis/RedisClientEvents.cs index 01ade08..1d99371 100644 --- a/src/FreeRedis/RedisClientEvents.cs +++ b/src/FreeRedis/RedisClientEvents.cs @@ -1,121 +1,155 @@ -using FreeRedis.Internal; -using System; -using System.Collections.Generic; +using System; +using System.Diagnostics; using System.Text; +using FreeRedis.Internal; namespace FreeRedis { public enum NoticeType { - Call, Info, Event + Call, + Info, + Event } + public class NoticeEventArgs : EventArgs { + public NoticeEventArgs(NoticeType noticeType, Exception exception, string log, object tag) + { + NoticeType = noticeType; + Exception = exception; + Log = log; + Tag = tag; + } + public NoticeType NoticeType { get; } + public Exception Exception { get; } + public string Log { get; } - public object Tag { get; } - public NoticeEventArgs(NoticeType noticeType, Exception exception, string log, object tag) - { - this.NoticeType = noticeType; - this.Exception = exception; - this.Log = log; - this.Tag = tag; - } + public object Tag { get; } } + public class ConnectedEventArgs : EventArgs { - public string Host { get; } - public RedisClientPool Pool { get; } - public RedisClient Client { get; } - public ConnectedEventArgs(string host, RedisClientPool pool, RedisClient cli) { - this.Host = host; - this.Pool = pool; - this.Client = cli; + Host = host; + Pool = pool; + Client = cli; } - } - public class DisconnectedEventArgs : EventArgs - { + public string Host { get; } + public RedisClientPool Pool { get; } + public RedisClient Client { get; } + } + public class DisconnectedEventArgs : EventArgs + { public DisconnectedEventArgs(string host, RedisClientPool pool, RedisClient cli) { - this.Host = host; - this.Pool = pool; - this.Client = cli; + Host = host; + Pool = pool; + Client = cli; } - } - public class UnavailableEventArgs : EventArgs - { + public string Host { get; } + public RedisClientPool Pool { get; } + public RedisClient Client { get; } + } + + public class UnavailableEventArgs : EventArgs + { public UnavailableEventArgs(string host, RedisClientPool pool) { - this.Host = host; - this.Pool = pool; + Host = host; + Pool = pool; } + + public string Host { get; } + + public RedisClientPool Pool { get; } } public interface IInterceptor { void Before(InterceptorBeforeEventArgs args); + void After(InterceptorAfterEventArgs args); } + public class InterceptorBeforeEventArgs { - public RedisClient Client { get; } - public CommandPacket Command { get; } - public Type ValueType { get; } + private object _value; public InterceptorBeforeEventArgs(RedisClient cli, CommandPacket cmd, Type valueType) { - this.Client = cli; - this.Command = cmd; - this.ValueType = valueType; + Client = cli; + Command = cmd; + ValueType = valueType; } + public long? OperationTimestamp { get; set; } + + public RedisClient Client { get; } + + public CommandPacket Command { get; } + + public Type ValueType { get; } + public object Value { get => _value; set { _value = value; - this.ValueIsChanged = true; + ValueIsChanged = true; } } - private object _value; + public bool ValueIsChanged { get; private set; } } + public class InterceptorAfterEventArgs { + public InterceptorAfterEventArgs(RedisClient cli, CommandPacket cmd, Type valueType, object value, + Exception exception, long elapsedMilliseconds) + { + Client = cli; + Command = cmd; + ValueType = valueType; + Value = value; + Exception = exception; + ElapsedMilliseconds = elapsedMilliseconds; + } + + public long? OperationTimestamp { get; set; } + public RedisClient Client { get; } + public CommandPacket Command { get; } + public Type ValueType { get; } public object Value { get; } + public Exception Exception { get; } - public long ElapsedMilliseconds { get; } - public InterceptorAfterEventArgs(RedisClient cli, CommandPacket cmd, Type valueType, object value, Exception exception, long elapsedMilliseconds) - { - this.Client = cli; - this.Command = cmd; - this.ValueType = valueType; - this.Value = value; - this.Exception = exception; - this.ElapsedMilliseconds = elapsedMilliseconds; - } + public long ElapsedMilliseconds { get; } } - class NoticeCallInterceptor : IInterceptor + internal class NoticeCallInterceptor : IInterceptor { - RedisClient _cli; +#if NETSTANDARD2_0_OR_GREATER + private static readonly DiagnosticSource _diagnosticListener = new DiagnosticListener(FreeRedisDiagnosticListenerNames.DiagnosticListenerName); +#endif + private readonly RedisClient _cli; + public NoticeCallInterceptor(RedisClient cli) { _cli = cli; @@ -123,8 +157,17 @@ public NoticeCallInterceptor(RedisClient cli) public void After(InterceptorAfterEventArgs args) { +#if NETSTANDARD2_0_OR_GREATER + args.OperationTimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + if (_diagnosticListener.IsEnabled(FreeRedisDiagnosticListenerNames.NoticeCallAfter)) + _diagnosticListener.Write(FreeRedisDiagnosticListenerNames.NoticeCallAfter, args); +#endif + string log; - if (args.Exception != null) log = $"{args.Exception.Message}"; + if (args.Exception != null) + { + log = $"{args.Exception.Message}"; + } else if (args.Value is Array array) { var sb = new StringBuilder().Append("["); @@ -134,11 +177,15 @@ public void After(InterceptorAfterEventArgs args) if (itemindex++ > 0) sb.Append(", "); sb.Append(item.ToInvariantCultureToString()); } + log = sb.Append("]").ToString(); sb.Clear(); } else + { log = $"{args.Value.ToInvariantCultureToString()}"; + } + _cli.OnNotice(null, new NoticeEventArgs( NoticeType.Call, args.Exception, @@ -148,6 +195,11 @@ public void After(InterceptorAfterEventArgs args) public void Before(InterceptorBeforeEventArgs args) { +#if NETSTANDARD2_0_OR_GREATER + args.OperationTimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + if (_diagnosticListener.IsEnabled(FreeRedisDiagnosticListenerNames.NoticeCallBefore)) + _diagnosticListener.Write(FreeRedisDiagnosticListenerNames.NoticeCallBefore, args); +#endif } } -} +} \ No newline at end of file