Skip to content

feat(telemetry): HTTP body logging, enricher refactor, and background job improvements#50

Merged
yilmaztayfun merged 2 commits into
masterfrom
48-telemetry-http-status-code-wildcard-headers-and-requestresponse-logging-configuration
Mar 12, 2026
Merged

feat(telemetry): HTTP body logging, enricher refactor, and background job improvements#50
yilmaztayfun merged 2 commits into
masterfrom
48-telemetry-http-status-code-wildcard-headers-and-requestresponse-logging-configuration

Conversation

@yilmaztayfun
Copy link
Copy Markdown
Contributor

@yilmaztayfun yilmaztayfun commented Mar 12, 2026

Summary

This PR adds configurable HTTP request/response body logging to the telemetry stack, refactors the log enricher, and improves background job handling (audit timestamps and scheduler cleanup).

Telemetry

  • HTTP body logging: New optional middleware UseHttpBodyLogging() to log request/response bodies and headers per request. Configuration via Telemetry:Logging:Body (EnableRequestBody/EnableResponseBody, MaxBodyLengthToCapture, AllowedContentTypes, path exclusion uses Logging.ExcludedPaths). Sensitive JSON fields and headers are redacted (built-in lists + AdditionalSensitiveJsonFields / AdditionalSensitiveHeaderNames).
  • Options: HttpBodyLoggingOptions and LoggingEnricherOptions added to TelemetryOptions; Tracing.Headers documented as explicit list (no wildcard).
  • Enricher: AetherLogEnricherProcessor replaced by EnricherLogProcessor; enrichers (CustomAttributes + Headers) apply to all log records and to the HTTP body log event scope, with duplicate enrichment avoided when the middleware already added headers.
  • Docs: Telemetry README updated with Logging.Body, Logging.Enrichers, and configuration examples.

Background jobs

  • Audit timestamps: BackgroundJobInfo sets CreatedAt in the constructor; EfCoreJobStore sets ModifiedAt on update and on status change.
  • Scheduler cleanup: JobDispatcher calls IJobScheduler.DeleteAsync when a job completes, fails, or is cancelled so the scheduler (e.g. Dapr) stops triggering it. Failures to delete are logged and do not affect DB state. Idempotency check refactored to use a single job fetch.

Testing

  • Telemetry: Configure Logging.Body and UseHttpBodyLogging(); verify logs and redaction.
  • Background jobs: Run jobs to completion/failure/cancel and confirm CreatedAt/ModifiedAt and scheduler deletion where applicable.

Made with Cursor

Summary by Sourcery

Add configurable HTTP request/response body logging to the telemetry stack, enhance logging/tracing enrichment options, and improve background job lifecycle handling and scheduler cleanup.

New Features:

  • Introduce optional HTTP body logging middleware configurable via Telemetry:Logging:Body for capturing request/response bodies and headers.
  • Add configuration options for tracing header tags and logging enrichers to apply consistent attributes across logs and spans.

Enhancements:

  • Refine telemetry configuration to use top-level Telemetry section keys and document tracing headers, log enrichers, and HTTP body logging usage.
  • Replace the previous log enricher processor with a new processor that applies configured enrichers to all log records while avoiding duplicate HTTP body enrichment.
  • Ensure background jobs record CreatedAt and ModifiedAt timestamps consistently and clean up scheduled jobs from external schedulers once they finish, fail, or are cancelled.

Documentation:

  • Expand telemetry documentation with examples for tracing header tags, logging enrichers, HTTP body logging configuration, and path exclusions.

Summary by CodeRabbit

  • New Features

    • HTTP request/response body logging with configurable content types, capture limits, and sensitive-field/header redaction.
    • Log enrichment with configurable custom attributes and per-request header values (sensitive values redacted).
    • Tracing can capture configured request headers as activity tags.
  • Improvements

    • Background job handling: more accurate created/modified timestamps and scheduler cleanup after job completion/failure.
  • Documentation

    • Telemetry guide updated with body logging, enrichment, tracing, and exclusion guidance.

… job improvements

Telemetry:
- Add configurable HTTP request/response body logging middleware (UseHttpBodyLogging)
  with Body options, path exclusion, and sensitive field/header redaction
- Introduce HttpBodyLoggingOptions, LoggingEnricherOptions in TelemetryOptions
- Replace AetherLogEnricherProcessor with EnricherLogProcessor; document
  Tracing.Headers as explicit list (no wildcard)
- Update telemetry README with Logging.Body and Enrichers configuration

Background jobs:
- Set CreatedAt in BackgroundJobInfo constructor, ModifiedAt in EfCoreJobStore
  on update and status change
- Integrate IJobScheduler.DeleteAsync in JobDispatcher on completion, failure,
  and cancellation; refactor idempotency check to use single job fetch

Made-with: Cursor
@yilmaztayfun yilmaztayfun requested review from a team and darcoakk March 12, 2026 22:12
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Mar 12, 2026

Reviewer's Guide

Adds configurable HTTP request/response body logging middleware to the telemetry stack, refactors log enrichment to use a new processor and tracing header options, and hardens background job processing with created/modified timestamps and scheduler cleanup on completion/failure/cancel.

Sequence diagram for HTTP request logging with body middleware and enricher processor

sequenceDiagram
    actor User
    participant Browser
    participant AspNetCoreApp
    participant HttpBodyLoggingMiddleware
    participant NextMiddleware
    participant ILogger
    participant EnricherLogProcessor
    participant OtelExporter

    User->>Browser: Send HTTP request
    Browser->>AspNetCoreApp: HTTP request
    AspNetCoreApp->>HttpBodyLoggingMiddleware: Invoke(HttpContext)

    HttpBodyLoggingMiddleware->>HttpBodyLoggingMiddleware: ShouldProcess(path, ExcludedPaths)
    alt Path excluded
        HttpBodyLoggingMiddleware->>NextMiddleware: Invoke(HttpContext)
        NextMiddleware-->>HttpBodyLoggingMiddleware: Response
        HttpBodyLoggingMiddleware-->>AspNetCoreApp: Response
    else Path included
        alt EnableRequestBody && allowed content type
            HttpBodyLoggingMiddleware->>HttpBodyLoggingMiddleware: CaptureRequestBodyAsync
        end
        HttpBodyLoggingMiddleware->>NextMiddleware: Invoke(HttpContext)
        NextMiddleware-->>HttpBodyLoggingMiddleware: Response
        alt EnableResponseBody && allowed content type
            HttpBodyLoggingMiddleware->>HttpBodyLoggingMiddleware: CaptureResponseBodyAsync
        end
        HttpBodyLoggingMiddleware->>HttpBodyLoggingMiddleware: Build scope
        HttpBodyLoggingMiddleware->>HttpBodyLoggingMiddleware: AddEnricherProperties(CustomAttributes + Headers)
        HttpBodyLoggingMiddleware->>ILogger: LogInformation("HTTP request/response logged ...")
        ILogger-->>AspNetCoreApp: LogRecord
    end

    note over ILogger,EnricherLogProcessor: Logging pipeline processes LogRecord
    ILogger->>EnricherLogProcessor: OnEnd(LogRecord)
    alt Record already has RequestHeaders/RequestBody
        EnricherLogProcessor->>EnricherLogProcessor: HasBodyOrHeaderEnrichment == true
        EnricherLogProcessor-->>ILogger: Skip enrichment
    else
        EnricherLogProcessor->>EnricherLogProcessor: Add CustomAttributes
        EnricherLogProcessor->>EnricherLogProcessor: Add header enrichers (RequestHeader.*, ResponseHeader.*) with redaction
        EnricherLogProcessor-->>ILogger: Enriched LogRecord
    end

    ILogger->>OtelExporter: Export enriched log
    OtelExporter-->>AspNetCoreApp: Ack
    AspNetCoreApp-->>Browser: HTTP response
