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)
| Category | Important commands | Purpose |
|---|---|---|
| Data source | fetch, data, describe, fieldsSnapshot | Load or inspect data |
| Metrics | timeseries, metrics | Query metric data |
| Filter/search | filter, filterOut, search, dedup | Reduce records |
| Selection | fields, fieldsAdd, fieldsRemove, fieldsRename | Control columns |
| Parsing | parse | Extract structured data |
| Ordering | sort, limit | Order/restrict output |
| Structuring | fieldsFlatten, expand | Work with nested records/arrays |
| Aggregation | summarize, makeTimeseries, fieldsSummary | Group and calculate |
| Correlation | join, lookup, append | Combine datasets |
| Smartscape | smartscapeNodes, smartscapeEdges, traverse | Entity 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 object | Query | Use case |
|---|---|---|
| Logs | fetch logs | Application, infrastructure, Kubernetes, process logs |
| Events | fetch events | System/security/custom events |
| Business events | fetch bizevents | Business transactions and custom business data |
| Spans | fetch spans | Distributed tracing/span-level analysis |
| Problems | fetch dt.davis.problems | Davis 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
| Feature | filter | search |
|---|---|---|
| Best for | Precise conditions | Text search |
| Case behavior | Depends on operator/function | Case-insensitive search behavior |
| Example | filter loglevel == "ERROR" | search "error" |
| Field-specific | Yes | Yes, with field ~ "term" |
| Good for | Status, duration, service, entity | Keyword 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:
| Type | Example | Use |
|---|---|---|
string | "checkout" | Text |
long | 500 | Integer |
double | 99.95 | Decimal |
boolean | true, false | Conditions |
timestamp | now() | Point in time |
duration | 2h, 500ms | Time duration |
timeframe | timeframe(from:..., to:...) | Start/end window |
array | array(1,2,3) | List |
record | record(name="app") | Nested object |
ip | IP address | Network fields |
uid | Entity/problem IDs | Unique 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
| View | Best for | Example query |
|---|---|---|
| Table | Raw rows, grouped summary | `fetch logs |
| Line chart | Time trend | timeseries avg(dt.host.cpu.usage) |
| Bar chart | Top N comparison | summarize count(), by:{service} |
| Pie chart | Share/distribution | Count by status/loglevel |
| Single value | KPI/SLO number | Error count, p95, CPU average |
| Markdown/Text | Notes/documentation | Notebook 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.