Class SplunkAuditSink

java.lang.Object
com.mcpdbwizard.pub.SplunkAuditSink
All Implemented Interfaces:
McpAuditSink

public class SplunkAuditSink extends Object implements McpAuditSink
Sends audit records to Splunk's HTTP Event Collector.

Select it with MCP_AUDIT_SINK=com.mcpdbwizard.pub.SplunkAuditSink. Like the syslog sink it needs no library — HEC is an HTTP POST, and the JDK has had a client since 11.

Environment
MCP_AUDIT_SPLUNK_URLRequired. The collector's base URL, e.g. https://splunk.example.com:8088. A URL already ending in /services/collector/event is taken as given.
MCP_AUDIT_SPLUNK_TOKEN_FILEFile holding the HEC token. Preferred.
MCP_AUDIT_SPLUNK_TOKENThe token itself, if a file is impossible.
MCP_AUDIT_SPLUNK_INDEXTarget index. Splunk's default if unset.
MCP_AUDIT_SPLUNK_SOURCETYPEDefault "mcpdbwizard:audit".
MCP_AUDIT_SPLUNK_BATCH_SIZERecords per POST, default 50.
MCP_AUDIT_SPLUNK_TIMEOUT_MSPer-request timeout, default 10000.

The token comes from a file by preference

Both spellings work, and the file is the one to use. A token in an environment variable is readable by anything that can see the process, appears in docker inspect and in whatever orchestrator holds the task definition, and tends to end up in a repository. The file form takes a mounted secret, and the token is read once at start-up rather than held anywhere it can be printed.

Batching, and what flush() therefore guarantees

Records accumulate and are POSTed when the batch fills or when flush() is called — HEC accepts several events in one request, and a POST per tool call would make the audit trail the slowest thing in the system.

flush() sends whatever is pending, waits for the response, and reports honestly. A batch Splunk rejects counts as lost, and the records are not retried here: retrying is what SpoolingAuditSink is for, and it can only do it if this tells the truth. Saying "delivered" about a rejected batch would let the spool delete records Splunk never took. Copyright 2003-2026 ATB Consultancy Services Ltd (formerly Orinda Software Ltd, Dublin, Ireland)

  • Field Details

  • Constructor Details

    • SplunkAuditSink

      public SplunkAuditSink()
      Built reflectively by McpAuditSinks.fromEnvironment().
    • SplunkAuditSink

      public SplunkAuditSink(URI theEndpointValue, String theTokenValue, String theIndexValue, String theSourceTypeValue, int theBatchSizeValue, int theTimeoutMillis, HttpClient theClientValue)
      Parameters:
      theClientValue - an HTTP client, or null to build one — the seam a test uses so no Splunk is needed
  • Method Details

    • record

      public void record(McpAuditEvent theEvent)
      Record one call. Must not throw.

      Never throws.

      Specified by:
      record in interface McpAuditSink
      Parameters:
      theEvent - the call to record; never null
    • flush

      public boolean flush()
      Confirm that everything handed to McpAuditSink.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, record returning 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.

      Posts what is pending, then reports whether anything has been lost since the previous flush. Compared against the count at the END OF THE PREVIOUS FLUSH, as KafkaAuditSink.flush() explains: a batch can be rejected inside record(com.mcpdbwizard.pub.McpAuditEvent) when it fills, long before any flush begins, and sampling at the top of this method would not see it — which is exactly how a spool comes to delete records that never arrived.

      Specified by:
      flush in interface McpAuditSink
      Returns:
      true if everything since the last flush is safely delivered
    • getDroppedCount

      public long getDroppedCount()
      Description copied from interface: McpAuditSink
      How 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:
      getDroppedCount in interface McpAuditSink
    • getDeliveredCount

      public long getDeliveredCount()
      Description copied from interface: McpAuditSink
      How many records were confirmed delivered, or -1 when the sink does not report.
      Specified by:
      getDeliveredCount in interface McpAuditSink
    • getPendingCount

      public long getPendingCount()
      Description copied from interface: McpAuditSink
      How many records are written down but not yet delivered, or -1 when not reported.
      Specified by:
      getPendingCount in interface McpAuditSink
    • describe

      public String describe()
      Description copied from interface: McpAuditSink
      A 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:
      describe in interface McpAuditSink
    • close

      public void close()
      Send what is left before the process goes away.
      Specified by:
      close in interface McpAuditSink