dt-obs-logs — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited dt-obs-logs (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Query, filter, and analyze Dynatrace log data using DQL for troubleshooting and monitoring.
Cross-source join required: If the query must combine logs with host attributes (OS type, hostname, IP address, cloud provider) → also read dt-dql-essentials/references/smartscape-topology-navigation.md before writing the query.Use this skill when users want to:
from:now() - <duration> for time windowsmatchesPhrase() and contains() for content searchFind specific log entries by time, severity, and content.
Typical steps:
Example:
fetch logs, from:now() - 1h
| filter status == "ERROR"
| fields timestamp, content, process_group = dt.process_group.detected_name
| sort timestamp desc
| limit 100Narrow down logs using multiple criteria (severity, entity, content).
Typical steps:
Example:
fetch logs, from:now() - 2h
| filter in(status, {"ERROR", "FATAL", "WARN"})
| summarize count(), by: {dt.process_group.id, dt.process_group.detected_name}
| fieldsAdd process_group = dt.process_group.detected_name
| sort `count()` descIdentify patterns, trends, and anomalies in log data.
Typical steps:
Example:
fetch logs, from:now() - 2h
| filter status == "ERROR"
| fieldsAdd
has_exception = if(matchesPhrase(content, "exception"), true, else: false),
has_timeout = if(matchesPhrase(content, "timeout"), true, else: false)
| summarize
count(),
exception_count = countIf(has_exception == true),
timeout_count = countIf(has_timeout == true),
by: {process_group = dt.process_group.detected_name}filter status == "ERROR" - Filter by status levelin(status, {"ERROR", "FATAL", "WARN"}) - Multi-status filter (use curly braces for literal sets)contains(content, "keyword") - Simple substring searchmatchesPhrase(content, "exact phrase") - Full-text phrase searchdt.process_group.detected_name - Get human-readable process group namefilter process_group == "service-name" - Filter by specific entitycount() - Count all log entriescountIf(condition) - Conditional countby: {dimension} - Group by entity or time bucketbin(timestamp, 5m) - Time bucketing for trendsfields timestamp, content, status - Select specific fieldsfieldsAdd name = expression - Add computed fieldsif(condition, true_value, else: false_value) - Conditional logicSimple substring search:
fetch logs, from:now() - 1h
| filter contains(content, "database")
| fields timestamp, content, statusFull-text phrase search:
fetch logs, from:now() - 1h
| filter matchesPhrase(content, "connection timeout")
| fields timestamp, content, process_group = dt.process_group.detected_nameCalculate error rates over time:
fetch logs, from:now() - 2h
| summarize
total_logs = count(),
error_logs = countIf(status == "ERROR"),
by: {time_bucket = bin(timestamp, 5m)}
| fieldsAdd error_rate = (error_logs * 100.0) / total_logs
| sort time_bucket ascFind most common errors:
fetch logs, from:now() - 24h
| filter status == "ERROR"
| summarize error_count = count(), by: {content}
| sort error_count desc
| limit 20Filter logs by process group:
fetch logs, from:now() - 1h
| fieldsAdd process_group = dt.process_group.detected_name
| filter process_group == "payment-service"
| filter status == "ERROR"
| fields timestamp, content, status
| sort timestamp descMany applications emit JSON-formatted log lines. Use parse to extract fields instead of dumping raw content:
fetch logs, from:now() - 1h
| filter status == "ERROR"
| parse content, "JSON:log"
| fieldsAdd level = log[level], message = log[msg], error = log[error]
| fields timestamp, level, message, error
| sort timestamp desc
| limit 50Aggregate by a parsed field:
fetch logs, from:now() - 4h
| filter status == "ERROR"
| parse content, "JSON:log"
| fieldsAdd message = log[msg]
| summarize error_count = count(), by: {message}
| sort error_count desc
| limit 20Notes:
parse content, "JSON:log" creates a record field log — access nested values with log[key]contains() before parse to reduce parsing overheadcontentfrom:now() - <duration> to limit datacontains() for simple, matchesPhrase() for exact| limit 100 to prevent overwhelming outputdt.process_group.detected_name or getNodeName() for human-readable outputbin(timestamp, 5m) for time-series analysisdt.process_group.id for service correlationbin() and time rangesmatchesPhrase()summarize and conditional functionsmatchesPhrase) may have performance implications on large datasets| Problem | Cause | Solution |
|---|---|---|
| No logs returned | Missing time range or too narrow | Widen from: window; verify log ingestion is active |
getNodeName() returns null | OneAgent not monitoring the entity or entity not yet resolved | Verify OneAgent is deployed and entity is discovered; use dt.process_group.detected_name as a reliable alternative |
matchesPhrase() slow on large data | Full-text search without pre-filtering | Add filter status == "ERROR" before matchesPhrase() |
Wrong field name log.level | Common mistake | Use loglevel (no dot) for severity; see dt-dql-essentials |
Empty content field | Log line was empty or not ingested | Check log source configuration in OneAgent |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.