EQL searchedit

Event Query Language (EQL) is a query language for event-based time series data, such as logs, metrics, and traces.

Advantages of EQLedit

  • EQL lets you express relationships between events.
    Many query languages allow you to match single events. EQL lets you match a sequence of events across different event categories and time spans.
  • EQL has a low learning curve.
    EQL syntax looks like other common query languages, such as SQL. EQL lets you write and read queries intuitively, which makes for quick, iterative searching.
  • EQL is designed for security use cases.
    While you can use it for any event-based data, we created EQL for threat hunting. EQL not only supports indicator of compromise (IOC) searches but can describe activity that goes beyond IOCs.

Required fieldsedit

With the exception of sample queries, EQL searches require that the searched data stream or index contains a timestamp field. By default, EQL uses the @timestamp field from the Elastic Common Schema (ECS).

EQL searches also require an event category field, unless you use the any keyword to search for documents without an event category field. By default, EQL uses the ECS event.category field.

To use a different timestamp or event category field, see Specify a timestamp or event category field.

While no schema is required to use EQL, we recommend using the ECS. EQL searches are designed to work with core ECS fields by default.

Run an EQL searchedit

Use the EQL search API to run a basic EQL query.

response = client.eql.search(
  index: 'my-data-stream',
  body: {
    query: "\n    process where process.name == \"regsvr32.exe\"\n  "
  }
)
puts response
GET /my-data-stream/_eql/search
{
  "query": """
    process where process.name == "regsvr32.exe"
  """
}

By default, basic EQL queries return the 10 most recent matching events in the hits.events property. These hits are sorted by timestamp, converted to milliseconds since the Unix epoch, in ascending order.

{
  "is_partial": false,
  "is_running": false,
  "took": 60,
  "timed_out": false,
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "events": [
      {
        "_index": ".ds-my-data-stream-2099.12.07-000001",
        "_id": "OQmfCaduce8zoHT93o4H",
        "_source": {
          "@timestamp": "2099-12-07T11:07:09.000Z",
          "event": {
            "category": "process",
            "id": "aR3NWVOs",
            "sequence": 4
          },
          "process": {
            "pid": 2012,
            "name": "regsvr32.exe",
            "command_line": "regsvr32.exe  /s /u /i:https://...RegSvr32.sct scrobj.dll",
            "executable": "C:\\Windows\\System32\\regsvr32.exe"
          }
        }
      },
      {
        "_index": ".ds-my-data-stream-2099.12.07-000001",
        "_id": "xLkCaj4EujzdNSxfYLbO",
        "_source": {
          "@timestamp": "2099-12-07T11:07:10.000Z",
          "event": {
            "category": "process",
            "id": "GTSmSqgz0U",
            "sequence": 6,
            "type": "termination"
          },
          "process": {
            "pid": 2012,
            "name": "regsvr32.exe",
            "executable": "C:\\Windows\\System32\\regsvr32.exe"
          }
        }
      }
    ]
  }
}

Use the size parameter to get a smaller or larger set of hits:

GET /my-data-stream/_eql/search
{
  "query": """
    process where process.name == "regsvr32.exe"
  """,
  "size": 50
}

Search for a sequence of eventsedit

Use EQL’s sequence syntax to search for a series of ordered events. List the event items in ascending chronological order, with the most recent event listed last:

GET /my-data-stream/_eql/search
{
  "query": """
    sequence
      [ process where process.name == "regsvr32.exe" ]
      [ file where stringContains(file.name, "scrobj.dll") ]
  """
}

The response’s hits.sequences property contains the 10 most recent matching sequences.

{
  ...
  "hits": {
    "total": ...,
    "sequences": [
      {
        "events": [
          {
            "_index": ".ds-my-data-stream-2099.12.07-000001",
            "_id": "OQmfCaduce8zoHT93o4H",
            "_source": {
              "@timestamp": "2099-12-07T11:07:09.000Z",
              "event": {
                "category": "process",
                "id": "aR3NWVOs",
                "sequence": 4
              },
              "process": {
                "pid": 2012,
                "name": "regsvr32.exe",
                "command_line": "regsvr32.exe  /s /u /i:https://...RegSvr32.sct scrobj.dll",
                "executable": "C:\\Windows\\System32\\regsvr32.exe"
              }
            }
          },
          {
            "_index": ".ds-my-data-stream-2099.12.07-000001",
            "_id": "yDwnGIJouOYGBzP0ZE9n",
            "_source": {
              "@timestamp": "2099-12-07T11:07:10.000Z",
              "event": {
                "category": "file",
                "id": "tZ1NWVOs",
                "sequence": 5
              },
              "process": {
                "pid": 2012,
                "name": "regsvr32.exe",
                "executable": "C:\\Windows\\System32\\regsvr32.exe"
              },
              "file": {
                "path": "C:\\Windows\\System32\\scrobj.dll",
                "name": "scrobj.dll"
              }
            }
          }
        ]
      }
    ]
  }
}

Use with maxspan to constrain matching sequences to a timespan:

GET /my-data-stream/_eql/search
{
  "query": """
    sequence with maxspan=1h
      [ process where process.name == "regsvr32.exe" ]
      [ file where stringContains(file.name, "scrobj.dll") ]
  """
}

Use ! to match missing events: events in a sequence that do not meet a condition within a given timespan:

GET /my-data-stream/_eql/search
{
  "query": """
    sequence with maxspan=1d
      [ process where process.name == "cmd.exe" ]
      ![ process where stringContains(process.command_line, "ocx") ]
      [ file where stringContains(file.name, "scrobj.dll") ]
  """
}

Missing events are indicated in the response as missing": true:

