Class FileAuditSink

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

public final class FileAuditSink extends Object implements McpAuditSink
Keeps the audit trail on this machine for a bounded window, and deletes it when the window passes.

This is the record every tier gets. A licence buys a longer window and the right to send the records somewhere else; it does not buy the existence of a record, because a product sold on knowing what an agent did cannot have a free tier that knows nothing.

The same file mechanics as the spool, the opposite lifecycle

SpoolingAuditSink also writes rolling JSONL segments, and the resemblance is deliberate. But a spool is a delivery queue: a segment exists until a delegate confirms it, and is then deleted as soon as possible. This is a retention store: a segment exists until it is old enough to delete, and there is no delegate to confirm anything. They disagree about the one thing that decides when a file may be removed, which is why this is a sibling rather than a subclass.

Do not wrap this in a spool. Spooling a sink whose delivery mechanism is "write it to disk" produces two copies of the same bytes, one of which is deleted the moment the other is written. McpAuditSinks composes it directly for that reason.

What "retention" costs, and what it does not promise

Records that age out are gone. That is the feature, not a limitation: a store that quietly kept everything would be a worse position to be in than one that deletes on a schedule, because the retention window is what makes it defensible under a storage-limitation rule.

The window is enforced at segment granularity, so a record may outlive it by up to one segment. Segments roll on size, so a quiet server holds its oldest records longer than a busy one. Nothing here should be relied on to delete a specific record at a specific minute.

MCP_AUDIT_RETENTION_DAYS=0 means no trail at all — this sink is not built. See fromEnvironment(String).

Two ways records are lost, and only one of them counts

Ageing out is the policy working and is not counted as a drop. Being evicted early because MCP_AUDIT_FILE_MAX_BYTES was reached is a drop: those records were inside the window an operator was promised and are gone anyway, which is exactly what getDroppedCount() exists to say. Copyright 2003-2026 ATB Consultancy Services Ltd (formerly Orinda Software Ltd, Dublin, Ireland)

  • Field Details

  • Constructor Details

    • FileAuditSink

      public FileAuditSink(Path theDirectoryValue, int theRetentionDaysValue, long theMaxBytesValue, long theSegmentBytesValue)
  • Method Details

    • fromEnvironment

      public static FileAuditSink fromEnvironment(String theSubdirectoryValue)
      Build from the environment, or null when no local trail is wanted.

      Returns null in two cases, and the difference matters to nobody but is worth stating: no DIRECTORY_VARIABLE means the deployment never asked for a trail, and MCP_AUDIT_RETENTION_DAYS=0 means it asked for one and then asked to keep nothing. Zero is a supported, deliberate setting — the deployment that streams every record to a SIEM and wants no production data resting on this box.

      Zero is never inferred. An unset window is one day, and a window that cannot be parsed stops start-up. Nothing here silently decides to keep no records.

      Parameters:
      theSubdirectoryValue - a per-process directory name, or null; a trail directory tolerates exactly one writer, for the same reason a spool does
    • fromEnvironment

      public static FileAuditSink fromEnvironment(String theSubdirectoryValue, int theCeilingDays)
      As fromEnvironment(String), with a hard ceiling on the window.

      The ceiling exists because a process cannot always be trusted with its own environment. A generated server is launched by something that sets its variables deliberately; the web application reads the variables an operator gave it, so a window it is not entitled to has to be cut down here rather than believed.

      Deliberately an int, not a licence. This module is the Apache-2.0 half and must carry no licensing logic; the caller works out what the ceiling is and passes a number.

      Parameters:
      theCeilingDays - the largest window to honour, whatever the environment asks for
    • record

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

      Appends to the trail. Never throws.

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

      public static long openedAt(File theSegment)
      When this segment was closed, from its name, falling back to the file's own timestamp.

      The fallback matters: a file someone copied, renamed or restored from a backup would otherwise be treated as having been opened at the epoch and deleted on the next prune.

    • oldestRecordMillis

      public long oldestRecordMillis()
      How far back the trail actually goes, in millis since the epoch, or -1 when it is empty.
    • getRetentionDays

      public int getRetentionDays()
      The configured window, in days.
    • getDirectory

      public Path getDirectory()
      Where the trail is, so a status page can name it.
    • getDroppedCount

      public long getDroppedCount()
      Records evicted before their window expired, or lost to a write failure.

      Deliberately NOT counting records that simply aged out. Those left on schedule, and reporting them as losses would make a healthy trail permanently look broken.

      Specified by:
      getDroppedCount in interface McpAuditSink
    • getDeliveredCount

      public long getDeliveredCount()
      Records written. For this sink, written is delivered.
      Specified by:
      getDeliveredCount in interface McpAuditSink
    • getPendingCount

      public long getPendingCount()
      Always zero: there is no delivery step to be behind on.
      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()
      Close the active segment so the trail on disk is complete, and say what was lost.
      Specified by:
      close in interface McpAuditSink