Class S3AuditSink
- All Implemented Interfaces:
McpAuditSink
Select it with MCP_AUDIT_SINK=com.mcpdbwizard.pub.S3AuditSink.
MCP_AUDIT_S3_BUCKET | Required. |
MCP_AUDIT_S3_PREFIX | Key prefix, default "mcp-audit". |
MCP_AUDIT_S3_REGION | Region. The SDK's own resolution if unset. |
MCP_AUDIT_S3_ENDPOINT | Override, for MinIO or a VPC endpoint. |
MCP_AUDIT_S3_ROLL_BYTES | Roll an object at this size, default 8388608L. |
MCP_AUDIT_S3_ROLL_SECONDS | ...or after this long, default 300L. |
An object store is not a stream, so this batches
A PUT per tool call would be slow and, at S3's per-request pricing, absurd. Records accumulate and become one object when it is big enough or old enough.
Keys are date-partitioned —
<prefix>/<config>/yyyy/MM/dd/<epochMillis>-<uuid>.jsonl — for two concrete reasons rather
than tidiness. A lifecycle rule can expire a prefix, which is how retention is done on the S3
side; and Athena or Glue can partition on the date without reading the objects.
The roll timer is not optional
Rolling only when a record arrives would mean a quiet server holds its last few records in
memory indefinitely, and loses them if the process stops. A daemon thread rolls on schedule so the
window in which a record exists only in memory is bounded by MCP_AUDIT_S3_ROLL_SECONDS
rather than by how busy the server happens to be.
Records in that window are in memory only. SpoolingAuditSink in front is what
closes it: it writes to disk before this ever sees a record, and deletes only once
flush() confirms the PUT.
Copyright 2003-2026 ATB Consultancy Services Ltd
(formerly Orinda Software Ltd, Dublin, Ireland)
-
Field Summary
Fields -
Constructor Summary
ConstructorsConstructorDescriptionBuilt reflectively byMcpAuditSinks.fromEnvironment().S3AuditSink(software.amazon.awssdk.services.s3.S3Client theClientValue, String theBucketValue, String thePrefixValue, String theConfigValue, long theRollBytesValue, long theRollSecondsValue) For tests: inject a client rather than reach AWS. -
Method Summary
Modifier and TypeMethodDescriptionvoidclose()Roll what is left, then release the client.describe()A short human description of what this sink is, for an operator reading a status page.booleanflush()Confirm that everything handed toMcpAuditSink.record(com.mcpdbwizard.pub.McpAuditEvent)since the last flush is durably accepted.longHow many records were confirmed delivered, or -1 when the sink does not report.longHow many records this sink is known to have LOST, or -1 when it does not report.longHow many records are written down but not yet delivered, or -1 when not reported.voidrecord(McpAuditEvent theEvent) Record one call.voidRoll if the pending batch is old enough.voidStart the scheduled roll.
-
Field Details
-
BUCKET_VARIABLE
- See Also:
-
PREFIX_VARIABLE
- See Also:
-
REGION_VARIABLE
- See Also:
-
ENDPOINT_VARIABLE
- See Also:
-
ROLL_BYTES_VARIABLE
- See Also:
-
ROLL_SECONDS_VARIABLE
- See Also:
-
DEFAULT_PREFIX
- See Also:
-
DEFAULT_ROLL_BYTES
public static final long DEFAULT_ROLL_BYTES- See Also:
-
DEFAULT_ROLL_SECONDS
public static final long DEFAULT_ROLL_SECONDS- See Also:
-
-
Constructor Details
-
S3AuditSink
public S3AuditSink()Built reflectively byMcpAuditSinks.fromEnvironment(). -
S3AuditSink
-
-
Method Details
-
startRolling
public void startRolling()Start the scheduled roll. Separate from the constructor so a test can roll deterministically. -
record
Record one call. Must not throw.Never throws.
- Specified by:
recordin interfaceMcpAuditSink- Parameters:
theEvent- the call to record; never null
-
rollIfDue
public void rollIfDue()Roll if the pending batch is old enough. Called by the timer, and testable on its own. -
flush
public boolean flush()Confirm that everything handed toMcpAuditSink.record(com.mcpdbwizard.pub.McpAuditEvent)since the last flush is durably accepted.This is what makes a spool possible. A spool writes each record to disk first and may only delete it once the sink has genuinely taken it — and for an asynchronous sink like Kafka,
recordreturning tells you nothing, because the send has not completed yet.The default returns true, which is correct for a sink that delivers synchronously inside
record. An asynchronous sink must override it, and must return false if anything since the last flush was lost — returning true on a failed batch would let a spool delete records that never arrived, which is the one way a spool can be worse than no spool.Forces a roll, then reports whether anything has been lost since the previous flush. Compared against the count at the END OF THE PREVIOUS FLUSH — the rule
KafkaAuditSink.flush()sets out, and it applies here for the same reason: a batch can be rejected insiderecord(com.mcpdbwizard.pub.McpAuditEvent)when it fills, before any flush begins.- Specified by:
flushin interfaceMcpAuditSink- Returns:
- true if everything since the last flush is safely delivered
-
getDroppedCount
public long getDroppedCount()Description copied from interface:McpAuditSinkHow many records this sink is known to have LOST, or -1 when it does not report.The number an operator actually needs, and the one nothing surfaced before: a trail is only worth citing if it is complete, and "complete" is exactly what a drop count denies. Reported through the SPI rather than by casting to a particular sink so a status page keeps working when the sink is swapped.
-1 means "this sink does not count", which a caller must show differently from 0. Claiming zero losses on a sink that cannot tell is the one wrong answer here.
- Specified by:
getDroppedCountin interfaceMcpAuditSink
-
getDeliveredCount
public long getDeliveredCount()Description copied from interface:McpAuditSinkHow many records were confirmed delivered, or -1 when the sink does not report.- Specified by:
getDeliveredCountin interfaceMcpAuditSink
-
getPendingCount
public long getPendingCount()Description copied from interface:McpAuditSinkHow many records are written down but not yet delivered, or -1 when not reported.- Specified by:
getPendingCountin interfaceMcpAuditSink
-
describe
Description copied from interface:McpAuditSinkA short human description of what this sink is, for an operator reading a status page.Defaults to the class name. A wrapper overrides it to name what it wraps — otherwise a spooled Kafka sink reports only "SpoolingAuditSink", and where the records finally go is the part being asked about.
- Specified by:
describein interfaceMcpAuditSink
-
close
public void close()Roll what is left, then release the client.- Specified by:
closein interfaceMcpAuditSink
-