{
  ...
  "hits": {
    "total": ...,
    "sequences": [
      {
        "events": [
          {
            "_index": ".ds-my-data-stream-2023.07.04-000001",
            "_id": "AnpTIYkBrVQ2QEgsWg94",
            "_source": {
              "@timestamp": "2099-12-07T11:06:07.000Z",
              "event": {
                "category": "process",
                "id": "cMyt5SZ2",
                "sequence": 3
              },
              "process": {
                "pid": 2012,
                "name": "cmd.exe",
                "executable": "C:\\Windows\\System32\\cmd.exe"
              }
            }
          },
          {
            "_index": "",
            "_id": "",
            "_source": {},
            "missing": true
          },
          {
            "_index": ".ds-my-data-stream-2023.07.04-000001",
            "_id": "BHpTIYkBrVQ2QEgsWg94",
            "_source": {
              "@timestamp": "2099-12-07T11:07:10.000Z",
              "event": {
                "category": "file",
                "id": "tZ1NWVOs",
                "sequence": 5
              },
              "process": {
                "pid": 2012,
                "name": "regsvr32.exe",
                "executable": "C:\\Windows\\System32\\regsvr32.exe"
              },
              "file": {
                "path": "C:\\Windows\\System32\\scrobj.dll",
                "name": "scrobj.dll"
              }
            }
          }
        ]
      }
    ]
  }
}

Use the by keyword to match events that share the same field values:

GET /my-data-stream/_eql/search
{
  "query": """
    sequence with maxspan=1h
      [ process where process.name == "regsvr32.exe" ] by process.pid
      [ file where stringContains(file.name, "scrobj.dll") ] by process.pid
  """
}

If a field value should be shared across all events, use the sequence by keyword. The following query is equivalent to the previous one.

GET /my-data-stream/_eql/search
{
  "query": """
    sequence by process.pid with maxspan=1h
      [ process where process.name == "regsvr32.exe" ]
      [ file where stringContains(file.name, "scrobj.dll") ]
  """
}

The hits.sequences.join_keys property contains the shared field values.

{
  ...
  "hits": ...,
    "sequences": [
      {
        "join_keys": [
          2012
        ],
        "events": ...
      }
    ]
  }
}

Use the until keyword to specify an expiration event for sequences. Matching sequences must end before this event.

GET /my-data-stream/_eql/search
{
  "query": """
    sequence by process.pid with maxspan=1h
      [ process where process.name == "regsvr32.exe" ]
      [ file where stringContains(file.name, "scrobj.dll") ]
    until [ process where event.type == "termination" ]
  """
}

Sample chronologically unordered eventsedit

Use EQL’s sample syntax to search for events that match one or more join keys and a set of filters. Samples are similar to sequences, but do not return events in chronological order. In fact, sample queries can run on data without a timestamp. Sample queries can be useful to find correlations in events that don’t always occur in the same sequence, or that occur across long time spans.

Click to show the sample data used in the examples below
response = client.indices.create(
  index: 'my-index-000001',
  body: {
    mappings: {
      properties: {
        ip: {
          type: 'ip'
        },
        version: {
          type: 'version'
        },
        missing_keyword: {
          type: 'keyword'
        },
        "@timestamp": {
          type: 'date'
        },
        type_test: {
          type: 'keyword'
        },
        "@timestamp_pretty": {
          type: 'date',
          format: 'dd-MM-yyyy'
        },
        event_type: {
          type: 'keyword'
        },
        event: {
          properties: {
            category: {
              type: 'alias',
              path: 'event_type'
            }
          }
        },
        host: {
          type: 'keyword'
        },
        os: {
          type: 'keyword'
        },
        bool: {
          type: 'boolean'
        },
        uptime: {
          type: 'long'
        },
        port: {
          type: 'long'
        }
      }
    }
  }
)
puts response

response = client.indices.create(
  index: 'my-index-000002',
  body: {
    mappings: {
      properties: {
        ip: {
          type: 'ip'
        },
        "@timestamp": {
          type: 'date'
        },
        "@timestamp_pretty": {
          type: 'date',
          format: 'yyyy-MM-dd'
        },
        type_test: {
          type: 'keyword'
        },
        event_type: {
          type: 'keyword'
        },
        event: {
          properties: {
            category: {
              type: 'alias',
              path: 'event_type'
            }
          }
        },
        host: {
          type: 'keyword'
        },
        op_sys: {
          type: 'keyword'
        },
        bool: {
          type: 'boolean'
        },
        uptime: {
          type: 'long'
        },
        port: {
          type: 'long'
        }
      }
    }
  }
)
puts response

response = client.indices.create(
  index: 'my-index-000003',
  body: {
    mappings: {
      properties: {
        host_ip: {
          type: 'ip'
        },
        "@timestamp": {
          type: 'date'
        },
        date: {
          type: 'date'
        },
        event_type: {
          type: 'keyword'
        },
        event: {
          properties: {
            category: {
              type: 'alias',
              path: 'event_type'
            }
          }
        },
        missing_keyword: {
          type: 'keyword'
        },
        host: {
          type: 'keyword'
        },
        os: {
          type: 'keyword'
        },
        bool: {
          type: 'boolean'
        },
        uptime: {
          type: 'long'
        },
        port: {
          type: 'long'
        }
      }
    }
  }
)
puts response

