pinqloq 4.3.2

dotnet add package pinqloq --version 4.3.2
                    
NuGet\Install-Package pinqloq -Version 4.3.2
                    
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.3.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="pinqloq" Version="4.3.2" />
                    
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.3.2
                    
#r "nuget: pinqloq, 4.3.2"
                    
#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.3.2
                    
#: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.3.2
                    
Install as a Cake Addin
#tool nuget:?package=pinqloq&version=4.3.2
                    
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
  • Correlation id read from the caller's header, falling back to the trace id
  • 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 fills the fixed Event and Path fields (the panel title and the request path) and captures the HTTP method 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 pinqloqClient)
{
    public void CreateOrder(Order order)
    {
        pinqloqClient.Enqueue(new PinqloqLogEntry
        {
            Event = "order.created",
            DeviceIdentifier = order.CustomerId.ToString(),
            LogLevel = PinqloqLogLevel.Information,
            LogSourceType = PinqloqLogSourceType.Backend,
            Metadata = new()
            {
                ["orderId"] = order.Id.ToString()
            }
        });
    }
}

Event is required on every entry and is used as the log title. The request-logging middleware fills it automatically. Path (the request path) is filled by the middleware too, but stays null on a manual entry unless you set it.

DeviceIdentifier is optional. Leave it unset on an entry to inherit the global PinqloqOptions.DeviceIdentifier. If neither is set, the log is stored without one.

Add Request Metadata

By default the middleware reads the optional DeviceIdentifier from the device-identifier request header. Override how it is resolved with SetDeviceIdentifier (a claim, trace id, a different header, …). The override wins. 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 log is stored without a DeviceIdentifier.

To attach searchable request metadata, replace the middleware call from Quick Start with the following configuration. Use AddMetadata for searchable values such as user and tenant IDs. Use AddDetail for additional drill-down information. The Event field (the panel title) defaults to "{method} {path}" and can be overridden with AddMetadata("event", …) — the override lands on the entry's Event field, not in Metadata.

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);
});

Correlation ID

Every log carries a CorrelationId that ties together the records of a single request or flow. The request-logging middleware fills it with no configuration:

  1. The caller's correlation-id request header, when present (case-insensitive).
  2. Otherwise HttpContext.TraceIdentifier.

A caller that already propagates a correlation id keeps it across services.

On a manual Enqueue the field is optional and stays null unless you set it:

pinqloqClient.Enqueue(new PinqloqLogEntry
{
    Event = "order.created",
    DeviceIdentifier = order.CustomerId.ToString(),
    CorrelationId = currentCorrelationId
});

Running your own request-logging middleware? Read the same header through the exposed constant rather than hardcoding it:

var header = context.Request.Headers[PinqloqRequestLoggingMiddleware.CorrelationIdHeaderName]
    .ToString();

entry.CorrelationId = string.IsNullOrWhiteSpace(header)
    ? context.TraceIdentifier
    : header;

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 (1)

Showing the top 1 NuGet packages that depend on pinqloq:

Package Downloads
Pinqponq.Observability

Pinqloq logging for ASP.NET Core: one structured record per request (redacted request/response bodies, route-based event names, error detail from Pinqponq.ErrorHandling), and an ILogService for application and job logs with stack trace trimming.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
4.3.2 197 9/14/2026
4.3.1 120 9/9/2026
4.3.0 874 9/8/2026
4.2.0 458 9/1/2026
4.1.0 224 8/25/2026
4.0.3 253 8/6/2026
4.0.2 343 7/22/2026
4.0.1 129 7/21/2026
4.0.0 127 7/20/2026
3.0.0 125 7/14/2026
2.0.8 154 7/14/2026
2.0.7 285 7/6/2026
2.0.6 120 7/6/2026
2.0.5 189 7/6/2026
2.0.4 130 7/6/2026
2.0.3 177 7/2/2026
2.0.2 131 7/2/2026
2.0.1 139 7/1/2026
2.0.0 197 6/23/2026
1.0.2 133 6/19/2026
Loading failed

4.3.2 DeviceIdentifier is now optional. PinqloqLogEntry.DeviceIdentifier is now string? and defaults to null. The request-logging middleware no longer rejects a request with HTTP 400 when no DeviceIdentifier resolves; the log is sent without one. Manual Enqueue no longer throws InvalidOperationException when an entry has no DeviceIdentifier and no global fallback is set. The ingest API accepts logs without a DeviceIdentifier and stores null. The resolution order is unchanged: SetDeviceIdentifier, then the "device-identifier" header, then PinqloqOptions.DeviceIdentifier. No consumer action needed. 4.3.1 Adds mcp-server and model-context-protocol to the NuGet package tags, now that Pinqloq's MCP server has shipped. No code changes; no consumer action needed. 4.3.0 Adds PinqloqLogEntry.Path, a fixed, indexed field for the request path (see pinqponq/pinqloq#108); the request-logging middleware fills it automatically, a manual Enqueue leaves it null unless set explicitly. CHANGED: Event and Path are no longer duplicated into Metadata — the request-logging middleware used to also write metadata["event"], metadata["path"] and metadata["RequestPath"]; those keys are gone from newly-sent logs (Event and Path travel as their own fields now). AddMetadata("event", ...) still overrides the event title, it just no longer leaves a copy in Metadata. If you built a saved filter or dashboard on metadata.event, metadata.path or metadata.RequestPath, switch it to the Event/Path fields directly. No other consumer action needed. 4.2.0 Points the embedded ingest base address at https://pinqloq-external-api.pinqponq.io/ (was https://log-panel-backend.thedeveng.com/). Internal to the SDK, not configurable from outside; no consumer action needed. 4.1.0 Adds CorrelationId, which ties logs from the same request or flow together. The request-logging middleware fills it automatically: the caller's "correlation-id" request header wins (case-insensitive), and when that header is absent it falls back to HttpContext.TraceIdentifier. The header name is exposed as PinqloqRequestLoggingMiddleware.CorrelationIdHeaderName so consumers running their own request-logging middleware can read the same header. On a manual Enqueue the field stays null unless set explicitly. The wire contract carries correlationId and the ingest API stores it on the log document. No migration needed: the field is optional and existing code is unaffected. 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).