Skip to Content
GovernanceAudit Logging

Audit Logging

Verify your agents log actions for accountability and compliance.

EU AI Act Article 12 requires AI systems to have automatic logging capabilities that enable tracing the system’s operation.

What is Article 12?

EU AI Act Article 12 (Record-Keeping) requires:

High-risk AI systems shall technically allow for the automatic recording of events (‘logs’) while the system is operating.

Logs must capture:

  1. Usage periods - When the system was in operation
  2. Input data - Reference database against which input was checked
  3. Identification of persons - Natural persons involved in verification
  4. Output data - For systems used for identification purposes

What Inkog Detects

Inkog’s universal_missing_audit_logging rule detects agents that perform sensitive operations without logging configured.

Detection Rule

id: universal_missing_audit_logging title: Missing Audit Logging for Agent Actions severity: MEDIUM category: governance compliance_mapping: eu_ai_act: ["Article 12"] nist_ai_rmf: ["GOVERN 5.2"] iso_42001: ["8.4"]

What Triggers Detection

PatternDetectedExplanation
No logging configuration in settingsYesLogging not enabled
High-risk actions without audit trailYesSensitive ops not logged
Missing log destinationsYesNo log storage configured
Insufficient log retentionYesLogs don’t meet retention requirements

Framework Examples

Microsoft Copilot Studio

Vulnerable Pattern

settings: authentication: enabled: true # VULNERABLE: No logging configuration topics: - id: "delete-account" nodes: - id: "delete" type: "powerautomate" # Deletion happens with no audit trail

Compliant Pattern

settings: authentication: enabled: true # COMPLIANT: Logging enabled logging: enabled: true level: "all" destinations: - type: "azure-monitor" workspace: "audit-logs" - type: "blob-storage" container: "agent-audit-trail" includeUserContext: true includeToolCalls: true retentionDays: 365 topics: - id: "delete-account" nodes: - id: "delete" type: "powerautomate" properties: auditLog: true # Explicit audit logging for this action

Python (LangChain/CrewAI)

Vulnerable Pattern

from langchain.agents import AgentExecutor agent_executor = AgentExecutor( agent=agent, tools=tools, # VULNERABLE: No callbacks or logging ) result = agent_executor.invoke({"input": query})

Compliant Pattern

import logging import json from datetime import datetime from langchain.callbacks import BaseCallbackHandler class AuditLogger(BaseCallbackHandler): """Callback handler for audit logging.""" def __init__(self, log_file: str): self.logger = logging.getLogger("audit") handler = logging.FileHandler(log_file) handler.setFormatter(logging.Formatter( '%(asctime)s - %(message)s' )) self.logger.addHandler(handler) self.logger.setLevel(logging.INFO) def on_tool_start(self, tool, input_str, **kwargs): self.logger.info(json.dumps({ "event": "tool_start", "tool": tool.name, "input": input_str, "timestamp": datetime.utcnow().isoformat() })) def on_tool_end(self, output, **kwargs): self.logger.info(json.dumps({ "event": "tool_end", "output": str(output)[:1000], # Truncate for safety "timestamp": datetime.utcnow().isoformat() })) # COMPLIANT: Audit logging enabled agent_executor = AgentExecutor( agent=agent, tools=tools, callbacks=[AuditLogger("/var/log/agent-audit.jsonl")] )

LangGraph

Vulnerable Pattern

graph = StateGraph(AgentState) # ... add nodes and edges ... # VULNERABLE: No checkpointer or logging app = graph.compile()

Compliant Pattern

from langgraph.checkpoint import MemorySaver graph = StateGraph(AgentState) # ... add nodes and edges ... # COMPLIANT: State persistence enables audit trail checkpointer = MemorySaver() # Or use persistent storage app = graph.compile(checkpointer=checkpointer) # Each run creates an auditable trace config = {"configurable": {"thread_id": "audit_" + str(uuid4())}} result = app.invoke(input_data, config)

Salesforce Agentforce

Vulnerable Pattern

<GenAiPlanner> <guardrails> <!-- VULNERABLE: No audit configuration --> <enabled>true</enabled> </guardrails> </GenAiPlanner>

Compliant Pattern

<GenAiPlanner> <guardrails> <enabled>true</enabled> </guardrails> <!-- COMPLIANT: Audit logging enabled --> <auditConfiguration> <enabled>true</enabled> <captureAllConversations>true</captureAllConversations> <captureToolInvocations>true</captureToolInvocations> <retentionPeriod>365</retentionPeriod> <destinations> <destination> <type>EventMonitoring</type> <enabled>true</enabled> </destination> </destinations> </auditConfiguration> </GenAiPlanner>

What to Log

For EU AI Act compliance, logs should include:

FieldDescriptionExample
timestampWhen the action occurred2025-12-21T14:30:00Z
session_idUnique conversation identifiersess_abc123
user_idWho initiated the actionuser_456
actionWhat was donedelete_customer
inputWhat data was provided{"customer_id": "789"}
outputWhat result was returned"Customer deleted"
statusSuccess or failuresuccess
duration_msHow long it took234

Log Storage Best Practices

1. Immutable Storage

Use append-only logs that cannot be modified:

# Write to append-only log with open(log_file, 'a') as f: f.write(json.dumps(audit_entry) + '\n')

2. Centralized Logging

Send logs to a central system for analysis:

import structlog logger = structlog.get_logger() logger.info("tool_executed", tool="delete_customer", customer_id="789", user_id="456" )

3. Retention Policy

Maintain logs for the required retention period:

RegulationMinimum Retention
EU AI ActNot specified, but “entire lifespan”
GDPRAs long as processing continues
SOC 21 year minimum
PCI DSS1 year minimum

4. Tamper Evidence

Use cryptographic hashing for tamper evidence:

import hashlib def log_with_integrity(entry): entry_json = json.dumps(entry, sort_keys=True) entry_hash = hashlib.sha256(entry_json.encode()).hexdigest() return {**entry, "hash": entry_hash}

Compliance Evidence

Inkog generates audit logging compliance reports:

{ "article_mapping": { "Article 12": { "status": "PASS", "description": "Record-Keeping", "finding_count": 0, "details": { "logging_configured": true, "destinations_configured": 2, "retention_days": 365, "captures_tool_calls": true } } } }
Last updated on