response = client.bulk(
  index: 'my-index-000001',
  refresh: true,
  body: [
    {
      index: {
        _id: 1
      }
    },
    {
      "@timestamp": '1234567891',
      "@timestamp_pretty": '12-12-2022',
      missing_keyword: 'test',
      type_test: 'abc',
      ip: '10.0.0.1',
      event_type: 'alert',
      host: 'doom',
      uptime: 0,
      port: 1234,
      os: 'win10',
      version: '1.0.0',
      id: 11
    },
    {
      index: {
        _id: 2
      }
    },
    {
      "@timestamp": '1234567892',
      "@timestamp_pretty": '13-12-2022',
      event_type: 'alert',
      type_test: 'abc',
      host: 'CS',
      uptime: 5,
      port: 1,
      os: 'win10',
      version: '1.2.0',
      id: 12
    },
    {
      index: {
        _id: 3
      }
    },
    {
      "@timestamp": '1234567893',
      "@timestamp_pretty": '12-12-2022',
      event_type: 'alert',
      type_test: 'abc',
      host: 'farcry',
      uptime: 1,
      port: 1234,
      bool: false,
      os: 'win10',
      version: '2.0.0',
      id: 13
    },
    {
      index: {
        _id: 4
      }
    },
    {
      "@timestamp": '1234567894',
      "@timestamp_pretty": '13-12-2022',
      event_type: 'alert',
      type_test: 'abc',
      host: 'GTA',
      uptime: 3,
      port: 12,
      os: 'slack',
      version: '10.0.0',
      id: 14
    },
    {
      index: {
        _id: 5
      }
    },
    {
      "@timestamp": '1234567895',
      "@timestamp_pretty": '17-12-2022',
      event_type: 'alert',
      host: 'sniper 3d',
      uptime: 6,
      port: 1234,
      os: 'fedora',
      version: '20.1.0',
      id: 15
    },
    {
      index: {
        _id: 6
      }
    },
    {
      "@timestamp": '1234568896',
      "@timestamp_pretty": '17-12-2022',
      event_type: 'alert',
      host: 'doom',
      port: 65_123,
      bool: true,
      os: 'redhat',
      version: '20.10.0',
      id: 16
    },
    {
      index: {
        _id: 7
      }
    },
    {
      "@timestamp": '1234567897',
      "@timestamp_pretty": '17-12-2022',
      missing_keyword: 'yyy',
      event_type: 'failure',
      host: 'doom',
      uptime: 15,
      port: 1234,
      bool: true,
      os: 'redhat',
      version: '20.2.0',
      id: 17
    },
    {
      index: {
        _id: 8
      }
    },
    {
      "@timestamp": '1234567898',
      "@timestamp_pretty": '12-12-2022',
      missing_keyword: 'test',
      event_type: 'success',
      host: 'doom',
      uptime: 16,
      port: 512,
      os: 'win10',
      version: '1.2.3',
      id: 18
    },
    {
      index: {
        _id: 9
      }
    },
    {
      "@timestamp": '1234567899',
      "@timestamp_pretty": '15-12-2022',
      missing_keyword: 'test',
      event_type: 'success',
      host: 'GTA',
      port: 12,
      bool: true,
      os: 'win10',
      version: '1.2.3',
      id: 19
    },
    {
      index: {
        _id: 10
      }
    },
    {
      "@timestamp": '1234567893',
      missing_keyword: nil,
      ip: '10.0.0.5',
      event_type: 'alert',
      host: 'farcry',
      uptime: 1,
      port: 1234,
      bool: true,
      os: 'win10',
      version: '1.2.3',
      id: 110
    }
  ]
)
puts response

response = client.bulk(
  index: 'my-index-000002',
  refresh: true,
  body: [
    {
      index: {
        _id: 1
      }
    },
    {
      "@timestamp": '1234567991',
      type_test: 'abc',
      ip: '10.0.0.1',
      event_type: 'alert',
      host: 'doom',
      uptime: 0,
      port: 1234,
      op_sys: 'win10',
      id: 21
    },
    {
      index: {
        _id: 2
      }
    },
    {
      "@timestamp": '1234567992',
      type_test: 'abc',
      event_type: 'alert',
      host: 'CS',
      uptime: 5,
      port: 1,
      op_sys: 'win10',
      id: 22
    },
    {
      index: {
        _id: 3
      }
    },
    {
      "@timestamp": '1234567993',
      type_test: 'abc',
      "@timestamp_pretty": '2022-12-17',
      event_type: 'alert',
      host: 'farcry',
      uptime: 1,
      port: 1234,
      bool: false,
      op_sys: 'win10',
      id: 23
    },
    {
      index: {
        _id: 4
      }
    },
    {
      "@timestamp": '1234567994',
      event_type: 'alert',
      host: 'GTA',
      uptime: 3,
      port: 12,
      op_sys: 'slack',
      id: 24
    },
    {
      index: {
        _id: 5
      }
    },
    {
      "@timestamp": '1234567995',
      event_type: 'alert',
      host: 'sniper 3d',
      uptime: 6,
      port: 1234,
      op_sys: 'fedora',
      id: 25
    },
    {
      index: {
        _id: 6
      }
    },
    {
      "@timestamp": '1234568996',
      "@timestamp_pretty": '2022-12-17',
      ip: '10.0.0.5',
      event_type: 'alert',
      host: 'doom',
      port: 65_123,
      bool: true,
      op_sys: 'redhat',
      id: 26
    },
    {
      index: {
        _id: 7
      }
    },
    {
      "@timestamp": '1234567997',
      "@timestamp_pretty": '2022-12-17',
      event_type: 'failure',
      host: 'doom',
      uptime: 15,
      port: 1234,
      bool: true,
      op_sys: 'redhat',
      id: 27
    },
    {
      index: {
        _id: 8
      }
    },
    {
      "@timestamp": '1234567998',
      ip: '10.0.0.1',
      event_type: 'success',
      host: 'doom',
      uptime: 16,
      port: 512,
      op_sys: 'win10',
      id: 28
    },
    {
      index: {
        _id: 9
      }
    },
    {
      "@timestamp": '1234567999',
      ip: '10.0.0.1',
      event_type: 'success',
      host: 'GTA',
      port: 12,
      bool: false,
      op_sys: 'win10',
      id: 29
    }
  ]
)
puts response

