Skip to contents

Tamper-Evident Audit Logging for Regulated Environments

Every analytical action taken in a consequential R environment should be documented — who did it, what they did, when, and why. In practice, almost none of it is.

regulog fills that gap. It records every action, change, note, and decision into a tamper-evident, hash-chained audit trail stored as newline-delimited JSON. Every entry is attributed to a named user, time-stamped in UTC, and linked to the previous entry via SHA-256 — so any modification after the fact, however subtle, is detectable by verify_log().

Works for regulated pharmaceutical environments (21 CFR Part 11, EU Annex 11), internal data pipelines, multi-user Shiny applications, and any context where accountability and traceability matter.

Installation

# Install from GitHub
pak::pak("repro-stats/regulog")

Quick start

library(regulog)

# Initialise a session
log <- regulog_init(
  app     = "primary-analysis",
  version = "1.0.0",
  user    = "analyst",
  path    = "logs/audit.rlog"
)

# Log actions, changes, and decisions
log_action(log,
  action = "data_read",
  object = "adsl.sas7bdat",
  reason = "Reading ADSL for primary efficacy analysis"
)

log_change(log,
  object = "alpha",
  field  = "value",
  before = "0.05",
  after  = "0.025",
  reason = "Updated per protocol amendment 2"
)

log_note(log,
  "Outlier in subject 01-042 retained per SAP section 8.3 —
   discussed with medical monitor 2026-06-20"
)

# Log data reads, scoped to a block — read() is logged automatically
with_log(log, {
  adsl <- read(haven::read_sas, "data/adsl.sas7bdat")
  adae <- read(haven::read_sas, "data/adae.sas7bdat")
})

# Apply an electronic signature
log_signature(log,
  "I certify this analysis is accurate and complete per SAP version 2.0")

# Verify tamper integrity
verify_log(log)
#> regulog: Log intact: 5 entries, chain unbroken

# Query the log
filter_log(log, type = "SIGNATURE")
filter_log(log, action = "data_read", from = "2026-06-01")

# Export for submission
export_audit_trail(log, format = "csv", signed = TRUE,
                   path = "outputs/audit_trail.csv")

Key functions

Function Purpose
regulog_init() Initialise an audit logging session
log_action() Log a discrete action
log_change() Log a before/after field change
log_note() Log a free-text annotation or analytical decision
log_signature() Apply an electronic signature
rl_read() Explicit, logged read of any data source
with_log() Scoped convenience: read() calls inside the block log automatically
verify_log() Verify SHA-256 hash chain integrity
filter_log() Query entries by type, user, action, or date
export_audit_trail() Export to CSV or JSON, with optional signing
regulog_shiny_init() Initialise inside a Shiny server function
regulog_observer() Auto-log Shiny reactive input events

The hash chain

Each entry hash is SHA-256 of all entry fields plus the prior hash:

h_0 = SHA256("GENESIS" | app | version | timestamp)
h_n = SHA256(entry_id | timestamp | app | version | user | type |
             <payload fields> | h_{n-1})

Any modification to any field in any entry breaks the chain from that point forward. verify_log() recomputes every hash and reports the first broken link — and works offline from the .rlog file, without an active R session.

Entry types

Type Created by Purpose
ACTION log_action() Discrete events: reads, runs, approvals
CHANGE log_change() Before/after field modifications
NOTE log_note() Decisions and free-text rationale
SIGNATURE log_signature() Named, dated, meaningful sign-off

Validation

Deploying any software in a regulated environment requires documented evidence that it is installed correctly, operates as specified, and performs reliably under real-world conditions. This is the IQ/OQ/PQ qualification process required under 21 CFR Part 11, EU Annex 11, and GAMP 5 before a tool can be used in GxP workflows.

regulog ships pre-written, executable qualification protocols. Instead of authoring validation documents from scratch — a process that typically takes weeks of internal effort — your team runs three commands and receives a complete, signed qualification record:

source(system.file("validation/IQ_regulog.R", package = "regulog"))
source(system.file("validation/OQ_regulog.R", package = "regulog"))
source(system.file("validation/PQ_regulog.R", package = "regulog"))

Each protocol is self-contained and produces a pass/fail summary against explicit acceptance criteria:

Protocol What it covers Tests
IQ — Installation Qualification R version, package installation, dependency integrity, file system access 10
OQ — Operational Qualification All 21 CFR §11.10 requirements: hash chain, tamper detection, user attribution, timestamps, export, signatures 26
PQ — Performance Qualification End-to-end clinical workflows: data review, regulatory export, multi-user sessions, 500-entry load test, inspector query simulation 7

Protocols are version-controlled alongside the package and updated with every release that affects qualified behaviour.

The qualification record produced by each run — including the platform, R version, date, and pass/fail status — can be retained as documented evidence of system qualification in your validated environment.

Regulatory coverage

Regulation Clause Coverage
21 CFR Part 11 §11.10(e) Hash-chained, time-stamped, user-attributed entries
21 CFR Part 11 §11.10(b) export_audit_trail() CSV and JSON
21 CFR Part 11 §11.100 log_signature() signer identity
21 CFR Part 11 §11.200 Signature components: identity, timestamp, meaning
EU Annex 11 Clause 9 Date, time, user, action on every entry
EU Annex 11 Clause 11 verify_log() periodic integrity evaluation