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
9 changes: 8 additions & 1 deletion framework/docs/aspects/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ Request → [Trace] → [Log] → [UnitOfWork] → Method → [UnitOfWork] → [
Integrates with OpenTelemetry to create spans for method execution.

```csharp
using BBT.Aether.Telemetry;

// Basic span creation
[Trace]
public async Task ProcessAsync() { }
Expand All @@ -77,6 +79,10 @@ public async Task ProcessAsync() { }
[Trace(Mode = TracingMode.Span)] // Creates new span (default)
[Trace(Mode = TracingMode.Event)] // Adds events to current span
[Trace(Mode = TracingMode.Enrich)] // Enriches current span with tags

// Trace aspects represent application/business spans in both profiles
[Trace]
public async Task ExecuteDiagnosticStepAsync() { }
```

**Properties:**
Expand All @@ -85,6 +91,8 @@ public async Task ProcessAsync() { }
- `OperationName` - Custom span name (default: ClassName.MethodName)
- `Tags` - Custom tags in "key:value" format

`[Trace]` annotations represent application/business spans and are emitted in both `Business` and `Verbose`. In the `Business` profile, completed spans whose final display name starts with `[` are filtered as ordered pipeline-step detail. `Telemetry:Tracing:DetailLevel` also controls diagnostic instrumentation such as EF Core, distributed cache/lock, and low-level client spans globally.

### [Log] - Structured Logging

Adds method entry/exit logging with performance tracking and enrichment.
Expand Down Expand Up @@ -260,4 +268,3 @@ public class OrderAppService

- [Unit of Work](../unit-of-work/README.md) - Detailed UoW documentation
- [Telemetry](../telemetry/README.md) - OpenTelemetry setup

7 changes: 6 additions & 1 deletion framework/docs/telemetry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,10 @@ Config section key: `Telemetry` or `Aether:Telemetry`.
"Protocol": "http/protobuf"
},
"Tracing": {
"DetailLevel": "Business",
"EnableAspNetCore": true,
"EnableHttpClient": true,
"EnableEntityFrameworkCore": true,
"AdditionalSources": ["MyApp.*"],
"ExcludedPaths": ["/health", "/metrics"],
"Headers": ["x-correlation-id", "x-request-id"],
Expand Down Expand Up @@ -105,6 +107,7 @@ Config section key: `Telemetry` or `Aether:Telemetry`.
}
```

- **Tracing.DetailLevel**: `Business` is the default and keeps service boundaries and `[Trace]` application/business spans while suppressing diagnostic instrumentation such as EF Core, distributed cache/lock, low-level Dapr calls, and spans whose final display name starts with `[` (ordered pipeline-step detail). `Verbose` keeps all configured instrumentation and must be selected explicitly.
- **Tracing.Headers**: Request header names added as activity tags on spans (no wildcard).
- **Logging.Enrichers**: CustomAttributes and Headers are added as attributes to **all** log records (via EnricherLogProcessor) and to the HTTP body log event (via middleware scope). Header values in the sensitive list are redacted.
- **Logging.Body**: Options for HTTP request/response body logging when `UseHttpBodyLogging()` is used. Path exclusion uses `Logging.ExcludedPaths`.
Expand Down Expand Up @@ -143,6 +146,9 @@ var response = await _httpClient.GetAsync("https://api.example.com/products");
var product = await _repository.GetAsync(id);
```

Custom static instrumentation can use `AetherTracingRuntime.IsVerbose` to
follow the same globally configured profile.

### Custom Spans with Aspects

```csharp
Expand Down Expand Up @@ -508,4 +514,3 @@ public class OrderServiceTests
- **[Application Services](../application-services/README.md)** - Automatically traced
- **[Unit of Work](../unit-of-work/README.md)** - Transaction spans
- **[Distributed Events](../distributed-events/README.md)** - Context propagation

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Diagnostics;
using OpenTelemetry;

namespace BBT.Aether.AspNetCore.Telemetry;

/// <summary>
/// Removes pipeline-detail spans from the export path when the Business tracing profile is active.
/// </summary>
/// <remarks>
/// Pipeline steps assign their final display names while the activity is running, so this filter
/// must run on completion rather than at sampling time. Clearing the recorded flag allows the
/// standard OpenTelemetry activity export processors to skip the span while preserving its
/// in-process activity context for any child operations created during execution.
/// </remarks>
internal sealed class BusinessSpanFilterProcessor : BaseProcessor<Activity>
{
public override void OnEnd(Activity activity)
{
ArgumentNullException.ThrowIfNull(activity);

if (activity.DisplayName.StartsWith("[", StringComparison.Ordinal))

Check warning on line 22 in framework/src/BBT.Aether.AspNetCore/BBT/Aether/AspNetCore/Telemetry/BusinessSpanFilterProcessor.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use 'string.StartsWith(char)' instead of 'string.StartsWith(string)' when you have a string with a single char

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCvajNyItDTr6xKcn&open=AZ_rCvajNyItDTr6xKcn&pullRequest=90
{
activity.ActivityTraceFlags &= ~ActivityTraceFlags.Recorded;
}
}
}
Comment on lines +1 to +27
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using BBT.Aether.Telemetry;

namespace BBT.Aether.AspNetCore.Telemetry;

Expand Down Expand Up @@ -113,6 +114,12 @@ public sealed class LoggingEnricherOptions

public sealed class AetherTracingOptions
{
/// <summary>
/// Controls whether only business spans or all diagnostic spans are produced.
/// Defaults to Business to keep production traces focused on service boundaries and business spans.
/// </summary>
public AetherTracingDetailLevel DetailLevel { get; set; } = AetherTracingDetailLevel.Business;

public bool EnableAspNetCore { get; set; } = true;
public bool EnableHttpClient { get; set; } = true;
public bool EnableEntityFrameworkCore { get; set; } = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Text.RegularExpressions;
using BBT.Aether.AspNetCore.Telemetry;
using BBT.Aether.Telemetry;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
Expand All @@ -20,7 +22,7 @@

public static class AetherTelemetryServiceCollectionExtensions
{
public static IServiceCollection AddAetherTelemetry(

Check failure on line 25 in framework/src/BBT.Aether.AspNetCore/Microsoft/Extensions/DependencyInjection/AetherTelemetryServiceCollectionExtensions.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 76 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCveFNyItDTr6xKcp&open=AZ_rCveFNyItDTr6xKcp&pullRequest=90
this IServiceCollection services,
IConfiguration configuration,
IHostEnvironment? environment = null,
Expand All @@ -37,6 +39,7 @@
}

section.Bind(opts);
AetherTracingRuntime.Configure(opts.Tracing.DetailLevel);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Configure tracing detail level after all configuration sources (including environment) are applied.

Currently AetherTracingRuntime.Configure is called before defaults and environment-variable overrides are applied, so AetherTracingRuntime.DetailLevel may not match the final opts.Tracing.DetailLevel. Please move this call to after all configuration sources are applied so the runtime state reflects the effective tracing configuration.


// Apply defaults and environment variables
var envName =
Expand All @@ -50,7 +53,7 @@
opts.ServiceName ??=
Environment.GetEnvironmentVariable("OTEL_SERVICE_NAME")
?? configuration["ApplicationName"]
?? "aether";

Check warning on line 56 in framework/src/BBT.Aether.AspNetCore/Microsoft/Extensions/DependencyInjection/AetherTelemetryServiceCollectionExtensions.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of using this literal 'aether' 4 times.

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCveFNyItDTr6xKco&open=AZ_rCveFNyItDTr6xKco&pullRequest=90

// Service version: config > entry assembly version > "1.0.0"
opts.ServiceVersion ??= serviceVersion;
Expand Down Expand Up @@ -82,7 +85,7 @@
{
resource
.AddService(
serviceName: opts.ServiceName!,

Check warning on line 88 in framework/src/BBT.Aether.AspNetCore/Microsoft/Extensions/DependencyInjection/AetherTelemetryServiceCollectionExtensions.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this null-forgiving operator; the compiler already knows this expression is not null here.

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCveFNyItDTr6xKcq&open=AZ_rCveFNyItDTr6xKcq&pullRequest=90
serviceVersion: opts.ServiceVersion,
serviceInstanceId: Environment.MachineName)
.AddAttributes(new Dictionary<string, object>
Expand Down Expand Up @@ -156,18 +159,23 @@
{
tracing.AddHttpClientInstrumentation(o =>
{
o.FilterHttpRequestMessage = req =>
!IsExcluded(req.RequestUri?.ToString(), excludedPatterns);
o.FilterHttpRequestMessage = req => ShouldTraceHttpRequest(req, excludedPatterns);
o.EnrichWithHttpRequestMessage = EnrichHttpClientActivity;
});
}

if (opts.Tracing.EnableEntityFrameworkCore)
if (opts.Tracing.EnableEntityFrameworkCore && AetherTracingRuntime.IsVerbose)
{
tracing.AddEntityFrameworkCoreInstrumentation();
}

tracing.AddSource("BBT.Aether.Aspects");
tracing.AddSource("BBT.Aether.Infrastructure");

if (!AetherTracingRuntime.IsVerbose)
{
tracing.AddProcessor(new BusinessSpanFilterProcessor());
}

// Custom sources
foreach (var src in opts.Tracing.AdditionalSources)
Expand Down Expand Up @@ -233,7 +241,7 @@
if (!opts.LoggingEnabled) return;

logging.SetResourceBuilder(ResourceBuilder.CreateDefault()
.AddService(opts.ServiceName!, opts.ServiceNamespace ?? "aether", opts.ServiceVersion ?? "1.0.0", false, Environment.MachineName)

Check warning on line 244 in framework/src/BBT.Aether.AspNetCore/Microsoft/Extensions/DependencyInjection/AetherTelemetryServiceCollectionExtensions.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this null-forgiving operator; the compiler already knows this expression is not null here.

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCveFNyItDTr6xKcr&open=AZ_rCveFNyItDTr6xKcr&pullRequest=90
.AddAttributes(opts.Logging.Enrichers.CustomAttributes
.ToDictionary(x => x.Key, x => (object)x.Value)));

Expand Down Expand Up @@ -262,7 +270,7 @@
}

private static List<Regex> CompileRegex(IEnumerable<string> patterns)
=> patterns.Select(p => new Regex(p, RegexOptions.Compiled | RegexOptions.IgnoreCase)).ToList();

Check warning on line 273 in framework/src/BBT.Aether.AspNetCore/Microsoft/Extensions/DependencyInjection/AetherTelemetryServiceCollectionExtensions.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Pass a timeout to limit the execution time.

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCveFNyItDTr6xKcs&open=AZ_rCveFNyItDTr6xKcs&pullRequest=90

private static bool IsExcluded(string? value, List<Regex> patterns)
{
Expand All @@ -285,6 +293,67 @@
return false;
}

private static bool ShouldTraceHttpRequest(HttpRequestMessage request, List<Regex> excludedPatterns)
{
if (IsExcluded(request.RequestUri?.ToString(), excludedPatterns))
{
return false;
}

return AetherTracingRuntime.IsVerbose || !IsDaprDiagnosticRequest(request.RequestUri);
}

private static bool IsDaprDiagnosticRequest(Uri? uri)
{
var path = uri?.AbsolutePath;
if (string.IsNullOrEmpty(path))
{
return false;
}
Comment on lines +306 to +312

if (path.StartsWith("/dapr.proto.runtime.v1.Dapr/", StringComparison.OrdinalIgnoreCase))
{
return path.EndsWith("/GetState", StringComparison.OrdinalIgnoreCase)
|| path.EndsWith("/GetBulkState", StringComparison.OrdinalIgnoreCase)
|| path.EndsWith("/SaveState", StringComparison.OrdinalIgnoreCase)
|| path.EndsWith("/DeleteState", StringComparison.OrdinalIgnoreCase)
|| path.EndsWith("/ExecuteStateTransaction", StringComparison.OrdinalIgnoreCase)
|| path.EndsWith("/GetSecret", StringComparison.OrdinalIgnoreCase)
|| path.EndsWith("/GetBulkSecret", StringComparison.OrdinalIgnoreCase)
|| path.EndsWith("/GetConfiguration", StringComparison.OrdinalIgnoreCase)
|| path.EndsWith("/SubscribeConfiguration", StringComparison.OrdinalIgnoreCase)
|| path.EndsWith("/TryLockAlpha1", StringComparison.OrdinalIgnoreCase)
|| path.EndsWith("/UnlockAlpha1", StringComparison.OrdinalIgnoreCase);
}

return path.StartsWith("/v1.0/state/", StringComparison.OrdinalIgnoreCase)
|| path.StartsWith("/v1.0-alpha1/state/", StringComparison.OrdinalIgnoreCase)
|| path.StartsWith("/v1.0-alpha1/lock/", StringComparison.OrdinalIgnoreCase)
|| path.StartsWith("/v1.0/secrets/", StringComparison.OrdinalIgnoreCase)
|| path.StartsWith("/v1.0/configuration/", StringComparison.OrdinalIgnoreCase);
Comment on lines +329 to +333

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict Dapr route matching to the sidecar

In the Business profile, every outgoing HTTP request whose path starts with one of these prefixes is suppressed, regardless of its host. Consequently, a normal dependency request such as https://api.example.com/v1.0/state/orders/123 is mistaken for Dapr traffic and loses its service-boundary span. Check that the destination is the configured Dapr sidecar before applying these path-based exclusions.

Useful? React with 👍 / 👎.

}

private static void EnrichHttpClientActivity(Activity activity, HttpRequestMessage request)
{
var segments = request.RequestUri?.AbsolutePath.Split('/', StringSplitOptions.RemoveEmptyEntries);
Comment on lines +336 to +338

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Guard against null RequestUri to avoid a potential NullReferenceException in the enricher.

Because the null-conditional applies only to AbsolutePath, Split is still invoked on a null string, which will throw. Please add an explicit guard (e.g., early return when request.RequestUri is null or assign AbsolutePath to a local variable and null-check before calling Split).

if (segments is not { Length: >= 5 }
Comment on lines +336 to +339
|| !string.Equals(segments[0], "v1.0", StringComparison.OrdinalIgnoreCase)
|| !string.Equals(segments[1], "invoke", StringComparison.OrdinalIgnoreCase)
|| !string.Equals(segments[3], "method", StringComparison.OrdinalIgnoreCase))
Comment on lines +339 to +342

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Enrich Dapr invocations made over gRPC

This recognizes only the Dapr HTTP invocation route, but the framework's DaprExternalService.InvokeAsync calls DaprClient.InvokeMethodAsync (DaprExternalService.cs:25-29), whose sidecar request uses the gRPC path /dapr.proto.runtime.v1.Dapr/InvokeService. Those are the invocation spans produced by the repository's normal Dapr flow, so they pass through without the new display name or rpc.*/dapr.app_id attributes.

Useful? React with 👍 / 👎.

{
return;
}

var appId = Uri.UnescapeDataString(segments[2]);
var method = Uri.UnescapeDataString(string.Join('/', segments.Skip(4)));

activity.DisplayName = $"Dapr invoke {appId}";
activity.SetTag("rpc.system", "dapr");
activity.SetTag("rpc.service", appId);
activity.SetTag("rpc.method", method);
activity.SetTag("dapr.app_id", appId);
}

private static string GetRoutePatternSafe(HttpContext httpContext)
{
try
Expand All @@ -302,4 +371,4 @@
{
return $"{environment}-{options.ServiceName}-{DateTime.UtcNow:yyyyMMdd-HHmmss}-{Guid.NewGuid().ToString("N")[..8]}";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System;
using System.Threading;

namespace BBT.Aether.Telemetry;

/// <summary>
/// Controls the amount of tracing detail produced by Aether instrumentation.
/// </summary>
public enum AetherTracingDetailLevel
{
/// <summary>
/// Keeps service boundaries and business spans while suppressing diagnostic detail.
/// </summary>
Business = 0,

/// <summary>
/// Keeps both business and diagnostic spans.
/// </summary>
Verbose = 1
}

/// <summary>
/// Provides the process-wide tracing detail level used by static instrumentation and aspects.
/// </summary>
public static class AetherTracingRuntime
{
private static int _detailLevel = (int)AetherTracingDetailLevel.Business;

/// <summary>
/// Gets the active tracing detail level.
/// </summary>
public static AetherTracingDetailLevel DetailLevel =>
(AetherTracingDetailLevel)Volatile.Read(ref _detailLevel);

/// <summary>
/// Gets whether diagnostic spans are enabled.
/// </summary>
public static bool IsVerbose => DetailLevel == AetherTracingDetailLevel.Verbose;

/// <summary>
/// Configures the process-wide tracing detail level.
/// </summary>
public static void Configure(AetherTracingDetailLevel detailLevel)
{
if (!Enum.IsDefined(detailLevel))
{
throw new ArgumentOutOfRangeException(nameof(detailLevel), detailLevel, "Unsupported tracing detail level.");
}

Volatile.Write(ref _detailLevel, (int)detailLevel);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public override Task RefreshAsync(string key, CancellationToken cancellationToke

private Activity? StartCacheActivity(string operationName, string key)
{
var activity = InfrastructureActivitySource.Source.StartActivity(
var activity = InfrastructureActivitySource.StartDiagnosticActivity(
operationName,
ActivityKind.Client,
Activity.Current?.Context ?? default);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@

if (!cachedValue.HasValue)
{
_logger.LogDebug("Cache miss for key: {Key}", key);

Check warning on line 36 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedCache/Redis/RedisDistributedCacheService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Evaluation of this argument may be expensive and unnecessary if logging is disabled

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCvisNyItDTr6xKc_&open=AZ_rCvisNyItDTr6xKc_&pullRequest=90
activity?.SetTag("cache.hit", false);
activity?.SetStatus(ActivityStatusCode.Ok);
return null;
}

var result = JsonSerializer.Deserialize<T>((string)cachedValue!, _jsonOptions);

Check warning on line 42 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedCache/Redis/RedisDistributedCacheService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this null-forgiving operator; the compiler already knows this expression is not null here.

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCvisNyItDTr6xKc-&open=AZ_rCvisNyItDTr6xKc-&pullRequest=90
_logger.LogDebug("Cache hit for key: {Key}", key);

Check warning on line 43 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedCache/Redis/RedisDistributedCacheService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Evaluation of this argument may be expensive and unnecessary if logging is disabled

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCvisNyItDTr6xKdA&open=AZ_rCvisNyItDTr6xKdA&pullRequest=90
activity?.SetTag("cache.hit", true);
activity?.SetStatus(ActivityStatusCode.Ok);
return result;
Expand Down Expand Up @@ -83,7 +83,7 @@
}

await database.StringSetAsync(key, serializedValue, expiry, keepTtl: false);
_logger.LogDebug("Successfully cached value for key: {Key} with expiry: {Expiry}", key, expiry);

Check warning on line 86 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedCache/Redis/RedisDistributedCacheService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Evaluation of this argument may be expensive and unnecessary if logging is disabled

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCvisNyItDTr6xKdB&open=AZ_rCvisNyItDTr6xKdB&pullRequest=90
activity?.SetStatus(ActivityStatusCode.Ok);
}
catch (Exception ex)
Expand All @@ -105,11 +105,11 @@

if (removed)
{
_logger.LogDebug("Successfully removed key from cache: {Key}", key);

Check warning on line 108 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedCache/Redis/RedisDistributedCacheService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Evaluation of this argument may be expensive and unnecessary if logging is disabled

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCvisNyItDTr6xKdC&open=AZ_rCvisNyItDTr6xKdC&pullRequest=90
}
else
{
_logger.LogDebug("Key not found in cache: {Key}", key);

Check warning on line 112 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedCache/Redis/RedisDistributedCacheService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Evaluation of this argument may be expensive and unnecessary if logging is disabled

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCvisNyItDTr6xKdD&open=AZ_rCvisNyItDTr6xKdD&pullRequest=90
}

activity?.SetStatus(ActivityStatusCode.Ok);
Expand All @@ -134,11 +134,11 @@
if (exists)
{
await database.KeyTouchAsync(key);
_logger.LogDebug("Successfully refreshed key: {Key}", key);

Check warning on line 137 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedCache/Redis/RedisDistributedCacheService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Evaluation of this argument may be expensive and unnecessary if logging is disabled

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCvisNyItDTr6xKdE&open=AZ_rCvisNyItDTr6xKdE&pullRequest=90
}
else
{
_logger.LogDebug("Key does not exist for refresh: {Key}", key);

Check warning on line 141 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedCache/Redis/RedisDistributedCacheService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Evaluation of this argument may be expensive and unnecessary if logging is disabled

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCvisNyItDTr6xKdF&open=AZ_rCvisNyItDTr6xKdF&pullRequest=90
}

activity?.SetStatus(ActivityStatusCode.Ok);
Expand All @@ -153,7 +153,7 @@

private static Activity? StartCacheActivity(string operationName, string key)
{
var activity = InfrastructureActivitySource.Source.StartActivity(
var activity = InfrastructureActivitySource.StartDiagnosticActivity(
operationName,
ActivityKind.Client,
Activity.Current?.Context ?? default);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
if (Interlocked.Exchange(ref _disposed, 1) == 1)
return;

using var activity = InfrastructureActivitySource.Source.StartActivity(
using var activity = InfrastructureActivitySource.StartDiagnosticActivity(
"DistributedLock.Release",
ActivityKind.Client,
Activity.Current?.Context ?? default);
Expand All @@ -74,8 +74,8 @@
activity?.SetTag("lock.released", true);
activity?.SetStatus(ActivityStatusCode.Ok);

logger.LogDebug("Released Dapr lock for resource {ResourceId} with owner {Owner}",
lockKey, owner);

Check warning on line 78 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedLock/Dapr/DaprDistributedLockHandle.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Evaluation of this argument may be expensive and unnecessary if logging is disabled

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCvh3NyItDTr6xKc1&open=AZ_rCvh3NyItDTr6xKc1&pullRequest=90
}
catch (Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
public class DaprDistributedLockService(
DaprClient daprClient,
ILogger<DaprDistributedLockService> logger,
IApplicationInfoAccessor applicationInfoAccessor,

Check warning on line 19 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedLock/Dapr/DaprDistributedLockService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Parameter 'applicationInfoAccessor' is unread.

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCvfNNyItDTr6xKcx&open=AZ_rCvfNNyItDTr6xKcx&pullRequest=90
string storeName)
: IDistributedLockService
{
Expand All @@ -32,9 +32,9 @@
await daprClient.Lock(storeName, resourceId, lockOwner, expiryInSeconds, cancellationToken);
if (resourceLock is { Success: true })
{
logger.LogDebug("Successfully acquired lock for resource {ResourceId} with owner {Owner}",
resourceId, lockOwner);

Check warning on line 36 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedLock/Dapr/DaprDistributedLockService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Evaluation of this argument may be expensive and unnecessary if logging is disabled

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCvfNNyItDTr6xKcy&open=AZ_rCvfNNyItDTr6xKcy&pullRequest=90
activity?.SetTag("lock.acquired", true);

Check warning on line 37 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedLock/Dapr/DaprDistributedLockService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of using this literal 'lock.acquired' 6 times.

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCvfNNyItDTr6xKct&open=AZ_rCvfNNyItDTr6xKct&pullRequest=90
activity?.SetStatus(ActivityStatusCode.Ok);
return new DaprDistributedLockHandle(daprClient, storeName, resourceId, lockOwner, logger);
}
Expand All @@ -52,10 +52,10 @@
}
}

[Obsolete("Use IDistributedLockHandle.ReleaseAsync() or dispose the handle returned by TryAcquireLockAsync. This method uses a static owner and cannot reliably release locks acquired concurrently.")]

Check warning on line 55 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedLock/Dapr/DaprDistributedLockService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not forget to remove this deprecated code someday.

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCvfNNyItDTr6xKcu&open=AZ_rCvfNNyItDTr6xKcu&pullRequest=90
public async Task<bool> ReleaseLockAsync(string resourceId, CancellationToken cancellationToken = default)
{
using var activity = InfrastructureActivitySource.Source.StartActivity(
using var activity = InfrastructureActivitySource.StartDiagnosticActivity(
"DistributedLock.Release",
ActivityKind.Client,
Activity.Current?.Context ?? default);
Expand Down Expand Up @@ -98,13 +98,13 @@
return (false, default);
}

logger.LogDebug("Successfully acquired lock for resource {ResourceId}", resourceId);

Check warning on line 101 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedLock/Dapr/DaprDistributedLockService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Evaluation of this argument may be expensive and unnecessary if logging is disabled

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCvfNNyItDTr6xKcz&open=AZ_rCvfNNyItDTr6xKcz&pullRequest=90
activity?.SetTag("lock.acquired", true);
var result = await function();
activity?.SetStatus(ActivityStatusCode.Ok);
return (true, result);
}
catch (Exception ex)

Check warning on line 107 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedLock/Dapr/DaprDistributedLockService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Either log this exception and handle it, or rethrow it with some contextual information.

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCvfNNyItDTr6xKcv&open=AZ_rCvfNNyItDTr6xKcv&pullRequest=90
{
logger.LogError(ex, "Error executing function with Dapr lock for resource {ResourceId}", resourceId);
RecordException(activity, ex);
Expand All @@ -130,13 +130,13 @@
return false;
}

logger.LogDebug("Successfully acquired lock for resource {ResourceId}", resourceId);

Check warning on line 133 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedLock/Dapr/DaprDistributedLockService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Evaluation of this argument may be expensive and unnecessary if logging is disabled

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCvfNNyItDTr6xKc0&open=AZ_rCvfNNyItDTr6xKc0&pullRequest=90
activity?.SetTag("lock.acquired", true);
await action();
activity?.SetStatus(ActivityStatusCode.Ok);
return true;
}
catch (Exception ex)

Check warning on line 139 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedLock/Dapr/DaprDistributedLockService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Either log this exception and handle it, or rethrow it with some contextual information.

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCvfNNyItDTr6xKcw&open=AZ_rCvfNNyItDTr6xKcw&pullRequest=90
{
logger.LogError(ex, "Error executing action with Dapr lock for resource {ResourceId}", resourceId);
RecordException(activity, ex);
Expand All @@ -151,7 +151,7 @@

private Activity? StartLockActivity(string operationName, string resourceId, int expiryInSeconds)
{
var activity = InfrastructureActivitySource.Source.StartActivity(
var activity = InfrastructureActivitySource.StartDiagnosticActivity(
operationName,
ActivityKind.Client,
Activity.Current?.Context ?? default);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
if (Volatile.Read(ref _disposed) == 1)
return false;

using var activity = InfrastructureActivitySource.Source.StartActivity(
using var activity = InfrastructureActivitySource.StartDiagnosticActivity(
"DistributedLock.Extend",
ActivityKind.Client,
Activity.Current?.Context ?? default);
Expand All @@ -70,8 +70,8 @@

if (extended)
{
logger.LogDebug("Extended Redis lock TTL for resource {ResourceId} by {LeaseSeconds}s",
lockKey, leaseSeconds);

Check warning on line 74 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedLock/Redis/RedisDistributedLockHandle.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Evaluation of this argument may be expensive and unnecessary if logging is disabled

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCviYNyItDTr6xKc7&open=AZ_rCviYNyItDTr6xKc7&pullRequest=90
}
else
{
Expand All @@ -96,7 +96,7 @@
if (Interlocked.Exchange(ref _disposed, 1) == 1)
return;

using var activity = InfrastructureActivitySource.Source.StartActivity(
using var activity = InfrastructureActivitySource.StartDiagnosticActivity(
"DistributedLock.Release",
ActivityKind.Client,
Activity.Current?.Context ?? default);
Expand All @@ -113,14 +113,14 @@

if (result > 0)
{
logger.LogDebug("Released Redis lock for resource {ResourceId} with owner {Owner}",
lockKey, owner);

Check warning on line 117 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedLock/Redis/RedisDistributedLockHandle.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Evaluation of this argument may be expensive and unnecessary if logging is disabled

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCviYNyItDTr6xKc8&open=AZ_rCviYNyItDTr6xKc8&pullRequest=90
activity?.SetTag("lock.released", true);
}
else
{
logger.LogDebug("No Redis lock to release or owner mismatch for resource {ResourceId} with owner {Owner}",
lockKey, owner);

Check warning on line 123 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedLock/Redis/RedisDistributedLockHandle.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Evaluation of this argument may be expensive and unnecessary if logging is disabled

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCviYNyItDTr6xKc9&open=AZ_rCviYNyItDTr6xKc9&pullRequest=90
activity?.SetTag("lock.released", false);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
public class RedisDistributedLockService(
IConnectionMultiplexer redisConnection,
ILogger<RedisDistributedLockService> logger,
IApplicationInfoAccessor applicationInfoAccessor)

Check warning on line 17 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedLock/Redis/RedisDistributedLockService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Parameter 'applicationInfoAccessor' is unread.

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCviPNyItDTr6xKc4&open=AZ_rCviPNyItDTr6xKc4&pullRequest=90
: IDistributedLockService
{
public async Task<IDistributedLockHandle?> TryAcquireLockAsync(string resourceId, int expiryInSeconds = 60,
Expand All @@ -37,9 +37,9 @@

if (acquired)
{
logger.LogDebug("Successfully acquired Redis lock for resource {ResourceId} with owner {LockOwner}",
resourceId, lockOwner);

Check warning on line 41 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedLock/Redis/RedisDistributedLockService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Evaluation of this argument may be expensive and unnecessary if logging is disabled

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCviPNyItDTr6xKc6&open=AZ_rCviPNyItDTr6xKc6&pullRequest=90
activity?.SetTag("lock.acquired", true);

Check warning on line 42 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedLock/Redis/RedisDistributedLockService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of using this literal 'lock.acquired' 6 times.

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCviPNyItDTr6xKc2&open=AZ_rCviPNyItDTr6xKc2&pullRequest=90
activity?.SetStatus(ActivityStatusCode.Ok);
return new RedisDistributedLockHandle(database, resourceId, lockOwner, logger);
}
Expand All @@ -57,10 +57,10 @@
}
}

[Obsolete("Use IDistributedLockHandle.ReleaseAsync() or dispose the handle returned by TryAcquireLockAsync. This method uses a static owner and cannot reliably release locks acquired concurrently.")]

Check warning on line 60 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedLock/Redis/RedisDistributedLockService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Do not forget to remove this deprecated code someday.

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCviPNyItDTr6xKc3&open=AZ_rCviPNyItDTr6xKc3&pullRequest=90
public async Task<bool> ReleaseLockAsync(string resourceId, CancellationToken cancellationToken = default)
{
using var activity = InfrastructureActivitySource.Source.StartActivity(
using var activity = InfrastructureActivitySource.StartDiagnosticActivity(
"DistributedLock.Release",
ActivityKind.Client,
Activity.Current?.Context ?? default);
Expand Down Expand Up @@ -89,8 +89,8 @@

if (released)
{
logger.LogDebug("Successfully released Redis lock for resource {ResourceId} with owner {LockOwner}",
resourceId, lockOwner);

Check warning on line 93 in framework/src/BBT.Aether.Infrastructure/BBT/Aether/DistributedLock/Redis/RedisDistributedLockService.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Evaluation of this argument may be expensive and unnecessary if logging is disabled

See more on https://sonarcloud.io/project/issues?id=burgan-tech_aether&issues=AZ_rCviPNyItDTr6xKc5&open=AZ_rCviPNyItDTr6xKc5&pullRequest=90
}
else
{
Expand Down Expand Up @@ -190,7 +190,7 @@

private static Activity? StartLockActivity(string operationName, string resourceId, int expiryInSeconds)
{
var activity = InfrastructureActivitySource.Source.StartActivity(
var activity = InfrastructureActivitySource.StartDiagnosticActivity(
operationName,
ActivityKind.Client,
Activity.Current?.Context ?? default);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,17 @@ public static class InfrastructureActivitySource
/// The shared ActivitySource instance for creating activities (spans) in Aether infrastructure.
/// </summary>
public static readonly ActivitySource Source = new(SourceName, Version);

/// <summary>
/// Starts an infrastructure diagnostic span only when the global tracing profile is Verbose.
/// </summary>
public static Activity? StartDiagnosticActivity(
string operationName,
ActivityKind kind,
ActivityContext parentContext = default)
{
return AetherTracingRuntime.IsVerbose
? Source.StartActivity(operationName, kind, parentContext)
: null;
}
}
Loading
Loading