Audit Trail
Enterprise feature
The audit trail is available in gdotv Enterprise, in server (web) deployments. It is not recorded in gdotv Team or in the desktop app — both lack the Keycloak identity that makes "who did this" answerable. If you'd like to discuss upgrading to gdotv Enterprise, contact us at sales@gdotv.com.
gdotv records who did what, when, from where, and with what result — every query run against a connected database, every export, and every change to connections, saved queries, dashboards and access control. Admins read it in-app; every event is also emitted as a JSON Lines stream on standard output, which your existing log pipeline collects and can forward to your SIEM.
Reading the trail
Open Settings → Audit Log (visible to Admins only). Filter by free text (which matches query text, usernames, connection names and target names), by event type, by outcome, and by time range. Expanding a row shows the full query text, the event's details, the source IP, the user agent and the correlation id that ties together several events from one request.
What is recorded
| Event | Recorded when |
|---|---|
query.execute | Any query runs — ad-hoc, a saved query, a dashboard panel or a path search. Includes queries rejected by the read-only policy, with the reason. |
results.export | A user downloads query results (CSV, JSON, GraphML, GEXF). |
connection.export / connection.import | A whole graph is exported from, or imported into, a connection. |
connection.create / .update / .delete / .set-password | A database connection is added, changed, removed, or has its credentials replaced. |
saved-query.*, dashboard.* | A shared saved query or dashboard is created, edited or deleted. |
role-binding.* | A Keycloak role is granted, changed or revoked on a connection. |
realm-role.*, user-role.*, group.*, group-member.*, group-role.* | Identity changes made through gdotv's Keycloak admin screens. |
settings.*, ai-provider.* | Deployment-wide settings and AI provider configuration change. |
Each event carries the actor (username, display name, email and effective role as they were at the time), the connection and target by name as well as by id, the outcome (SUCCESS, DENIED, ERROR, TIMED_OUT, CANCELLED), the source IP, and — for queries — the query text, language, duration and number of elements returned.
Why names and not just ids
An audit record is a statement about the past, so gdotv stores the name of everything it refers to as it stood at the time, and holds no foreign keys into the live tables. Rename a connection and old records still show the old name; delete it and the history of what was done through it survives intact.
What is not recorded
- Result data. The rows a query returned are never written to the trail — only how many there were.
- Parameter values. The names of a query's parameters are always recorded; their values are the literal data the query ran with, so they are recorded only if you turn them on (see below).
- Secrets. Passwords, API keys and tokens are never recorded. Changing a credential is recorded as a fact; the credential itself is not.
- Field-by-field change history. The trail records that a connection or dashboard was changed, by whom and when — not a before-and-after of every field.
Client-reported events
Exporting query results happens entirely in the gdotv client: the rows are already on the user's machine, and the export never reaches the server. Those events are therefore reported by the client rather than observed by the server, and are marked UI_CLIENT in the channel field so you can see the difference.
Retention, and shipping to your SIEM
The audit table keeps a 90-day window by default. It is an investigation window, not an archive: a purge runs nightly, and audit tables that grow without bound are a reliable way to make an application database unmanageable.
Long-term retention is handled by the audit event stream. Every event is written, as it happens, as one JSON object on one line to gdotv's standard output, alongside the ordinary application log. Whatever already collects gdotv's container logs — CloudWatch, your cluster's node agent, the Docker logging driver — is already collecting your audit trail; you need only forward and retain it.
Each line follows Open Cybersecurity Schema Framework naming, so your existing Splunk, Elastic, Datadog or Sentinel content can consume it without a custom parser, and carries metadata.log_name = gdotv-audit so you can select audit events out of the log stream:
{"time":1758018621446,"time_dt":"2026-09-16T09:10:21.446Z","class_uid":6005,"class_name":"Datastore Activity","activity_name":"QUERY","type_name":"query.execute","status":"Success","status_id":1,"outcome":"SUCCESS","actor":{"user":{"uid":"8f14e45f-ceea-467a-9575-1a0d1a2b3c4d","name":"alice","full_name":"Alice Smith","email_addr":"alice@example.com","type":"USER","role":"DEVELOPER"}},"src_endpoint":{"ip":"10.12.0.44"},"metadata":{"uid":"1f0c...","correlation_uid":"9b2e...","log_name":"gdotv-audit","log_provider":"gdotv","product":{"name":"gdotv","vendor_name":"gdotv"}},"channel":"UI","database":{"uid":"c41e...","name":"Production EU","type":"Neo4j","graph":"neo4j"},"query_info":{"query_string":"MATCH (n:Customer) RETURN n LIMIT 25","language":"cypher","mutating":false},"duration":18,"count":25}Why stdout and not a file
Every gdotv deployment — Kubernetes, ECS and the VM images — runs with file logging switched off and its logs collected from the container's output. Kubernetes pods in particular run with a read-only root filesystem, so a log file would have to live on ephemeral pod storage that nothing collects and that is discarded when the pod restarts. Writing to stdout puts the audit trail on the one path that is already wired up, monitored and durable in each environment.
Collecting the stream
Pick the section for your deployment. In each case the goal is the same: select the lines where metadata.log_name is gdotv-audit and forward them to your SIEM.
Amazon EKS, Azure AKS, Google GKE
Container stdout is written to the node and picked up by your cluster's log agent. If you have not deployed one yet, use the managed option for your platform:
- EKS — the Amazon CloudWatch Observability add-on (Fluent Bit) ships container logs to the
/aws/containerinsights/<cluster>/applicationlog group. - AKS — Container Insights writes to the
ContainerLogV2table in your Log Analytics workspace. - GKE — Cloud Logging collects container output automatically; no agent to install.
From there, forward to your SIEM with the platform's own mechanism — a CloudWatch Logs subscription filter to Kinesis Firehose or Splunk, an Azure Monitor data export rule, or a Cloud Logging sink to Pub/Sub. Filter on the gdotv container and the audit marker, for example in Log Analytics:
ContainerLogV2
| where ContainerName == "gdotv"
| extend Audit = parse_json(LogMessage)
| where Audit.metadata.log_name == "gdotv-audit"If you would rather ship directly from the cluster, a Fluent Bit or Vector DaemonSet reading the gdotv pod's logs and filtering on the same field works equally well, and avoids a round trip through the cloud provider's log store.
Amazon ECS (Fargate)
The gdotv task already uses the awslogs driver, so every event lands in the stack's CloudWatch log group under the gdotv stream prefix with no further configuration. To get it into a SIEM, add a subscription filter on that log group pointing at Kinesis Data Firehose (and on to Splunk, S3 or your collector of choice):
aws logs put-subscription-filter \
--log-group-name "/ecs/<your-stack>" \
--filter-name gdotv-audit \
--filter-pattern '{ $.metadata.log_name = "gdotv-audit" }' \
--destination-arn "arn:aws:firehose:<region>:<account>:deliverystream/<stream>" \
--role-arn "arn:aws:iam::<account>:role/<cwl-to-firehose-role>"The filter pattern above is evaluated by CloudWatch against the JSON of each line, so only audit events are forwarded and billed.
Virtual machine images (AWS AMI, Azure VM, Google Cloud VM)
The stack runs under Docker Compose, so the events go to the container's log via the Docker logging driver. To read them directly:
docker logs gdotv-team 2>/dev/null | grep '"log_name":"gdotv-audit"'For continuous collection, install your SIEM's agent on the VM and point it at the Docker JSON log file (/var/lib/docker/containers/<id>/<id>-json.log), or switch the gdotv service to a driver that forwards directly. The latter is usually simpler — add a logging block to the gdotv service in the stack's docker-compose.yaml:
logging:
driver: awslogs # or: gcplogs, fluentd, splunk, syslog
options:
awslogs-region: eu-west-1
awslogs-group: gdotvDocker's built-in syslog driver is available here if your estate is syslog-based; note it forwards the whole container log, so filter for gdotv-audit on the receiving side.
Filter, don't drop
Whichever mechanism you use, filter on metadata.log_name rather than turning off application logging. The audit stream and the application log share stdout by design, and the application log is what you will want when diagnosing why something in the audit trail looks odd.
Exporting from the screen
For a single investigation, the Audit Log screen can download the events matching your current filters as JSONL — convenient for handing a specific incident to someone. It is capped; for bulk or historical work, query your SIEM.
Configuration
All settings are application properties, each overridable by the environment variable shown.
| Property | Environment variable | Default |
|---|---|---|
gdotv.audit.enabled | GDOTV_AUDIT_ENABLED | true |
gdotv.audit.retention-days | GDOTV_AUDIT_RETENTION_DAYS | 90 |
gdotv.audit.purge-cron | GDOTV_AUDIT_PURGE_CRON | 0 30 3 * * * |
gdotv.audit.purge-batch-size | GDOTV_AUDIT_PURGE_BATCH_SIZE | 5000 |
gdotv.audit.query-text.enabled | GDOTV_AUDIT_QUERY_TEXT_ENABLED | true |
gdotv.audit.query-text.max-length | GDOTV_AUDIT_QUERY_TEXT_MAX_LENGTH | 10000 |
gdotv.audit.parameter-values.enabled | GDOTV_AUDIT_PARAMETER_VALUES_ENABLED | false |
gdotv.audit.jsonl.enabled | GDOTV_AUDIT_JSONL_ENABLED | true |
Turning gdotv.audit.jsonl.enabled off stops the stdout stream and leaves the in-app table as the only record — bounded by retention-days, and therefore no longer a durable one. Only do that if nothing collects your container logs.
The stream itself is emitted by the AUDIT appender in /var/gdotv/logback-spring.xml, bound to the com.gdotv.audit.jsonl logger. It writes raw JSON to stdout with no pattern of its own; if you customise that file, keep additivity="false" on the logger, or every audit line will also be re-emitted wrapped in the application log's text pattern and stop being parseable.
Recording parameter values
Typed query variables are how a report is run for a particular customer, date or account. Their names are always recorded, so you can always see which report ran with which variables. Setting gdotv.audit.parameter-values.enabled to true also records the values — which is the fullest forensic record, but puts real customer data in the audit store and in your SIEM. Turn it on deliberately, with your own data-protection position in mind.
Immutability
gdotv never updates or deletes an audit record. The application exposes no way to edit the trail, and the retention purge is the only thing that removes rows. Because gdotv is self-hosted, the database is yours: hold the audit table to whatever access controls and backup policy you apply to the rest of it, and treat the copy in your SIEM — outside the system being audited, under your own retention and tamper controls — as the authoritative long-term record.
Related
- Logins, logouts and changes made directly in Keycloak are recorded in Keycloak's own event log, enabled by default in the realms gdotv ships, and visible in the Keycloak admin console under Realm settings → Events.
- Access Control (Enterprise) — the roles and per-connection grants whose changes appear in this trail.