Dynatrace Query Language Tutorial: Complete Step-by-Step DQL Guide from Basics to Advanced Observability Queries

Dynatrace Query Language, or DQL, is the query language used to explore, filter, aggregate, correlate, and visualize data stored in Dynatrace Grail. Dynatrace describes DQL as a language for exploring data, discovering patterns, identifying anomalies/outliers, and building analysis on top of Grail data. (docs.dynatrace.com)

This guide is designed for hands-on learning in the Dynatrace Playground, especially from:

Dynatrace Playground → Notebooks → Add → DQL

You can copy/paste most examples directly.


1. Where to run DQL in Dynatrace

You can run DQL in multiple Dynatrace places.

Option 1: Notebooks — best place for learning

Use this for practice, training, investigation, and experiments.

Steps:

1. Open Dynatrace Playground
2. Go to Apps
3. Open Notebooks
4. Click Notebook or Create new notebook
5. Open Add menu
6. Select DQL
7. Paste query
8. Click Run

Dynatrace’s own tutorial recommends Notebooks for running DQL examples: go to Notebooks, create a notebook, open Add, select DQL, enter your query, and click Run. (docs.dynatrace.com)

Option 2: Dashboards — best place for reusable charts

Use this when you want to convert your DQL into a dashboard tile.

Steps:

1. Open Dynatrace Playground
2. Go to Dashboards
3. Open or create a dashboard
4. Click Add > DQL
5. On Data tab, paste query
6. Click Run
7. Go to Visual tab
8. Choose Table, Line chart, Bar chart, Single value, etc.

Dynatrace dashboards support Add > DQL, where the Data tab is used to define the query, Run executes it, and the Visual tab controls the result format. (docs.dynatrace.com)

Option 3: Logs app — best for log investigation

Use this when your main target is logs.

Typical flow:

1. Go to Logs
2. Use filter/search
3. Switch to advanced DQL/edit DQL query mode if available
4. Run query

Dynatrace documentation describes testing DQL matchers from Logs by going to Logs, opening the actions menu next to the filter field, selecting Edit DQL query, entering the DQL query, and running it. (docs.dynatrace.com)

Option 4: Documentation “Run in Playground”

Many Dynatrace documentation examples include Run in Playground. That is very useful because it opens a ready-made query in the Playground.


2. DQL mental model

DQL is a read-only, pipeline-based query language. A DQL query contains one or more commands, and each command passes records to the next command using the pipe operator |. Dynatrace explains that each command returns tabular output: records are rows, fields are columns, and command order matters for both results and performance. (docs.dynatrace.com)

Think like this:

Load data → filter data → select fields → transform → aggregate → visualize

Mermaid view of DQL pipeline

flowchart LR
    A[fetch / data / timeseries] --> B[filter / search]
    B --> C[fields / fieldsAdd / fieldsRemove]
    C --> D[parse / fieldsFlatten]
    D --> E[summarize / makeTimeseries]
    E --> F[sort / limit]
    F --> G[Table / Line chart / Bar chart / Single value]

Basic DQL example:

fetch logs
| filter loglevel == "ERROR"
| summarize error_count = count()

Expected result:

One-row table with error_count.

3. DQL command categories

Dynatrace groups DQL commands into categories such as data source commands, metric commands, filter/search commands, selection/modification commands, parsing commands, ordering commands, structuring commands, aggregation commands, correlation/join commands, and Smartscape commands. (docs.dynatrace.com)

CategoryImportant commandsPurpose
Data sourcefetch, data, describe, fieldsSnapshotLoad or inspect data
Metricstimeseries, metricsQuery metric data
Filter/searchfilter, filterOut, search, dedupReduce records
Selectionfields, fieldsAdd, fieldsRemove, fieldsRenameControl columns
ParsingparseExtract structured data
Orderingsort, limitOrder/restrict output
StructuringfieldsFlatten, expandWork with nested records/arrays
Aggregationsummarize, makeTimeseries, fieldsSummaryGroup and calculate
Correlationjoin, lookup, appendCombine datasets
SmartscapesmartscapeNodes, smartscapeEdges, traverseEntity topology

4. First safe command: data

Before using real Dynatrace logs, learn with data.

The data command creates sample records during query runtime. It is useful for testing and documentation because it does not depend on whether your Playground has logs/spans/events at that exact time. (docs.dynatrace.com)

Run this in Notebooks

data record(service = "checkout", status = 200, duration_ms = 120),
     record(service = "checkout", status = 500, duration_ms = 900),
     record(service = "payment", status = 200, duration_ms = 300),
     record(service = "payment", status = 503, duration_ms = 1500)

Expected result:

A table with four rows:
service | status | duration_ms

This is your safest learning playground inside the Dynatrace Playground.


5. Load real data with fetch

The fetch command loads data from a Dynatrace/Grail data object. The simplest example is:

fetch logs

Dynatrace documents fetch logs as the simplest form of the fetch command, and fetch supports parameters such as from, to, timeframe, bucket, samplingRatio, and scanLimitGBytes. (docs.dynatrace.com)

Common data objects

Data objectQueryUse case
Logsfetch logsApplication, infrastructure, Kubernetes, process logs
Eventsfetch eventsSystem/security/custom events
Business eventsfetch bizeventsBusiness transactions and custom business data
Spansfetch spansDistributed tracing/span-level analysis
Problemsfetch dt.davis.problemsDavis problem analysis

Run: recent logs

