--- title: "Query the audit log" slug: "dev-catalog-guides-audit-log" updated: 2026-10-28T02:15:03Z published: 2026-10-28T02:15:03Z canonical: "docs.vastdata.com/dev-catalog-guides-audit-log" --- > ## Documentation Index > Fetch the complete documentation index at: https://docs.vastdata.com/llms.txt > Use this file to discover all available pages before exploring further. # Query the audit log *Applies to VAST Cluster 5.5.* When your platform team saves protocol audit logs to VAST DataBase, every audited operation is a row in the audit log table. This guide answers the usual questions with the Python SDK: what happened recently, who accessed a bucket, and which requests failed. Each code block is complete on its own. They read your connection settings from the environment, as in the [quickstart](/develop/docs/dev-catalog-quickstart#before-you-start). ## Before you start - **Ask what's audited.** The audit log only has rows for the protocols and operation categories your platform team chose to audit, globally or in the view policy of the views you care about. Records are kept for a retention period, which defaults to one hour. An operation that wasn't audited, or whose record has expired, leaves no row. See [concepts: the audit log](/develop/docs/dev-catalog-concepts-index#the-audit-log). - **Access:** a key whose identity policy allows querying the audit log. See [access](/develop/docs/dev-catalog-operations-access). - For the bucket examples, set `AUDIT_BUCKET` to an S3 bucket whose operations are audited. ## Look at the columns Open the audit log with `tx.audit_log()` and list its columns with their types: ``` import vastdb session = vastdb.connect() with session.transaction() as tx: fields = tx.audit_log().columns() for field in fields: print(field.name, field.type) ``` ``` cluster_name string cnode_name string time timestamp[ns] client_ip string cluster_vip string tenant string protocol string rpc_type string path struct> view_path string name struct> login_name string uid int32 sid string s3_access_keys list status string nfs3_required_permissions int32 nfs3_granted_permissions int32 nfs_link_name string nfs_link_path string rename_path string rename_name string nfs_rpc_sub_types list nfs_symlink string nfs4_required_access int32 nfs4_granted_access int32 nfs4_ftype string s3_used_access_key string s3_request_id int64 connection_type string s3_bucket_name string s3_multipart_upload_id int64 s3_version_id int64 s3_version_phandle struct s3_source_object struct, phandle: struct>> smb_ads_name struct> smb_create_action string smb_create_option int32 smb_create_disposition int32 smb_delete_on_close bool smb_info_type string smb_info_class string smb_rename_struct struct, path: string> num_ops int32 num_bytes int64 time_str string object_type string transaction_id int64 true_client_ip string mtls_nfs_client_certificate_info string link_path struct ``` Each field is described in the [audit log schema](/develop/docs/dev-catalog-reference-audit-log-schema). Many apply to one protocol only, and are empty for the others. ## What happened in the last hour Count the operations of the last hour by protocol and operation type: ``` from datetime import datetime, timedelta, timezone import vastdb from ibis import _ since = datetime.now(timezone.utc).replace(tzinfo=None) - timedelta(hours=1) session = vastdb.connect() with session.transaction() as tx: rows = tx.audit_log().select( columns=["protocol", "rpc_type"], predicate=_.time >= since, ).read_all() counts = rows.group_by(["protocol", "rpc_type"]).aggregate([("rpc_type", "count")]) for row in counts.sort_by([("rpc_type_count", "descending")]).slice(0, 15).to_pylist(): print(f"{row['protocol']:<8} {row['rpc_type']:<28} {row['rpc_type_count']:>8,}") ``` ``` S3 PUT_OBJECT 21 S3 LIST_OBJECTS 4 S3 GET_OBJECT 3 S3 LIST_OBJECTS_V2 1 S3 DELETE_OBJECT 1 ``` Always filter on `time`. The audit log grows with every audited operation, and a filter on time keeps the query to the period you care about. ## Who accessed a bucket List who read or wrote objects in a bucket in the last hour, and from where: ``` import os from datetime import datetime, timedelta, timezone import vastdb from ibis import _ bucket = os.environ["AUDIT_BUCKET"] since = datetime.now(timezone.utc).replace(tzinfo=None) - timedelta(hours=1) session = vastdb.connect() with session.transaction() as tx: rows = tx.audit_log().select( columns=["time", "client_ip", "uid", "login_name", "s3_used_access_key", "rpc_type", "status"], predicate=(_.time >= since) & (_.s3_bucket_name == bucket) & _.rpc_type.isin(["GET_OBJECT", "PUT_OBJECT", "DELETE_OBJECT"]), ).read_all() who = rows.group_by(["uid", "login_name", "client_ip", "rpc_type"]).aggregate([("rpc_type", "count")]) for row in who.sort_by([("rpc_type_count", "descending")]).to_pylist(): print(row["uid"], row["login_name"], row["client_ip"], row["rpc_type"], row["rpc_type_count"]) ``` ``` 8601 cat-app 15.0.0.1 PUT_OBJECT 21 8601 cat-app 15.0.0.1 GET_OBJECT 3 8601 cat-app 15.0.0.1 DELETE_OBJECT 1 ``` `login_name` is filled in only if your platform team turned on user names in the audit settings, and only when the cluster could get the name from the authentication provider. `uid` (NFS) and `sid` (SMB) identify the user otherwise; for S3, `s3_used_access_key` is the key that signed the request. The S3 operation types include `PUT_OBJECT`, `GET_OBJECT`, `DELETE_OBJECT`, `LIST_OBJECTS` and `LIST_OBJECTS_V2`. On views that impersonate users, 5.5 records only the original user's UID, not their login name or SID (a known limitation in 5.5). ## Failed requests Find requests that didn't succeed. `status` is `Success` for a request that worked; otherwise it holds the error, such as the S3 error code `NoSuchKey`: ``` from datetime import datetime, timedelta, timezone import vastdb from ibis import _ since = datetime.now(timezone.utc).replace(tzinfo=None) - timedelta(hours=1) session = vastdb.connect() with session.transaction() as tx: rows = tx.audit_log().select( columns=["time_str", "protocol", "rpc_type", "status", "path", "client_ip"], predicate=(_.time >= since) & (_.status != "Success"), limit_rows=20, ).read_all() for row in rows.sort_by("time_str").to_pylist(): print(row["time_str"], row["protocol"], row["rpc_type"], row["status"], row["path"]["path"], row["client_ip"]) ``` ``` 2026-10-09T16:41:42.293Z S3 GET_OBJECT NoSuchKey /cat-data/missing/no-such-object.txt 15.0.0.1 ``` `time_str` is the record's time as text, which is easier to print than `time`: like the Catalog's times, `time` has nanosecond precision, finer than Python's `datetime`. `path` is a structure; its `path` field is the full path when your platform team turned on full paths in the audit settings. A request that failed before the cluster could tell what it was acting on has `object_type` `UNKNOWN`. Some failed requests are recorded with only part of their fields, for example when the access key or bucket name is invalid. ## Things to know - **Use the SDK or SQL for large results.** In 5.5, the VAST Web UI's audit log page (and the VMS REST API behind it) can't sort more than 1,000 matching rows, and returns 1,000 random ones instead. The SDK, Trino and Spark don't have that limit. - **Presigned POST uploads** are recorded with `rpc_type` `PUT_OBJECT` in 5.5. - **Some operations aren't audited at all**, such as VAST Event Broker produce and consume. - **The audit log isn't replicated** to another cluster. ## In SQL The same queries work through Trino or Spark; see [Trino and Spark](/develop/docs/dev-catalog-integrations-trino-and-spark). For dashboards, see [Grafana](/develop/docs/dev-catalog-integrations-grafana). ## See also - [Audit log schema](/develop/docs/dev-catalog-reference-audit-log-schema). - KB: [Audited Protocol Operations](/docs-5-5/docs/audited-protocol-operations): which operations each category covers, per protocol. - KB: [Viewing Protocol Audit Logs](/docs-5-5/docs/viewing-protocol-audit-logs): the Web UI page and the JSON audit files.