eql.asciidoc 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. [role="xpack"]
  2. [testenv="basic"]
  3. [[eql]]
  4. = EQL search
  5. ++++
  6. <titleabbrev>EQL</titleabbrev>
  7. ++++
  8. beta::[]
  9. Event Query Language (EQL) is a query language for event-based time series
  10. data, such as logs, metrics, and traces.
  11. [discrete]
  12. [[eql-advantages]]
  13. == Advantages of EQL
  14. * *EQL lets you express relationships between events.* +
  15. Many query languages allow you to match single events. EQL lets you match a
  16. sequence of events across different event categories and time spans.
  17. * *EQL has a low learning curve.* +
  18. <<eql-syntax,EQL syntax>> looks like other common query languages, such as SQL.
  19. EQL lets you write and read queries intuitively, which makes for quick,
  20. iterative searching.
  21. * *EQL is designed for security use cases.* +
  22. While you can use it for any event-based data, we created EQL for threat
  23. hunting. EQL not only supports indicator of compromise (IOC) searches but can
  24. describe activity that goes beyond IOCs.
  25. [discrete]
  26. [[eql-required-fields]]
  27. == Required fields
  28. To run an EQL search, the searched data stream or index must contain a
  29. _timestamp_ and _event category_ field. By default, EQL uses the `@timestamp`
  30. and `event.category` fields from the {ecs-ref}[Elastic Common Schema
  31. (ECS)]. To use a different timestamp or event category field, see
  32. <<specify-a-timestamp-or-event-category-field>>.
  33. TIP: While no schema is required to use EQL, we recommend using the
  34. {ecs-ref}[ECS]. EQL searches are designed to work with core ECS fields by
  35. default.
  36. [discrete]
  37. [[run-an-eql-search]]
  38. == Run an EQL search
  39. Use the <<eql-search-api,EQL search API>> to run a <<eql-basic-syntax,basic
  40. EQL query>>:
  41. [source,console]
  42. ----
  43. GET /my-index-000001/_eql/search
  44. {
  45. "query": """
  46. process where process.name == "regsvr32.exe"
  47. """
  48. }
  49. ----
  50. // TEST[setup:sec_logs]
  51. By default, basic EQL queries return the top 10 matching events in the
  52. `hits.events` property. These hits are sorted by timestamp, converted to
  53. milliseconds since the {wikipedia}/Unix_time[Unix epoch], in ascending order.
  54. [source,console-result]
  55. ----
  56. {
  57. "is_partial": false,
  58. "is_running": false,
  59. "took": 60,
  60. "timed_out": false,
  61. "hits": {
  62. "total": {
  63. "value": 2,
  64. "relation": "eq"
  65. },
  66. "events": [
  67. {
  68. "_index": "my-index-000001",
  69. "_id": "OQmfCaduce8zoHT93o4H",
  70. "_source": {
  71. "@timestamp": "2099-12-07T11:07:09.000Z",
  72. "event": {
  73. "category": "process",
  74. "id": "aR3NWVOs",
  75. "sequence": 4
  76. },
  77. "process": {
  78. "pid": 2012,
  79. "name": "regsvr32.exe",
  80. "command_line": "regsvr32.exe /s /u /i:https://...RegSvr32.sct scrobj.dll",
  81. "executable": "C:\\Windows\\System32\\regsvr32.exe"
  82. }
  83. }
  84. },
  85. {
  86. "_index": "my-index-000001",
  87. "_id": "xLkCaj4EujzdNSxfYLbO",
  88. "_source": {
  89. "@timestamp": "2099-12-07T11:07:10.000Z",
  90. "event": {
  91. "category": "process",
  92. "id": "GTSmSqgz0U",
  93. "sequence": 6,
  94. "type": "termination"
  95. },
  96. "process": {
  97. "pid": 2012,
  98. "name": "regsvr32.exe",
  99. "executable": "C:\\Windows\\System32\\regsvr32.exe"
  100. }
  101. }
  102. }
  103. ]
  104. }
  105. }
  106. ----
  107. // TESTRESPONSE[s/"took": 60/"took": $body.took/]
  108. // TESTRESPONSE[s/"_id": "OQmfCaduce8zoHT93o4H"/"_id": $body.hits.events.0._id/]
  109. // TESTRESPONSE[s/"_id": "xLkCaj4EujzdNSxfYLbO"/"_id": $body.hits.events.1._id/]
  110. Use the `size` parameter to get a smaller or larger set of hits:
  111. [source,console]
  112. ----
  113. GET /my-index-000001/_eql/search
  114. {
  115. "query": """
  116. process where process.name == "regsvr32.exe"
  117. """,
  118. "size": 50
  119. }
  120. ----
  121. // TEST[setup:sec_logs]
  122. [discrete]
  123. [[eql-search-sequence]]
  124. === Search for a sequence of events
  125. Use EQL's <<eql-sequences,sequence syntax>> to search for a series of
  126. ordered events. List the event items in ascending chronological order,
  127. with the most recent event listed last:
  128. [source,console]
  129. ----
  130. GET /my-index-000001/_eql/search
  131. {
  132. "query": """
  133. sequence
  134. [ process where process.name == "regsvr32.exe" ]
  135. [ file where stringContains(file.name, "scrobj.dll") ]
  136. """
  137. }
  138. ----
  139. // TEST[setup:sec_logs]
  140. Matching sequences are returned in the `hits.sequences` property.
  141. [source,console-result]
  142. ----
  143. {
  144. "is_partial": false,
  145. "is_running": false,
  146. "took": 60,
  147. "timed_out": false,
  148. "hits": {
  149. "total": {
  150. "value": 1,
  151. "relation": "eq"
  152. },
  153. "sequences": [
  154. {
  155. "events": [
  156. {
  157. "_index": "my-index-000001",
  158. "_id": "OQmfCaduce8zoHT93o4H",
  159. "_source": {
  160. "@timestamp": "2099-12-07T11:07:09.000Z",
  161. "event": {
  162. "category": "process",
  163. "id": "aR3NWVOs",
  164. "sequence": 4
  165. },
  166. "process": {
  167. "pid": 2012,
  168. "name": "regsvr32.exe",
  169. "command_line": "regsvr32.exe /s /u /i:https://...RegSvr32.sct scrobj.dll",
  170. "executable": "C:\\Windows\\System32\\regsvr32.exe"
  171. }
  172. }
  173. },
  174. {
  175. "_index": "my-index-000001",
  176. "_id": "yDwnGIJouOYGBzP0ZE9n",
  177. "_source": {
  178. "@timestamp": "2099-12-07T11:07:10.000Z",
  179. "event": {
  180. "category": "file",
  181. "id": "tZ1NWVOs",
  182. "sequence": 5
  183. },
  184. "process": {
  185. "pid": 2012,
  186. "name": "regsvr32.exe",
  187. "executable": "C:\\Windows\\System32\\regsvr32.exe"
  188. },
  189. "file": {
  190. "path": "C:\\Windows\\System32\\scrobj.dll",
  191. "name": "scrobj.dll"
  192. }
  193. }
  194. }
  195. ]
  196. }
  197. ]
  198. }
  199. }
  200. ----
  201. // TESTRESPONSE[s/"took": 60/"took": $body.took/]
  202. // TESTRESPONSE[s/"_id": "OQmfCaduce8zoHT93o4H"/"_id": $body.hits.sequences.0.events.0._id/]
  203. // TESTRESPONSE[s/"_id": "yDwnGIJouOYGBzP0ZE9n"/"_id": $body.hits.sequences.0.events.1._id/]
  204. Use the <<eql-with-maxspan-keywords,`with maxspan` keywords>> to constrain
  205. matching sequences to a timespan:
  206. [source,console]
  207. ----
  208. GET /my-index-000001/_eql/search
  209. {
  210. "query": """
  211. sequence with maxspan=1h
  212. [ process where process.name == "regsvr32.exe" ]
  213. [ file where stringContains(file.name, "scrobj.dll") ]
  214. """
  215. }
  216. ----
  217. // TEST[setup:sec_logs]
  218. Use the <<eql-by-keyword,`by` keyword>> to match events that share the
  219. same field values:
  220. [source,console]
  221. ----
  222. GET /my-index-000001/_eql/search
  223. {
  224. "query": """
  225. sequence with maxspan=1h
  226. [ process where process.name == "regsvr32.exe" ] by process.pid
  227. [ file where stringContains(file.name, "scrobj.dll") ] by process.pid
  228. """
  229. }
  230. ----
  231. // TEST[setup:sec_logs]
  232. If a field value should be shared across all events, use the `sequence by`
  233. keyword. The following query is equivalent to the previous one.
  234. [source,console]
  235. ----
  236. GET /my-index-000001/_eql/search
  237. {
  238. "query": """
  239. sequence by process.pid with maxspan=1h
  240. [ process where process.name == "regsvr32.exe" ]
  241. [ file where stringContains(file.name, "scrobj.dll") ]
  242. """
  243. }
  244. ----
  245. // TEST[setup:sec_logs]
  246. The `hits.sequences.join_keys` property contains the shared field values.
  247. [source,console-result]
  248. ----
  249. {
  250. "is_partial": false,
  251. "is_running": false,
  252. "took": 60,
  253. "timed_out": false,
  254. "hits": {
  255. "total": {
  256. "value": 1,
  257. "relation": "eq"
  258. },
  259. "sequences": [
  260. {
  261. "join_keys": [
  262. 2012
  263. ],
  264. "events": [
  265. {
  266. "_index": "my-index-000001",
  267. "_id": "OQmfCaduce8zoHT93o4H",
  268. "_source": {
  269. "@timestamp": "2099-12-07T11:07:09.000Z",
  270. "event": {
  271. "category": "process",
  272. "id": "aR3NWVOs",
  273. "sequence": 4
  274. },
  275. "process": {
  276. "pid": 2012,
  277. "name": "regsvr32.exe",
  278. "command_line": "regsvr32.exe /s /u /i:https://...RegSvr32.sct scrobj.dll",
  279. "executable": "C:\\Windows\\System32\\regsvr32.exe"
  280. }
  281. }
  282. },
  283. {
  284. "_index": "my-index-000001",
  285. "_id": "yDwnGIJouOYGBzP0ZE9n",
  286. "_source": {
  287. "@timestamp": "2099-12-07T11:07:10.000Z",
  288. "event": {
  289. "category": "file",
  290. "id": "tZ1NWVOs",
  291. "sequence": 5
  292. },
  293. "process": {
  294. "pid": 2012,
  295. "name": "regsvr32.exe",
  296. "executable": "C:\\Windows\\System32\\regsvr32.exe"
  297. },
  298. "file": {
  299. "path": "C:\\Windows\\System32\\scrobj.dll",
  300. "name": "scrobj.dll"
  301. }
  302. }
  303. }
  304. ]
  305. }
  306. ]
  307. }
  308. }
  309. ----
  310. // TESTRESPONSE[s/"took": 60/"took": $body.took/]
  311. // TESTRESPONSE[s/"_id": "OQmfCaduce8zoHT93o4H"/"_id": $body.hits.sequences.0.events.0._id/]
  312. // TESTRESPONSE[s/"_id": "yDwnGIJouOYGBzP0ZE9n"/"_id": $body.hits.sequences.0.events.1._id/]
  313. Use the <<eql-until-keyword,`until` keyword>> to specify an expiration
  314. event for sequences. Matching sequences must end before this event.
  315. [source,console]
  316. ----
  317. GET /my-index-000001/_eql/search
  318. {
  319. "query": """
  320. sequence by process.pid with maxspan=1h
  321. [ process where process.name == "regsvr32.exe" ]
  322. [ file where stringContains(file.name, "scrobj.dll") ]
  323. until [ process where event.type == "termination" ]
  324. """
  325. }
  326. ----
  327. // TEST[setup:sec_logs]
  328. [discrete]
  329. [[specify-a-timestamp-or-event-category-field]]
  330. === Specify a timestamp or event category field
  331. The EQL search API uses the `@timestamp` and `event.category` fields from the
  332. {ecs-ref}[ECS] by default. To specify different fields, use the
  333. `timestamp_field` and `event_category_field` parameters:
  334. [source,console]
  335. ----
  336. GET /my-index-000001/_eql/search
  337. {
  338. "timestamp_field": "file.accessed",
  339. "event_category_field": "file.type",
  340. "query": """
  341. file where (file.size > 1 and file.type == "file")
  342. """
  343. }
  344. ----
  345. // TEST[setup:sec_logs]
  346. The event category field must be mapped as a <<keyword,`keyword`>> family field
  347. type. The timestamp field should be mapped as a <<date,`date`>> field type.
  348. <<date_nanos,`date_nanos`>> timestamp fields are not supported. You cannot use a
  349. <<nested,`nested`>> field or the sub-fields of a `nested` field as the timestamp
  350. or event category field.
  351. [discrete]
  352. [[eql-search-specify-a-sort-tiebreaker]]
  353. === Specify a sort tiebreaker
  354. By default, the EQL search API returns matching events by timestamp. If two or
  355. more events share the same timestamp, {es} uses a tiebreaker field value to sort
  356. the events in ascending, lexicographic order.
  357. `event.sequence` is the default tiebreaker field. To specify another tiebreaker
  358. field, use the `tiebreaker_field` parameter:
  359. [source,console]
  360. ----
  361. GET /my-index-000001/_eql/search
  362. {
  363. "tiebreaker_field": "event.id",
  364. "query": """
  365. process where process.name == "cmd.exe" and stringContains(process.executable, "System32")
  366. """
  367. }
  368. ----
  369. // TEST[setup:sec_logs]
  370. [discrete]
  371. [[eql-search-filter-query-dsl]]
  372. === Filter using query DSL
  373. The `filter` parameter uses <<query-dsl,query DSL>> to limit the documents on
  374. which an EQL query runs.
  375. [source,console]
  376. ----
  377. GET /my-index-000001/_eql/search
  378. {
  379. "filter": {
  380. "range" : {
  381. "file.size" : {
  382. "gte" : 1,
  383. "lte" : 1000000
  384. }
  385. }
  386. },
  387. "query": """
  388. file where (file.type == "file" and file.name == "cmd.exe")
  389. """
  390. }
  391. ----
  392. // TEST[setup:sec_logs]
  393. [discrete]
  394. [[eql-search-async]]
  395. === Run an async EQL search
  396. EQL searches are designed to run on large volumes of data quickly, often
  397. returning results in milliseconds. For this reason, EQL searches are
  398. _synchronous_ by default. The search request waits for complete results before
  399. returning a response.
  400. However, complete results can take longer for searches across:
  401. * <<frozen-indices,Frozen indices>>
  402. * <<modules-cross-cluster-search,Multiple clusters>>
  403. * Many shards
  404. To avoid long waits, use the `wait_for_completion_timeout` parameter to run an
  405. _asynchronous_, or _async_, EQL search.
  406. Set `wait_for_completion_timeout` to a duration you'd like to wait
  407. for complete search results. If the search request does not finish within this
  408. period, the search becomes async and returns a response that includes:
  409. * A search ID, which can be used to monitor the progress of the async search.
  410. * An `is_partial` value of `true`, meaning the response does not contain
  411. complete search results.
  412. * An `is_running` value of `true`, meaning the search is async and ongoing.
  413. The async search continues to run in the background without blocking
  414. other requests.
  415. The following request searches the `frozen-my-index-000001` index, which has been
  416. <<frozen-indices,frozen>> for storage and is rarely searched.
  417. Because searches on frozen indices are expected to take longer to complete, the
  418. request contains a `wait_for_completion_timeout` parameter value of `2s` (two
  419. seconds). If the request does not return complete results in two seconds, the
  420. search becomes async and returns a search ID.
  421. [source,console]
  422. ----
  423. GET /frozen-my-index-000001/_eql/search
  424. {
  425. "wait_for_completion_timeout": "2s",
  426. "query": """
  427. process where process.name == "cmd.exe"
  428. """
  429. }
  430. ----
  431. // TEST[setup:sec_logs]
  432. // TEST[s/frozen-my-index-000001/my-index-000001/]
  433. After two seconds, the request returns the following response. Note `is_partial`
  434. and `is_running` properties are `true`, indicating an async search.
  435. [source,console-result]
  436. ----
  437. {
  438. "id": "FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE=",
  439. "is_partial": true,
  440. "is_running": true,
  441. "took": 2000,
  442. "timed_out": false,
  443. "hits": ...
  444. }
  445. ----
  446. // TESTRESPONSE[s/FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE=/$body.id/]
  447. // TESTRESPONSE[s/"is_partial": true/"is_partial": $body.is_partial/]
  448. // TESTRESPONSE[s/"is_running": true/"is_running": $body.is_running/]
  449. // TESTRESPONSE[s/"took": 2000/"took": $body.took/]
  450. // TESTRESPONSE[s/"hits": \.\.\./"hits": $body.hits/]
  451. Use the <<get-async-eql-search-api,get async EQL search API>> to check the progress
  452. of an async search.
  453. The get async EQL search API also accepts a `wait_for_completion_timeout`
  454. parameter. If an ongoing search does not complete during this period, the response
  455. returns an `is_partial` value of `true` and no search results.
  456. The following get async EQL search API request checks the progress of the
  457. previous async EQL search. The request specifies a `wait_for_completion_timeout`
  458. query parameter value of `2s` (two seconds).
  459. [source,console]
  460. ----
  461. GET /_eql/search/FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE=?wait_for_completion_timeout=2s
  462. ----
  463. // TEST[skip: no access to search ID]
  464. The request returns the following response. Note `is_partial` and `is_running`
  465. are `false`, indicating the async search has finished and the search results
  466. in the `hits` property are complete.
  467. [source,console-result]
  468. ----
  469. {
  470. "id": "FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE=",
  471. "is_partial": false,
  472. "is_running": false,
  473. "took": 2000,
  474. "timed_out": false,
  475. "hits": ...
  476. }
  477. ----
  478. // TESTRESPONSE[s/FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE=/$body.id/]
  479. // TESTRESPONSE[s/"took": 2000/"took": $body.took/]
  480. // TESTRESPONSE[s/"hits": \.\.\./"hits": $body.hits/]
  481. [discrete]
  482. [[eql-search-store-async-eql-search]]
  483. === Change the search retention period
  484. By default, the EQL search API stores async searches for five days. After this
  485. period, any searches and their results are deleted. Use the `keep_alive`
  486. parameter to change this retention period.
  487. In the following EQL search request, the `keep_alive` parameter is `2d` (two
  488. days). If the search becomes async, its results
  489. are stored on the cluster for two days. After two days, the async
  490. search and its results are deleted, even if it's still ongoing.
  491. [source,console]
  492. ----
  493. GET /my-index-000001/_eql/search
  494. {
  495. "keep_alive": "2d",
  496. "wait_for_completion_timeout": "2s",
  497. "query": """
  498. process where process.name == "cmd.exe"
  499. """
  500. }
  501. ----
  502. // TEST[setup:sec_logs]
  503. You can use the <<get-async-eql-search-api,get async EQL search API>>'s
  504. `keep_alive` parameter to later change the retention period. The new retention
  505. period starts after the get request runs.
  506. The following request sets the `keep_alive` query parameter to `5d` (five days).
  507. The async search and its results are deleted five days after the get request
  508. executes.
  509. [source,console]
  510. ----
  511. GET /_eql/search/FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE=?keep_alive=5d
  512. ----
  513. // TEST[skip: no access to search ID]
  514. Use the <<delete-async-eql-search-api,delete async EQL search API>> to
  515. manually delete an async EQL search before the `keep_alive` period ends. If the
  516. search is still ongoing, {es} cancels the search request.
  517. The following request deletes an async EQL search and its results.
  518. [source,console]
  519. ----
  520. DELETE /_eql/search/FmNJRUZ1YWZCU3dHY1BIOUhaenVSRkEaaXFlZ3h4c1RTWFNocDdnY2FSaERnUTozNDE=?keep_alive=5d
  521. ----
  522. // TEST[skip: no access to search ID]
  523. [discrete]
  524. [[eql-search-store-sync-eql-search]]
  525. === Store synchronous EQL searches
  526. By default, the EQL search API only stores async searches that cannot be
  527. completed within the period set by `wait_for_completion_timeout`.
  528. To save the results of searches that complete during this period, set the
  529. `keep_on_completion` parameter to `true`.
  530. In the following search request, `keep_on_completion` is `true`. Search results
  531. are stored on the cluster, even if the search completes within the `2s`
  532. (two-second) period set by the `wait_for_completion_timeout` parameter.
  533. [source,console]
  534. ----
  535. GET /my-index-000001/_eql/search
  536. {
  537. "keep_on_completion": true,
  538. "wait_for_completion_timeout": "2s",
  539. "query": """
  540. process where process.name == "cmd.exe"
  541. """
  542. }
  543. ----
  544. // TEST[setup:sec_logs]
  545. The API returns the following response. A search ID is provided in the `id`
  546. property. `is_partial` and `is_running` are `false`, indicating the EQL search
  547. was synchronous and returned complete results in `hits`.
  548. [source,console-result]
  549. ----
  550. {
  551. "id": "FjlmbndxNmJjU0RPdExBTGg0elNOOEEaQk9xSjJBQzBRMldZa1VVQ2pPa01YUToxMDY=",
  552. "is_partial": false,
  553. "is_running": false,
  554. "took": 52,
  555. "timed_out": false,
  556. "hits": ...
  557. }
  558. ----
  559. // TESTRESPONSE[s/FjlmbndxNmJjU0RPdExBTGg0elNOOEEaQk9xSjJBQzBRMldZa1VVQ2pPa01YUToxMDY=/$body.id/]
  560. // TESTRESPONSE[s/"took": 52/"took": $body.took/]
  561. // TESTRESPONSE[s/"hits": \.\.\./"hits": $body.hits/]
  562. Use the search ID and the <<get-async-eql-search-api,get async EQL search API>>
  563. to retrieve the same results later.
  564. [source,console]
  565. ----
  566. GET /_eql/search/FjlmbndxNmJjU0RPdExBTGg0elNOOEEaQk9xSjJBQzBRMldZa1VVQ2pPa01YUToxMDY=
  567. ----
  568. // TEST[skip: no access to search ID]
  569. Saved synchronous searches are still subject to the retention period set by the
  570. `keep_alive` parameter. After this period, the search and its results are
  571. deleted.
  572. You can also manually delete saved synchronous searches using the
  573. <<delete-async-eql-search-api,delete async EQL search API>>.
  574. include::syntax.asciidoc[]
  575. include::functions.asciidoc[]
  576. include::pipes.asciidoc[]
  577. include::detect-threats-with-eql.asciidoc[]