About oodle-logs
oodle-logs is a skill for searching Oodle log data with OpenSearch Query DSL and finding available index patterns. It guides agents to verify patterns and fields and set explicit time ranges before querying.
Key features
- 01Discover log index patterns and fields
- 02Build NDJSON queries with OpenSearch Query DSL
- 03Search logs across specified time ranges
- 04Aggregate results by log fields
- 05Extract plain-text values on the client with jq
Use cases
- 01Find matching log events for an investigation
- 02Count errors grouped by service
- 03Inspect historical log lines and extract embedded values
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ oodle-ai/ agent-skills --skill oodle-logsAfter installing, type/oodle-logsin Claude Code to confirm it loaded.
Details
From the project READMEOodle Logs — Search and Index Pattern Discovery
This skill teaches the agent to search log data using the OpenSearch-compatible query DSL and to discover available log index patterns.
Prerequisites
brew install oodle-ai/oodle/oodle
oodle configure
Confirm the logs endpoint works:
oodle logs index-patterns -o json | jq 'length'
Command Execution Order
Before running any log query:
- Run
oodle logs index-patterns -o jsonto discover available log index patterns. - Pick the appropriate index pattern
titlefor your query. - Build the NDJSON query file using the discovered index pattern and OpenSearch Query DSL.
- Set
--startand--endto cover the time range you need. They default to-1handnow. - Do not guess index names — always confirm with
index-patternsfirst.
Quick Reference
| Task | Command |
|---|---|
| List log index patterns | oodle logs index-patterns -o json |
| Search logs | oodle logs query -f query.ndjson --start <start> --end <end> -o json |
Common Operations
List log index patterns
Discover available log index patterns before building log queries. The returned title field is used as the index value in NDJSON query files.
# CORRECT — list all available index patterns
oodle logs index-patterns -o json
# CORRECT — extract just the pattern titles
oodle logs index-patterns -o json | jq '.[].title'
# CORRECT — use a discovered pattern in a log query
oodle logs index-patterns -o json | jq '.[].title'
# Then use the title in your NDJSON file:
# {"index": "logs-*"}
# {"query": {"match_all": {}}, "size": 10}
Log query
Search log data using the OpenSearch-compatible multi-search API. The request body uses NDJSON format with exactly two JSON lines: the first selects the index, the second contains the search query using OpenSearch Query DSL. Pass the body via -f <file>.
Important constraints:
- The CLI only supports a single search body (exactly 2 NDJSON lines: one header + one search). Multiple search pairs are not supported.
- Use
--startand--endflags for time range filtering. Values can be epoch milliseconds (e.g.,1716825600000), RFC 3339 (e.g.,2026-01-02T00:00:00Z),now, or relative expressions (e.g.,-1h,-24h,-7d). --startdefaults to-1hand--enddefaults tonow. If you are searching for data older than 1 hour, you must pass an explicit--startor the query will silently return 0 results.- Do not put range filters on
timestampin the query body — the CLI auto-injects atimestamprange filter from--start/--end.
# CORRECT — basic query (searches last 1 hour by default)
cat > query.ndjson <<'EOF'
{"index": "logs-*"}
{"query": {"match_all": {}}, "size": 10}
EOF
oodle logs query -f query.ndjson -o json
# CORRECT — search for specific log messages with explicit time range
cat > query.ndjson <<'EOF'
{"index": "logs-*"}
{"query": {"bool": {"must": [{"match_phrase": {"message": "error"}}]}}, "size": 50, "sort": [{"timestamp": {"order": "desc"}}]}
EOF
oodle logs query -f query.ndjson --start -6h --end now -o json
# CORRECT — search older data using relative time
cat > query.ndjson <<'EOF'
{"index": "logs-*"}
{"query": {"bool": {"must": [{"match_phrase": {"message": "OOMKilled"}}]}}, "size": 100}
EOF
oodle logs query -f query.ndjson --start -7d --end -6d -o json
# CORRECT — search older data using epoch milliseconds
cat > query.ndjson <<'EOF'
{"index": "logs-*"}
{"query": {"match_phrase": {"message": "connection refused"}}, "size": 50}
EOF
oodle logs query -f query.ndjson --start 1716825600000 --end 1716912000000 -o json
# WRONG — omitting --start when searching for data older than 1 hour
cat > query.ndjson <<'EOF'
{"index": "logs-*"}
{"query": {"match_phrase": {"message": "yesterday's deploy error"}}, "size": 50}
EOF
oodle logs query -f query.ndjson -o json
# ^^^ Returns 0 results! --start defaults to -1h, so older data is excluded.
# FIX: add --start -24h (or whatever range covers the target timeframe)
# WRONG — putting a range filter on timestamp in the query body
cat > query.ndjson <<'EOF'
{"index": "logs-*"}
{"query": {"bool": {"must": [{"range": {"timestamp": {"gte": "now-1h", "lte": "now"}}}]}}, "size": 100}
EOF
# The CLI already injects a timestamp range from --start/--end. In-query range filters conflict.
# FIX: use --start and --end flags instead.
# WRONG — passing the query inline without -f
oodle logs query --body '{"index":"logs-*"}'
# WRONG — using JSON instead of NDJSON format
oodle logs query -f query.json # file must be valid NDJSON (exactly two lines)
Aggregations
Put OpenSearch aggregations in an aggs block. Set "size": 0 when you need only the aggregation. Read the result with jq '.responses[0].aggregations'. The field names below are examples; confirm them with oodle logs index-patterns. Numeric aggregations need a numeric field, not text inside message.
# CORRECT — error lines per service (top 20)
cat > query.ndjson <<'EOF'
{"index": "logs-*"}
{"size": 0, "query": {"term": {"level": "error"}}, "aggs": {"by_service": {"terms": {"field": "service_name", "size": 20}}}}
EOF
oodle logs query -f query.ndjson --start -24h --end now -o json \
| jq -r '.responses[0].aggregations.by_service.buckets[] | "\(.key)\t\(.doc_count)"'
# CORRECT — error lines per hour
cat > query.ndjson <<'EOF'
{"index": "logs-*"}
{"size": 0, "query": {"match_phrase": {"message": "error"}}, "aggs": {"per_hour": {"date_histogram": {"field": "timestamp", "fixed_interval": "1h"}}}}
EOF
oodle logs query -f query.ndjson --start -24h --end now -o json \
| jq -r '.responses[0].aggregations.per_hour.buckets[] | "\(.key_as_string)\t\(.doc_count)"'
Values inside plain-text log lines
regexp queries, scripts, and runtime fields are not supported. So a log query cannot filter on, or group by, a value that is only inside the text of a plain-text line. For lines like tool Bash ok in 12345 ms, a query cannot select "more than 10000 ms" or group by the tool name. Do not tell the user that Oodle cannot do it. Offer one of these:
- Log metrics rule (oodle-log-metrics skill): extracts the value with a regex into a metric with labels. Use it for counts, latency, and alerts on new logs.
- Past data: find the lines with
match_phrase, and extract the value on the client withjq.
| Need | Use |
|---|---|
| Find lines, read events, investigate the past | oodle logs query |
| Count or group by a real field, over the past | oodle logs query with aggs |
| Filter or group by a value only inside plain text | Log metrics rule (new data), or jq on the client (past data) |
| Alert on log lines (count, rate, latency, per user) | Log metrics rule + monitor |
| Trend over weeks on a dashboard | Log metrics rule |
Worked example: whose credit ran out in the last 24 hours?
First check if a log metrics rule already counts these lines: oodle log-metrics list -o json. If one does, read its metric, for example oodle metrics query --query 'sum by (user) (increase(oodle_logs_credit_exhausted[24h])) > 0' -o json. A rule counts only lines that arrived after it was made, so use the logs below for older data.
cat > credit.ndjson <<'EOF'
{"index": "logs-*"}
{"query": {"match_phrase": {"message": "credit exhausted"}}, "size": 1000, "sort": [{"timestamp": {"order": "desc"}}]}
EOF
# If the user is a real field (for example user_id): use a terms aggregation on it.
# If the user is only inside the text ("... for user=alice ..."): extract it on the client.
oodle logs query -f credit.ndjson --start -24h --end now -o json \
| jq -r '.responses[0].hits.hits[]._source.message | capture("user=(?<user>[^ ]+)").user' \
| sort | uniq -c | sort -rn
If the query returns size lines, there can be more: split the time range (for example, four 6-hour queries) and combine the results. To alert per user from now on, create a log metrics rule that extracts user with the regex user=(\S+), and a monitor on sum by (user) (increase(oodle_logs_credit_exhausted[10m])). The oodle-log-metrics skill has the rule JSON.
Best Practices
Discover index patterns before querying
Always run oodle logs index-patterns to find the correct index name. Guessing index names may return empty results or errors.
# CORRECT — discover first, then query
oodle logs index-patterns -o json | jq '.[].title'
# Use the discovered title in your NDJSON file
# WRONG — guessing index names
cat > query.ndjson <<'EOF'
{"index": "my-logs"}
{"query": {"match_all": {}}, "size": 10}
EOF
oodle logs query -f query.ndjson -o json
Always use --start and --end for time range filtering
The CLI injects a timestamp range filter automatically from --start/--end. Do not add your own range filter on timestamp in the query body — it conflicts with the CLI-injected one.
# CORRECT — use CLI flags for time range
oodle logs query -f query.ndjson --start -24h --end now -o json
# CORRECT — use epoch milliseconds for precise ranges
oodle logs query -f query.ndjson --start 1716825600000 --end 1716912000000 -o json
# WRONG — relying on the default 1-hour window for older data
oodle logs query -f query.ndjson -o json
# ^^^ Only searches the last 1 hour. If the data you need is older, you get 0 results.
# WRONG — putting range filters on timestamp in the query body
{"query": {"bool": {"must": [{"range": {"timestamp": {"gte": "now-6h"}}}]}}}
# FIX: remove the range from the query body and use --start -6h instead.
Use -o json for log query results
Log query responses contain nested JSON structures. Always use -o json and pipe through jq for extraction.
# CORRECT — extract log messages from the response
oodle logs query -f query.ndjson -o json | jq '.responses[].hits.hits[]._source.message'
# WRONG — using table output for complex nested log data
oodle logs query -f query.ndjson -o table
Use index pattern fields to build precise queries
Each index pattern includes a fields array with field names and types. Use these to build targeted queries instead of match_all.
# CORRECT — check available fields first
oodle logs index-patterns -o json | jq '.[0].fields[].name'
# Then build a query using confirmed field names
Failure Handling
| Error | Cause | Fix |
|---|---|---|
| 401 Unauthorized | Invalid or missing API key | Run oodle configure or set OODLE_API_KEY |
| 400 Bad Request | Invalid NDJSON body or Query DSL | Validate the NDJSON file has exactly two lines; check OpenSearch Query DSL syntax |
| Empty result (not an error) | No matching logs in the index or time range | Widen the time range or check the index name with oodle logs index-patterns |
| 0 results for older data | --start defaults to -1h; data outside that window is excluded | Pass explicit --start for older data, e.g. --start -24h or --start <epoch_ms> |
| connection refused | Wrong OODLE_DEPLOYMENT URL | Check OODLE_DEPLOYMENT env var, ensure no trailing slash |
| 429 Too Many Requests | Rate limited | Add --retries 3, back off, reduce query frequency |
400 on regexp, script, or runtime field | Not supported | Use match_phrase, or a log metrics rule (see "Values inside plain-text log lines") |
required flag(s) "file" not set | Missing required flag for log query | Add -f flag with path to NDJSON file |