The audit log
audit.log.jsonl format and querying
This page is implementation detail, beyond what normal use requires.
Every init and add is appended to audit.log.jsonl in the storage
directory. The log is append-only and newline-delimited JSON, with one operation
per line.
🔗Setup
options(width = 1000)
library(dvs)
library(fs)
library(here)source(here::here("R/mkdatasetfiles.R"))storage <- basename(tempfile(fileext = "_storage"))
new_project <- basename(tempfile(fileext = "_project"))
dir.create(here::here(storage))
dir.create(here::here(new_project))
Run a sequence of dvs operations to populate the log:
setwd(here::here(new_project))
mkdatasetfiles(n_files = 3, size_mb = 2, prefix = "file_", dir = "data", show_progress = !nzchar(Sys.getenv("QUARTO_DOCUMENT_PATH")))
dvs_init(here::here(storage))# A tibble: 1 × 4
compression metadata_folder_name backend_path backend_group
<chr> <lgl> <chr> <chr>
1 zstd NA /Users/elea/Documents/a2ai_github/dvs2-demo-repo/file76c926e34747_storage staff setwd(here::here(new_project))
invisible(dvs_add("data/file_1.csv", message = "initial add of file 1"))
invisible(dvs_add("data/file_2.csv", message = "initial add of file 2"))
invisible(dvs_add("data/file_3.csv", message = "initial add of file 3"))
Restore a file with get. get does not append to the audit log; only init
and add are recorded.
setwd(here::here(new_project))
unlink("data/file_1.csv")
dvs_get("data/file_1.csv")# A tibble: 1 × 3
path outcome size
<chr> <chr> <bytes>
1 data/file_1.csv copied 2.0 MB
Modify file_2.csv and re-add:
setwd(here::here(new_project))
cat("99,0,0,0,0,0\n", file = "data/file_2.csv", append = TRUE)
invisible(dvs_add("data/file_2.csv", message = "updated file 2, added row 99"))🔗Reading the log
The audit log is a newline-delimited JSON file (audit.log.jsonl) in the
storage directory. Each line is one operation:
audit_path <- here::here(storage, "audit.log.jsonl")
Raw file, one JSON object per line:
lines <- readLines(audit_path)
cat("```json\n")
```r
cat(lines, sep = "\n")
{"operation_id":"36a11dc6-3af1-4d70-91b0-04d8c08d0cb5","timestamp":1786391069,"user":"elea","action":{"init":{"settings":{"compression":"zstd","metadata_folder_name":null,"backend":{"path":"/Users/elea/Documents/a2ai_github/dvs2-demo-repo/file76c926e34747_storage","group":"staff"}},"project_path":"/Users/elea/Documents/a2ai_github/dvs2-demo-repo/file76c9135c9a43_project"}}} {"operation_id":"4ec573a3-b5ae-4fb1-945d-31f5a7b4a474","timestamp":1786391069,"user":"elea","action":{"add":{"file":{"path":"data/file_1.csv","hashes":{"blake3":"7eccc458c3f4941f01cf32d26b0ac67bde4bb9377f0add92e8b989be7bf2e242"}},"compression":"zstd"}}} {"operation_id":"002b8eb7-0348-4e7b-9ef7-228a76dd27d8","timestamp":1786391069,"user":"elea","action":{"add":{"file":{"path":"data/file_2.csv","hashes":{"blake3":"855734680f86ce5ad3352dacd08c78c8041ab94bed7c862a36a0542c2bafb1cd"}},"compression":"zstd"}}} {"operation_id":"39505bdd-c96c-4b5c-8077-c41a885dbd18","timestamp":1786391069,"user":"elea","action":{"add":{"file":{"path":"data/file_3.csv","hashes":{"blake3":"f409a6bf55091606e5afc101ead8b864820467472f69890f05fdb9fd7392f77d"}},"compression":"zstd"}}} {"operation_id":"b33801cd-ce8d-4a84-90b5-c57351a96acf","timestamp":1786391070,"user":"elea","action":{"add":{"file":{"path":"data/file_2.csv","hashes":{"blake3":"166399740a4c8c9c363523dcbe5548f35c115e891bf3492a66f7930f6cbed09d"}},"compression":"zstd"}}}
cat("\n```\n")
Same entries, pretty-printed:
```r
cat("```json\n")
```r
for (l in lines) {
cat(jsonlite::toJSON(jsonlite::fromJSON(l, simplifyVector = FALSE), pretty = TRUE, auto_unbox = TRUE))
cat("\n")
}
{ "operation_id": "36a11dc6-3af1-4d70-91b0-04d8c08d0cb5", "timestamp": 1786391069, "user": "elea", "action": { "init": { "settings": { "compression": "zstd", "metadata_folder_name": {}, "backend": { "path": "/Users/elea/Documents/a2ai_github/dvs2-demo-repo/file76c926e34747_storage", "group": "staff" } }, "project_path": "/Users/elea/Documents/a2ai_github/dvs2-demo-repo/file76c9135c9a43_project" } } } { "operation_id": "4ec573a3-b5ae-4fb1-945d-31f5a7b4a474", "timestamp": 1786391069, "user": "elea", "action": { "add": { "file": { "path": "data/file_1.csv", "hashes": { "blake3": "7eccc458c3f4941f01cf32d26b0ac67bde4bb9377f0add92e8b989be7bf2e242" } }, "compression": "zstd" } } } { "operation_id": "002b8eb7-0348-4e7b-9ef7-228a76dd27d8", "timestamp": 1786391069, "user": "elea", "action": { "add": { "file": { "path": "data/file_2.csv", "hashes": { "blake3": "855734680f86ce5ad3352dacd08c78c8041ab94bed7c862a36a0542c2bafb1cd" } }, "compression": "zstd" } } } { "operation_id": "39505bdd-c96c-4b5c-8077-c41a885dbd18", "timestamp": 1786391069, "user": "elea", "action": { "add": { "file": { "path": "data/file_3.csv", "hashes": { "blake3": "f409a6bf55091606e5afc101ead8b864820467472f69890f05fdb9fd7392f77d" } }, "compression": "zstd" } } } { "operation_id": "b33801cd-ce8d-4a84-90b5-c57351a96acf", "timestamp": 1786391070, "user": "elea", "action": { "add": { "file": { "path": "data/file_2.csv", "hashes": { "blake3": "166399740a4c8c9c363523dcbe5548f35c115e891bf3492a66f7930f6cbed09d" } }, "compression": "zstd" } } }
cat("```\n")
# Parsing the log
Each entry has the shape:
```json
{
"operation_id": "<uuid>",
"timestamp": <unix-seconds>,
"user": "<username>",
"action": { "<command>": { ... } }
}
The action field is a tagged union. The single key is the command name,
either "init" or "add" (get is not logged). Parse it with purrr and
jsonlite:
parse_entry <- function(line) {
entry <- jsonlite::fromJSON(line)
cmd <- names(entry$action)[1]
# init records settings and project_path, not a file, so it has no path.
path <- switch(cmd,
add = entry$action$add$file$path,
init = NA_character_,
NA_character_
)
list(
operation_id = entry$operation_id,
timestamp = as.POSIXct(entry$timestamp, origin = "1970-01-01"),
user = entry$user,
command = cmd,
path = path
)
}
audit_tbl <- readLines(audit_path) |>
purrr::map(parse_entry) |>
dplyr::bind_rows()
audit_tbl# A tibble: 5 × 5
operation_id timestamp user command path
<chr> <dttm> <chr> <chr> <chr>
1 36a11dc6-3af1-4d70-91b0-04d8c08d0cb5 2026-08-10 21:44:29 elea init <NA>
2 4ec573a3-b5ae-4fb1-945d-31f5a7b4a474 2026-08-10 21:44:29 elea add data/file_1.csv
3 002b8eb7-0348-4e7b-9ef7-228a76dd27d8 2026-08-10 21:44:29 elea add data/file_2.csv
4 39505bdd-c96c-4b5c-8077-c41a885dbd18 2026-08-10 21:44:29 elea add data/file_3.csv
5 b33801cd-ce8d-4a84-90b5-c57351a96acf 2026-08-10 21:44:30 elea add data/file_2.csv🔗Querying the log
Operations by command type:
dplyr::count(audit_tbl, command, sort = TRUE)# A tibble: 2 × 2
command n
<chr> <int>
1 add 4
2 init 1
Files touched per operation (excluding init):
audit_tbl |>
dplyr::filter(!is.na(path)) |>
dplyr::select(timestamp, command, path) |>
dplyr::arrange(timestamp)# A tibble: 4 × 3
timestamp command path
<dttm> <chr> <chr>
1 2026-08-10 21:44:29 add data/file_1.csv
2 2026-08-10 21:44:29 add data/file_2.csv
3 2026-08-10 21:44:29 add data/file_3.csv
4 2026-08-10 21:44:30 add data/file_2.csv
How many times was each file touched?
audit_tbl |>
dplyr::filter(!is.na(path)) |>
dplyr::count(path, command, sort = TRUE)# A tibble: 3 × 3
path command n
<chr> <chr> <int>
1 data/file_2.csv add 2
2 data/file_1.csv add 1
3 data/file_3.csv add 1🔗Cleanup
unlink(here::here(new_project), recursive = TRUE)
unlink(here::here(storage), recursive = TRUE)🔗See also
- Storage and meta files: the blob and meta layout.
- The dvs.toml project file: the project configuration.
- The command references: dvs_add() / dvs add.