Oracle Unified Audit Records to Cloud Logging Using JSON

In the previous blog, we looked at forwarding Oracle Unified Audit records using syslog to Cloud Logging, and we quickly ran into the limitations of that approach. Syslog worked well for real-time visibility, but it was not the right transport mechanism if we wanted to see richer audit data, including full SQL text inside Cloud Logging.

So in this final blog of the series, we will look at exporting Oracle Unified Audit records directly from UNIFIED_AUDIT_TRAIL, converting them into NDJSON format, and using the Google Cloud Ops Agent to forward them into Google Cloud Logging.

If you already have a commercial platform such as Oracle Audit Vault and Database Firewall (AVDF), Splunk DB Connect, or IBM Guardium, then you may not need to build this pipeline yourself. In our case, we wanted something lightweight, transparent, cloud-native, and easy to customize operationally.

So the solution consisted of:

  • Oracle Unified Auditing policies to generate audit records
  • Custom export automation using shell and PL/SQL to generate NDJSON-formatted audit exports
  • Google Cloud Ops Agent for ingestion
  • Google Cloud Logging for centralized analysis

Choosing JSON as the Audit Export Format

Most cloud-native logging agents already provide a JSON parser, so choosing JSON as the output format was a no-brainer. I first started with a simple SQL*Plus spool to dump the audit data, but I quickly ran into issues while reading and writing large audit fields.

As we know, SQL_TEXT and SQL_BINDS in UNIFIED_AUDIT_TRAIL are CLOB columns. Attempting to read larger SQL text via the package DBMS_LOB.SUBSTR failed with:

ORA-06502: PL/SQL: numeric or value error: character string buffer too small

So I tried reading as a JSON_OBJECT(... RETURNING CLOB). That solved part of the problem, but SQL*Plus spooling still remained risky. Long JSON records could wrap or split across multiple physical lines, which would break the downstream JSON parser.

So I moved the file writing logic into PL/SQL. Here, instead of relying on SQL*Plus output formatting, the approach used was to read the CLOB in chunks and write it directly to the file using UTL_FILE.

DBMS_LOB.READ(v_json_clob, v_amount, v_pos, v_buffer);
UTL_FILE.PUT(v_file, v_buffer);

This produces clean one-line JSON records that the logging parser can ingest reliably. The script also uses a checkpoint table to track which audit records have already been exported, so each run only processes new records.

You can find the setup steps to create a checkpoint table as well as the script here.

Export Workflow Overview

The shell script that exports the audit data follows the following workflow:

  1. Cron triggers the export script on a scheduled interval.
  2. The script checks the last exported timestamp from the checkpoint table.
  3. New unified audit records are queried from UNIFIED_AUDIT_TRAIL based on the exported timestamp.
  4. Each audit record is written as NDJSON, with one audit record per line.
  5. The script first writes the data into a temporary file.
  6. After successful completion, the temporary file is appended to the NDJSON file.
  7. The Google Cloud Ops Agent monitors the NDJSON file and forwards new records into Cloud Logging.
  8. logrotate manages retention, compression, and cleanup.

This design avoids partial writes or broken JSON records that may lead to issues downstream. Sample record in the ndjson file would look like this.

{"oracle_sid":"DB1","event_timestamp":"2026-05-21T15:27:59.572940","event_timestamp_utc":"2026-05-21T15:27:59.572940","dbid":XXXX1234432,"instance_id":1,"sessionid":98323449,"entry_id":75,"statement_id":187,"action_name":"UPDATE","return_code":0,"dbusername":"MYUSER","os_username":"first.lastname","userhost":"9090909","terminal":"unknown","client_program_name":"SQL Developer","authentication_type":"(TYPE=(DATABASE));(CLIENT ADDRESS=((ADDRESS=(PROTOCOL=tcp)(HOST=10.0.0.0)(PORT=61913))));(CLIENT ADDRESS=());","external_userid":null,"sql_text":"update table1 set url=:\"SYS_B_00\", name=:\"SYS_B_01\"||sysdate,\nlogin = :\"SYS_B_02\"||sysdate, account_status=:\"SYS_B_03\"\nwhere sid in (:\"SYS_B_04\",:\"SYS_B_05\",:\"SYS_B_06\",:\"SYS_B_07\",:\"SYS_B_08\",:\"SYS_B_09\",:\"SYS_B_10\")"}

