convert-audit-log
Convert workspace audit log files into CSV, TSV, JSON Lines or JSON, for a spreadsheet, a report or a system that does not read the workspace's format. A template picks the columns and what each one contains. Every row can carry the acting user's user name, email, first name and last name, taken from that user's login entries.
It works on local copies of the files. It takes no --server and no --token, and never contacts a workspace.
For the file format itself, see Audit logs.
Syntax
Usage: curiosity-cli convert-audit-log <files|folders|wildcard>... [--template <file>] [--output <file>] [--format <format>] [--verify]
Usage: curiosity-cli audit-log-template
curiosity-cli convert-audit-log ./audit --output ./audit.csv
curiosity-cli convert-audit-log ./audit --template ./logins.template --output ./logins.tsv --verify
curiosity-cli convert-audit-log ./audit/2026-09-*.jsonl --format jsonl > september.jsonl
Arguments and options
| Argument / option | Description |
|---|---|
<inputs> |
Audit log files, folders, or a wildcard in the file name. A folder contributes its *.jsonl files and its *audit*.log configuration logs. |
--template |
Path to a template file. Alias -t. Default: one column per field (see audit-log-template). |
--output |
File to write. Alias -o. Default: standard output. |
--format |
csv, tsv, jsonl or json. Alias -f. Overrides the template's @format. Without either, the output file's extension decides, and otherwise csv. |
--verify |
Run the verify-audit-log check first, and convert nothing (exit code 1) if any file was altered. |
Status messages go to standard error, so they never mix with a conversion written to standard output:
Converted 2817 entries from 2 file(s) to './audit.csv'; 41 user(s) captured from login entries.
3 user(s) have no login entry in these files, so only their UID is known.
Templates
A template is a text file with one statement per line:
# Who did what, in a spreadsheet
@format csv # csv (default), tsv, jsonl or json
@types Login, Viewed, Download
@header true # header row for csv/tsv (default: true)
When = {timestamp:yyyy-MM-dd HH:mm:ss}
Who = {user.firstname} {user.lastname} <{user.email}>
Login = {user.username}
Action = {type}
Target = {target}
Detail = {details}
- A column is
Name = text. The columns are written in the order they appear. The text is copied as is, with each{field}replaced by that field of the entry.{field:format}passes a format, which onlytimestampuses. Write{{and}}for literal braces. Wrap the text in double quotes to keep leading or trailing spaces. @format: the output format.@types: only convert entries of these audit types, separated by commas. Without it, every type is converted.@header:falseleaves out the CSV/TSV header row.- Lines starting with
#are comments. A template with an unknown field, an unknown directive or no column is rejected with the line number.
Fields
Field names are case-insensitive, and - and _ in them are ignored (user.first-name works).
| Field | Value |
|---|---|
id |
The entry's sequence number. It restarts at 1 when the server restarts. |
timestamp |
When the entry was written, in UTC. The default is ISO 8601 (2026-09-24T10:15:03.123Z). Pass a .NET date format ({timestamp:yyyy-MM-dd}), unix (seconds) or unixms (milliseconds). |
type |
The audit type: Login, Viewed, Download, Search, ChatAI, AdminAction, … |
user.uid |
UID of the user who acted. Also user. |
user.username |
The user's user name. Also user.name. |
user.email |
The user's email address. |
user.firstname |
The user's first name. |
user.lastname |
The user's last name. |
user.fullname |
First and last name, or the user name when both are empty. |
target |
UID of the node the entry is about, if any. |
details |
The details text. For an entry with a JSON payload instead (ChatAI), the payload. For a configuration-change line, its message. |
payload |
The entry's JSON payload, if any. |
host |
The host name of the server that wrote the entry, from the start entry of its chain. |
signature |
The entry's hash-chain signature. |
file |
Name of the file the entry was read from. |
line |
Line number of the entry in that file. |
raw.<key> |
Any key of the entry exactly as written, for example {raw.m} for a configuration change's message. |
In jsonl and json output every value is a string. The exception is a column holding nothing but {payload}
(or {details} on an entry that has only a payload), which is written as JSON, so a ChatAI entry's messages stay
an array.
audit-log-template
Prints the default template with the list of fields as comments. Use it as a starting point:
curiosity-cli audit-log-template > my.template
The default template writes every field except payload and signature, one column each.
How users are filled in
An audit entry names its user only by UID. The workspace writes the user's user name, email, first name and last name on that user's Login entries, and the conversion captures them as it reads.
It reads the files twice:
- The first pass collects every user described by a login entry.
- The second pass writes the rows. Each row uses the most recent description of its user read so far, so a name change shows up from that point on. An entry from before the user's first login in these files uses the first description found anywhere in them.
A user with no login entry in the files keeps only user.uid, and the command reports how many users that
affects. To cover them, include files from a period in which they signed in. Files written by workspace
versions from before login entries carried user details contain UIDs only.
What is not converted
- Server start entries (
'k':'-1'). They are not audit events. Their host name is available on every row as{host}. - Lines that cannot be parsed. They are skipped and counted in the summary. Run
verify-audit-logto find them. - Entries excluded by
@types. They are counted in the summary.
Rows are written file by file, in date order, and in the order the entries appear within each file.
Examples
Logins for an access review:
# logins.template
@format csv
@types Login
When = {timestamp:yyyy-MM-dd HH:mm}
User = {user.username}
Name = {user.fullname}
Email = {user.email}
Details = {details} # the sign-in method, or the IP address
curiosity-cli convert-audit-log ./audit -t logins.template -o logins.csv --verify
Downloads with the file they refer to, as JSON Lines for a SIEM:
@format jsonl
@types Download
time = {timestamp:unixms}
user = {user.email}
node = {target}
detail = {details}
host = {host}
AI chat transcripts, keeping the messages as JSON:
@format json
@types ChatAI
When = {timestamp}
Who = {user.email}
Messages = {payload}
Related
verify-audit-log: check the files' hash chain.- Audit logs: how auditing is configured and what an entry contains.