Loading

Sequence diagram for background job dispatch with scheduler cleanup

sequenceDiagram
    participant Scheduler as ExternalScheduler
    participant JobDispatcher
    participant Scope as DI_Scope
    participant IUnitOfWorkManager
    participant IJobStore
    participant IJobScheduler
    participant IUnitOfWork
    participant JobHandler

    Scheduler->>JobDispatcher: DispatchAsync(jobId, handlerName)
    JobDispatcher->>Scope: CreateScope()
    Scope-->>JobDispatcher: scope

    JobDispatcher->>Scope: Resolve IUnitOfWorkManager
    JobDispatcher->>Scope: Resolve IJobStore
    JobDispatcher->>Scope: Resolve IJobScheduler

    JobDispatcher->>IUnitOfWorkManager: BeginRequiresNew()
    IUnitOfWorkManager-->>JobDispatcher: IUnitOfWork uow

    JobDispatcher->>IJobStore: GetAsync(jobId)
    IJobStore-->>JobDispatcher: BackgroundJobInfo jobInfo
    alt jobInfo null
        JobDispatcher->>IUnitOfWork: CommitAsync()
        JobDispatcher-->>Scheduler: return
    end

    JobDispatcher->>JobDispatcher: IsJobAlreadyProcessed(jobInfo,...)
    alt Already completed or cancelled
        JobDispatcher->>IUnitOfWork: CommitAsync()
        JobDispatcher->>IJobScheduler: TryDeleteFromSchedulerAsync(handlerName, jobInfo.JobName)
        IJobScheduler-->>JobDispatcher: DeleteAsync result (ignored on failure)
        JobDispatcher-->>Scheduler: return
    else Not processed yet
        JobDispatcher->>Scope: Resolve JobHandler
        JobDispatcher->>IJobStore: UpdateStatusAsync(jobId, Running,...)
        JobDispatcher->>IUnitOfWork: CommitAsync()

        JobDispatcher->>IUnitOfWorkManager: BeginRequiresNew()
        IUnitOfWorkManager-->>JobDispatcher: IUnitOfWork handlerUow
        JobDispatcher->>JobHandler: HandleAsync(jobInfo)
        alt Handler completes
            JobHandler-->>JobDispatcher: success
            JobDispatcher->>IJobStore: UpdateStatusAsync(jobId, Completed,...)
            JobDispatcher->>IUnitOfWork: CommitAsync()
            JobDispatcher->>IJobScheduler: TryDeleteFromSchedulerAsync(handlerName, jobInfo.JobName)
        else Handler cancelled
            JobHandler-->>JobDispatcher: OperationCanceledException
            JobDispatcher->>JobDispatcher: MarkJobStatusAsync(Cancelled)
            JobDispatcher->>IJobScheduler: TryDeleteFromSchedulerAsync(handlerName, jobInfo.JobName)
        else Handler throws
            JobHandler-->>JobDispatcher: Exception
            JobDispatcher->>JobDispatcher: MarkJobStatusAsync(Failed)
            JobDispatcher->>IJobScheduler: TryDeleteFromSchedulerAsync(handlerName, jobInfo.JobName)
        end
        JobDispatcher-->>Scheduler: return
    end
Loading

Class diagram for updated telemetry logging options and processors

classDiagram
    class AetherTelemetryOptions {
        +bool TracingEnabled
        +bool MetricsEnabled
        +bool LoggingEnabled
        +AetherLoggingOptions Logging
        +AetherTracingOptions Tracing
        +AetherMetricsOptions Metrics
    }

    class AetherLoggingOptions {
        +bool EnableOtlpExporter
        +bool EnableConsoleExporter
        +List~string~ ExcludedPaths
        +LoggingEnricherOptions Enrichers
        +HttpBodyLoggingOptions Body
    }

    class HttpBodyLoggingOptions {
        <<sealed>>
        +static IReadOnlyList~string~ DefaultSensitiveJsonFields
        +static IReadOnlyList~string~ DefaultSensitiveHeaderNames
        +bool EnableRequestBody
        +bool EnableResponseBody
        +int MaxBodyLengthToCapture
        +List~string~ AllowedContentTypes
        +List~string~ AdditionalSensitiveJsonFields
        +List~string~ AdditionalSensitiveHeaderNames
    }

    class LoggingEnricherOptions {
        <<sealed>>
        +Dictionary~string,string~ CustomAttributes
        +List~string~ Headers
    }

    class AetherTracingOptions {
        <<sealed>>
        +bool EnableAspNetCore
        +bool EnableHttpClient
        +List~string~ AdditionalSources
        +List~string~ ExcludedPaths
        +List~string~ Headers
    }

    class HttpBodyLoggingMiddleware {
        <<sealed>>
        -RequestDelegate _next
        -ILogger_HttpBodyLoggingMiddleware_ _logger
        -HttpBodyLoggingOptions _bodyOptions
        -LoggingEnricherOptions _enricherOptions
        -List~Regex~ _excludedPatterns
        -HashSet~string~ _sensitiveJsonFields
        -HashSet~string~ _sensitiveHeaderNames
        +HttpBodyLoggingMiddleware(RequestDelegate next, ILogger_HttpBodyLoggingMiddleware_ logger, IOptions_AetherTelemetryOptions_ options)
        +Task Invoke(HttpContext context)
        -bool ShouldProcess(HttpContext context)
        -bool CanReadRequestBody(HttpRequest request)
        -bool CanReadResponseBody(HttpResponse response)
        -bool IsAllowedContentType(string contentType)
        -Task<(string Body,int Size,bool Truncated,bool Captured)> CaptureRequestBodyAsync(HttpRequest request)
        -Task<(string Body,int Size,bool Truncated,bool Captured)> CaptureResponseBodyAsync(HttpResponse response, MemoryStream buffer)
        -void LogBodies(HttpContext context, string requestBody, string responseBody, int requestBodySize, int responseBodySize, bool requestTruncated, bool responseTruncated, bool requestCaptured, bool responseCaptured, string requestHeadersJson, string responseHeadersJson)
        -void AddEnricherProperties(List~KeyValuePair~string,object~~ scope, HttpContext context, string requestHeadersJson, string responseHeadersJson)
    }

    class EnricherLogProcessor {
        <<sealed>>
        -HashSet~string~ _sensitiveHeaderNames
        +EnricherLogProcessor(AetherTelemetryOptions options, IHttpContextAccessor httpContextAccessor)
        +void OnEnd(LogRecord record)
        -static HashSet~string~ BuildSensitiveHeaderNames(AetherTelemetryOptions options)
        -static bool HasBodyOrHeaderEnrichment(LogRecord record)
        -static string NormalizeHeaderKey(string key)
    }

    class HttpBodyLoggingApplicationBuilderExtensions {
        <<static>>
        +IApplicationBuilder UseHttpBodyLogging(IApplicationBuilder app)
    }

    AetherTelemetryOptions --> AetherLoggingOptions : uses
    AetherTelemetryOptions --> AetherTracingOptions : uses
    AetherLoggingOptions --> LoggingEnricherOptions : has
    AetherLoggingOptions --> HttpBodyLoggingOptions : has
    HttpBodyLoggingMiddleware --> HttpBodyLoggingOptions : config
    HttpBodyLoggingMiddleware --> LoggingEnricherOptions : config
    EnricherLogProcessor --> AetherTelemetryOptions : config
    EnricherLogProcessor --> HttpBodyLoggingOptions : redaction
    HttpBodyLoggingApplicationBuilderExtensions --> HttpBodyLoggingMiddleware : registers
    AetherTracingOptions --> AetherTelemetryOptions : part of
