pinqloq 4.0.3

dotnet add package pinqloq --version 4.0.3
                    
NuGet\Install-Package pinqloq -Version 4.0.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="pinqloq" Version="4.0.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="pinqloq" Version="4.0.3" />
                    
Directory.Packages.props
<PackageReference Include="pinqloq" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add pinqloq --version 4.0.3
                    
#r "nuget: pinqloq, 4.0.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package pinqloq@4.0.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=pinqloq&version=4.0.3
                    
Install as a Cake Addin
#tool nuget:?package=pinqloq&version=4.0.3
                    
Install as a Cake Tool

Pinqloq

NuGet Downloads License: MIT

Pinqloq is an ASP.NET Core structured logging and log shipping SDK for centralized application logs. It captures HTTP request/response logs through middleware and sends manual application events to the Pinqloq log management platform using in-memory buffering, batching, and HTTPS delivery.

Features

  • Automatic ASP.NET Core request logging
  • Attribute-based redaction of sensitive fields and endpoints
  • Manual structured application events
  • Buffered and batched HTTPS delivery
  • No customer-managed message broker or token refresh flow
  • Graceful shutdown flush

Requirements

  • .NET 8.0 or later
  • A Pinqloq project and secret key

Installation

dotnet add package pinqloq

Quick Start

Store your secret key in configuration, an environment variable, or a secret manager. Do not hardcode production credentials.

{
  "Pinqloq": {
    "SecretKey": "your-secret-key",
    "ApiLogsCollectionName": "myapp_api_logs",
    "DeviceIdentifier": "myapp-instance-1"
  }
}

Register Pinqloq and enable automatic request logging:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddPinqloq(options =>
{
    options.SecretKey =
        builder.Configuration["Pinqloq:SecretKey"]
        ?? throw new InvalidOperationException("Pinqloq secret key is missing.");

    options.ApiLogsCollectionName =
        builder.Configuration["Pinqloq:ApiLogsCollectionName"];

    options.DeviceIdentifier =
        builder.Configuration["Pinqloq:DeviceIdentifier"];
});

var app = builder.Build();

app.UsePinqloqRequestLogging(options =>
{
    options.ExcludePaths("/health", "/swagger");
});

app.MapControllers();
app.Run();

The middleware captures the HTTP method, path, and status code as searchable metadata. The request body, response body, request headers, and response headers go to the log detail as InputJson, OutputJson, RequestHeaders, and ResponseHeaders. Bodies are truncated at 32 KB.

Manual Logging

Inject IPinqloqLogger to send structured application events:

public class OrderService(IPinqloqLogger logger)
{
    public void CreateOrder(Order order)
    {
        logger.Enqueue(new PinqloqLogEntry
        {
            Event = "order.created",
            DeviceIdentifier = order.CustomerId.ToString(),
            LogLevel = PinqloqLogLevel.Information,
            LogSourceType = PinqloqLogSourceType.Backend,
            Metadata = new()
            {
                ["orderId"] = order.Id.ToString()
            }
        });
    }
}

Event and DeviceIdentifier are required on every entry. Event is used as the log title; both are created automatically by the request-logging middleware. Leave DeviceIdentifier unset on an entry to inherit the global PinqloqOptions.DeviceIdentifier. Enqueue throws an InvalidOperationException if an entry has no DeviceIdentifier and no global PinqloqOptions.DeviceIdentifier is set — a missing required field fails loudly rather than being silently dropped.

Add Request Metadata

By default the middleware reads the required DeviceIdentifier from the device-identifier request header automatically — no configuration needed as long as callers send that header. Override how it is resolved with SetDeviceIdentifier (a claim, trace id, a different header, …); the override wins, and if it returns null/empty the middleware falls back to the device-identifier header, then to the global PinqloqOptions.DeviceIdentifier. If none of these resolve a value, the middleware rejects the request with HTTP 400 before it runs (rather than silently dropping the log).

To attach searchable request metadata, replace the middleware call from Quick Start with the following configuration. Use AddMetadata for searchable values such as user, tenant, and correlation IDs. Use AddDetail for additional drill-down information. The event key (the panel title) defaults to "{method} {path}" and can be overridden with AddMetadata("event", …).

app.UsePinqloqRequestLogging(options =>
{
    options.ExcludePaths("/health", "/swagger");

    options.SetDeviceIdentifier(context =>
        context.User.FindFirst("sub")?.Value);

    options.SetAppVersionName(context =>
        context.Request.Headers["X-App-Version"].ToString());

    options.AddMetadata("userId", context =>
        context.User.FindFirst("sub")?.Value);

    options.AddMetadata("correlationId", context =>
        context.TraceIdentifier);
});

Redacting Sensitive Values

Request and response bodies and headers may contain credentials, tokens, or personal information. Mask them at the source with two attributes, both MVC-controller-only (they resolve through ControllerActionDescriptor, so minimal API endpoints are not scanned):

  • [PinqloqRedact] on a DTO property masks just that property's value with *****REDACTED***** wherever it appears in InputJson/OutputJson — the property can belong to the action's parameter type, its return type, or anything nested inside them (nested objects, collections).
  • [PinqloqRedactEndpoint] on an action (or on the controller class, covering every action in it) masks every value in InputJson, OutputJson, RequestHeaders, and ResponseHeaders for that endpoint, keeping the JSON structure and header names intact.
public class AuthResponse
{
    [PinqloqRedact]
    public string Token { get; set; } = "";

