Try it free

Upgrade from classic audit logs to Grail-based audit queries

  • Latest Dynatrace
  • Upgrade guide
  • 10-min read
  • Published Aug 19, 2026

Dynatrace Classic retrieves audit events through the Environment API's v2/auditlogs endpoint. In Latest Dynatrace, audit events are stored in Grail as dt.system.events, queryable with Dynatrace Query Language (DQL) alongside your logs, metrics, and traces. This guide walks you through inventorying the integrations that call v2/auditlogs, translating their filter logic to DQL, and retiring the classic API calls once the DQL replacements are validated.

Why upgrade?

  • Unified querying: Audit events sit alongside logs, metrics, and traces in Grail, so a single DQL query can correlate an audit event with a trace, a log line, or a metric from the same time window.
  • Richer field set: Grail audit events carry fields not available in the classic API, including token identifiers, OAuth grant types, browser session IDs, and full before and after state for configuration changes.
  • Token-level audit filtering: The authentication.token field lets you filter all API calls made by a specific credential, with no classic API equivalent.
  • Longer retention: Audit events are retained for one year in Grail, compared to 30 days in the classic API.
  • Always on: Grail audit logging can't be disabled, regardless of the classic audit log setting.

What is not changing?

  • Audit events remain immutable. Like the classic API, Grail audit events can't be changed after they're recorded.
  • The Account Management audit log (IAM changes, SSO configuration, budget changes) stays a separate system in both models. Neither v2/auditlogs nor dt.system.events captures it.

What will you do?

In this upgrade guide, you'll:

  1. Inventory the integrations calling v2/auditlogs.
  2. Classify each integration.
  3. Translate each integration's filter logic to DQL, using the mapping in this guide.
  4. Validate the DQL replacement against the original results.
  5. Remove the classic API calls once validated.

Before you begin

Prerequisites

To complete this upgrade guide, you need:

  • IAM permission policy statements granting ALLOW storage:system:read WHERE storage:event.kind="AUDIT_EVENT" and ALLOW storage:buckets:read WHERE storage:bucket-name="dt_system_events".
  • An existing classic access token with the auditLogs.read scope, to inventory current v2/auditlogs callers.
  • A platform token, to call the Grail Query API.

Prior knowledge

  • Basic familiarity with DQL
  • Familiarity with where your integrations call v2/auditlogs from: scripts, CI/CD pipelines, scheduled exports, or SIEM tools

Breaking changes

The following table summarizes breaking changes when upgrading to Latest Dynatrace audit queries.

ChangeRequired action

v2/auditlogs isn't available in latest environments

Complete the migration to DQL before your environment reaches the latest state.

Filter syntax changes from URL query parameters to DQL pipeline commands

Translate each integration's filter= expression using the mapping in Translate filter logic to DQL.

Response field names and formats differ (for example, success changes from a boolean to a string outcome)

Update any code that parses the API response to use the new field names and formats.

New concepts

dt.system.events

The Grail data object holding all audit and system events. Every DQL query for audit events must filter to event.kind == "AUDIT_EVENT", since dt.system.events also holds non-audit system events.

Grail Query API

The API used to run DQL queries programmatically, either synchronously, with results returned inline, or asynchronously, by polling for results with a request token.

Hybrid environment

An environment with both classic and latest functionality. v2/auditlogs and Grail-based audit queries are both available and can run in parallel. This is the most common state for environments that are migrating to Latest Dynatrace functionality.

How to upgrade

1. Inventory integrations that call v2/auditlogs

