fix(auditlog): capture request/response payloads on outbound API audit rows
The outbound ApiCall emitter hard-coded RequestSummary/ResponseSummary to null, so audited API calls carried no inputs/outputs — contrary to the Audit Log payload-capture spec. Thread the call arguments into the sync ApiCall emitter and the cached immediate-completion path (CachedSubmit / ApiCallCached / CachedResolve), and stamp the response body from ExternalCallResult.ResponseJson. The writer's payload filter still applies the size cap + redaction downstream. The S&F retry-loop cached rows are unchanged — request data is not threaded through the store-and-forward buffer (same boundary as SourceScript).
This commit is contained in:
@@ -420,7 +420,7 @@ public class ScriptRuntimeContext
|
||||
{
|
||||
var elapsedMs = (int)((Stopwatch.GetTimestamp() - startTicks)
|
||||
* 1000d / Stopwatch.Frequency);
|
||||
EmitCallAudit(systemName, methodName, occurredAtUtc, elapsedMs, result, thrown);
|
||||
EmitCallAudit(systemName, methodName, occurredAtUtc, elapsedMs, result, thrown, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,7 +458,7 @@ public class ScriptRuntimeContext
|
||||
// Submitted row even if the immediate-delivery attempt happens to
|
||||
// resolve before this method returns.
|
||||
await EmitCachedSubmitTelemetryAsync(
|
||||
systemName, methodName, target, trackedId, occurredAtUtc, cancellationToken)
|
||||
systemName, methodName, target, trackedId, occurredAtUtc, parameters, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// Hand off to the existing cached-call path. The TrackedOperationId
|
||||
@@ -503,7 +503,7 @@ public class ScriptRuntimeContext
|
||||
if (result is { WasBuffered: false })
|
||||
{
|
||||
await EmitImmediateTerminalTelemetryAsync(
|
||||
systemName, methodName, target, trackedId, result, cancellationToken)
|
||||
systemName, methodName, target, trackedId, result, parameters, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -521,6 +521,7 @@ public class ScriptRuntimeContext
|
||||
string target,
|
||||
TrackedOperationId trackedId,
|
||||
DateTime occurredAtUtc,
|
||||
IReadOnlyDictionary<string, object?>? parameters,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (_cachedForwarder == null)
|
||||
@@ -544,6 +545,8 @@ public class ScriptRuntimeContext
|
||||
SourceScript = _sourceScript,
|
||||
Target = target,
|
||||
Status = AuditStatus.Submitted,
|
||||
// Submit precedes the call — request args only, no response yet.
|
||||
RequestSummary = SerializeRequest(parameters),
|
||||
ForwardState = AuditForwardState.Pending,
|
||||
},
|
||||
Operational: new SiteCallOperational(
|
||||
@@ -599,6 +602,7 @@ public class ScriptRuntimeContext
|
||||
string target,
|
||||
TrackedOperationId trackedId,
|
||||
ExternalCallResult result,
|
||||
IReadOnlyDictionary<string, object?>? parameters,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (_cachedForwarder == null)
|
||||
@@ -653,6 +657,8 @@ public class ScriptRuntimeContext
|
||||
Status = AuditStatus.Attempted,
|
||||
HttpStatus = httpStatus,
|
||||
ErrorMessage = result.Success ? null : result.ErrorMessage,
|
||||
RequestSummary = SerializeRequest(parameters),
|
||||
ResponseSummary = result.ResponseJson,
|
||||
ForwardState = AuditForwardState.Pending,
|
||||
},
|
||||
Operational: new SiteCallOperational(
|
||||
@@ -712,6 +718,8 @@ public class ScriptRuntimeContext
|
||||
Status = auditTerminalStatus,
|
||||
HttpStatus = httpStatus,
|
||||
ErrorMessage = result.Success ? null : result.ErrorMessage,
|
||||
RequestSummary = SerializeRequest(parameters),
|
||||
ResponseSummary = result.ResponseJson,
|
||||
ForwardState = AuditForwardState.Pending,
|
||||
},
|
||||
Operational: new SiteCallOperational(
|
||||
@@ -762,7 +770,8 @@ public class ScriptRuntimeContext
|
||||
DateTime occurredAtUtc,
|
||||
int durationMs,
|
||||
ExternalCallResult? result,
|
||||
Exception? thrown)
|
||||
Exception? thrown,
|
||||
IReadOnlyDictionary<string, object?>? parameters)
|
||||
{
|
||||
if (_auditWriter == null)
|
||||
{
|
||||
@@ -772,7 +781,8 @@ public class ScriptRuntimeContext
|
||||
AuditEvent evt;
|
||||
try
|
||||
{
|
||||
evt = BuildCallAuditEvent(systemName, methodName, occurredAtUtc, durationMs, result, thrown);
|
||||
evt = BuildCallAuditEvent(
|
||||
systemName, methodName, occurredAtUtc, durationMs, result, thrown, parameters);
|
||||
}
|
||||
catch (Exception buildEx)
|
||||
{
|
||||
@@ -828,7 +838,8 @@ public class ScriptRuntimeContext
|
||||
DateTime occurredAtUtc,
|
||||
int durationMs,
|
||||
ExternalCallResult? result,
|
||||
Exception? thrown)
|
||||
Exception? thrown,
|
||||
IReadOnlyDictionary<string, object?>? parameters)
|
||||
{
|
||||
// Status: Delivered on a Success result; Failed otherwise (the
|
||||
// ExternalSystemClient already maps HTTP non-2xx + transient
|
||||
@@ -885,13 +896,41 @@ public class ScriptRuntimeContext
|
||||
DurationMs = durationMs,
|
||||
ErrorMessage = errorMessage,
|
||||
ErrorDetail = errorDetail,
|
||||
RequestSummary = null,
|
||||
ResponseSummary = null,
|
||||
// Payload capture: the request arguments and the response body.
|
||||
// The audit writer's payload filter applies the configured size
|
||||
// cap and header/secret redaction downstream — the emitter just
|
||||
// hands over the raw values.
|
||||
RequestSummary = SerializeRequest(parameters),
|
||||
ResponseSummary = result?.ResponseJson,
|
||||
PayloadTruncated = false,
|
||||
Extra = null,
|
||||
ForwardState = AuditForwardState.Pending,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialises the outbound-call argument dictionary into the JSON
|
||||
/// <c>RequestSummary</c> stamped on <c>ApiOutbound</c> audit rows.
|
||||
/// Returns <c>null</c> for a null/empty argument set. Serialization
|
||||
/// failure is swallowed (returns <c>null</c>) — a payload that cannot be
|
||||
/// summarised must never abort the best-effort audit emission.
|
||||
/// </summary>
|
||||
private static string? SerializeRequest(IReadOnlyDictionary<string, object?>? parameters)
|
||||
{
|
||||
if (parameters is null || parameters.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Serialize(parameters);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user