Loading

Class diagram for updated background job components

classDiagram
    class BackgroundJobInfo {
        +Guid Id
        +string HandlerName
        +string JobName
        +BackgroundJobStatus Status
        +DateTime CreatedAt
        +DateTime? ModifiedAt
        +DateTime? HandledTime
        +ExtraPropertyDictionary ExtraProperties
        +byte[] Payload
        +BackgroundJobInfo(Guid id, string handlerName, string jobName)
    }

    class EfCoreJobStore {
        -DbContext _dbContext
        +Task SaveAsync(BackgroundJobInfo jobInfo, CancellationToken cancellationToken)
        +Task UpdateStatusAsync(Guid id, BackgroundJobStatus status, DateTime? handledTime, string result, CancellationToken cancellationToken)
    }

    class JobDispatcher {
        -ILogger_JobDispatcher_ logger
        +Task DispatchAsync(Guid jobId, string handlerName, CancellationToken cancellationToken)
        -bool IsJobAlreadyProcessed(BackgroundJobInfo jobInfo, Guid jobId, string handlerName)
        -Task TryDeleteFromSchedulerAsync(IJobScheduler jobScheduler, string handlerName, string jobName, CancellationToken cancellationToken)
        -Task MarkJobStatusAsync(IUnitOfWorkManager uowManager, IJobStore jobStore, Guid jobId, BackgroundJobStatus status, string reason, CancellationToken cancellationToken)
    }

    class IJobStore {
        <<interface>>
        +Task<BackgroundJobInfo> GetAsync(Guid id, CancellationToken cancellationToken)
        +Task SaveAsync(BackgroundJobInfo jobInfo, CancellationToken cancellationToken)
        +Task UpdateStatusAsync(Guid id, BackgroundJobStatus status, DateTime? handledTime, string result, CancellationToken cancellationToken)
    }

    class IJobScheduler {
        <<interface>>
        +Task DeleteAsync(string handlerName, string jobName, CancellationToken cancellationToken)
    }

    class IUnitOfWorkManager {
        <<interface>>
        +Task<IUnitOfWork> BeginRequiresNew(CancellationToken cancellationToken)
    }

    class IUnitOfWork {
        <<interface>>
        +Task CommitAsync(CancellationToken cancellationToken)
        +ValueTask DisposeAsync()
    }

    EfCoreJobStore ..|> IJobStore
    JobDispatcher --> IJobStore : uses
    JobDispatcher --> IJobScheduler : uses
    JobDispatcher --> IUnitOfWorkManager : uses
    EfCoreJobStore --> BackgroundJobInfo : persists
Loading

File-Level Changes

Change Details Files
Introduce configurable HTTP request/response body logging middleware with redaction and path exclusions, plus associated options and documentation.
  • Add HttpBodyLoggingOptions to AetherLoggingOptions with enable flags, size limits, allowed content types, and sensitive field/header configuration.
  • Implement HttpBodyLoggingMiddleware to capture and log request/response bodies and headers with JSON redaction, truncation, and regex-based path exclusion using Logging.ExcludedPaths.
  • Provide UseHttpBodyLogging() extension to register the middleware in the ASP.NET Core pipeline.
  • Update telemetry README with Telemetry/Logging configuration examples, Logging.Body semantics, pipeline ordering, and tracing/logging headers/enrichers documentation.
framework/src/BBT.Aether.AspNetCore/BBT/Aether/AspNetCore/Telemetry/TelemetryOptions.cs
framework/src/BBT.Aether.AspNetCore/BBT/Aether/AspNetCore/Telemetry/HttpBodyLoggingMiddleware.cs
framework/src/BBT.Aether.AspNetCore/Microsoft/AspNetCore/Builder/HttpBodyLoggingApplicationBuilderExtensions.cs
framework/docs/telemetry/README.md
Refactor log enrichment to a new EnricherLogProcessor and clarify tracing header configuration.
  • Replace AetherLogEnricherProcessor with EnricherLogProcessor that adds Logging.Enrichers CustomAttributes and selected headers as attributes on all log records, skipping records already enriched by HTTP body logging.
  • Wire EnricherLogProcessor into AddAetherTelemetry logging setup using IHttpContextAccessor for header access.
  • Move tracing header enrichment to AetherTracingOptions.Headers and use it when building ASP.NET Core tracing instrumentation, removing dependence on Logging.Enrichers.Headers for spans.
  • Document Telemetry:Tracing:Headers and Telemetry:Logging:Enrichers behavior in README, including no-wildcard header semantics and redaction behavior.
framework/src/BBT.Aether.AspNetCore/BBT/Aether/AspNetCore/Telemetry/EnricherLogProcessor.cs
framework/src/BBT.Aether.AspNetCore/Microsoft/Extensions/DependencyInjection/AetherTelemetryServiceCollectionExtensions.cs
framework/src/BBT.Aether.AspNetCore/BBT/Aether/AspNetCore/Telemetry/TelemetryOptions.cs
framework/docs/telemetry/README.md
framework/src/BBT.Aether.AspNetCore/BBT/Aether/AspNetCore/Telemetry/AetherLogEnricherProcessor.cs
Improve background job lifecycle tracking with timestamps and scheduler cleanup on completion or non-processing.
  • Set CreatedAt in BackgroundJobInfo constructor so new jobs always have a creation timestamp.
  • Update EfCoreJobStore.SaveAsync and UpdateStatusAsync to set ModifiedAt whenever a job is updated or its status changes.
  • Refactor JobDispatcher to fetch BackgroundJobInfo once, perform idempotency check in-process, and obtain IJobScheduler to delete jobs from the external scheduler when jobs complete, fail, are cancelled, or are skipped as already processed.
  • Add TryDeleteFromSchedulerAsync helper that calls IJobScheduler.DeleteAsync and logs a warning without failing the dispatch when deletion fails.
