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 only timestamp uses. 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: false leaves 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:

  1. The first pass collects every user described by a login entry.
  2. 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-log to 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}
© 2026 Curiosity. All rights reserved.