fetch logs
| limit 20

Expected result:

Up to 20 log records.

If you see no result, change the Notebook timeframe to a wider window, such as Last 24 hours, or use an explicit timeframe:

fetch logs, from:-24h
| limit 20

Run: recent spans

fetch spans, from:-24h
| limit 20

Expected result:

Up to 20 distributed trace span records.

If no result appears, the Playground dataset/timeframe may not contain spans in that window.


6. Understand timeframe

There are two ways to control time.

Method 1: UI timeframe

Use the time selector in the top-right of Notebook/Dashboard.

Good for beginners.

Method 2: DQL timeframe

Use from, to, or timeframe.

Dynatrace recommends using the UI timeframe controls, but DQL can override them using from, to, or timeframe. If no timeframe is specified in the app/API, Dynatrace notes a default timeframe behavior; the documentation example also shows relative and absolute timeframes. (docs.dynatrace.com)

Last 2 hours

fetch logs, from:now() - 2h
| limit 20

Last 24 hours, excluding last 2 hours

fetch logs, from:now() - 24h, to:now() - 2h
| limit 20

Shorter syntax

fetch logs, from:-2h
| limit 20

Absolute timeframe

fetch logs, timeframe:"2026-05-28T00:00:00Z/2026-05-29T00:00:00Z"
| limit 20

Expected result:

Records only from the specified time window.

7. Select fields with fields

Raw records can contain many fields. Use fields to keep only the fields you want.

Dynatrace lists fields under selection/modification commands and describes it as keeping only specified fields. (docs.dynatrace.com)

Run

fetch logs, from:-2h
| fields timestamp, loglevel, log.source, content
| limit 20

Expected result:

A cleaner table with only timestamp, loglevel, log.source, and content.

With sample data

data record(service = "checkout", status = 200, duration_ms = 120),
     record(service = "checkout", status = 500, duration_ms = 900),
     record(service = "payment", status = 503, duration_ms = 1500)
| fields service, status

Expected result:

Only service and status columns.

8. Add calculated fields with fieldsAdd

fieldsAdd evaluates an expression and appends or replaces a field. (docs.dynatrace.com)

Run

data record(service = "checkout", status = 200, duration_ms = 120),
     record(service = "checkout", status = 500, duration_ms = 900),
     record(service = "payment", status = 503, duration_ms = 1500)
| fieldsAdd is_error = status >= 500

Expected result:

New column is_error:
false
true
true

Add severity category

data record(service = "checkout", status = 200, duration_ms = 120),
     record(service = "checkout", status = 500, duration_ms = 900),
     record(service = "payment", status = 503, duration_ms = 1500)
| fieldsAdd severity = if(status >= 500, "error", else:"ok")

Expected result:

New severity column:
ok
error
error

9. Sort and limit

Use sort to order results and limit to reduce result size.

Dynatrace documents sort as sorting records and limit as limiting the number of returned records. (docs.dynatrace.com)

Sort sample records

data record(service = "checkout", status = 200, duration_ms = 120),
     record(service = "checkout", status = 500, duration_ms = 900),
     record(service = "payment", status = 503, duration_ms = 1500),
     record(service = "catalog", status = 200, duration_ms = 80)
| sort duration_ms desc
| limit 2

Expected result:

Top 2 slowest records.

Logs sorted latest first

fetch logs, from:-2h
| fields timestamp, loglevel, content
| sort timestamp desc
| limit 20

Expected result:

20 latest log records.

Performance note: Dynatrace recommends putting sort near the end of the query rather than sorting immediately after fetch, because sorting early can reduce performance. (docs.dynatrace.com)


10. Filter data with filter

filter keeps only records matching a condition. Dynatrace describes filter as reducing the number of records by keeping only records that match the specified condition. (docs.dynatrace.com)

Equality filter

data record(service = "checkout", status = 200),
     record(service = "checkout", status = 500),
     record(service = "payment", status = 503)
| filter service == "checkout"

Expected result:

Only records where service is checkout.

Not equal

data record(service = "checkout", status = 200),
     record(service = "checkout", status = 500),
     record(service = "payment", status = 503)
| filter service != "payment"

Expected result:

Everything except payment.

Greater than / less than

data record(service = "checkout", duration_ms = 120),
     record(service = "payment", duration_ms = 1500),
     record(service = "catalog", duration_ms = 80)
| filter duration_ms > 500

Expected result:

Only slow records above 500 ms.

Multiple conditions

data record(service = "checkout", status = 200, duration_ms = 120),
     record(service = "checkout", status = 500, duration_ms = 900),
     record(service = "payment", status = 503, duration_ms = 1500)
| filter status >= 500 and duration_ms > 1000

Expected result:

Only payment 503 with duration 1500.

OR condition

data record(service = "checkout", status = 200),
     record(service = "checkout", status = 500),
     record(service = "payment", status = 503)
| filter status == 500 or status == 503

Expected result:

Only 500 and 503 records.

11. String filtering: contains, startsWith, endsWith

DQL functions like contains, startsWith, and endsWith are useful for string filtering. Dynatrace lists string functions including contains, endsWith, matchesPhrase, parse, startsWith, substring, and others. (docs.dynatrace.com)

contains

data record(message = "Payment failed due to timeout"),
     record(message = "Checkout completed"),
     record(message = "Database connection failed")
| filter contains(message, "failed")

Expected result:

Rows where message contains failed.

startsWith

