Help us improve
Share bugs, ideas, or general feedback.
From elastic-search-logs
Queries and analyzes application logs stored in Elasticsearch. Supports search, count, filtering by trace IDs, and field extraction for debugging requests and error analysis.
npx claudepluginhub jawwadfirdousi/agent-skills --plugin elastic-search-logsHow this skill is triggered — by the user, by Claude, or both
Slash command
/elastic-search-logs:elastic-search-logsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill provides access to Elasticsearch APIs for querying application logs. Elasticsearch stores structured log data that can be searched, filtered, and analyzed using its REST API.
Interact with Elasticsearch and Kibana via curl REST API for querying (Query DSL), indexing, CRUD, index management, mappings, aggregations, cluster health, ILM, ES|QL, dashboards, OpenTelemetry patterns, and troubleshooting.
Queries OpenSearch logs using PPL for severity filtering, trace correlation, error patterns, and volume analysis in OTEL indices.
ELK Stack, structured logging, log query patterns, and centralized log management.
Share bugs, ideas, or general feedback.
This skill provides access to Elasticsearch APIs for querying application logs. Elasticsearch stores structured log data that can be searched, filtered, and analyzed using its REST API.
All curl commands should use -u "$ES_USERNAME:$ES_PASSWORD" for authentication.
By default, credentials come from the shell env vars ES_USERNAME / ES_PASSWORD.
An environment in environments.json may override these per-env (see below).
Per-environment settings (URL, index pattern, optional credentials) are stored in
environments.json at the skill root and managed via scripts/envs.py. At least
one environment must be configured for the skill to work.
If no environments.json exists yet, copy environments.example.json to
environments.json and edit it, or run add (below) to create the first env.
# List configured envs
python3 scripts/envs.py list
# Add an env (URL required; index pattern, description, creds optional)
python3 scripts/envs.py add dev --url https://es-dev.example.com:9243
python3 scripts/envs.py add prod \
--url https://es-prod.example.com:9243 \
--index-pattern app-logs-* \
--description "Production cluster"
# Update fields on an existing env
python3 scripts/envs.py update dev --url https://es-dev-new.example.com:9243
python3 scripts/envs.py update prod --username myuser --password 'sekret'
# Remove an env (blocked if it's the last one)
python3 scripts/envs.py remove rc
# Get an env (human / json / shell export)
python3 scripts/envs.py get dev
python3 scripts/envs.py get dev --format json
python3 scripts/envs.py get dev --format export
# Verify at least one env exists (exits non-zero otherwise)
python3 scripts/envs.py check
environments.json is gitignored because it may contain credentials. The script
sets file permissions to 600 automatically on Unix.
IMPORTANT: Always select an environment before running a query. If the user hasn't specified one and multiple envs exist, ask which one to use.
Selection flow:
python3 scripts/envs.py list and either:
# Load the chosen env and run the query in one shell command.
eval "$(python3 scripts/envs.py get <env-name> --format export)" \
&& curl -X POST "$ES_BASE_URL/$ES_INDEX_PATTERN/_search" \
-u "$ES_USERNAME:$ES_PASSWORD" \
-H "Content-Type: application/json" \
-d '{ "query": { "match_all": {} }, "size": 5 }'
The --format export output sets ES_BASE_URL, ES_INDEX_PATTERN, and (if the
env defines them) ES_USERNAME / ES_PASSWORD. Per-env credentials override the
shell env vars; otherwise the shell env vars are used.
app-logs-* (or $ES_INDEX_PATTERN)application/json_search (Most Commonly Used)The _search endpoint is your primary tool for querying logs. Use it for finding specific logs, filtering by fields, and extracting relevant data.
Note: The examples below assume ES_BASE_URL (and optionally ES_INDEX_PATTERN) are already set in the current shell. Load them by prepending each command with eval "$(python3 scripts/envs.py get <env> --format export)" &&.
curl -X POST "$ES_BASE_URL/$ES_INDEX_PATTERN/_search" \
-u "$ES_USERNAME:$ES_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"query": {
"match_all": {}
},
"size": 10,
"sort": [
{ "@timestamp": "desc" }
]
}'
curl -X POST "$ES_BASE_URL/$ES_INDEX_PATTERN/_search" \
-u "$ES_USERNAME:$ES_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"query": {
"term": {
"trace.id": "YOUR_TRACE_ID"
}
},
"size": 100,
"sort": [
{ "@timestamp": "desc" }
]
}'
To reduce response size and improve readability, specify only the fields you need:
curl -X POST "$ES_BASE_URL/$ES_INDEX_PATTERN/_search" \
-u "$ES_USERNAME:$ES_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"query": {
"term": {
"trace.id": "YOUR_TRACE_ID"
}
},
"_source": [
"@timestamp",
"message",
"req.url",
"req.query",
"kubernetes.container.name",
"appContext.userId",
"appPayload.res.statusCode",
"level"
],
"size": 100,
"sort": [
{ "@timestamp": "desc" }
]
}'
Clean up the response by filtering metadata fields:
# Add ?filter_path=hits.hits._source to URL to show only _source content
curl -X POST "$ES_BASE_URL/$ES_INDEX_PATTERN/_search?filter_path=hits.hits._source" \
-u "$ES_USERNAME:$ES_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"query": {
"term": {
"trace.id": "YOUR_TRACE_ID"
}
},
"_source": ["@timestamp", "message"],
"size": 100,
"sort": [
{ "@timestamp": "desc" }
]
}'
curl -X POST "$ES_BASE_URL/$ES_INDEX_PATTERN/_search" \
-u "$ES_USERNAME:$ES_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"query": {
"match": {
"level": "DEBUG"
}
},
"size": 100,
"sort": [
{ "@timestamp": "desc" }
]
}'
curl -X POST "$ES_BASE_URL/$ES_INDEX_PATTERN/_search" \
-u "$ES_USERNAME:$ES_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"query": {
"bool": {
"must": [
{ "term": { "trace.id": "YOUR_TRACE_ID" }},
{ "match": { "level": "ERROR" }}
]
}
},
"size": 100,
"sort": [
{ "@timestamp": "desc" }
]
}'
curl -X POST "$ES_BASE_URL/$ES_INDEX_PATTERN/_search" \
-u "$ES_USERNAME:$ES_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"query": {
"bool": {
"must": [
{ "match": { "level": "ERROR" }},
{ "range": { "@timestamp": { "gte": "now-1h" }}}
]
}
},
"size": 100,
"sort": [
{ "@timestamp": "desc" }
]
}'
Here are frequently queried fields in the log structure:
| Field Path | Description |
|---|---|
@timestamp | Log timestamp |
message | Log message content |
level | Log level (info, debug, error, warning) |
trace.id | Distributed trace identifier |
span.id | Span identifier within a trace |
req.url | Request URL |
req.method | HTTP method |
req.query | Query parameters |
req.headers.* | Request headers |
appContext.userId | App-specific user ID (example) |
appContext.requestId | App-specific request correlation ID (example) |
appPayload.res.statusCode | Response status code |
kubernetes.container.name | Container/service name |
kubernetes.pod.name | Kubernetes pod name |
hostname | Service hostname |
Note: Replace ES_BASE_URL with your selected environment endpoint.
_count - Count Matching DocumentsFaster than _search when you only need the count, not the actual documents.
curl -X POST "$ES_BASE_URL/$ES_INDEX_PATTERN/_count" \
-u "$ES_USERNAME:$ES_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"query": {
"term": {
"trace.id": "YOUR_TRACE_ID"
}
}
}'
Response:
{
"count": 8,
"_shards": {
"total": 18,
"successful": 18,
"skipped": 2,
"failed": 0
}
}
_mapping - View Index StructureView the field types and structure of your log indices.
curl -X GET "$ES_BASE_URL/$ES_INDEX_PATTERN/_mapping" \
-u "$ES_USERNAME:$ES_PASSWORD" \
-H "Content-Type: application/json"
Use this to discover available fields and their types.
_msearch - Multiple Searches in One RequestExecute multiple search queries in a single HTTP request.
curl -X POST "$ES_BASE_URL/$ES_INDEX_PATTERN/_msearch" \
-u "$ES_USERNAME:$ES_PASSWORD" \
-H "Content-Type: application/x-ndjson" \
-d '
{}
{"query": {"match": {"level": "ERROR"}}, "size": 10}
{}
{"query": {"match": {"level": "WARNING"}}, "size": 10}
'
_scroll - Paginate Through Large Result SetsFor retrieving more than 10,000 documents, use scroll API.
# Initial request with scroll parameter
curl -X POST "$ES_BASE_URL/$ES_INDEX_PATTERN/_search?scroll=1m" \
-u "$ES_USERNAME:$ES_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"query": {
"match_all": {}
},
"size": 1000
}'
# Subsequent requests using scroll_id from previous response
curl -X POST "$ES_BASE_URL/_search/scroll" \
-u "$ES_USERNAME:$ES_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"scroll": "1m",
"scroll_id": "SCROLL_ID_FROM_PREVIOUS_RESPONSE"
}'
/_cat/indices - List All IndicesView all available indices in the cluster.
curl -X GET "$ES_BASE_URL/_cat/indices?v" \
-u "$ES_USERNAME:$ES_PASSWORD"
/_stats - Index StatisticsGet statistics about index size, document count, etc.
curl -X GET "$ES_BASE_URL/$ES_INDEX_PATTERN/_stats" \
-u "$ES_USERNAME:$ES_PASSWORD" \
-H "Content-Type: application/json"
Use for exact matching on keyword fields (IDs, enums, etc.):
{
"query": {
"term": {
"trace.id": "YOUR_TRACE_ID"
}
}
}
Use for full-text search on analyzed text fields:
{
"query": {
"match": {
"message": "error occurred"
}
}
}
Combine multiple queries:
{
"query": {
"bool": {
"must": [
{ "term": { "trace.id": "YOUR_TRACE_ID" }},
{ "match": { "level": "ERROR" }}
],
"should": [
{ "match": { "message": "timeout" }}
],
"must_not": [
{ "term": { "kubernetes.container.name": "test-service" }}
]
}
}
}
Filter by numeric or date ranges:
{
"query": {
"range": {
"@timestamp": {
"gte": "now-1h",
"lte": "now"
}
}
}
}
Find documents where a field exists:
{
"query": {
"exists": {
"field": "appPayload.res.statusCode"
}
}
}
_source filtering to request only needed fieldssize limits (default is 10, max is 10000)term for exact matches on IDs and keywordsmatch for text search on message fields@timestamp for chronological orderingfilter_path URL parameter to reduce response verbosity_count instead of _search when only the count is neededNote: Use ES_BASE_URL for the selected environment in the examples below.
curl -X POST "$ES_BASE_URL/$ES_INDEX_PATTERN/_search?filter_path=hits.hits._source" \
-u "$ES_USERNAME:$ES_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"query": {
"term": {
"trace.id": "YOUR_TRACE_ID"
}
},
"_source": [
"@timestamp",
"message",
"kubernetes.container.name",
"req.url",
"appPayload.res.statusCode"
],
"size": 100,
"sort": [
{ "@timestamp": "asc" }
]
}'
curl -X POST "$ES_BASE_URL/$ES_INDEX_PATTERN/_search" \
-u "$ES_USERNAME:$ES_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"query": {
"bool": {
"must": [
{ "match": { "level": "ERROR" }},
{ "match": { "kubernetes.container.name": "my-api-service" }},
{ "range": { "@timestamp": { "gte": "now-1h" }}}
]
}
},
"_source": ["@timestamp", "message", "trace.id"],
"size": 50,
"sort": [
{ "@timestamp": "desc" }
]
}'
# First, get the trace ID from the initial request
# Then query all services for that trace ID to see the full flow
curl -X POST "$ES_BASE_URL/$ES_INDEX_PATTERN/_search?filter_path=hits.hits._source" \
-u "$ES_USERNAME:$ES_PASSWORD" \
-H "Content-Type: application/json" \
-d '{
"query": {
"term": {
"trace.id": "YOUR_TRACE_ID"
}
},
"_source": [
"@timestamp",
"kubernetes.container.name",
"message",
"req.url",
"req.method",
"appPayload.res.statusCode",
"appPayload.responseTime"
],
"size": 100,
"sort": [
{ "@timestamp": "asc" }
]
}'
match_all) to see available dataES_USERNAME and ES_PASSWORD are set in the shell, or stored on the
env via scripts/envs.py update <env> --username ... --password ...python3 scripts/envs.py list to see configured envspython3 scripts/envs.py add <name> --url <url>remove is blocked on the final env_mapping endpoint to discover available fieldsreq.headers.host)environments.json via scripts/envs.py (list, add, update, remove, get). At least one env must always exist.eval "$(python3 scripts/envs.py get <env> --format export)" sets ES_BASE_URL, ES_INDEX_PATTERN, and optional ES_USERNAME/ES_PASSWORD._search endpoint - use this for 95% of log querying needs.$ES_USERNAME:$ES_PASSWORD from the shell or from a per-env override in environments.json.term (exact match), match (text search), bool (combinations)._source (field filtering), size (result limit), sort (ordering).