framework/src/BBT.Aether.Domain/BBT/Aether/Domain/Entities/BackgroundJobInfo.cs
framework/src/BBT.Aether.Infrastructure/BBT/Aether/BackgroundJob/EfCoreJobStore.cs
framework/src/BBT.Aether.Infrastructure/BBT/Aether/BackgroundJob/JobDispatcher.cs

Assessment against linked issues

Issue Objective Addressed Explanation
#48 Add HTTP response status code to telemetry for each HTTP request, preferably via the standard OpenTelemetry attribute (e.g. http.response.status_code) in the existing HTTP response enrichment (such as EnrichWithHttpResponse), so it appears on traces and, where applicable, logs. The changes only log the HTTP status code inside the new HttpBodyLoggingMiddleware (as a log scope property and in log messages). There is no change to the ASP.NET Core tracing enrichment (e.g. EnrichWithHttpResponse) to add http.response.status_code as an Activity tag or OpenTelemetry attribute, so traces still do not have the response status code as required.
#48 Support a wildcard (*) in header enricher configuration so that all (non-sensitive or redacted) request and optionally response headers can be logged/traced, with sensitive headers excluded or redacted by default and an option to override. The PR continues to treat header lists as explicit names only. Tracing.Headers and Logging.Enrichers.Headers are documented and implemented as explicit lists; there is no logic to interpret "" as a wildcard, no branch to enumerate all headers when "" is present, and no new configuration to control wildcard behavior. While sensitive headers are redacted in the body middleware and enricher, wildcard support itself is not implemented.
#48 Introduce separate configuration for request and response logging/enrichment, allowing independent enable/disable flags and distinct header lists for request vs response, while maintaining backward compatibility with the existing single Headers list. The PR adds HttpBodyLoggingOptions with EnableRequestBody and EnableResponseBody for body logging only, but LoggingEnricherOptions still has a single Headers list and no per-request/response enable flags. EnricherLogProcessor and HttpBodyLoggingMiddleware both use the same Headers list to emit RequestHeader.* and ResponseHeader.* properties; there is no configuration to enable/disable request vs response enrichment separately, nor separate RequestHeaders and ResponseHeaders lists, so the requested independent configuration is not provided.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 12, 2026

Caution

Review failed

Pull request was closed or merged during review

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key(s) in object: 'review'
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
📝 Walkthrough

Walkthrough

Adds HTTP request/response body logging middleware, replaces the previous log enricher with a new EnricherLogProcessor, expands telemetry configuration (tracing headers, logging enrichers, body options), and applies timestamp updates and scheduler cleanup to background job flows.

Changes

Cohort / File(s) Summary
Documentation
framework/docs/telemetry/README.md
Updated docs to reflect new Telemetry top-level config, tracing headers, logging enrichers (CustomAttributes, Headers), HTTP body logging options, middleware integration, redaction and excluded paths.
Log Enrichment
framework/src/BBT.Aether.AspNetCore/.../Telemetry/AetherLogEnricherProcessor.cs, framework/src/BBT.Aether.AspNetCore/.../Telemetry/EnricherLogProcessor.cs
Removed legacy AetherLogEnricherProcessor; added EnricherLogProcessor that aggregates custom attributes and request/response headers, normalizes keys, redacts sensitive headers, skips if record already has body/header enrichment, and safely merges attributes.
HTTP Body Logging Middleware
framework/src/BBT.Aether.AspNetCore/.../Telemetry/HttpBodyLoggingMiddleware.cs, framework/src/BBT.Aether.AspNetCore/Microsoft/AspNetCore/Builder/HttpBodyLoggingApplicationBuilderExtensions.cs
New middleware to capture/log request and response bodies and headers with content-type allow-listing, truncation, JSON-field/header redaction, path exclusions, and extension method to register middleware.
Telemetry Configuration
framework/src/BBT.Aether.AspNetCore/.../Telemetry/TelemetryOptions.cs
Added HttpBodyLoggingOptions and defaults, added Logging.Enrichers.CustomAttributes, moved/added Tracing.Headers, and new body-related configuration properties (EnableRequestBody/ResponseBody, MaxBodyLengthToCapture, AllowedContentTypes, additional sensitive fields/headers).
Service Registration
framework/src/BBT.Aether.AspNetCore/.../DependencyInjection/AetherTelemetryServiceCollectionExtensions.cs
Switched tracing header source to Tracing.Headers; replaced old enricher registration with EnricherLogProcessor; adjusted exclusion pattern usage for logging/tracing setup.
Background Job Lifecycle
framework/src/BBT.Aether.Domain/.../BackgroundJobInfo.cs, framework/src/BBT.Aether.Infrastructure/.../BackgroundJob/EfCoreJobStore.cs, framework/src/BBT.Aether.Infrastructure/.../BackgroundJob/JobDispatcher.cs
Initialize CreatedAt on job creation; set ModifiedAt on updates; JobDispatcher now uses IJobScheduler, removes jobs from scheduler after various outcomes, changes idempotency checks to use prefetched BackgroundJobInfo and performs scheduler cleanup on duplicate/failure paths.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant App as App Pipeline
    participant BodyMW as HttpBodyLoggingMiddleware
    participant Handler as Request Handler
    participant Logger as ILogger/OpenTelemetry
    participant Enricher as EnricherLogProcessor

    Client->>App: HTTP Request
    App->>BodyMW: invoke
    BodyMW->>BodyMW: check excluded paths
    alt included
        BodyMW->>BodyMW: capture request headers/body
        BodyMW->>Handler: invoke handler
        Handler-->>BodyMW: response
        BodyMW->>BodyMW: capture response headers/body, redact/truncate
        BodyMW->>Logger: emit log record (with body/headers)
        Logger->>Enricher: OnEnd(LogRecord)
        Enricher->>Enricher: detect existing enrichment
        Enricher->>Enricher: aggregate custom attrs + headers, redact sensitive
        Enricher->>Logger: merge attributes back
    else excluded
        BodyMW->>Handler: pass through
    end
    BodyMW-->>Client: HTTP Response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • darcoakk
  • ukaratas

Poem

🐇 I hop through headers, bodies, and logs,
Capturing whispers and redacting the clogs,
Enrichers hum, jobs tidy their tracks,
Middleware listens and stitches it back,
A rabbit-approved telemetry bounce!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the three main changes: HTTP body logging middleware addition, log enricher refactoring, and background job lifecycle improvements, which align directly with the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 48-telemetry-http-status-code-wildcard-headers-and-requestresponse-logging-configuration
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the application's observability and background job management. It introduces a new, configurable HTTP body logging middleware for detailed request/response inspection, refactors the log enrichment mechanism for broader and more consistent application across all logs, and improves background job reliability by adding audit timestamps and ensuring jobs are automatically removed from schedulers upon completion or failure.

