pinqloq 4.3.2
dotnet add package pinqloq --version 4.3.2
NuGet\Install-Package pinqloq -Version 4.3.2
<PackageReference Include="pinqloq" Version="4.3.2" />
<PackageVersion Include="pinqloq" Version="4.3.2" />
<PackageReference Include="pinqloq" />
paket add pinqloq --version 4.3.2
#r "nuget: pinqloq, 4.3.2"
#:package pinqloq@4.3.2
#addin nuget:?package=pinqloq&version=4.3.2
#tool nuget:?package=pinqloq&version=4.3.2
Pinqloq
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:
- The caller's
correlation-idrequest header, when present (case-insensitive). - 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 inInputJson/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 inInputJson,OutputJson,RequestHeaders, andResponseHeadersfor 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 | Versions 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. |
-
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 |
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).