data record(path = "/api/orders"),
     record(path = "/admin/login"),
     record(path = "/api/payments")
| filter startsWith(path, "/api")

Expected result:

/api/orders
/api/payments

endsWith

data record(file = "app.log"),
     record(file = "audit.log"),
     record(file = "error.txt")
| filter endsWith(file, ".log")

Expected result:

app.log
audit.log

Real log example

fetch logs, from:-2h
| filter loglevel == "ERROR" and contains(content, "Exception")
| fields timestamp, loglevel, content
| limit 20

Expected result:

ERROR logs where content contains Exception.

12. Search with search

Use search when you want a simple search-bar style query.

Dynatrace says the search command works like a search bar in DQL, can search across all fields or specific fields, and performs case-insensitive string matching. (docs.dynatrace.com)

Search all fields

fetch logs, from:-2h
| search "timeout"
| fields timestamp, loglevel, content
| limit 20

Expected result:

Logs containing timeout in any searchable field.

Search specific field

fetch logs, from:-2h
| search content ~ "timeout"
| fields timestamp, loglevel, content
| limit 20

Expected result:

Logs where content matches timeout.

Search with wildcard

fetch logs, from:-2h
| search content ~ "time*"
| fields timestamp, loglevel, content
| limit 20

Expected result:

Logs where content has token beginning with time.

Difference between filter and search

Featurefiltersearch
Best forPrecise conditionsText search
Case behaviorDepends on operator/functionCase-insensitive search behavior
Examplefilter loglevel == "ERROR"search "error"
Field-specificYesYes, with field ~ "term"
Good forStatus, duration, service, entityKeyword investigation

Use filter when you know the field and exact condition. Use search when you are exploring.


13. Exclude data with filterOut

filterOut removes records that match a condition. Dynatrace notes one important difference: filterOut x keeps records where x is null, while filter not x removes records where x is null. (docs.dynatrace.com)

Run

data record(service = "checkout", loglevel = "INFO"),
     record(service = "checkout", loglevel = "ERROR"),
     record(service = "payment", loglevel = "WARN")
| filterOut loglevel == "INFO"

Expected result:

ERROR and WARN records only.

Real log example

fetch logs, from:-2h
| filterOut loglevel == "INFO" or loglevel == "NONE"
| fields timestamp, loglevel, content
| limit 20

Expected result:

Logs except INFO/NONE.

14. Remove duplicates with dedup

dedup removes duplicate records based on one or more fields. Dynatrace explains that dedup removes duplicate values, while summarize groups records and aggregates them. (docs.dynatrace.com)

Run

data record(service = "checkout", status = 200),
     record(service = "checkout", status = 500),
     record(service = "payment", status = 503),
     record(service = "payment", status = 200)
| dedup service

Expected result:

One row per service.

Keep latest per service

data record(timestamp = 1, service = "checkout", status = 200),
     record(timestamp = 2, service = "checkout", status = 500),
     record(timestamp = 3, service = "payment", status = 503),
     record(timestamp = 4, service = "payment", status = 200)
| dedup service, sort:{timestamp desc}

Expected result:

Latest record per service.

15. Understand DQL data types

DQL is strongly typed. Functions and operators expect specific declared data types, and types are assigned during parsing or casting. (docs.dynatrace.com)

Important types:

TypeExampleUse
string"checkout"Text
long500Integer
double99.95Decimal
booleantrue, falseConditions
timestampnow()Point in time
duration2h, 500msTime duration
timeframetimeframe(from:..., to:...)Start/end window
arrayarray(1,2,3)List
recordrecord(name="app")Nested object
ipIP addressNetwork fields
uidEntity/problem IDsUnique identifiers

Dynatrace documents primitive types such as Boolean, Long, Double, Timestamp, Timeframe, Duration, String, IP address, UID, and complex types like Array and Record. (docs.dynatrace.com)

Check type using type()

Dynatrace lists type as a conversion/type function that returns the type of a value as a string. (docs.dynatrace.com)

data record(service = "checkout", status = 500, duration_ms = 900)
| fields service_type = type(service),
         status_type = type(status),
         duration_type = type(duration_ms)

Expected result:

service_type  = string
status_type   = long
duration_type = long

Casting examples

data record(status_text = "500", duration_text = "120.5", enabled_text = "true")
| fields status_num = toLong(status_text),
         duration_num = toDouble(duration_text),
         enabled_bool = toBoolean(enabled_text)

Expected result:

status_num as long
duration_num as double
enabled_bool as boolean

16. Functions in DQL

Functions transform, test, calculate, or format values.

Dynatrace groups DQL functions into categories such as aggregation, string, conversion/type, conditional, boolean, time, array, mathematical, join, and general functions. (docs.dynatrace.com)

Important function categories

mindmap
  root((DQL Functions))
    Aggregation
      count
      avg
      sum
      min
      max
      percentile
      countIf
    String
      contains
      startsWith
      endsWith
      lower
      concat
      matchesPhrase
      parse
    Type conversion
      toLong
      toDouble
      toString
      toTimestamp
      type
    Conditional
      if
      coalesce
    Boolean
      isNull
      isNotNull
      exists
    Time
      now
      formatTimestamp
      getHour
      getDayOfWeek
    Array
      arrayAvg
      arrayMax
      arraySize
      arrayFirst
    Entity
      entityName
      entityAttr

Conditional function: if

data record(service = "checkout", status = 200),
     record(service = "payment", status = 503)