Highlights

  • HTTP Body Logging: Introduced an optional middleware (UseHttpBodyLogging()) for configurable logging of HTTP request and response bodies and headers, including redaction of sensitive data and content type filtering.
  • Log Enricher Refactoring: Replaced AetherLogEnricherProcessor with EnricherLogProcessor to consistently apply custom attributes and headers to all log records, while avoiding duplicate enrichment for HTTP body logs.
  • Background Job Audit Timestamps: CreatedAt is now automatically set upon job creation, and ModifiedAt is updated when a job is saved or its status changes, improving job lifecycle auditing.
  • Background Job Scheduler Cleanup: Implemented automatic deletion of completed, failed, or cancelled jobs from the scheduler (e.g., Dapr) to prevent unnecessary re-triggering.
  • Telemetry Configuration Updates: Extended TelemetryOptions to include HttpBodyLoggingOptions and added Tracing.Headers for configurable request header tagging on spans.
  • Documentation: Updated the telemetry README to reflect new features, configuration options, and usage examples for HTTP body logging and log enrichers.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • framework/docs/telemetry/README.md
    • Updated the feature list to include trace header tags, log enrichers, and optional HTTP body logging.
    • Revised the appsettings.json configuration example to demonstrate Telemetry:Logging:Body and Tracing:Headers settings.
    • Added new documentation sections explaining "Enrichers on All Logs" and "HTTP Request/Response Body and Header Logging".
    • Expanded the "Exclude Health Checks" section to cover Logging.ExcludedPaths for HTTP body logging.
  • framework/src/BBT.Aether.AspNetCore/BBT/Aether/AspNetCore/Telemetry/AetherLogEnricherProcessor.cs
    • Removed the AetherLogEnricherProcessor class.
  • framework/src/BBT.Aether.AspNetCore/BBT/Aether/AspNetCore/Telemetry/EnricherLogProcessor.cs
    • Added the EnricherLogProcessor class to apply custom attributes and headers to all log records, with logic to prevent duplicate enrichment.
  • framework/src/BBT.Aether.AspNetCore/BBT/Aether/AspNetCore/Telemetry/HttpBodyLoggingMiddleware.cs
    • Added the HttpBodyLoggingMiddleware class for capturing and logging HTTP request/response bodies and headers, including redaction and truncation.
  • framework/src/BBT.Aether.AspNetCore/BBT/Aether/AspNetCore/Telemetry/TelemetryOptions.cs
    • Introduced HttpBodyLoggingOptions to configure HTTP body logging.
    • Added a Body property to AetherLoggingOptions.
    • Added a Headers property to AetherTracingOptions for specifying request headers to be added as activity tags.
    • Updated LoggingEnricherOptions with detailed XML comments for CustomAttributes and Headers.
  • framework/src/BBT.Aether.AspNetCore/Microsoft/AspNetCore/Builder/HttpBodyLoggingApplicationBuilderExtensions.cs
    • Added UseHttpBodyLogging extension method to IApplicationBuilder for integrating the HTTP body logging middleware.
  • framework/src/BBT.Aether.AspNetCore/Microsoft/Extensions/DependencyInjection/AetherTelemetryServiceCollectionExtensions.cs
    • Modified the tracing configuration to use opts.Tracing.Headers for activity tags.
    • Replaced the registration of AetherLogEnricherProcessor with EnricherLogProcessor in the logging pipeline.
  • framework/src/BBT.Aether.Domain/BBT/Aether/Domain/Entities/BackgroundJobInfo.cs
    • Set the CreatedAt property to DateTime.UtcNow in the constructor.
  • framework/src/BBT.Aether.Infrastructure/BBT/Aether/BackgroundJob/EfCoreJobStore.cs
    • Updated the SaveAsync method to set ModifiedAt when an existing job is updated.
    • Updated the UpdateStatusAsync method to set ModifiedAt when a job's status changes.
  • framework/src/BBT.Aether.Infrastructure/BBT/Aether/BackgroundJob/JobDispatcher.cs
    • Refactored the idempotency check to retrieve job information once.
    • Added calls to IJobScheduler.DeleteAsync upon job completion, cancellation, or failure.
    • Introduced TryDeleteFromSchedulerAsync to safely attempt job deletion from the scheduler.
Activity
  • Initial implementation of HTTP body logging middleware.
  • Refactored log enricher logic for improved consistency.
  • Added CreatedAt and ModifiedAt timestamps to background job entities.
  • Implemented scheduler cleanup for background jobs upon terminal states.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • In JobDispatcher.TryDeleteFromSchedulerAsync you reference logger, but that symbol only exists as a local variable in DispatchAsync; consider using a class-level logger (e.g. _logger) or passing the logger into the helper to avoid a compile-time error.
  • HttpBodyLoggingMiddleware.IsAllowedContentType allocates a new HashSet for allowed content types on every call; you can precompute this set once in the constructor to avoid per-request allocations.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In JobDispatcher.TryDeleteFromSchedulerAsync you reference `logger`, but that symbol only exists as a local variable in DispatchAsync; consider using a class-level logger (e.g. `_logger`) or passing the logger into the helper to avoid a compile-time error.
- HttpBodyLoggingMiddleware.IsAllowedContentType allocates a new HashSet for allowed content types on every call; you can precompute this set once in the constructor to avoid per-request allocations.

## Individual Comments

### Comment 1
<location path="framework/src/BBT.Aether.AspNetCore/BBT/Aether/AspNetCore/Telemetry/HttpBodyLoggingMiddleware.cs" line_range="199-175" />
<code_context>
+        return IsAllowedContentType(response.ContentType);
+    }
+
+    private bool IsAllowedContentType(string contentType)
+    {
+        var normalized = contentType.Split(';', 2)[0].Trim();
+        var allowed = new HashSet<string>(_bodyOptions.AllowedContentTypes ?? [], StringComparer.OrdinalIgnoreCase);
+
+        if (allowed.Contains(normalized))
+            return true;
+
+        if (normalized.EndsWith("+json", StringComparison.OrdinalIgnoreCase))
+            return true;
+
+        if (normalized.StartsWith("text/", StringComparison.OrdinalIgnoreCase))
+            return true;
+
+        return false;
+    }
+
</code_context>
<issue_to_address>
**suggestion (performance):** Avoid allocating a new HashSet on every IsAllowedContentType call.

`IsAllowedContentType` allocates a new `HashSet<string>` on every call from `_bodyOptions.AllowedContentTypes`. Since this may run per request (for both request/response), consider building the `HashSet<string>` once (e.g., in the constructor or lazily) and reusing it, so this method only normalizes the content type and checks membership plus the `+json`/`text/` heuristics, reducing GC pressure.

Suggested implementation:

```csharp
public sealed class HttpBodyLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private HashSet<string>? _allowedContentTypes;

```

```csharp
    private bool IsAllowedContentType(string contentType)
    {
        var normalized = contentType.Split(';', 2)[0].Trim();

        var allowed = _allowedContentTypes ??= new HashSet<string>(
            _bodyOptions.AllowedContentTypes ?? [],
            StringComparer.OrdinalIgnoreCase);

        if (allowed.Contains(normalized))
            return true;

        if (normalized.EndsWith("+json", StringComparison.OrdinalIgnoreCase))
            return true;

        if (normalized.StartsWith("text/", StringComparison.OrdinalIgnoreCase))
            return true;

        return false;
    }

```
</issue_to_address>