response = client.bulk(
  index: 'my-index-000003',
  refresh: true,
  body: [
    {
      index: {
        _id: 1
      }
    },
    {
      "@timestamp": '1334567891',
      host_ip: '10.0.0.1',
      event_type: 'alert',
      host: 'doom',
      uptime: 0,
      port: 12,
      os: 'win10',
      id: 31
    },
    {
      index: {
        _id: 2
      }
    },
    {
      "@timestamp": '1334567892',
      event_type: 'alert',
      host: 'CS',
      os: 'win10',
      id: 32
    },
    {
      index: {
        _id: 3
      }
    },
    {
      "@timestamp": '1334567893',
      event_type: 'alert',
      host: 'farcry',
      bool: true,
      os: 'win10',
      id: 33
    },
    {
      index: {
        _id: 4
      }
    },
    {
      "@timestamp": '1334567894',
      event_type: 'alert',
      host: 'GTA',
      os: 'slack',
      bool: true,
      id: 34
    },
    {
      index: {
        _id: 5
      }
    },
    {
      "@timestamp": '1234567895',
      event_type: 'alert',
      host: 'sniper 3d',
      os: 'fedora',
      id: 35
    },
    {
      index: {
        _id: 6
      }
    },
    {
      "@timestamp": '1234578896',
      host_ip: '10.0.0.1',
      event_type: 'alert',
      host: 'doom',
      bool: true,
      os: 'redhat',
      id: 36
    },
    {
      index: {
        _id: 7
      }
    },
    {
      "@timestamp": '1234567897',
      event_type: 'failure',
      missing_keyword: 'test',
      host: 'doom',
      bool: true,
      os: 'redhat',
      id: 37
    },
    {
      index: {
        _id: 8
      }
    },
    {
      "@timestamp": '1234577898',
      event_type: 'success',
      host: 'doom',
      os: 'win10',
      id: 38,
      date: '1671235200000'
    },
    {
      index: {
        _id: 9
      }
    },
    {
      "@timestamp": '1234577899',
      host_ip: '10.0.0.5',
      event_type: 'success',
      host: 'GTA',
      bool: true,
      os: 'win10',
      id: 39
    }
  ]
)
puts response
PUT /my-index-000001
{
    "mappings": {
        "properties": {
            "ip": {
                "type":"ip"
            },
            "version": {
                "type": "version"
            },
            "missing_keyword": {
                "type": "keyword"
            },
            "@timestamp": {
              "type": "date"
            },
            "type_test": {
                "type": "keyword"
            },
            "@timestamp_pretty": {
              "type": "date",
              "format": "dd-MM-yyyy"
            },
            "event_type": {
              "type": "keyword"
            },
            "event": {
              "properties": {
                "category": {
                  "type": "alias",
                  "path": "event_type"
                }
              }
            },
            "host": {
              "type": "keyword"
            },
            "os": {
              "type": "keyword"
            },
            "bool": {
              "type": "boolean"
            },
            "uptime" : {
              "type" : "long"
            },
            "port" : {
              "type" : "long"
            }
        }
    }
}

PUT /my-index-000002
{
    "mappings": {
        "properties": {
            "ip": {
                "type":"ip"
            },
            "@timestamp": {
              "type": "date"
            },
            "@timestamp_pretty": {
              "type": "date",
              "format": "yyyy-MM-dd"
            },
            "type_test": {
                "type": "keyword"
            },
            "event_type": {
              "type": "keyword"
            },
            "event": {
              "properties": {
                "category": {
                  "type": "alias",
                  "path": "event_type"
                }
              }
            },
            "host": {
              "type": "keyword"
            },
            "op_sys": {
              "type": "keyword"
            },
            "bool": {
              "type": "boolean"
            },
            "uptime" : {
              "type" : "long"
            },
            "port" : {
              "type" : "long"
            }
        }
    }
}

PUT /my-index-000003
{
    "mappings": {
        "properties": {
            "host_ip": {
                "type":"ip"
            },
            "@timestamp": {
              "type": "date"
            },
            "date": {
              "type": "date"
            },
            "event_type": {
              "type": "keyword"
            },
            "event": {
              "properties": {
                "category": {
                  "type": "alias",
                  "path": "event_type"
                }
              }
            },
            "missing_keyword": {
                "type": "keyword"
            },
            "host": {
              "type": "keyword"
            },
            "os": {
              "type": "keyword"
            },
            "bool": {
              "type": "boolean"
            },
            "uptime" : {
              "type" : "long"
            },
            "port" : {
              "type" : "long"
            }
        }
    }
}