Google Cloud Ops Agent Integration

Before configuring log ingestion, make sure the Google Cloud Ops Agent is installed and running on the VM. The VM also needs the required connectivity and IAM permissions to write logs into Google Cloud Logging.

sudo systemctl status google-cloud-ops-agent

Once your agent is running on the server, it can monitor NDJSON file and forward the records into Google Cloud Logging. Example config

$ sudo cat /etc/google-cloud-ops-agent/config.yaml

logging:
  receivers:
    oracle_audit_db1:
      type: files
      include_paths:
        - /u01/app/oracle/audit_logs/DB1/*.ndjson

  processors:
    parse_oracle_audit:
      type: parse_json
      time_key: event_timestamp
      time_format: "%Y-%m-%dT%H:%M:%S.%f"

    enrich_oracle_audit:
      type: modify_fields
      fields:
        labels.log_type:
          static_value: oracle_audit

  service:
    pipelines:
      oracle_audit_db1_pipeline:
        receivers: [oracle_audit_db1]
        processors: [parse_oracle_audit, enrich_oracle_audit]

In this configuration, I use the modify_fields processor to add a static label called oracle_audit to each audit record. This makes it easier to filter Oracle audit records in Google Cloud Logging or any downstream logging platform.

I also use the parse_json processor to parse each NDJSON audit record and set the log entry timestamp from the event_timestamp field exported from UNIFIED_AUDIT_TRAIL.

After updating config.yaml, restart the Google Cloud Ops Agent so it loads the new configuration:


sudo systemctl restart google-cloud-ops-agent
sudo systemctl status google-cloud-ops-agent

Housekeeping with Logrotate

Since the NDJSON files continuously grow over time, log rotation becomes important fairly quickly. I used logrotate for both the NDJSON exports and the syslog audit files. Configuration example:

/u01/app/oracle/audit_logs/*/*.ndjson
{
    size 100M
    rotate 30
    missingok
    notifempty
    compress
    delaycompress
    dateext
    create 0640 oracle oinstall
}

/var/log/oracle_audit*.log
{
    weekly
    rotate 4
    compress
    copytruncate
    delaycompress
    notifempty
    missingok
}

I prefer copytruncate for syslog here because it keeps the active file handle intact while rotation happens.

Google Cloud Exporter

Once the pipeline is configured, the audit records become searchable directly from Google Cloud Log Explorer. At this point the audit records become significantly easier to work with compared to raw syslog parsing.

You can now filter directly using fields such as: database name, database user, action type, client program etc. This becomes especially useful for investigations and compliance reporting.

resource.type="gce_instance"
labels.log_type="oracle_audit"
jsonPayload.oracle_sid="DB1"

Syslog vs Structured NDJSON Export

I still think both pipelines are useful. They just solve different operational problems.

FeatureSyslogNDJSON Export
Realtime VisibilityYesNear-Realtime
SQLText & other fields Capture NoYes
FlexibilityNo control over which Extremely customizable.
ASM Audit SupportYesNo
Setup ComplexityLowMedium

Conclusion

Syslog has its own place. For lightweight real-time monitoring and centralized visibility, it works well, and I would still use it. But once we needed richer audit analytics, structured search, and complete SQL payloads, the JSON export approach became much easier to work with.

What I liked about this setup was that it did not need anything too fancy. The setup mostly uses:

  • native Oracle functionality
  • standard Linux tooling
  • Google Cloud Ops Agent
  • Cloud Logging

That said, this is not perfect.

I am still not very comfortable with audit data sitting in JSON files without file-level encryption, even if the files are only used as temporary handoff files. Also, this is not truly real-time. There will always be some delay based on the export schedule and agent ingestion.

But for now, this gives us a middle ground.

And together with the previous blogs in this series, this completes a practical end-to-end approach for transmitting Oracle Unified Audit records into an external cloud logging platform.


Discover more from oratrails-aish

Subscribe to get the latest posts sent to your email.