| fieldsAdd result = if(status >= 500, "failed", else:"success")

Expected result:

checkout → success
payment  → failed

Null handling: coalesce

data record(service = "checkout", owner = "team-a"),
     record(service = "payment")
| fieldsAdd owner_safe = coalesce(owner, "unknown")

Expected result:

checkout → team-a
payment  → unknown

Time function

data record(service = "checkout", timestamp = now())
| fields timestamp,
         hour = formatTimestamp(timestamp, format:"HH"),
         day = formatTimestamp(timestamp, format:"EE")

Expected result:

timestamp plus formatted hour/day columns.

17. Aggregation with summarize

summarize groups records and calculates values.

Dynatrace describes summarize as grouping records that have the same field values and aggregating them. (docs.dynatrace.com)

Count all records

data record(service = "checkout", status = 200),
     record(service = "checkout", status = 500),
     record(service = "payment", status = 503)
| summarize total = count()

Expected result:

total = 3

Count by service

data record(service = "checkout", status = 200),
     record(service = "checkout", status = 500),
     record(service = "payment", status = 503)
| summarize total = count(), by:{service}

Expected result:

checkout = 2
payment  = 1

Count failures by service

data record(service = "checkout", status = 200),
     record(service = "checkout", status = 500),
     record(service = "payment", status = 503),
     record(service = "payment", status = 200)
| summarize failures = countIf(status >= 500), total = count(), by:{service}

Expected result:

checkout → failures 1, total 2
payment  → failures 1, total 2

Average and percentile

data record(service = "checkout", duration_ms = 120),
     record(service = "checkout", duration_ms = 900),
     record(service = "checkout", duration_ms = 1500),
     record(service = "payment", duration_ms = 300),
     record(service = "payment", duration_ms = 2000)
| summarize avg_duration = avg(duration_ms),
            p95_duration = percentile(duration_ms, 95),
            max_duration = max(duration_ms),
            requests = count(),
  by:{service}
| sort p95_duration desc

Expected result:

One row per service with average, p95, max, and request count.

Aggregation functions include avg, count, countIf, max, median, min, percentile, sum, and several more. (docs.dynatrace.com)


18. Create time charts with makeTimeseries

Use makeTimeseries when you start from raw records such as logs, events, or spans and want to aggregate them over time.

Dynatrace says DQL provides commands like makeTimeseries to aggregate raw event records into chartable time series. (docs.dynatrace.com)

Error logs over time

fetch logs, from:-2h
| filter loglevel == "ERROR" or loglevel == "SEVERE"
| makeTimeseries errors = count(), by:{loglevel}, interval:5m

Expected result:

A time series grouped by loglevel.

In Notebooks or Dashboards, choose:

Visualization → Line chart

Sample data with timestamps

data record(timestamp = now() - 20m, service = "checkout", status = 500),
     record(timestamp = now() - 15m, service = "checkout", status = 200),
     record(timestamp = now() - 10m, service = "payment", status = 503),
     record(timestamp = now() - 5m, service = "payment", status = 200)
| filter status >= 500
| makeTimeseries failures = count(), by:{service}, interval:5m

Expected result:

Time series of failures by service.

19. Query metrics with timeseries

Use timeseries for metric data.

Dynatrace describes timeseries as a starting DQL command that combines loading, filtering, and aggregating metric data into a time series output. (docs.dynatrace.com)

Average CPU usage

timeseries usage = avg(dt.host.cpu.usage)

Expected result:

Time series showing average CPU usage.

Visualization:

Line chart

CPU by host

timeseries usage = avg(dt.host.cpu.usage), by:{dt.entity.host}

Expected result:

One CPU usage series per host.

p99 service response time

timeseries p99 = percentile(dt.service.request.response_time, 99),
by:{dt.entity.service}
| limit 5

Expected result:

p99 response time series for up to 5 services.

Dynatrace documents metric aggregation functions for timeseries, including sum, avg, min, max, count, percentile, and countDistinct. (docs.dynatrace.com)


20. Explore available metrics with metrics

Use metrics to discover metric keys and dimensions.

Dynatrace explains that metrics retrieves metric series for exploring metric keys, dimension keys, and values, but recommends timeseries for charting/calculations because metrics does not return timestamps or time-series values. (docs.dynatrace.com)

Run

metrics
| limit 20

Expected result:

Metric metadata rows.

Find CPU metrics

metrics
| filter contains(metric.key, "cpu")
| dedup metric.key
| sort metric.key asc
| limit 50

Expected result:

List of metric keys containing cpu.

21. Parse logs with parse

Use parse when your log content is text/JSON and you need to extract fields.

Dynatrace describes parse as parsing a record field into one or more fields using a pattern, and notes that it works with Dynatrace Pattern Language. (docs.dynatrace.com)

Parse JSON from sample data

data record(content = "{\"service\":\"checkout\",\"status\":500,\"duration_ms\":900}"),
     record(content = "{\"service\":\"payment\",\"status\":200,\"duration_ms\":300}")
| parse content, "JSON:json"
| fields json[service], json[status], json[duration_ms]

Expected result:

Extracted service, status, and duration_ms from JSON.

Flatten parsed JSON

data record(content = "{\"service\":\"checkout\",\"status\":500,\"duration_ms\":900}"),
     record(content = "{\"service\":\"payment\",\"status\":200,\"duration_ms\":300}")
| parse content, "JSON:json"
| fieldsFlatten json