POST /my-index-000001/_bulk?refresh
{"index":{"_id":1}}
{"@timestamp":"1234567891","@timestamp_pretty":"12-12-2022","missing_keyword":"test","type_test":"abc","ip":"10.0.0.1","event_type":"alert","host":"doom","uptime":0,"port":1234,"os":"win10","version":"1.0.0","id":11}
{"index":{"_id":2}}
{"@timestamp":"1234567892","@timestamp_pretty":"13-12-2022","event_type":"alert","type_test":"abc","host":"CS","uptime":5,"port":1,"os":"win10","version":"1.2.0","id":12}
{"index":{"_id":3}}
{"@timestamp":"1234567893","@timestamp_pretty":"12-12-2022","event_type":"alert","type_test":"abc","host":"farcry","uptime":1,"port":1234,"bool":false,"os":"win10","version":"2.0.0","id":13}
{"index":{"_id":4}}
{"@timestamp":"1234567894","@timestamp_pretty":"13-12-2022","event_type":"alert","type_test":"abc","host":"GTA","uptime":3,"port":12,"os":"slack","version":"10.0.0","id":14}
{"index":{"_id":5}}
{"@timestamp":"1234567895","@timestamp_pretty":"17-12-2022","event_type":"alert","host":"sniper 3d","uptime":6,"port":1234,"os":"fedora","version":"20.1.0","id":15}
{"index":{"_id":6}}
{"@timestamp":"1234568896","@timestamp_pretty":"17-12-2022","event_type":"alert","host":"doom","port":65123,"bool":true,"os":"redhat","version":"20.10.0","id":16}
{"index":{"_id":7}}
{"@timestamp":"1234567897","@timestamp_pretty":"17-12-2022","missing_keyword":"yyy","event_type":"failure","host":"doom","uptime":15,"port":1234,"bool":true,"os":"redhat","version":"20.2.0","id":17}
{"index":{"_id":8}}
{"@timestamp":"1234567898","@timestamp_pretty":"12-12-2022","missing_keyword":"test","event_type":"success","host":"doom","uptime":16,"port":512,"os":"win10","version":"1.2.3","id":18}
{"index":{"_id":9}}
{"@timestamp":"1234567899","@timestamp_pretty":"15-12-2022","missing_keyword":"test","event_type":"success","host":"GTA","port":12,"bool":true,"os":"win10","version":"1.2.3","id":19}
{"index":{"_id":10}}
{"@timestamp":"1234567893","missing_keyword":null,"ip":"10.0.0.5","event_type":"alert","host":"farcry","uptime":1,"port":1234,"bool":true,"os":"win10","version":"1.2.3","id":110}

POST /my-index-000002/_bulk?refresh
{"index":{"_id":1}}
{"@timestamp":"1234567991","type_test":"abc","ip":"10.0.0.1","event_type":"alert","host":"doom","uptime":0,"port":1234,"op_sys":"win10","id":21}
{"index":{"_id":2}}
{"@timestamp":"1234567992","type_test":"abc","event_type":"alert","host":"CS","uptime":5,"port":1,"op_sys":"win10","id":22}
{"index":{"_id":3}}
{"@timestamp":"1234567993","type_test":"abc","@timestamp_pretty":"2022-12-17","event_type":"alert","host":"farcry","uptime":1,"port":1234,"bool":false,"op_sys":"win10","id":23}
{"index":{"_id":4}}
{"@timestamp":"1234567994","event_type":"alert","host":"GTA","uptime":3,"port":12,"op_sys":"slack","id":24}
{"index":{"_id":5}}
{"@timestamp":"1234567995","event_type":"alert","host":"sniper 3d","uptime":6,"port":1234,"op_sys":"fedora","id":25}
{"index":{"_id":6}}
{"@timestamp":"1234568996","@timestamp_pretty":"2022-12-17","ip":"10.0.0.5","event_type":"alert","host":"doom","port":65123,"bool":true,"op_sys":"redhat","id":26}
{"index":{"_id":7}}
{"@timestamp":"1234567997","@timestamp_pretty":"2022-12-17","event_type":"failure","host":"doom","uptime":15,"port":1234,"bool":true,"op_sys":"redhat","id":27}
{"index":{"_id":8}}
{"@timestamp":"1234567998","ip":"10.0.0.1","event_type":"success","host":"doom","uptime":16,"port":512,"op_sys":"win10","id":28}
{"index":{"_id":9}}
{"@timestamp":"1234567999","ip":"10.0.0.1","event_type":"success","host":"GTA","port":12,"bool":false,"op_sys":"win10","id":29}

POST /my-index-000003/_bulk?refresh
{"index":{"_id":1}}
{"@timestamp":"1334567891","host_ip":"10.0.0.1","event_type":"alert","host":"doom","uptime":0,"port":12,"os":"win10","id":31}
{"index":{"_id":2}}
{"@timestamp":"1334567892","event_type":"alert","host":"CS","os":"win10","id":32}
{"index":{"_id":3}}
{"@timestamp":"1334567893","event_type":"alert","host":"farcry","bool":true,"os":"win10","id":33}
{"index":{"_id":4}}
{"@timestamp":"1334567894","event_type":"alert","host":"GTA","os":"slack","bool":true,"id":34}
{"index":{"_id":5}}
{"@timestamp":"1234567895","event_type":"alert","host":"sniper 3d","os":"fedora","id":35}
{"index":{"_id":6}}
{"@timestamp":"1234578896","host_ip":"10.0.0.1","event_type":"alert","host":"doom","bool":true,"os":"redhat","id":36}
{"index":{"_id":7}}
{"@timestamp":"1234567897","event_type":"failure","missing_keyword":"test","host":"doom","bool":true,"os":"redhat","id":37}
{"index":{"_id":8}}
{"@timestamp":"1234577898","event_type":"success","host":"doom","os":"win10","id":38,"date":"1671235200000"}
{"index":{"_id":9}}
{"@timestamp":"1234577899","host_ip":"10.0.0.5","event_type":"success","host":"GTA","bool":true,"os":"win10","id":39}

A sample query specifies at least one join key, using the by keyword, and up to five filters:

GET /my-index*/_eql/search
{
  "query": """
    sample by host
      [any where uptime > 0]
      [any where port > 100]
      [any where bool == true]
  """
}

By default, the response’s hits.sequences property contains up to 10 samples. Each sample has a set of join_keys and an array with one matching event for each of the filters. Events are returned in the order of the filters they match:

{
  ...
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "sequences": [
      {
        "join_keys": [
          "doom"                                      
        ],
        "events": [
          {                                           
            "_index": "my-index-000001",
            "_id": "7",
            "_source": {
              "@timestamp": "1234567897",
              "@timestamp_pretty": "17-12-2022",
              "missing_keyword": "yyy",
              "event_type": "failure",
              "host": "doom",
              "uptime": 15,
              "port": 1234,
              "bool": true,
              "os": "redhat",
              "version": "20.2.0",
              "id": 17
            }
          },
          {                                           
            "_index": "my-index-000001",
            "_id": "1",
            "_source": {
              "@timestamp": "1234567891",
              "@timestamp_pretty": "12-12-2022",
              "missing_keyword": "test",
              "type_test": "abc",
              "ip": "10.0.0.1",
              "event_type": "alert",
              "host": "doom",
              "uptime": 0,
              "port": 1234,
              "os": "win10",
              "version": "1.0.0",
              "id": 11
            }
          },
          {                                           
            "_index": "my-index-000001",
            "_id": "6",
            "_source": {
              "@timestamp": "1234568896",
              "@timestamp_pretty": "17-12-2022",
              "event_type": "alert",
              "host": "doom",
              "port": 65123,
              "bool": true,
              "os": "redhat",
              "version": "20.10.0",
              "id": 16
            }
          }
        ]
      },
      {
        "join_keys": [
          "farcry"                                    
        ],
        "events": [
          {
            "_index": "my-index-000001",
            "_id": "3",
            "_source": {
              "@timestamp": "1234567893",
              "@timestamp_pretty": "12-12-2022",
              "event_type": "alert",
              "type_test": "abc",
              "host": "farcry",
              "uptime": 1,
              "port": 1234,
              "bool": false,
              "os": "win10",
              "version": "2.0.0",
              "id": 13
            }
          },
          {
            "_index": "my-index-000001",
            "_id": "10",
            "_source": {
              "@timestamp": "1234567893",
              "missing_keyword": null,
              "ip": "10.0.0.5",
              "event_type": "alert",
              "host": "farcry",
              "uptime": 1,
              "port": 1234,
              "bool": true,
              "os": "win10",
              "version": "1.2.3",
              "id": 110
            }
          },
          {
            "_index": "my-index-000003",
            "_id": "3",
            "_source": {
              "@timestamp": "1334567893",
              "event_type": "alert",
              "host": "farcry",
              "bool": true,
              "os": "win10",
              "id": 33
            }
          }
        ]
      }
    ]
  }
}

The events in the first sample have a value of doom for host.

This event matches the first filter.

This event matches the second filter.

This event matches the third filter.

The events in the second sample have a value of farcry for host.

You can specify multiple join keys:

GET /my-index*/_eql/search
{
  "query": """
    sample by host
      [any where uptime > 0]   by os
      [any where port > 100]   by op_sys
      [any where bool == true] by os
  """
}

This query will return samples where each of the events shares the same value for os or op_sys, as well as for host. For example:

{
  ...
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "sequences": [
      {
        "join_keys": [
          "doom",                                      
          "redhat"
        ],
        "events": [
          {
            "_index": "my-index-000001",
            "_id": "7",
            "_source": {
              "@timestamp": "1234567897",
              "@timestamp_pretty": "17-12-2022",
              "missing_keyword": "yyy",
              "event_type": "failure",
              "host": "doom",
              "uptime": 15,
              "port": 1234,
              "bool": true,
              "os": "redhat",
              "version": "20.2.0",
              "id": 17
            }
          },
          {
            "_index": "my-index-000002",
            "_id": "6",
            "_source": {
              "@timestamp": "1234568996",
              "@timestamp_pretty": "2022-12-17",
              "ip": "10.0.0.5",
              "event_type": "alert",
              "host": "doom",
              "port": 65123,
              "bool": true,
              "op_sys": "redhat",
              "id": 26
            }
          },
          {
            "_index": "my-index-000001",
            "_id": "6",
            "_source": {
              "@timestamp": "1234568896",
              "@timestamp_pretty": "17-12-2022",
              "event_type": "alert",
              "host": "doom",
              "port": 65123,
              "bool": true,
              "os": "redhat",
              "version": "20.10.0",
              "id": 16
            }
          }
        ]
      },
      {
        "join_keys": [
          "farcry",
          "win10"
        ],
        "events": [
          {
            "_index": "my-index-000001",
            "_id": "3",
            "_source": {
              "@timestamp": "1234567893",
              "@timestamp_pretty": "12-12-2022",
              "event_type": "alert",
              "type_test": "abc",
              "host": "farcry",
              "uptime": 1,
              "port": 1234,
              "bool": false,
              "os": "win10",
              "version": "2.0.0",
              "id": 13
            }
          },
          {
            "_index": "my-index-000002",
            "_id": "3",
            "_source": {
              "@timestamp": "1234567993",
              "type_test": "abc",
              "@timestamp_pretty": "2022-12-17",
              "event_type": "alert",
              "host": "farcry",
              "uptime": 1,
              "port": 1234,
              "bool": false,
              "op_sys": "win10",
              "id": 23
            }
          },
          {
            "_index": "my-index-000001",
            "_id": "10",
            "_source": {
              "@timestamp": "1234567893",
              "missing_keyword": null,
              "ip": "10.0.0.5",
              "event_type": "alert",
              "host": "farcry",
              "uptime": 1,
              "port": 1234,
              "bool": true,
              "os": "win10",
              "version": "1.2.3",
              "id": 110
            }
          }
        ]
      }
    ]
  }
}

The events in this sample have a value of doom for host and a value of redhat for os or op_sys.