    public string UserId { get; set; } = "";
}

[ApiController]
[Route("api/user")]
public class UserController : ControllerBase
{
    [HttpPost("Auth")]
    public AuthResponse Auth([FromBody] AuthRequest request) => ...;

    [PinqloqRedactEndpoint]
    [HttpPost("Payment")]
    public IActionResult Payment([FromBody] PaymentRequest request) => ...;
}

Logs Auth produces:

{ "token": "*****REDACTED*****", "userId": "64" }

Logs Payment produces (every value masked, including headers):

{ "cardNumber": "*****REDACTED*****", "amount": "*****REDACTED*****" }

[PinqloqRedact] only affects properties reachable from a type the middleware can see through reflection (the action's parameter and return types). It has no effect on serialization — the real value still goes out on the wire; only the log entry is masked. A non-JSON body (form data, plain text) with [PinqloqRedactEndpoint] applied is logged as a single masked string, since there is no JSON structure to preserve.

Using redaction from your own middleware

If you have your own request-logging middleware instead of UsePinqloqRequestLogging, call PinqloqRedaction.Resolve(HttpContext) directly — it runs the same reflection (cached per action) and returns a PinqloqRedactionPlan:

var plan = PinqloqRedaction.Resolve(httpContext);

// RedactAll is true when [PinqloqRedactEndpoint] applies; ShouldRedact covers both
// RedactAll and any [PinqloqRedact]-marked property name (case-insensitive).
bool isSensitive = existingSensitiveKeyCheck(fieldKey) || plan.ShouldRedact(fieldKey);

Resolve it once per request (it's endpoint-dependent, not field-dependent) and reuse the same PinqloqRedactionPlan for every field you check — plan.HasRedactions tells you upfront whether there's anything to mask at all, so you can skip the check entirely when it's false. ShouldRedact only knows about names (JSON property names and, for RedactAll, everything); it does not itself walk JSON — you still own how bodies/headers are traversed.

Security and Reliability

Redaction is opt-in and reflection-based — it protects only fields you've marked. Exclude sensitive endpoints entirely with ExcludePaths if you'd rather not log them at all.

Logs are buffered in memory and sent in batches. Buffered logs may be lost if the process is terminated without a graceful shutdown.

Delivery failures are reported through onFailed callbacks and, even without callbacks, as throttled entries in your application log — never silently discarded, but also never blocking. If your secret key is authorized for more than one collection, set ApiLogsCollectionName (or a per-entry CollectionName); otherwise the batch is rejected.

Documentation

License

MIT

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
4.0.3 142 8/6/2026
4.0.2 306 7/22/2026
4.0.1 105 7/21/2026
4.0.0 105 7/20/2026
3.0.0 103 7/14/2026
2.0.8 110 7/14/2026
2.0.7 263 7/6/2026
2.0.6 103 7/6/2026
2.0.5 159 7/6/2026
2.0.4 108 7/6/2026
2.0.3 156 7/2/2026
2.0.2 108 7/2/2026
2.0.1 118 7/1/2026
2.0.0 158 6/23/2026
1.0.2 114 6/19/2026
1.0.1 186 6/12/2026
1.0.0 109 6/11/2026

4.0.3 BREAKING: Identifier renamed to DeviceIdentifier across PinqloqLogEntry, PinqloqOptions, and the wire contract. SetIdentifier is now SetDeviceIdentifier; the auto-read request header is now "device-identifier" (was "identifier"). Update entry.Identifier to entry.DeviceIdentifier, options.Identifier to options.DeviceIdentifier, and SetIdentifier(...) to SetDeviceIdentifier(...). Also BREAKING: the request-logging middleware now writes a fixed Detail set — InputJson, OutputJson, RequestHeaders, ResponseHeaders. The RequestMethod and ResponseCode detail keys were removed; read metadata["method"] and metadata["statusCode"] instead. Request and response headers are now captured automatically. Added [PinqloqRedact] (property-level) and [PinqloqRedactEndpoint] (action/controller-level) attributes to mask sensitive values with *****REDACTED***** in logs; both resolve via MVC's ControllerActionDescriptor, so minimal API endpoints are not scanned. BREAKING: a missing DeviceIdentifier now fails loudly instead of being silently dropped. The request-logging middleware resolves DeviceIdentifier as SetDeviceIdentifier -> "device-identifier" header -> PinqloqOptions.DeviceIdentifier and, if none yield a value, rejects the request with HTTP 400. Manual Enqueue throws InvalidOperationException when the entry has no DeviceIdentifier and no global fallback is set. Guarantee a value with SetDeviceIdentifier(ctx => ctx.Request.Headers["device-identifier"].FirstOrDefault() ?? Environment.MachineName) or PinqloqOptions.DeviceIdentifier. 4.0.1: AppVersionName sent as null (not "") when unset. 4.0.0 BREAKING: DeviceUid renamed to Identifier and made required — the ingest API rejects logs with an empty Identifier (single log to 400, batch entry skipped). Populate it per log via PinqloqLogEntry.Identifier, globally via PinqloqOptions.Identifier, or per request. The request-logging middleware now reads the "identifier" request header automatically; SetIdentifier overrides it and falls back to the header then to PinqloqOptions.Identifier. Also adds SetAppVersionName to override AppVersionName per request. The package now multi-targets net8.0, net9.0, and net10.0 (previously net9.0 only).