Expected result:

Fields from JSON become normal table columns.

Dynatrace documents fieldsFlatten as extracting/flattening fields from a nested record. (docs.dynatrace.com)

Real log JSON parsing

fetch logs, from:-2h
| filter contains(content, "{")
| parse content, "JSON:json"
| fields timestamp, loglevel, json
| limit 20

Expected result:

Parsed JSON records, if your logs contain JSON.

If parsing fails, the log may not be valid JSON or may have prefix/suffix text.


22. Flatten nested records with fieldsFlatten

Use this after parsing JSON or when records contain nested structures.

data record(payload = record(service = "checkout", status = 500, duration_ms = 900)),
     record(payload = record(service = "payment", status = 200, duration_ms = 300))
| fieldsFlatten payload, prefix:"app."

Expected result:

app.service
app.status
app.duration_ms

23. Work with arrays

Arrays appear in timeseries results, JSON arrays, or custom records.

Dynatrace documents arrays as complex types containing a sequence of values identified by index. (docs.dynatrace.com)

Create and inspect array

data record(service = "checkout", durations = array(100, 200, 500, 900))
| fields service,
         first_value = durations[0],
         avg_value = arrayAvg(durations),
         max_value = arrayMax(durations)

Expected result:

first_value = 100
avg_value   = 425
max_value   = 900

Dynatrace lists array functions such as arrayAvg, arrayMax, arrayMin, arrayFirst, arrayLast, arrayDistinct, and others. (docs.dynatrace.com)


24. Add entity names with entityName

Dynatrace entity IDs are often not human-friendly. Use entityName() when available.

Dynatrace lists entityName as a general DQL function that returns the name of an entity. (docs.dynatrace.com)

Host CPU with host name

timeseries usage = avg(dt.host.cpu.usage), by:{dt.entity.host}
| fieldsAdd host_name = entityName(dt.entity.host)
| fields host_name, usage, timeframe, interval

Expected result:

CPU series with readable host_name.

25. Join and lookup basics

Use joins when you need to combine results from two datasets.

Dynatrace lists join, joinNested, lookup, and append as correlation/join commands. join joins records when source and subquery fulfill a join condition, while lookup adds fields from a subquery by matching source and lookup fields. (docs.dynatrace.com)

Simple lookup with sample data

data record(service = "checkout", owner_id = 1, errors = 10),
     record(service = "payment", owner_id = 2, errors = 5)
| lookup [
    data record(owner_id = 1, owner = "Team A"),
         record(owner_id = 2, owner = "Team B")
  ],
  sourceField:owner_id,
  lookupField:owner_id

Expected result:

Original records enriched with owner data.

Join sample data

data record(service = "checkout", errors = 10),
     record(service = "payment", errors = 5)
| join [
    data record(service = "checkout", requests = 1000),
         record(service = "payment", requests = 500)
  ],
  on:{service}

Expected result:

Errors joined with request counts by service.

26. Visualization views in Dynatrace

After running DQL in Notebooks or Dashboards, choose the right visualization.

Dynatrace lets you choose visualization types for dashboard tiles and notebook sections after adding a query. In Notebooks, the Visualization section displays visualization types; in Dashboards, the Visual tab is available after running the query. (docs.dynatrace.com)

Common views