Before making any changes, identify every integration, script, or tool calling v2/auditlogs. Compliance and SIEM integrations often run on monthly or quarterly schedules. Without an inventory, a periodic integration can break silently, weeks after migration completes, by which point the classic API may no longer be available for rollback.

  1. Calls to v2/auditlogs are themselves recorded as audit events in Grail, so query dt.system.events directly to see who calls the endpoint, from where, and how often.

    fetch dt.system.events, from: now()-30d
    | filter event.kind == "AUDIT_EVENT"
    | filter event.provider == "API_GATEWAY"
    | filter contains(resource, "/api/v2/auditlogs")
    | fields timestamp, user.name, user.id, origin.address, resource
    | sort timestamp desc

    Here's an example query to group results by caller instead of listing every call:

    fetch dt.system.events, from: now()-30d
    | filter event.kind == "AUDIT_EVENT"
    | filter event.provider == "API_GATEWAY"
    | filter contains(resource, "/api/v2/auditlogs")
    | summarize calls = count(), last_seen = max(timestamp), by: {user.id, origin.address}
    | sort last_seen desc

For each integration you find, document the full URL it calls (including query parameters), what it does with the data, who owns it and where it runs, and how frequently it runs. Record where you stored the inventory in your migration ticket.

Once you complete the inventory, you will have a documented list of every integration that calls v2/auditlogs, including ownership and run frequency.

2. Classify each integration

Classify before acting. Compliance-driven integrations often look idle between runs, so an apparently orphaned integration may be waiting for its next scheduled cycle. The following table provides a guideline for how to classify each integration based on the inventory from step 1.

ClassificationSignal

Actively used

Runs at least weekly; last run within 30 days

Periodic or scheduled

Runs monthly, quarterly, or per audit cycle

Orphaned or unknown

No identified owner; no recent evidence of use

These thresholds are examples, not rules. Review them against the real run cadence of each integration, especially compliance-driven workflows that may run monthly, quarterly, or less often.

3. Translate filter logic to DQL

Every DQL query for audit events starts from the same base.

fetch dt.system.events, from: <time-range>
| filter event.kind == "AUDIT_EVENT"
| filter <your-criteria>
| sort timestamp desc
| limit <page-size>

The from: time range replaces the classic from/to parameters, | filter lines replace the classic filter= expression, and | limit replaces pageSize.

  1. Translate each classic filter= expression using this mapping.

    Classic filter= expressionDQL | filter line

    category("CONFIG")

    event.provider == "SETTINGS"

    category("TOKEN")

    event.provider == "API_GATEWAY" and contains(resource, "/tokens")

    eventType("CREATE")

    event.type == "POST"

    eventType("UPDATE")

    event.type == "PUT" or event.type == "PATCH"

    eventType("DELETE")

    event.type == "DELETE"

    user("email@example.com")

    user.id == "<user-uuid>". Resolve email to UUID with the IAM API first; see How do I resolve a user UUID to a profile? in the FAQ.

    dt.settings.schema_id("builtin:...")

    details.dt.settings.schema_id == "builtin:..."

    from=now-7d

    from: now()-7d on the fetch line

    pageSize=1000

    | limit 1000

    Classic eventType values don't map one to one to Grail event.type values; see the field mapping below for the complete picture.

  2. Translate the response fields your integration reads, using the field mapping between the classic API response and Grail DQL.

    Classic API fieldGrail/DQL fieldNotes

    logId

    event.id

    Stable unique identifier

    timestamp

    timestamp

    Direct equivalent

    user (email)

    user.name

    May not be populated in every environment; see the FAQ

    user (UUID)

    user.id

    Primary user identifier in Grail

    eventType

    event.type

    Value mapping differs; see the filter mapping above

    category

    event.provider

    Partial mapping; see the filter mapping above

    entityId

    resource

    Roughly equivalent; value format may differ

    success

    event.outcome

    Format differs: classic uses a boolean, Grail a string ("200", "success", "failure")

    patch

    details.json_before and details.json_after

    Format differs: classic uses a diff, Grail stores full before and after states

    To call DQL from a script or integration, use the Grail Query API.

    POST https://{your-environment-id}.apps.dynatrace.com/platform/storage/query/v1/query:execute

    Here's an example curl call:

    curl -s -X POST \
    -H "Authorization: Bearer <your-platform-token>" \
    -H "Content-Type: application/json" \
    -d '{
    "query": "fetch dt.system.events | filter event.kind == \"AUDIT_EVENT\" | sort timestamp desc | limit 1000",
    "defaultTimeframeStart": "now()-7d",
    "defaultTimeframeEnd": "now()",
    "fetchTimeoutMilliseconds": 60000
    }' \
    "https://{your-environment-id}.apps.dynatrace.com/platform/storage/query/v1/query:execute"

    This synchronous call is a good fit for queries expected to finish within about 30 seconds. The default timeout is 300,000 ms (five minutes) and the maximum is 3,600,000 ms (1 hour). For large result sets, submit the same query and poll for results using the requestToken returned in the response, with GET .../query:poll?requestToken={requestToken}.