By default, the response of a sample query contains up to 10 samples, with one sample per unique set of join keys. Use the size parameter to get a smaller or larger set of samples. To retrieve more than one sample per set of join keys, use the max_samples_per_key parameter. Pipes are not supported for sample queries.

GET /my-index*/_eql/search
{
  "max_samples_per_key": 2,     
  "size": 20,                   
  "query": """
    sample
      [any where uptime > 0]   by host,os
      [any where port > 100]   by host,op_sys
      [any where bool == true] by host,os
  """
}

Retrieve up to 2 samples per set of join keys.

Retrieve up to 20 samples in total.

Retrieve selected fieldsedit

By default, each hit in the search response includes the document _source, which is the entire JSON object that was provided when indexing the document.

You can use the filter_path query parameter to filter the API response. For example, the following search returns only the timestamp and PID from the _source of each matching event.

GET /my-data-stream/_eql/search?filter_path=hits.events._source.@timestamp,hits.events._source.process.pid
{
  "query": """
    process where process.name == "regsvr32.exe"
  """
}

The API returns the following response.

{
  "hits": {
    "events": [
      {
        "_source": {
          "@timestamp": "2099-12-07T11:07:09.000Z",
          "process": {
            "pid": 2012
          }
        }
      },
      {
        "_source": {
          "@timestamp": "2099-12-07T11:07:10.000Z",
          "process": {
            "pid": 2012
          }
        }
      }
    ]
  }
}

You can also use the fields parameter to retrieve and format specific fields in the response. This field is identical to the search API’s fields parameter.

Because it consults the index mappings, the fields parameter provides several advantages over referencing the _source directly. Specifically, the fields parameter:

The following search request uses the fields parameter to retrieve values for the event.type field, all fields starting with process., and the @timestamp field. The request also uses the filter_path query parameter to exclude the _source of each hit.

GET /my-data-stream/_eql/search?filter_path=-hits.events._source
{
  "query": """
    process where process.name == "regsvr32.exe"
  """,
  "fields": [
    "event.type",
    "process.*",                
    {
      "field": "@timestamp",
      "format": "epoch_millis"  
    }
  ]
}

Both full field names and wildcard patterns are accepted.

Use the format parameter to apply a custom format for the field’s values.

The response includes values as a flat list in the fields section for each hit.

{
  ...
  "hits": {
    "total": ...,
    "events": [
      {
        "_index": ".ds-my-data-stream-2099.12.07-000001",
        "_id": "OQmfCaduce8zoHT93o4H",
        "fields": {
          "process.name": [
            "regsvr32.exe"
          ],
          "process.name.keyword": [
            "regsvr32.exe"
          ],
          "@timestamp": [
            "4100324829000"
          ],
          "process.command_line": [
            "regsvr32.exe  /s /u /i:https://...RegSvr32.sct scrobj.dll"
          ],
          "process.command_line.keyword": [
            "regsvr32.exe  /s /u /i:https://...RegSvr32.sct scrobj.dll"
          ],
          "process.executable.keyword": [
            "C:\\Windows\\System32\\regsvr32.exe"
          ],
          "process.pid": [
            2012
          ],
          "process.executable": [
            "C:\\Windows\\System32\\regsvr32.exe"
          ]
        }
      },
      ....
    ]
  }
}

Use runtime fieldsedit

Use the runtime_mappings parameter to extract and create runtime fields during a search. Use the fields parameter to include runtime fields in the response.

The following search creates a day_of_week runtime field from the @timestamp and returns it in the response.

GET /my-data-stream/_eql/search?filter_path=-hits.events._source
{
  "runtime_mappings": {
    "day_of_week": {
      "type": "keyword",
      "script": "emit(doc['@timestamp'].value.dayOfWeekEnum.toString())"
    }
  },
  "query": """
    process where process.name == "regsvr32.exe"
  """,
  "fields": [
    "@timestamp",
    "day_of_week"
  ]
}

The API returns:

{
  ...
  "hits": {
    "total": ...,
    "events": [
      {
        "_index": ".ds-my-data-stream-2099.12.07-000001",
        "_id": "OQmfCaduce8zoHT93o4H",
        "fields": {
          "@timestamp": [
            "2099-12-07T11:07:09.000Z"
          ],
          "day_of_week": [
            "MONDAY"
          ]
        }
      },
      ....
    ]
  }
}

Specify a timestamp or event category fieldedit

The EQL search API uses the @timestamp and event.category fields from the ECS by default. To specify different fields, use the timestamp_field and event_category_field parameters:

GET /my-data-stream/_eql/search
{
  "timestamp_field": "file.accessed",
  "event_category_field": "file.type",
  "query": """
    file where (file.size > 1 and file.type == "file")
  """
}

The event category field must be mapped as a keyword family field type. The timestamp field should be mapped as a date field type. date_nanos timestamp fields are not supported. You cannot use a nested field or the sub-fields of a nested field as the timestamp or event category field.

Specify a sort tiebreakeredit

By default, the EQL search API returns matching hits by timestamp. If two or more events share the same timestamp, Elasticsearch uses a tiebreaker field value to sort the events in ascending order. Elasticsearch orders events with no tiebreaker value after events with a value.

If you don’t specify a tiebreaker field or the events also share the same tiebreaker value, Elasticsearch considers the events concurrent and may not return them in a consistent sort order.

To specify a tiebreaker field, use the tiebreaker_field parameter. If you use the ECS, we recommend using event.sequence as the tiebreaker field.

GET /my-data-stream/_eql/search
{
  "tiebreaker_field": "event.sequence",
  "query": """
    process where process.name == "cmd.exe" and stringContains(process.executable, "System32")
  """
}

Filter using Query DSLedit

The filter parameter uses Query DSL to limit the documents on which an EQL query runs.