ViewBest forExample query
TableRaw rows, grouped summary`fetch logs
Line chartTime trendtimeseries avg(dt.host.cpu.usage)
Bar chartTop N comparisonsummarize count(), by:{service}
Pie chartShare/distributionCount by status/loglevel
Single valueKPI/SLO numberError count, p95, CPU average
Markdown/TextNotes/documentationNotebook explanation

Line chart

Use a line chart for measurements and trends over time, comparing multiple series, or finding correlations between variables. Dynatrace’s line chart example uses timeseries avg(dt.host.cpu.usage). (docs.dynatrace.com)

timeseries avg_cpu = avg(dt.host.cpu.usage)

Expected visual:

Line chart of CPU over time.

Single value

Use single value for one important KPI, business metric, or SLO-type measurement. Dynatrace documents single value visualizations for aggregated measurements, KPIs, and infrastructure-related SLOs. (docs.dynatrace.com)

timeseries sparkline = avg(dt.host.cpu.usage)
| fieldsAdd value = arrayAvg(sparkline)

Expected visual:

Single value with optional sparkline.

27. Discover fields before writing queries

A very important beginner skill: first discover what fields exist.

Use fieldsSnapshot

fieldsSnapshot returns a snapshot of fields present in records for supported data objects such as logs, spans, smartscape.nodes, and metrics, and Dynatrace notes it does not scan raw data and does not produce consumption. (docs.dynatrace.com)

fieldsSnapshot logs
| sort relative_count desc
| limit 50

Expected result:

Top fields found in logs.

Fields by bucket and type

fieldsSnapshot logs, by:{dt.system.bucket, data_type}
| sort relative_count desc
| limit 100

Expected result:

Fields with bucket and data type information.

Find sparse fields

fieldsSnapshot logs, by:{dt.system.bucket}
| filter relative_count < 10
| sort relative_count asc
| limit 50

Expected result:

Fields present in fewer than 10% of log records.

Use describe

describe returns known fields and their data types for a data object. (docs.dynatrace.com)

describe bizevents

Expected result:

Known fields and data types for business events.

28. Beginner practice labs

Lab 1: Load logs

Where to run:

Notebooks → Add → DQL

Query:

fetch logs, from:-2h
| limit 20

Expect:

20 logs or fewer.

If no result:

fetch logs, from:-24h
| limit 20

Lab 2: Show important log columns

fetch logs, from:-2h
| fields timestamp, loglevel, log.source, content
| limit 20

Expect:

Clean log table.

Lab 3: Error logs only

fetch logs, from:-2h
| filter loglevel == "ERROR" or loglevel == "SEVERE"
| fields timestamp, loglevel, content
| sort timestamp desc
| limit 50

Expect:

Latest ERROR/SEVERE logs.

Lab 4: Search timeout logs

fetch logs, from:-24h
| search content ~ "timeout"
| fields timestamp, loglevel, content
| sort timestamp desc
| limit 50

Expect:

Logs containing timeout.

Lab 5: Count logs by log level

fetch logs, from:-24h
| summarize logs = count(), by:{loglevel}
| sort logs desc

Expect:

Table: loglevel | logs

Best visualization:

Bar chart or Pie chart

Lab 6: Errors over time

fetch logs, from:-24h
| filter loglevel == "ERROR" or loglevel == "SEVERE"
| makeTimeseries errors = count(), by:{loglevel}, interval:15m

Expect:

Time series of error count.

Best visualization:

Line chart

29. Intermediate practice labs

Lab 7: Top sources producing errors

fetch logs, from:-24h
| filter loglevel == "ERROR" or loglevel == "SEVERE"
| summarize errors = count(), by:{log.source}
| sort errors desc
| limit 10

Expect:

Top 10 log sources by error count.

Lab 8: Find repeated messages

fetch logs, from:-24h
| filter loglevel == "ERROR" or loglevel == "SEVERE"
| summarize occurrences = count(), by:{content}
| sort occurrences desc
| limit 20

Expect:

Most repeated error messages.

Warning:

content can be high-cardinality. Use carefully on large datasets.

Lab 9: Parse JSON logs

fetch logs, from:-24h
| filter contains(content, "{")
| parse content, "JSON:json"
| fields timestamp, loglevel, json
| limit 20

Expect:

Parsed JSON object if log content is JSON.

Lab 10: Flatten JSON logs

fetch logs, from:-24h
| filter contains(content, "{")
| parse content, "JSON:json"
| fieldsFlatten json
| limit 20

Expect:

JSON keys become fields.

30. Metrics practice labs

Lab 11: Average CPU usage

timeseries cpu = avg(dt.host.cpu.usage), from:-2h

Expect:

CPU usage time series.

Best visualization:

Line chart

Lab 12: CPU usage by host

timeseries cpu = avg(dt.host.cpu.usage), by:{dt.entity.host}, from:-2h
| fieldsAdd host = entityName(dt.entity.host)

Expect:

CPU series grouped by host.

Lab 13: Top hosts by average CPU

timeseries cpu = avg(dt.host.cpu.usage), by:{dt.entity.host}, from:-2h
| fieldsAdd host = entityName(dt.entity.host)
| fieldsAdd avg_cpu = arrayAvg(cpu)
| sort avg_cpu desc
| limit 10

Expect:

Top 10 hosts by average CPU.

Best visualization:

Table or Single value grid

Lab 14: Service response time p99

timeseries p99 = percentile(dt.service.request.response_time, 99),
by:{dt.entity.service},
from:-2h
| fieldsAdd service = entityName(dt.entity.service)
| limit 10

Expect:

p99 response time for services.

Note: Dynatrace documents that dt.service.request.response_time is reported in microseconds in its percentile examples, so be careful when interpreting units. (docs.dynatrace.com)


31. Distributed tracing/spans practice labs

Dynatrace’s trace/DQL tutorial says DQL can help identify inefficient database queries, workload patterns, abnormalities, and performance-analysis signals using traces and logs. (docs.dynatrace.com)

Lab 15: Show recent spans

fetch spans, from:-2h
| limit 20

Expect:

Recent span records.

Lab 16: Inspect span fields

fieldsSnapshot spans
| sort relative_count desc
| limit 50

Expect:

Common fields available in spans.

Lab 17: Slow spans

fetch spans, from:-2h
| filter duration > 1s
| fields timestamp, duration, span.name, dt.entity.service
| sort duration desc
| limit 20

Expect:

Slow span operations.

If span.name is not present in your environment, inspect fields first:

fieldsSnapshot spans
| filter contains(field, "span")
| limit 50

Lab 18: Failed spans

fetch spans, from:-2h
| filter request.is_failed == true
| summarize failed_spans = count(), by:{dt.entity.service}
| sort failed_spans desc
| limit 10

Expect:

Services with failed spans.

If request.is_failed is unavailable, use fieldsSnapshot spans and search for failure/error fields.


32. Davis problem analysis

Count problems in last 24 hours

fetch dt.davis.problems, from:now()-24h, to:now()
| summarize problemCount = countDistinct(event.id)

Dynatrace’s own DQL example for Dynatrace Intelligence uses fetch dt.davis.problems and countDistinct(event.id) to count distinct problems in the last 24 hours. (docs.dynatrace.com)

Expected result:

One number: problemCount.

33. Advanced: compare current period with past period

Dynatrace documents the shift parameter for timeseries, which lets you compare a metric series with another timeframe, such as the same period 7 days earlier. (docs.dynatrace.com)

CPU now vs 7 days ago

timeseries cpu_now = avg(dt.host.cpu.usage), by:{dt.entity.host}, from:-24h
| append [
    timeseries cpu_7d_ago = avg(dt.host.cpu.usage), by:{dt.entity.host}, shift:-7d, from:-24h
  ]

Expected result:

Current CPU series plus shifted CPU series from 7 days ago.

Best visualization:

Line chart

34. Advanced: calculate failure rate from logs

fetch logs, from:-24h
| fieldsAdd is_error = loglevel == "ERROR" or loglevel == "SEVERE"
| summarize total_logs = count(),
            error_logs = countIf(is_error)
| fieldsAdd error_rate_percent = toDouble(error_logs) / toDouble(total_logs) * 100

Expected result:

total_logs
error_logs
error_rate_percent

Best visualization:

Single value

35. Advanced: service health summary from sample data

This query is fully runnable because it uses data.

data record(service = "checkout", status = 200, duration_ms = 120),
     record(service = "checkout", status = 500, duration_ms = 900),
     record(service = "checkout", status = 503, duration_ms = 1500),
     record(service = "payment", status = 200, duration_ms = 300),
     record(service = "payment", status = 200, duration_ms = 400),
     record(service = "catalog", status = 500, duration_ms = 2000)
| summarize total_requests = count(),
            failed_requests = countIf(status >= 500),
            avg_duration = avg(duration_ms),
            p95_duration = percentile(duration_ms, 95),
  by:{service}
| fieldsAdd failure_rate_percent = toDouble(failed_requests) / toDouble(total_requests) * 100
| sort failure_rate_percent desc

Expected result:

Health summary per service.

This is a very good pattern for real service observability.


36. Advanced: log message classification

fetch logs, from:-24h
| fieldsAdd category = if(contains(content, "timeout"), "timeout",
                    else:if(contains(content, "connection"), "connection",
                    else:if(contains(content, "permission"), "permission",
                    else:"other")))
| summarize logs = count(), by:{category}
| sort logs desc

Expected result:

Logs grouped by message category.

Best visualization:

Bar chart or Pie chart

37. DQL query structure cheat sheet

flowchart TD
    A[Start Command] --> B{What kind of data?}
    B -->|Logs/Events/Spans| C[fetch logs/events/spans]
    B -->|Metrics| D[timeseries]
    B -->|Practice data| E[data record...]
    C --> F[filter/search/filterOut]
    D --> G[by/filter/from/to]
    E --> F
    F --> H[fields/fieldsAdd/parse]
    H --> I[summarize/makeTimeseries]
    I --> J[sort/limit]
    J --> K[Visualization]

38. Best practices for writing DQL

1. Start small

Bad for beginners:

fetch logs

Better:

fetch logs, from:-30m
| limit 20

2. Filter early

Good:

fetch logs, from:-2h
| filter loglevel == "ERROR"
| fields timestamp, loglevel, content
| limit 50

3. Sort late

Dynatrace recommends sorting near the end rather than immediately after fetch. (docs.dynatrace.com)

Better:

fetch logs, from:-2h
| filter loglevel == "ERROR"
| fields timestamp, loglevel, content
| sort timestamp desc
| limit 50

4. Use fields to reduce noisy output

fetch logs, from:-2h
| filter loglevel == "ERROR"
| fields timestamp, loglevel, content

5. Do not limit before aggregation unless intentional

Dynatrace’s best-practice guidance says to use summarize for tabular aggregation and makeTimeseries for time charts, and not to use limit before aggregation unless that is intentional because it can produce wrong aggregates. (docs.dynatrace.com)

Wrong:

fetch logs, from:-24h
| limit 100
| summarize count(), by:{loglevel}

Better:

fetch logs, from:-24h
| summarize logs = count(), by:{loglevel}
| sort logs desc

6. Discover fields before assuming names

fieldsSnapshot logs
| limit 100

7. Use data to learn syntax

data record(a = "DQL", b = 1)

8. Use timeseries for metrics, not fetch

For metrics, use:

timeseries avg(dt.host.cpu.usage)

Use metrics only to discover metric keys/dimensions. Dynatrace recommends timeseries for actual time-series analysis. (docs.dynatrace.com)


39. Common DQL mistakes and fixes

Mistake 1: No data returned

Possible causes:

Wrong timeframe
Wrong field name
No matching data in Playground
No permission for bucket/table
Wrong data object

Fix:

fetch logs, from:-24h
| limit 20

Then inspect fields:

fieldsSnapshot logs
| limit 100

Mistake 2: Field does not exist

Fix:

fieldsSnapshot logs
| filter contains(field, "service")
| limit 50

Mistake 3: Comparing string and number

Wrong:

data record(status = "500")
| filter status >= 500

Better:

data record(status = "500")
| fieldsAdd status_num = toLong(status)
| filter status_num >= 500

Mistake 4: Expecting search and filter to behave the same

Use search for text exploration:

fetch logs
| search "timeout"

Use filter for exact logic:

fetch logs
| filter loglevel == "ERROR"

Mistake 5: Trying to chart non-time data as line chart

This is table/bar-style data:

fetch logs, from:-24h
| summarize logs = count(), by:{loglevel}

This is line-chart data:

fetch logs, from:-24h
| makeTimeseries logs = count(), by:{loglevel}, interval:15m

40. Copy-ready command reference

Load

fetch logs
fetch logs, from:-2h
fetch events, from:-24h
fetch spans, from:-24h
fetch bizevents, from:-24h

Inspect

fieldsSnapshot logs
fieldsSnapshot spans
describe bizevents

Filter

fetch logs, from:-2h
| filter loglevel == "ERROR"
fetch logs, from:-2h
| filter loglevel != "INFO"
fetch logs, from:-2h
| filter contains(content, "Exception")

Search

fetch logs, from:-2h
| search "timeout"
fetch logs, from:-2h
| search content ~ "timeout"

Select fields

fetch logs, from:-2h
| fields timestamp, loglevel, content

Aggregate

fetch logs, from:-24h
| summarize logs = count(), by:{loglevel}

Time chart from logs

fetch logs, from:-24h
| makeTimeseries logs = count(), by:{loglevel}, interval:15m

Metrics

timeseries cpu = avg(dt.host.cpu.usage)
timeseries cpu = avg(dt.host.cpu.usage), by:{dt.entity.host}
metrics
| filter contains(metric.key, "cpu")
| dedup metric.key

Parse JSON

fetch logs, from:-24h
| filter contains(content, "{")
| parse content, "JSON:json"
| fields timestamp, json
| limit 20

Flatten JSON

fetch logs, from:-24h
| filter contains(content, "{")
| parse content, "JSON:json"
| fieldsFlatten json
| limit 20

41. Recommended learning path

Day 1: DQL basics

Practice:

data
fetch
pipe |
fields
limit
sort

Run:

data record(service = "checkout", status = 200),
     record(service = "payment", status = 500)
| fields service, status

Day 2: Filtering and searching

Practice:

filter
filterOut
search
contains
startsWith
endsWith
and/or/not

Run:

data record(message = "payment timeout", status = 500),
     record(message = "checkout success", status = 200)
| filter status >= 500 and contains(message, "timeout")

Day 3: Logs

Practice:

fetch logs
loglevel
content
log.source
timestamp

Run:

fetch logs, from:-24h
| filter loglevel == "ERROR"
| fields timestamp, loglevel, content
| sort timestamp desc
| limit 50

Day 4: Aggregation

Practice:

summarize
count
countIf
avg
percentile
by:{}

Run:

fetch logs, from:-24h
| summarize logs = count(), by:{loglevel}
| sort logs desc

Day 5: Time series

Practice:

makeTimeseries
timeseries
interval
by
line chart

Run:

fetch logs, from:-24h
| makeTimeseries logs = count(), by:{loglevel}, interval:15m

Day 6: Metrics

Practice:

timeseries
metrics
metric.key
entityName
arrayAvg

Run:

timeseries cpu = avg(dt.host.cpu.usage), by:{dt.entity.host}
| fieldsAdd host = entityName(dt.entity.host)
| fieldsAdd avg_cpu = arrayAvg(cpu)
| sort avg_cpu desc
| limit 10

Day 7: Parsing

Practice:

parse
JSON
fieldsFlatten
record
array

Run:

data record(content = "{\"service\":\"checkout\",\"status\":500,\"duration_ms\":900}")
| parse content, "JSON:json"
| fieldsFlatten json

Day 8: Spans and distributed tracing

Practice:

fetch spans
duration
service
failed requests
slow operations

Run:

fetch spans, from:-24h
| filter duration > 1s
| fields timestamp, duration, dt.entity.service
| sort duration desc
| limit 20

Day 9: Dashboards

Practice:

Add > DQL
Data tab
Visual tab
Table
Line chart
Single value

Create these dashboard tiles:

1. Error count by loglevel
2. Errors over time
3. CPU usage by host
4. Top slow spans
5. Problem count

Day 10: Real troubleshooting notebook

Build one notebook with sections:

1. Recent errors
2. Top error sources
3. Error trend
4. CPU trend
5. Slow spans
6. Failed spans
7. Problem count
8. Notes/analysis

That is how you start using DQL like a real Dynatrace observability engineer.

Related Posts

Amazon Q Developer Complete Tutorial: From Basics to Advanced

1. Introduction Amazon Q Developer is AWS’s generative AI assistant for developers, cloud engineers, DevOps engineers, security engineers, and anyone building applications on AWS. It helps you…

Read More

AWS SAM CLI — Complete Tutorial from Basics to Advanced

AWS SAM CLI is a command-line tool used to create, build, test, debug, package, deploy, monitor, sync, and delete serverless applications on AWS. SAM stands for Serverless…

Read More

Notion MCP Access Control: How to Secure Teamspaces, Permissions, and AI Client Access

A Notion teamspace is not usually “enabled for MCP” by itself.Instead, access is controlled by two layers: So if your IT team approves ChatGPT as an MCP…

Read More

Complete Tutorial: MCP and Notion MCP, Explained Step by Step

Complete Tutorial: MCP and Notion MCP, Explained Step by Step MCP means Model Context Protocol. Think of it as a standard “connector language” that lets AI tools…

Read More

HashiCorp Vault: Step-by-Step Tutorial: Vault CLI on Linux — KV Secrets, Userpass Auth, Policy, and CRUD

This lab uses Vault CLI on Linux and demonstrates the full flow: Vault’s kv command works with both KV v1 and KV v2, but for KV v2…

Read More

Low-Level Authentication Flow Design for Student, Trainer, and Consultant Services Using Keycloak

Yes — the clean low-level flow should use Keycloak as the central Identity Provider, and Student / Trainer / Consultant services should never handle passwords directly. Recommended…

Read More