### Comment 2
<location path="framework/docs/telemetry/README.md" line_range="272" />
<code_context>

+### Enrichers on All Logs
+
+`Telemetry:Logging:Enrichers` (CustomAttributes and Headers) are added as attributes to **every** log record in the application via `EnricherLogProcessor`. This allows querying all logs with the same enrich fields (e.g. `env`, `app`, `RequestHeader.x_correlation_id`) in a single observability query.
+
+- **CustomAttributes**: Key-value pairs added to every log.
</code_context>
<issue_to_address>
**issue (typo):** Consider rephrasingsame enrich fieldstosame enriched fieldsorsame enrichment fieldsfor correct grammar.

The rest of the text is quite polished, so updating this phrase tosame enriched fieldsorsame enrichment fields” (e.g., “This allows querying all logs with the same enriched fields …”) would improve clarity and grammatical correctness.

```suggestion
`Telemetry:Logging:Enrichers` (CustomAttributes and Headers) are added as attributes to **every** log record in the application via `EnricherLogProcessor`. This allows querying all logs with the same enriched fields (e.g. `env`, `app`, `RequestHeader.x_correlation_id`) in a single observability query.
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.


### Enrichers on All Logs

`Telemetry:Logging:Enrichers` (CustomAttributes and Headers) are added as attributes to **every** log record in the application via `EnricherLogProcessor`. This allows querying all logs with the same enrich fields (e.g. `env`, `app`, `RequestHeader.x_correlation_id`) in a single observability query.
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 (typo): Consider rephrasing “same enrich fields” to “same enriched fields” or “same enrichment fields” for correct grammar.

The rest of the text is quite polished, so updating this phrase to “same enriched fields” or “same enrichment fields” (e.g., “This allows querying all logs with the same enriched fields …”) would improve clarity and grammatical correctness.

Suggested change
`Telemetry:Logging:Enrichers` (CustomAttributes and Headers) are added as attributes to **every** log record in the application via `EnricherLogProcessor`. This allows querying all logs with the same enrich fields (e.g. `env`, `app`, `RequestHeader.x_correlation_id`) in a single observability query.
`Telemetry:Logging:Enrichers` (CustomAttributes and Headers) are added as attributes to **every** log record in the application via `EnricherLogProcessor`. This allows querying all logs with the same enriched fields (e.g. `env`, `app`, `RequestHeader.x_correlation_id`) in a single observability query.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces significant improvements to telemetry with configurable HTTP body logging, a refactored log enricher, and enhanced background job handling. The changes are well-structured and add valuable functionality. My review includes suggestions to improve documentation consistency, optimize performance in the new middleware, enhance the robustness of string truncation, and resolve an issue with duplicate attribute enrichment in the logging setup.

"EnableRequestBody": false,
"EnableResponseBody": false,
"MaxBodyLengthToCapture": 16384,
"AllowedContentTypes": ["application/json", "application/problem+json", "text/plain"],
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The example for AllowedContentTypes is missing application/xml and text/xml, which are present in the default configuration in the code. To ensure the documentation is accurate and consistent with the default behavior, please update the example to include these missing content types.

Suggested change
"AllowedContentTypes": ["application/json", "application/problem+json", "text/plain"],
"AllowedContentTypes": ["application/json", "application/problem+json", "application/xml", "text/plain", "text/xml"],

Comment on lines 240 to 241
.AddAttributes(opts.Logging.Enrichers.CustomAttributes
.ToDictionary(x => x.Key, x => (object)x.Value)));
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The CustomAttributes from Logging.Enrichers are being added here to the logging ResourceBuilder. However, the EnricherLogProcessor (added on line 248) also adds these same attributes to each log record. This will result in duplicate attributes on exported logs. To avoid this, you should remove the .AddAttributes(...) call for CustomAttributes from this ResourceBuilder configuration, as the EnricherLogProcessor is the more appropriate place for log-specific enrichment.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
framework/src/BBT.Aether.AspNetCore/BBT/Aether/AspNetCore/Telemetry/HttpBodyLoggingMiddleware.cs (2)

199-214: Consider caching the HashSet in IsAllowedContentType.

A new HashSet is allocated on every call to IsAllowedContentType, which is invoked for both request and response content-type checks on every processed request. Since _bodyOptions.AllowedContentTypes doesn't change after construction, consider building this set once in the constructor.

♻️ Suggested optimization
 private readonly HashSet<string> _sensitiveJsonFields;
 private readonly HashSet<string> _sensitiveHeaderNames;
+private readonly HashSet<string> _allowedContentTypes;

 public HttpBodyLoggingMiddleware(...)
 {
     // ... existing initialization ...
     _sensitiveHeaderNames = new HashSet<string>(
         HttpBodyLoggingOptions.DefaultSensitiveHeaderNames.Concat(_bodyOptions.AdditionalSensitiveHeaderNames ?? []),
         StringComparer.OrdinalIgnoreCase);
+    _allowedContentTypes = new HashSet<string>(
+        _bodyOptions.AllowedContentTypes ?? [],
+        StringComparer.OrdinalIgnoreCase);
 }

 private bool IsAllowedContentType(string contentType)
 {
     var normalized = contentType.Split(';', 2)[0].Trim();
-    var allowed = new HashSet<string>(_bodyOptions.AllowedContentTypes ?? [], StringComparer.OrdinalIgnoreCase);

-    if (allowed.Contains(normalized))
+    if (_allowedContentTypes.Contains(normalized))
         return true;
     // ... rest unchanged ...
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@framework/src/BBT.Aether.AspNetCore/BBT/Aether/AspNetCore/Telemetry/HttpBodyLoggingMiddleware.cs`
around lines 199 - 214, IsAllowedContentType currently allocates a new HashSet
from _bodyOptions.AllowedContentTypes on every call; instead create a readonly
cached HashSet (e.g., _allowedContentTypesSet) initialized once in the class
constructor from _bodyOptions.AllowedContentTypes (handle null/empty safely and
use StringComparer.OrdinalIgnoreCase), then update IsAllowedContentType to
consult that cached set and keep the same +json and text/ checks.

385-393: UTF-8 truncation may produce malformed sequences.

Encoding.UTF8.GetString(bytes, 0, maxBytes) can produce malformed output if maxBytes falls in the middle of a multi-byte character sequence. The decoder will insert replacement characters (�) for incomplete sequences.

Consider using a character-based approach or finding a valid UTF-8 boundary:

♻️ Safe UTF-8 truncation
 private static string TruncateUtf8(string value, int maxBytes)
 {
     var bytes = Encoding.UTF8.GetBytes(value);
     if (bytes.Length <= maxBytes)
         return value;

-    var truncated = Encoding.UTF8.GetString(bytes, 0, maxBytes);
+    // Find valid UTF-8 boundary by backing off from incomplete sequences
+    var truncateAt = maxBytes;
+    while (truncateAt > 0 && (bytes[truncateAt] & 0xC0) == 0x80)
+    {
+        truncateAt--;
+    }
+    var truncated = Encoding.UTF8.GetString(bytes, 0, truncateAt);
     return truncated + "…[TRUNCATED]";
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@framework/src/BBT.Aether.AspNetCore/BBT/Aether/AspNetCore/Telemetry/HttpBodyLoggingMiddleware.cs`
around lines 385 - 393, The TruncateUtf8 method currently slices raw UTF-8 bytes
which can split multi-byte characters and yield replacement chars; replace that
logic by iterating the input string (method TruncateUtf8) and accumulating bytes
per character using Encoding.UTF8.GetByteCount(char[]) (or GetByteCount of each
char) until adding the next character would exceed maxBytes, then return the
substring up to that char plus the "…[TRUNCATED]" marker. This preserves valid
UTF-8 boundaries and avoids malformed sequences while keeping the same
signature.
framework/src/BBT.Aether.Domain/BBT/Aether/Domain/Entities/BackgroundJobInfo.cs (1)

29-29: Use IClock abstraction instead of DateTime.UtcNow for testability.

Direct use of DateTime.UtcNow bypasses the IClock abstraction used elsewhere in the codebase (e.g., JobDispatcher uses clock.UtcNow). This breaks testability and consistency.

Since entity constructors don't have access to DI, consider either:

  1. Removing this line and relying on the AuditInterceptor to set CreatedAt (it checks for null and sets via clock.UtcNow)
  2. Passing a timestamp parameter from the caller that has access to IClock

Also, minor formatting: there are two spaces before DateTime.UtcNow.

Option 1: Remove and rely on AuditInterceptor
     public BackgroundJobInfo(Guid id, string handlerName, string jobName) : base(id)
     {
         HandlerName = handlerName;
         JobName = jobName;
         ExtraProperties = new ExtraPropertyDictionary();
-        CreatedAt =  DateTime.UtcNow;
     }
Option 2: Accept timestamp from caller
-    public BackgroundJobInfo(Guid id, string handlerName, string jobName) : base(id)
+    public BackgroundJobInfo(Guid id, string handlerName, string jobName, DateTime createdAt) : base(id)
     {
         HandlerName = handlerName;
         JobName = jobName;
         ExtraProperties = new ExtraPropertyDictionary();
-        CreatedAt =  DateTime.UtcNow;
+        CreatedAt = createdAt;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@framework/src/BBT.Aether.Domain/BBT/Aether/Domain/Entities/BackgroundJobInfo.cs`
at line 29, BackgroundJobInfo currently sets CreatedAt using DateTime.UtcNow
which bypasses the IClock abstraction and hurts testability; remove the
"CreatedAt = DateTime.UtcNow" assignment in the BackgroundJobInfo constructor
and rely on AuditInterceptor (which sets CreatedAt via IClock.UtcNow when null),
or alternatively add an optional DateTimeOffset createdAt parameter to the
BackgroundJobInfo constructor and have callers (e.g., callers with access to
IClock/JobDispatcher) pass clock.UtcNow into that parameter; also fix the
double-space before DateTime.UtcNow if keeping the line.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@framework/src/BBT.Aether.Infrastructure/BBT/Aether/BackgroundJob/EfCoreJobStore.cs`:
- Line 45: Replace the direct DateTime.UtcNow assignment to jobInfo.ModifiedAt
in EfCoreJobStore by injecting and using the IClock abstraction (add an IClock
_clock field and constructor parameter) and remove the manual assignment
entirely because BackgroundJobInfo is audited and AuditInterceptor sets
ModifiedAt; also remove the stray double space before the '=' in the existing
assignments (the occurrences around jobInfo.ModifiedAt and the other similar
assignment) so spacing matches project style; ensure JobDispatcher usage of
IClock remains consistent with this change.

---

Nitpick comments:
In
`@framework/src/BBT.Aether.AspNetCore/BBT/Aether/AspNetCore/Telemetry/HttpBodyLoggingMiddleware.cs`:
- Around line 199-214: IsAllowedContentType currently allocates a new HashSet
from _bodyOptions.AllowedContentTypes on every call; instead create a readonly
cached HashSet (e.g., _allowedContentTypesSet) initialized once in the class
constructor from _bodyOptions.AllowedContentTypes (handle null/empty safely and
use StringComparer.OrdinalIgnoreCase), then update IsAllowedContentType to
consult that cached set and keep the same +json and text/ checks.
- Around line 385-393: The TruncateUtf8 method currently slices raw UTF-8 bytes
which can split multi-byte characters and yield replacement chars; replace that
logic by iterating the input string (method TruncateUtf8) and accumulating bytes
per character using Encoding.UTF8.GetByteCount(char[]) (or GetByteCount of each
char) until adding the next character would exceed maxBytes, then return the
substring up to that char plus the "…[TRUNCATED]" marker. This preserves valid
UTF-8 boundaries and avoids malformed sequences while keeping the same
signature.

In
`@framework/src/BBT.Aether.Domain/BBT/Aether/Domain/Entities/BackgroundJobInfo.cs`:
- Line 29: BackgroundJobInfo currently sets CreatedAt using DateTime.UtcNow
which bypasses the IClock abstraction and hurts testability; remove the
"CreatedAt = DateTime.UtcNow" assignment in the BackgroundJobInfo constructor
and rely on AuditInterceptor (which sets CreatedAt via IClock.UtcNow when null),
or alternatively add an optional DateTimeOffset createdAt parameter to the
BackgroundJobInfo constructor and have callers (e.g., callers with access to
IClock/JobDispatcher) pass clock.UtcNow into that parameter; also fix the
double-space before DateTime.UtcNow if keeping the line.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cacd0b2e-6925-4fac-aa4a-5026976be6da

📥 Commits

Reviewing files that changed from the base of the PR and between 93c6613 and efece1c.

📒 Files selected for processing (10)
  • framework/docs/telemetry/README.md
  • framework/src/BBT.Aether.AspNetCore/BBT/Aether/AspNetCore/Telemetry/AetherLogEnricherProcessor.cs
  • framework/src/BBT.Aether.AspNetCore/BBT/Aether/AspNetCore/Telemetry/EnricherLogProcessor.cs
  • framework/src/BBT.Aether.AspNetCore/BBT/Aether/AspNetCore/Telemetry/HttpBodyLoggingMiddleware.cs
  • framework/src/BBT.Aether.AspNetCore/BBT/Aether/AspNetCore/Telemetry/TelemetryOptions.cs
  • framework/src/BBT.Aether.AspNetCore/Microsoft/AspNetCore/Builder/HttpBodyLoggingApplicationBuilderExtensions.cs
  • framework/src/BBT.Aether.AspNetCore/Microsoft/Extensions/DependencyInjection/AetherTelemetryServiceCollectionExtensions.cs
  • framework/src/BBT.Aether.Domain/BBT/Aether/Domain/Entities/BackgroundJobInfo.cs
  • framework/src/BBT.Aether.Infrastructure/BBT/Aether/BackgroundJob/EfCoreJobStore.cs
  • framework/src/BBT.Aether.Infrastructure/BBT/Aether/BackgroundJob/JobDispatcher.cs
💤 Files with no reviewable changes (1)
  • framework/src/BBT.Aether.AspNetCore/BBT/Aether/AspNetCore/Telemetry/AetherLogEnricherProcessor.cs

if (existingJob != null)
{
// Update existing job
jobInfo.ModifiedAt = DateTime.UtcNow;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if AuditInterceptor sets ModifiedAt for modified entities
ast-grep --pattern $'ApplyConceptsForModifiedEntity($$$) {
  $$$
}'

# Also search for ModifiedAt handling in the interceptor
rg -n "ModifiedAt" --type cs -C 3 framework/src/BBT.Aether.Infrastructure/BBT/Aether/Domain/EntityFrameworkCore/Interceptors/

Repository: burgan-tech/aether

Length of output: 1128


🏁 Script executed:

# First, let's see the full EfCoreJobStore.cs file (or at least the relevant sections)
wc -l "framework/src/BBT.Aether.Infrastructure/BBT/Aether/BackgroundJob/EfCoreJobStore.cs"

Repository: burgan-tech/aether

Length of output: 148


🏁 Script executed:

# Read the EfCoreJobStore.cs file with context around lines 45 and 116
cat -n "framework/src/BBT.Aether.Infrastructure/BBT/Aether/BackgroundJob/EfCoreJobStore.cs" | head -150 | tail -110

Repository: burgan-tech/aether

Length of output: 4680


🏁 Script executed:

# Find JobInfo class definition to check if it implements IHasModifyTime
rg -n "class JobInfo" -A 3 --type cs framework/src/BBT.Aether.Infrastructure/

Repository: burgan-tech/aether

Length of output: 44


🏁 Script executed:

# Check if JobInfo or related background job entities implement IHasModifyTime
rg -n "IHasModifyTime" --type cs framework/src/BBT.Aether.Infrastructure/BBT/Aether/BackgroundJob/

Repository: burgan-tech/aether

Length of output: 44


🏁 Script executed:

# Find BackgroundJobInfo class definition
fd -e cs | xargs rg -l "class BackgroundJobInfo" framework/src/BBT.Aether.Infrastructure/

Repository: burgan-tech/aether

Length of output: 141


🏁 Script executed:

# Search for BackgroundJobInfo in the entire codebase
rg "BackgroundJobInfo" --type cs framework/src/BBT.Aether.Infrastructure/ | head -20

Repository: burgan-tech/aether

Length of output: 2383


🏁 Script executed:

# Check if BackgroundJobInfo implements IHasModifyTime
rg -n "class BackgroundJobInfo" -A 5 --type cs

Repository: burgan-tech/aether

Length of output: 692


🏁 Script executed:

# Verify IClock exists and how it's used elsewhere
rg -n "interface IClock" --type cs framework/src/

Repository: burgan-tech/aether

Length of output: 144


🏁 Script executed:

# Find FullAuditedEntity definition
rg -n "class FullAuditedEntity" -A 3 --type cs

Repository: burgan-tech/aether

Length of output: 2210


🏁 Script executed:

# Check the inheritance chain - look for IHasModifyTime
rg -n "IHasModifyTime" --type cs framework/src/

Repository: burgan-tech/aether

Length of output: 1199


🏁 Script executed:

# See what FullAuditedEntity implements
rg -n "FullAuditedEntity" -B 2 -A 3 --type cs framework/src/BBT.Aether.Domain/ | head -30

Repository: burgan-tech/aether

Length of output: 3356


🏁 Script executed:

# Check what IFullAuditedObject implements/extends
rg -n "interface IFullAuditedObject" -A 3 --type cs

Repository: burgan-tech/aether

Length of output: 447


🏁 Script executed:

# Check AuditedEntity to see if it implements IHasModifyTime
rg -n "class AuditedEntity" -A 3 --type cs framework/src/BBT.Aether.Domain/

Repository: burgan-tech/aether

Length of output: 1607


🏁 Script executed:

# Check IModifyAuditedObject to understand the hierarchy
rg -n "interface IModifyAuditedObject" -A 3 --type cs

Repository: burgan-tech/aether

Length of output: 488


🏁 Script executed:

# Check IAuditedObject hierarchy
rg -n "interface IAuditedObject" -A 3 --type cs framework/src/BBT.Aether.Domain/

Repository: burgan-tech/aether

Length of output: 437


🏁 Script executed:

# Check CreationAuditedEntity to see the full chain
rg -n "class CreationAuditedEntity" -A 3 --type cs framework/src/BBT.Aether.Domain/

Repository: burgan-tech/aether

Length of output: 1132


Use IClock abstraction for consistency and testability.

Direct use of DateTime.UtcNow breaks consistency with the rest of the codebase (e.g., JobDispatcher uses injected IClock). Additionally, since BackgroundJobInfo implements IHasModifyTime through its inheritance chain (FullAuditedEntityAuditedEntityIAuditedObjectIModifyAuditedObject), the AuditInterceptor already handles setting ModifiedAt when the entity is modified—this manual assignment is redundant.

Also fix the double space before = on lines 45 and 116.

Inject IClock and use it for timestamps
 public class EfCoreJobStore<TDbContext> : IJobStore
     where TDbContext : DbContext, IHasEfCoreBackgroundJobs
 {
     private readonly TDbContext _dbContext;
+    private readonly IClock _clock;
 
-    public EfCoreJobStore(TDbContext dbContext)
+    public EfCoreJobStore(TDbContext dbContext, IClock clock)
     {
         _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
+        _clock = clock ?? throw new ArgumentNullException(nameof(clock));
     }

Then replace both occurrences:

-            jobInfo.ModifiedAt  = DateTime.UtcNow;
+            jobInfo.ModifiedAt = _clock.UtcNow;
-        job.ModifiedAt  = DateTime.UtcNow;
+        job.ModifiedAt = _clock.UtcNow;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@framework/src/BBT.Aether.Infrastructure/BBT/Aether/BackgroundJob/EfCoreJobStore.cs`
at line 45, Replace the direct DateTime.UtcNow assignment to jobInfo.ModifiedAt
in EfCoreJobStore by injecting and using the IClock abstraction (add an IClock
_clock field and constructor parameter) and remove the manual assignment
entirely because BackgroundJobInfo is audited and AuditInterceptor sets
ModifiedAt; also remove the stray double space before the '=' in the existing
assignments (the occurrences around jobInfo.ModifiedAt and the other similar
assignment) so spacing matches project style; ensure JobDispatcher usage of
IClock remains consistent with this change.

@yilmaztayfun yilmaztayfun self-assigned this Mar 12, 2026
Initialize a cached, case-insensitive _allowedContentTypes HashSet from options in the constructor and use it in IsAllowedContentType to avoid allocating a new set on each call (retains the +json suffix handling). Rewrite TruncateUtf8 to check UTF-8 byte count before truncating and use an Encoding.UTF8 Decoder to avoid splitting multi-byte characters, appending "…[TRUNCATED]" when truncated.
@yilmaztayfun yilmaztayfun merged commit 787cace into master Mar 12, 2026
4 of 5 checks passed
@sonarqubecloud
Copy link
Copy Markdown

❌ The last analysis has failed.

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Telemetry: HTTP status code, wildcard headers, and request/response logging configuration

1 participant