GET /my-data-stream/_eql/search
{
  "filter": {
    "range": {
      "@timestamp": {
        "gte": "now-1d/d",
        "lt": "now/d"
      }
    }
  },
  "query": """
    file where (file.type == "file" and file.name == "cmd.exe")
  """
}

Run an async EQL searchedit

By default, EQL search requests are synchronous and wait for complete results before returning a response. However, complete results can take longer for searches across large data sets or frozen data.

To avoid long waits, run an async EQL search. Set wait_for_completion_timeout to a duration you’d like to wait for synchronous results.

GET /my-data-stream/_eql/search
{
  "wait_for_completion_timeout": "2s",
  "query": """
    process where process.name == "cmd.exe"
  """
}

If the request doesn’t finish within the timeout period, the search becomes async and returns a response that includes:

  • A search ID
  • An is_partial value of true, indicating the search results are incomplete
  • An is_running value of true, indicating the search is ongoing

The async search continues to run in the background without blocking other requests.

{
  "id": "FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE=",
  "is_partial": true,
  "is_running": true,
  "took": 2000,
  "timed_out": false,
  "hits": ...
}

To check the progress of an async search, use the get async EQL search API with the search ID. Specify how long you’d like for complete results in the wait_for_completion_timeout parameter.

response = client.eql.get(
  id: 'FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE=',
  wait_for_completion_timeout: '2s'
)
puts response
GET /_eql/search/FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE=?wait_for_completion_timeout=2s

If the response’s is_running value is false, the async search has finished. If the is_partial value is false, the returned search results are complete.

{
  "id": "FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE=",
  "is_partial": false,
  "is_running": false,
  "took": 2000,
  "timed_out": false,
  "hits": ...
}

Another more lightweight way to check the progress of an async search is to use the get async EQL status API with the search ID.

response = client.eql.get_status(
  id: 'FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE='
)
puts response
GET /_eql/search/status/FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE=
{
  "id": "FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE=",
  "is_running": false,
  "is_partial": false,
  "expiration_time_in_millis": 1611690295000,
  "completion_status": 200
}

Change the search retention periodedit

By default, the EQL search API stores async searches for five days. After this period, any searches and their results are deleted. Use the keep_alive parameter to change this retention period:

GET /my-data-stream/_eql/search
{
  "keep_alive": "2d",
  "wait_for_completion_timeout": "2s",
  "query": """
    process where process.name == "cmd.exe"
  """
}

You can use the get async EQL search API's keep_alive parameter to later change the retention period. The new retention period starts after the get request runs.

response = client.eql.get(
  id: 'FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE=',
  keep_alive: '5d'
)
puts response
GET /_eql/search/FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE=?keep_alive=5d

Use the delete async EQL search API to manually delete an async EQL search before the keep_alive period ends. If the search is still ongoing, Elasticsearch cancels the search request.

response = client.eql.delete(
  id: 'FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE='
)
puts response
DELETE /_eql/search/FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE=

Store synchronous EQL searchesedit

By default, the EQL search API only stores async searches. To save a synchronous search, set keep_on_completion to true:

GET /my-data-stream/_eql/search
{
  "keep_on_completion": true,
  "wait_for_completion_timeout": "2s",
  "query": """
    process where process.name == "cmd.exe"
  """
}

The response includes a search ID. is_partial and is_running are false, indicating the EQL search was synchronous and returned complete results.

{
  "id": "FjlmbndxNmJjU0RPdExBTGg0elNOOEEaQk9xSjJBQzBRMldZa1VVQ2pPa01YUToxMDY=",
  "is_partial": false,
  "is_running": false,
  "took": 52,
  "timed_out": false,
  "hits": ...
}

Use the get async EQL search API to get the same results later:

response = client.eql.get(
  id: 'FjlmbndxNmJjU0RPdExBTGg0elNOOEEaQk9xSjJBQzBRMldZa1VVQ2pPa01YUToxMDY='
)
puts response
GET /_eql/search/FjlmbndxNmJjU0RPdExBTGg0elNOOEEaQk9xSjJBQzBRMldZa1VVQ2pPa01YUToxMDY=

Saved synchronous searches are still subject to the keep_alive parameter’s retention period. When this period ends, the search and its results are deleted.

You can also check only the status of the saved synchronous search without results by using get async EQL status API.

You can also manually delete saved synchronous searches using the delete async EQL search API.

Run an EQL search across clustersedit

This functionality is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features.

The EQL search API supports cross-cluster search. However, the local and remote clusters must use the same Elasticsearch version if they have versions prior to 7.17.7 (included) or prior to 8.5.1 (included).

The following cluster update settings request adds two remote clusters: cluster_one and cluster_two.

response = client.cluster.put_settings(
  body: {
    persistent: {
      cluster: {
        remote: {
          cluster_one: {
            seeds: [
              '127.0.0.1:9300'
            ]
          },
          cluster_two: {
            seeds: [
              '127.0.0.1:9301'
            ]
          }
        }
      }
    }
  }
)
puts response
PUT /_cluster/settings
{
  "persistent": {
    "cluster": {
      "remote": {
        "cluster_one": {
          "seeds": [
            "127.0.0.1:9300"
          ]
        },
        "cluster_two": {
          "seeds": [
            "127.0.0.1:9301"
          ]
        }
      }
    }
  }
}

To target a data stream or index on a remote cluster, use the <cluster>:<target> syntax.

GET /cluster_one:my-data-stream,cluster_two:my-data-stream/_eql/search
{
  "query": """
    process where process.name == "regsvr32.exe"
  """
}

EQL circuit breaker settingsedit

The relevant circuit breaker settings can be found in the Circuit Breakers page.