For each integration in your inventory, you now have a DQL query that reproduces its classic filter= logic and reads the equivalent response fields.

4. Validate before retiring classic API calls

For each migrated integration, confirm all of the following before removing the classic API call.

  1. Confirm the DQL query returns data for the expected time range and event types, and that the results match the expected volume and content from the classic API. Running both the classic API call and the DQL query in parallel during a validation window is a valid way to compare results directly before switching over.

  2. Confirm the platform token works end-to-end, with no HTTP 401 or 403 responses, and that it has the minimum required Grail permissions. If it doesn't, see Why does my DQL query return a 403?.

  3. Confirm every location where the classic API call exists (scripts, CI/CD pipeline variables, configuration files, and scheduled jobs) has been updated.

Don't remove classic API calls until the DQL replacement is confirmed working.

5. Remove classic API calls

Once the DQL replacement is confirmed working:

  1. Update the integration to call the Grail Query API instead of v2/auditlogs.

  2. Wait at least one full job cycle before confirming retirement. For a monthly compliance export, that means waiting the full month.

  3. Revoke the classic access token if it was used exclusively for audit log access. Disable it first, wait through at least one job cycle, then delete it using its unique ID, not its name.

Migration completed

You've completed the migration when:

  • Every integration in your inventory queries dt.system.events in Grail instead of calling v2/auditlogs.
  • Any classic access tokens used only for audit log access have been retired.

FAQ

Do I need to re-export historical data before migrating?

No, for data already exported to external systems: this guide covers how new data is retrieved going forward. If you need classic API audit events from before your environment moved to Grail, contact Dynatrace Support. Once the classic API is removed, that data can't be recovered from Grail.

How do I resolve a user UUID to a profile?

Grail audit events include both user.id (the UUID) and user.name (the email address), but whether user.name is always populated depends on your environment configuration. If it's missing, resolve the UUID with the IAM API.

GET https://api.dynatrace.com/iam/v1/accounts/{accountUuid}/users/{userId}

This requires the ALLOW iam:users:read permission.

Is there a separate audit log for account-level changes?

Yes. This guide covers the environment audit log: API calls and configuration changes within a specific environment (v2/auditlogs in classic, dt.system.events in Grail). IAM changes, group permissions, SSO configuration, and budget changes are captured separately in the Account Management audit log, accessed through the Account Management UI or the Account Audits API with the account-audit-logs-read scope, and retained for 10 years. Neither system captures changes to OAuth tokens.

Why does my DQL query return a 403?

Either the platform token doesn't have the required Grail permission statements, or the user or service user it belongs to doesn't have those permissions. Both must be in place; add the scoped policy statements from Prerequisites.

The built-in policies "Storage Default Monitoring Read," "Read System Events," and "All Grail data read access" each include an unconditional storage:system:read grant, which overrides any narrower WHERE clause in another policy. A user holding any of these sees all system events, not only audit events, regardless of a more restrictive policy also assigned to them. To enforce least-privilege audit access, make sure users in that role don't hold any of these built-in policies.

Does disabling the classic audit log setting affect Grail?

No. The classic audit log feature (Settings > Preferences > Log audit events) only controls whether data flows into v2/auditlogs. It has no effect on Grail, which is always on and stores audit events regardless of the classic setting.

Related topics

  • Audit logs via API
  • Audit logs on Grail
  • Audit logs API - GET audit log
Related tags
Log Analytics