Elasticsearch:Painless scripting 語言(一)

Painless 是一種高性能、安全的腳本語言,專為 Elasticsearch 設計。你可以使用 Painless 在 Elasticsearch 支持腳本的任何地方安全地編寫內聯和存儲腳本。

Painless 提供眾多功能,這些功能圍繞以下核心原則:

  • 安全性:確保集群的安全性至關重要。為此,Painless 使用細粒度的允許列表,粒度細到類的成員。任何不屬于允許列表的內容都會導致編譯錯誤。請參閱 Painless API 參考,了解每個腳本上下文的可用類、方法和字段的完整列表。
  • 性能:Painless 直接編譯為 JVM 字節碼,以利用 JVM 提供的所有可能的優化。此外,Painless 通常會避免在運行時需要額外較慢檢查的功能。
  • 簡單性:Painless 實現的語法對任何具有一些基本編碼經驗的人來說都很自然熟悉。Painless 使用 Java 語法的子集,并進行了一些額外的改進,以增強可讀性并消除樣板。

本篇文章的是系列文章中的其中一篇:

  • Elasticsearch:Painless scripting 語言(一)

  • Elasticsearch:Painless scripting 語言(二)

開始編寫腳本

準備好開始使用 Painless 編寫腳本了嗎?讓我開始編寫我們的第一個腳本。

只要 Elasticsearch API 支持腳本,語法就會遵循相同的模式;你可以指定腳本的語言、提供腳本邏輯(或源代碼),并添加傳遞到腳本中的參數:

  "script": {"lang":   "...","source" | "id": "...","params": { ... }}
條目描述
lang指定腳本所用的語言。默認為 painless。
source,?id腳本本身,你將其指定為內聯腳本的源或存儲腳本的 id。使用存儲腳本 API 創建和管理存儲腳本。
params指定作為變量傳遞到腳本中的任何命名參數。使用參數而不是硬編碼值來減少編譯時間。

書寫你的第一個腳本

Painless 是 Elasticsearch 的默認腳本語言。它安全、高效,并為任何具有一點編碼經驗的人提供自然的語法。

Painless 腳本的結構為一個或多個語句,并且可以在開頭選擇一個或多個用戶定義的函數。腳本必須始終至少有一個語句。

Painless 執行 API 提供了使用簡單的用戶定義參數測試腳本并接收結果的能力。讓我們從完整的腳本開始并查看其組成部分。

首先,使用單個字段索引文檔,以便我們可以使用一些數據:

PUT my-index-000001/_doc/1
{"my_field": 5
}

然后,我們可以構建一個對該字段進行操作的腳本,并作為查詢的一部分運行評估該腳本。以下查詢使用搜索 API 的 script_fields 參數來檢索腳本評估。這里發生了很多事情,但我們將分解各個組件以分別理解它們。現在,你只需要了解此腳本接受 my_field 并對其進行操作。

GET my-index-000001/_search
{"script_fields": {"my_doubled_field": {"script": {  // 1"source": "doc['my_field'].value * params['multiplier']",  // 2"params": {"multiplier": 2}}}}
}
  1. script 對象
  2. script 代碼

script 是一個標準 JSON 對象,用于定義 Elasticsearch 中大多數 API 下的腳本。此對象需要 source 來定義腳本本身。script 未指定語言,因此默認為 Painless。

運行上面的代碼,我們可以看到如下的結果:

從上面的輸出中,我們可以看到有一個新的字段叫做?my_doubled_field。它的紙是 10。是之前的字段 my_field 的值的兩倍。

請注意,從 Elastic Stack 7.11 發布后,我們可以使用 runtime fields 來實現同樣的功能:

GET my-index-000001/_search
{"runtime_mappings": {"my_doubled_field": {"type": "long","script": {"source": "emit(doc['my_field'].value * 2)"}}},"fields": ["my_doubled_field"]
}
上面運行的結果為:

從 Elastic Stack 8.14 后,我們甚至可以使用 ES|QL 來實現同樣的功能:

POST _query?format=txt
{"query": """FROM my-index-000001| EVAL my_doubled_field = my_field*2| LIMIT 1"""
}

上面代碼執行的結果為:

在腳本中使用參數

Elasticsearch 第一次看到新腳本時,它會編譯該腳本并將編譯后的版本存儲在緩存中。編譯可能是一個繁重的過程。與其在腳本中硬編碼值,不如將它們作為命名參數傳遞。

例如,在上一個腳本中,我們可以只硬編碼值并編寫一個看似不太復雜的腳本。我們可以只檢索 my_field 的第一個值,然后將其乘以 2:

"source": "return doc['my_field'].value * 2"

雖然這種方法有效,但靈活性很差。我們必須修改腳本源代碼才能更改乘數,而且每次乘數更改時,Elasticsearch 都必須重新編譯腳本。

不用對值進行硬編碼,而是使用命名參數來提高腳本的靈活性,同時還可以減少腳本運行時的編譯時間。現在,你可以更改 multiplier 參數,而無需 Elasticsearch 重新編譯腳本。

"source": "doc['my_field'].value * params['multiplier']",
"params": {"multiplier": 2
}

默認情況下,每 5 分鐘最多可以編譯 150 個腳本。對于采集上下文,默認腳本編譯速率不受限制。

script.context.field.max_compilations_rate=100/10m

重要:如果你在短時間內編譯了太多唯一腳本,Elasticsearch 會拒絕新的動態腳本并出現 circuit_breaking_exception 錯誤。

縮短腳本

使用 Painless 固有的語法功能,你可以減少腳本的冗長程度并使其更短。下面是我們可以縮短的簡單腳本:

GET my-index-000001/_search
{"script_fields": {"my_doubled_field": {"script": {"lang":   "painless","source": "doc['my_field'].value * params.get('multiplier');","params": {"multiplier": 2}}}}
}

讓我們看一下腳本的縮短版本,看看它與上一次版本相比有哪些改進:

GET my-index-000001/_search
{"script_fields": {"my_doubled_field": {"script": {"source": "field('my_field').get(null) * params['multiplier']","params": {"multiplier": 2}}}}
}

此版本的腳本刪除了幾個組件并顯著簡化了語法:

  • lang 聲明。由于 Painless 是默認語言,因此如果你正在編寫 Painless 腳本,則無需指定語言。
  • return 關鍵字。Painless 會自動使用腳本中的最后一個語句(如果可能)在需要返回值的腳本上下文中生成返回值。
  • get 方法,用括號 [] 替換。Painless 專門為 Map 類型使用了快捷方式,允許我們使用括號而不是較長的 get 方法。
  • source 語句末尾的分號。Painless 不需要在塊的最后一個語句中使用分號 ;。但是,在其他情況下確實需要它們來消除歧義。

在 Elasticsearch 支持腳本的任何地方使用此縮寫語法,例如當你創建 runtime fields。

存儲和檢索腳本

你可以使用存儲腳本 API 從集群狀態存儲和檢索腳本。存儲腳本可減少編譯時間并加快搜索速度。

注意:與常規腳本不同,存儲腳本要求你使用 lang 參數指定腳本語言。

要創建腳本,請使用 create stored script API。例如,以下請求將創建一個名為 calculate-score 的存儲腳本。

POST _scripts/calculate-score
{"script": {"lang": "painless","source": "Math.log(_score * 2) + params['my_modifier']"}
}

你可以使用?get stored script API?檢索該腳本。

GET _scripts/calculate-score

要在查詢中使用存儲的腳本,請在腳本聲明中包含腳本 ID:

GET my-index-000001/_search
{"query": {"script_score": {"query": {"match": {"message": "some message"}},"script": {"id": "calculate-score", // 1"params": {"my_modifier": 2}}}}
}

  1. ?id 是存儲腳本的 id

要刪除存儲的腳本,請提交 delete stored script API?請求。

DELETE _scripts/calculate-score

使用腳本更新文檔

你可以使用?update API 使用指定的腳本更新文檔。腳本可以更新、刪除或跳過修改文檔。更新 API 還支持傳遞部分文檔,該部分文檔將合并到現有文檔中。

首先,讓我們索引一個簡單的文檔:

DELETE my-index-000001PUT my-index-000001/_doc/1
{"counter" : 1,"tags" : ["red"]
}

要增加 counter,你可以使用以下腳本提交更新請求:

POST my-index-000001/_update/1
{"script" : {"source": "ctx._source.counter += params.count","lang": "painless","params" : {"count" : 4}}
}

我們可通過如下的命令來進行查看:

GET my-index-000001/_doc/1

我們可以看見 counter 的值從 1?變為 5。

類似地,你可以使用更新腳本將標簽添加到 tag 列表中。因為這只是一個列表,所以即使標簽存在,也會被添加:

POST my-index-000001/_update/1
{"script": {"source": "ctx._source.tags.add(params['tag'])","lang": "painless","params": {"tag": "blue"}}
}
GET my-index-000001/_doc/1

我們可以看到 blue 已經被添加。

注意:在修改 source 的時候,我們需要使用正確的上下文,也即使用 ctx._source 來進行修改。

你還可以從標簽列表中刪除標簽。Java List 的 remove 方法在 Painless 中可用。它采用要刪除的元素的索引。為避免可能的運行時錯誤,你首先需要確保標簽存在。如果列表包含標簽的重復項,則此腳本只會刪除一個出現項。

POST my-index-000001/_update/1
{"script": {"source": "if (ctx._source.tags.contains(params['tag'])) { ctx._source.tags.remove(ctx._source.tags.indexOf(params['tag'])) }","lang": "painless","params": {"tag": "blue"}}
}

你還可以在文檔中添加和刪除字段。例如,此腳本添加了字段 new_field:

POST my-index-000001/_update/1
{"script" : "ctx._source.new_field = 'value_of_new_field'"
}

相反,此腳本刪除了字段 new_field:

POST my-index-000001/_update/1
{"script" : "ctx._source.remove('new_field')"
}

除了更新文檔之外,你還可以更改從腳本內部執行的操作。例如,如果 tags 字段包含 green,則此請求將刪除文檔。否則它不執行任何操作(noop):

POST my-index-000001/_update/1
{"script": {"source": "if (ctx._source.tags.contains(params['tag'])) { ctx.op = 'delete' } else { ctx.op = 'none' }","lang": "painless","params": {"tag": "green"}}
}

腳本、緩存和搜索速度

Elasticsearch 執行了許多優化,以使使用腳本的速度盡可能快。一個重要的優化是腳本緩存。編譯后的腳本放置在緩存中,以便引用腳本的請求不會產生編譯懲罰。

緩存大小很重要。你的腳本緩存應該足夠大,以容納用戶需要同時訪問的所有腳本。

如果您看到大量腳本緩存驅逐和節點統計信息(node stats)中的編譯數量不斷增加,則你的緩存可能太小。

默認情況下,所有腳本都緩存,因此只需在發生更新時重新編譯它們。默認情況下,腳本沒有基于時間的過期時間。你可以使用 script.cache.expire 設置更改此行為。使用 script.cache.max_size 設置配置緩存的大小。

注意:腳本的大小限制為 65,535 字節。設置 script.max_size_in_bytes 的值以增加該軟限制。如果你的腳本確實很大,那么請考慮使用本機腳本引擎。

提高搜索速度

腳本非常有用,但不能使用 Elasticsearch 的索引結構或相關優化。這種關系有時會導致搜索速度變慢。

如果你經常使用腳本來轉換索引數據,則可以通過在攝取期間轉換數據來加快搜索速度。但是,這通常意味著索引速度變慢。讓我們看一個實際的例子來說明如何提高搜索速度。

運行搜索時,通常按兩個值的總和對結果進行排序。例如,考慮一個名為 my_test_scores 的索引,其中包含測試分數數據。此索引包括兩個 long 類型的字段:

  • math_score
  • verbal_score

你可以使用將這些值加在一起的腳本運行查詢。這種方法沒有錯,但是查詢會變慢,因為腳本估值是請求的一部分。以下請求返回 grad_year 等于 2099 的文檔,并按腳本的估值對結果進行排序。

GET /my_test_scores/_search
{"query": {"term": {"grad_year": "2099"}},"sort": [{"_script": {"type": "number","script": {"source": "doc['math_score'].value + doc['verbal_score'].value"},"order": "desc"}}]
}

如果你正在搜索一個小型索引,那么將腳本作為搜索查詢的一部分可能是一個很好的解決方案。如果你想加快搜索速度,你可以在提取期間執行此計算,并將總和索引到字段。

首先,我們將向名為 total_score 的索引添加一個新的字段,它將包含 math_score 和 verbal_score 字段值的總和。

PUT my_test_scores/_doc/1
{"math_score": 90,"verbal_score": 80
}PUT /my_test_scores/_mapping
{"properties": {"total_score": {"type": "long"}}
}

接下來,使用包含?script processor 的? ingest pipeline?計算 math_score 和 verbal_score 的總和,并將其索引到 total_score 字段中。

PUT _ingest/pipeline/my_test_scores_pipeline
{"description": "Calculates the total test score","processors": [{"script": {"source": "ctx.total_score = (ctx.math_score + ctx.verbal_score)"}}]
}

要更新現有數據,請使用此管道將 my_test_scores 中的所有文檔 reindex 到名為 my_test_scores_2 的新索引。

POST /_reindex
{"source": {"index": "my_test_scores"},"dest": {"index": "my_test_scores_2","pipeline": "my_test_scores_pipeline"}
}

繼續使用管道將任何新文檔索引到 my_test_scores_2。

POST /my_test_scores_2/_doc/?pipeline=my_test_scores_pipeline
{"student": "kimchy","grad_year": "2099","math_score": 1200,"verbal_score": 800
}

我們可以通過如的代碼來查詢?my_test_scores_2:

GET my_test_scores_2/_search

我們可以看到一個新的字段 total_score,并且它的值是 math_score 和 verbal_score 兩個字段的總和。

這些更改會減慢索引過程,但可以加快搜索速度。你可以使用 total_score 字段對 my_test_scores_2 上的搜索進行排序,而無需使用腳本。響應幾乎是實時的!雖然此過程會減慢采集時間,但它會大大增加搜索時的查詢次數。

GET /my_test_scores_2/_search
{"query": {"term": {"grad_year": "2099"}},"sort": [{"total_score": {"order": "desc"}}]
}

Dissect 數據

Dissect 將單個文本字段與定義的模式進行匹配。剖析模式由要丟棄的字符串部分定義。特別注意字符串的每個部分有助于構建成功的 dissect 模式。

如果你不需要正則表達式的強大功能,請使用剖析模式而不是 grok。Dissect 使用的語法比 grok 簡單得多,而且通常總體上速度更快。dissect 的語法是透明的:告訴 dissect 你想要什么,它就會將這些結果返回給你。

Dissect 模式

Dissect 模式由變量和分隔符組成。任何由百分號和花括號 %{} 定義的內容都被視為變量,例如 %{clientip}。你可以將變量分配給字段中任何部分的數據,然后僅返回所需的部分。分隔符是變量之間的任何值,可以是空格、破折號或其他分隔符。

例如,假設你有一個日志數據,其中的 message 字段如下所示:

"message" : "247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] \"GET /images/hm_nbg.jpg HTTP/1.0\" 304 0"

你將變量分配給數據的每個部分以構建成功的 dissect 模式。請記住,告訴剖析你想要匹配的內容。

數據的第一部分看起來像一個 IP 地址,因此你可以分配一個變量,例如 %{clientip}。接下來的兩個字符是破折號,兩邊各有一個空格。你可以為每個破折號分配一個變量,或者分配一個變量來表示破折號和空格。接下來是一組包含時間戳的括號。括號是分隔符,因此你可以將它們包含在剖析模式中。到目前為止,數據和匹配的剖析模式如下所示:

247.37.0.0 - - [30/Apr/2020:14:31:22 -0500]  #1%{clientip} %{ident} %{auth} [%{@timestamp}] #2
  1. message 字段中的第一個數據塊
  2. dissect 模式以匹配所選數據塊

使用相同的邏輯,你可以為剩余的數據塊創建變量。雙引號是分隔符,因此請將其包含在你的 dissect 模式中。該模式用 %{verb} 變量替換 GET,但將 HTTP 保留為模式的一部分。

\"GET /images/hm_nbg.jpg HTTP/1.0\" 304 0"%{verb} %{request} HTTP/%{httpversion}" %{response} %{size}

將這兩種模式結合起來會得到如下所示的 dissect 模式:

%{clientip} %{ident} %{auth} [%{@timestamp}] \"%{verb} %{request} HTTP/%{httpversion}\" %{status} %{size}

現在您有了 dissect 模式,如何測試和使用它?

使用 Painless 測試 dissect 模式

你可以將 dissect 模式合并到 Painless 腳本中以提取數據。要測試你的腳本,請使用 Painless execute?API 的?field contexts 或創建包含腳本的運行時字段。運行時字段提供了更大的靈活性并接受多個文檔,但如果你在測試腳本的集群上沒有寫入權限,則 Painless execute API 是一個不錯的選擇。

例如,通過包含 Painless 腳本和與你的數據匹配的單個文檔,使用 Painless execute?API 測試你的剖析模式。首先將消息字段索引為 wildcard 數據類型:

PUT my-index
{"mappings": {"properties": {"message": {"type": "wildcard"}}}
}

如果要檢索 HTTP 響應代碼,請將你的解析模式添加到提取響應值的 Painless 腳本中。要從字段中提取值,請使用此函數:

`.extract(doc["<field_name>"].value)?.<field_value>`

在此示例中,message 是 <field_name>,response 是 <field_value>:

POST /_scripts/painless/_execute
{"script": {"source": """String response=dissect('%{clientip} %{ident} %{auth} [%{@timestamp}] "%{verb} %{request} HTTP/%{httpversion}" %{response} %{size}').extract(doc["message"].value)?.response;if (response != null) emit(Integer.parseInt(response)); //1"""},"context": "long_field", //2"context_setup": {"index": "my-index","document": {          //3"message": """247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] "GET /images/hm_nbg.jpg HTTP/1.0" 304 0"""}}
}

  1. 運行時字段需要 emit 方法才能返回值。
  2. 由于響應代碼是整數,因此請使用 long_field 上下文。
  3. 包含與你的數據匹配的示例文檔。

在運行時字段中使用 dissect 模式和腳本

如果你有一個功能性 dissect 模式,則可以將其添加到運行時字段以操作數據。由于運行時字段不需要你索引字段,因此你可以非常靈活地修改腳本及其功能。如果你已經使用 Painless exectute?API 測試了 dissect 模式,則可以在運行時字段中使用該 Painless 腳本。

首先,像上一節一樣將 message 字段添加為 wildcard 類型,但也要添加 @timestamp 作為日期,以防你想在其他用例中對該字段進行操作:

DELETE my-indexPUT /my-index/
{"mappings": {"properties": {"@timestamp": {"format": "strict_date_optional_time||epoch_second","type": "date"},"message": {"type": "wildcard"}}}
}

如果你想使用 dissect 模式提取 HTTP 響應代碼,你可以創建一個運行時字段,如 http.response:

PUT my-index/_mappings
{"runtime": {"http.response": {"type": "long","script": """String response=dissect('%{clientip} %{ident} %{auth} [%{@timestamp}] "%{verb} %{request} HTTP/%{httpversion}" %{response} %{size}').extract(doc["message"].value)?.response;if (response != null) emit(Integer.parseInt(response));"""}}
}

映射要檢索的字段后,將日志數據中的幾條記錄索引到 Elasticsearch 中。以下請求使用 bulk API將原始日志數據索引到 my-index 中:

POST /my-index/_bulk?refresh=true
{"index":{}}
{"timestamp":"2020-04-30T14:30:17-05:00","message":"40.135.0.0 - - [30/Apr/2020:14:30:17 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
{"index":{}}
{"timestamp":"2020-04-30T14:30:53-05:00","message":"232.0.0.0 - - [30/Apr/2020:14:30:53 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
{"index":{}}
{"timestamp":"2020-04-30T14:31:12-05:00","message":"26.1.0.0 - - [30/Apr/2020:14:31:12 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
{"index":{}}
{"timestamp":"2020-04-30T14:31:19-05:00","message":"247.37.0.0 - - [30/Apr/2020:14:31:19 -0500] \"GET /french/splash_inet.html HTTP/1.0\" 200 3781"}
{"index":{}}
{"timestamp":"2020-04-30T14:31:22-05:00","message":"247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] \"GET /images/hm_nbg.jpg HTTP/1.0\" 304 0"}
{"index":{}}
{"timestamp":"2020-04-30T14:31:27-05:00","message":"252.0.0.0 - - [30/Apr/2020:14:31:27 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
{"index":{}}
{"timestamp":"2020-04-30T14:31:28-05:00","message":"not a valid apache log"}

你可以定義一個簡單的查詢來搜索特定的 HTTP 響應并返回所有相關字段。使用 search API 的 fields 參數來檢索 http.response 運行時字段。

GET my-index/_search
{"query": {"match": {"http.response": "304"}},"fields" : ["http.response"]
}

或者,你可以在搜索請求的上下文中定義相同的運行時字段。運行時定義和腳本與先前在索引映射中定義的完全相同。只需將該定義復制到搜索請求的 “runtime_mappings” 部分下,并包含與運行時字段匹配的查詢即可。此查詢返回的結果與先前在索引映射中為 http.response 運行時字段定義的搜索查詢相同,但僅限于此特定搜索的上下文:

GET my-index/_search
{"runtime_mappings": {"http.response": {"type": "long","script": """String response=dissect('%{clientip} %{ident} %{auth} [%{@timestamp}] "%{verb} %{request} HTTP/%{httpversion}" %{response} %{size}').extract(doc["message"].value)?.response;if (response != null) emit(Integer.parseInt(response));"""}},"query": {"match": {"http.response": "304"}},"fields" : ["http.response"]
}

Grokking grok

Grok 是一種支持可重復使用別名表達式的正則表達式方言。Grok 非常適合處理 syslog 日志、Apache 和其他 Web 服務器日志、mysql 日志以及通常為人類而非計算機使用而編寫的任何日志格式。

Grok 位于 Oniguruma 正則表達式庫之上,因此任何正則表達式在 grok 中都是有效的。Grok 使用這種正則表達式語言來命名現有模式并將它們組合成與你的字段匹配的更復雜的模式。

Grok 模式

Elastic Stack 附帶許多預定義的 grok 模式,可簡化 grok 的使用。重復使用 grok 模式的語法采用以下形式之一:

%{SYNTAX}? ? ? %{SYNTAX:ID}? ? ?%{SYNTAX:ID:TYPE}

條目描述
SYNTAX將與你的文本匹配的模式的名稱。例如,NUMBER 和 IP 都是默認模式集中提供的模式。NUMBER 模式匹配 3.44 之類的數據,而 IP 模式匹配 55.3.244.1 之類的數據。
ID你為匹配的文本片段指定的標識符。例如,3.44 可能是事件的持續時間,因此您可以將其稱為 duration。字符串 55.3.244.1 可能標識發出請求的 client。
TYPE你想要轉換命名字段的數據類型。支持的類型包括 int、long、double、float 和 boolean。

例如,假設你有如下消息數據:

3.44 55.3.244.1

第一個值是一個數字,后面跟著一個看起來像是 IP 地址的東西。你可以使用以下 grok 表達式匹配此文本:

%{NUMBER:duration} %{IP:client}

遷移到 Elastic Common Schema (ECS)

為了簡化遷移到 Elastic Common Schema (ECS) 的過程,除了現有模式之外,還提供一組新的符合 ECS 的模式。新的 ECS 模式定義捕獲符合該模式的事件字段名稱。

ECS 模式集包含舊集的所有模式定義,并且是直接替換。使用 ecs-compatability 設置切換模式。

新功能和增強功能將添加到符合 ECS 的文件中。舊模式可能仍會收到向后兼容的錯誤修復。

在 Painless 腳本中使用 grok 模式

你可以將預定義的 grok 模式合并到 Painless 腳本中以提取數據。要測試你的腳本,請使用 Painless exectute?API 的 field contexts 文或創建包含腳本的運行時字段。運行時字段提供了更大的靈活性并接受多個文檔,但如果你在測試腳本的集群上沒有寫入權限,Painless execute?API 是一個不錯的選擇。

如果你需要幫助構建 grok 模式以匹配你的數據,請使用 Kibana 中的 Grok 調試器工具。有關 Grok 的使用,請閱讀文章 “Logstash:日志解析的 Grok 模式示例”。

例如,如果你正在處理 Apache 日志數據,則可以使用 %{COMMONAPACHELOG} 語法,該語法可以理解 Apache 日志的結構。示例文檔可能如下所示:

"timestamp":"2020-04-30T14:30:17-05:00","message":"40.135.0.0 - -
[30/Apr/2020:14:30:17 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"

要從消息字段中提取 IP 地址,你可以編寫一個包含 %{COMMONAPACHELOG} 語法的 Painless 腳本。你可以使用 Painless 執行 API 的 ip 字段上下文測試此腳本,但我們改用運行時字段。

根據示例文檔,索引 @timestamp 和 message 字段。為了保持靈活性,請使用通配符作為 message 的字段類型:

DELETE my-indexPUT /my-index/
{"mappings": {"properties": {"@timestamp": {"format": "strict_date_optional_time||epoch_second","type": "date"},"message": {"type": "wildcard"}}}
}

接下來,使用 bulk?API 將一些日志數據索引到 my-index 中。

POST /my-index/_bulk?refresh
{"index":{}}
{"timestamp":"2020-04-30T14:30:17-05:00","message":"40.135.0.0 - - [30/Apr/2020:14:30:17 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
{"index":{}}
{"timestamp":"2020-04-30T14:30:53-05:00","message":"232.0.0.0 - - [30/Apr/2020:14:30:53 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
{"index":{}}
{"timestamp":"2020-04-30T14:31:12-05:00","message":"26.1.0.0 - - [30/Apr/2020:14:31:12 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
{"index":{}}
{"timestamp":"2020-04-30T14:31:19-05:00","message":"247.37.0.0 - - [30/Apr/2020:14:31:19 -0500] \"GET /french/splash_inet.html HTTP/1.0\" 200 3781"}
{"index":{}}
{"timestamp":"2020-04-30T14:31:22-05:00","message":"247.37.0.0 - - [30/Apr/2020:14:31:22 -0500] \"GET /images/hm_nbg.jpg HTTP/1.0\" 304 0"}
{"index":{}}
{"timestamp":"2020-04-30T14:31:27-05:00","message":"252.0.0.0 - - [30/Apr/2020:14:31:27 -0500] \"GET /images/hm_bg.jpg HTTP/1.0\" 200 24736"}
{"index":{}}
{"timestamp":"2020-04-30T14:31:28-05:00","message":"not a valid apache log"}

在運行時字段中整合 grok 模式和腳本

現在,你可以在包含 Painless 腳本和 grok 模式的映射中定義一個運行時字段。如果模式匹配,腳本將發出匹配 IP 地址的值。如果模式不匹配(clientip != null),腳本只會返回字段值而不會崩潰。

PUT my-index/_mappings
{"runtime": {"http.clientip": {"type": "ip","script": """String clientip=grok('%{COMMONAPACHELOG}').extract(doc["message"].value)?.clientip;if (clientip != null) emit(clientip);"""}}
}

或者,你可以在搜索請求的上下文中定義相同的運行時字段。運行時定義和腳本與之前在索引映射中定義的完全相同。只需將該定義復制到搜索請求的 “runtime_mappings” 部分下,并包含與運行時字段匹配的查詢即可。此查詢返回的結果與你在索引映射中為 http.clientip 運行時字段定義搜索查詢的結果相同,但僅限于此特定搜索的上下文:

GET my-index/_search
{"runtime_mappings": {"http.clientip": {"type": "ip","script": """String clientip=grok('%{COMMONAPACHELOG}').extract(doc["message"].value)?.clientip;if (clientip != null) emit(clientip);"""}},"query": {"match": {"http.clientip": "40.135.0.0"}},"fields" : ["http.clientip"]
}

返回計算結果

使用 http.clientip 運行時字段,您可以定義一個簡單的查詢來搜索特定的 IP 地址并返回所有相關字段。_search API 上的 fields 參數適用于所有字段,甚至包括那些未作為原始 _source 的一部分發送的字段:

GET my-index/_search
{"query": {"match": {"http.clientip": "40.135.0.0"}},"fields" : ["http.clientip"]
}

響應包含您在搜索查詢中指出的特定 IP 地址。Painless 腳本中的 grok 模式在運行時從 message 字段中提取了此值。

請繼續閱讀文章 “Elasticsearch:Painless scripting 語言(二)”。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/38876.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/38876.shtml
英文地址,請注明出處:http://en.pswp.cn/web/38876.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

安卓gdb 建立鏈接

adbshell gdbserver :1234 testdcam --sensor 0 --workmode 0 --args preview-size1024x600,picture-size640x480, --time 10 adb forwardtcp:1234 tcp:1234 //設置adb的轉發 ./prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7/bin/arm-linux-androideabi-gdb out/tar…

近紅外光譜腦功能成像(fNIRS):1.光學原理、變量選取與預處理

一、朗伯-比爾定律與修正的朗伯-比爾定律 朗伯-比爾定律 是一個描述光通過溶液時被吸收的規律。想象你有一杯有色液體&#xff0c;比如一杯紅茶。當你用一束光照射這杯液體時&#xff0c;光的一部分會被液體吸收&#xff0c;導致透過液體的光變弱。朗伯-比爾定律告訴我們&#…

mmdetection3D指定版本安裝指南

1. 下載指定版本號 選擇指定版本號下載mmdetection3d的源碼&#xff0c;如這里選擇的是0.17.2版本 git clone https://github.com/open-mmlab/mmdetection3d.git -b v0.17.22. 安裝 cd mmdetection3d安裝依賴庫 pip install -r requirment.txt編譯安裝 pip install -v e .…

redis主從復制哨兵模式集群管理

主從復制&#xff1a; 主從復制是高可用Redis的基礎&#xff0c;哨兵和集群都是在主從復制基礎上實現高可用的。主從復制主要實現了數據的多機備份&#xff0c;以及對于讀操作的負載均衡和簡單的故障恢復。缺陷&#xff1a;故障恢復無法自動化&#xff1b;寫操作無法負載均衡&…

軟件測試與質量保證 | 云班課簡答題庫

目錄 第14章 質量相關簡答題 第15章 測試實際相關簡答題 第16章 測試基本相關簡答題 第14章 質量相關簡答題 1. 簡述基本的測量原則。 測量應該基于該應用領域正確的理論之上&#xff0c;并在測量的定義中確定測度的目標&#xff1b;每一個技術測量的定義應該具有一致性和客…

HbuilderX:安卓打包證書.keystore生成與使用

前置條件 已安裝jdk或配置好jre環境。 .keystore生成 打開cmd,切換到目標路徑,輸入以下命令, keytool -genkey -alias testalias -keyalg RSA -keysize 2048 -validity 36500 -keystore test.keystore 輸入密鑰庫口令(要記住), 然后輸入一系列信息, …

ui.perfetto.dev sql 查詢某個事件范圍內,某個事件的耗時并降序排列

ui.perfetto.dev sql 查詢某個事件范圍內,某個事件的耗時并降序排列 1.打開https://ui.perfetto.dev 導入Chrome Trace Json文件2.ParallelMLP.forward下的RowParallelLinear.forward3.點擊Query(SQL),在輸入框中輸入以下內容,按CtrlEnter,顯示查詢結果4.點擊Show timeline,點擊…

2024年07年01日 Redis數據類型以及使用場景

String Hash List Set Sorted Set String&#xff0c;用的最多&#xff0c;對象序列化成json然后存儲 1.對象緩存&#xff0c;單值緩存 2.分布式鎖 Hash&#xff0c;不怎么用到 1.可緩存經常需要修改值的對象&#xff0c;可單獨對對象某個屬性進行修改 HMSET user {userI…

Windows快速打開某個路徑下的PowerShell

按住Shift右鍵打開&#xff1a; 在桌面或者文件夾頁面中&#xff0c;按住右鍵&#xff0c;在彈出的右鍵菜單中選擇“在終端中打開”或“在此處打開Powershell窗口“&#xff0c;就可打開windows PowerShell界面&#xff0c;且路徑為桌面或打開的文件夾所在路徑。

淺談貝葉斯定理

引言 貝葉斯定理用于確定事件的條件概率。它以一位英國統計學家的名字命名&#xff0c;托馬斯貝葉斯他在1763年發現了這個公式。貝葉斯定理是數學中一個非常重要的定理&#xff0c;它為一種獨特的統計推斷方法奠定了基礎。貝氏推論它用于根據可能與事件相關的條件的先驗知識&a…

C++基礎(三):C++入門(二)

上一篇博客我們正式進入C的學習&#xff0c;這一篇博客我們繼續學習C入門的基礎內容&#xff0c;一定要學好入門階段的內容&#xff0c;這是后續學習C的基礎&#xff0c;方便我們后續更加容易的理解C。 目錄 一、內聯函數 1.0 產生的原因 1.1 概念 1.2 特性 1.3 面試題 …

用隨機森林算法進行的一次故障預測

本案例將帶大家使用一份開源的S.M.A.R.T.數據集和機器學習中的隨機森林算法&#xff0c;來訓練一個硬盤故障預測模型&#xff0c;并測試效果。 實驗目標 掌握使用機器學習方法訓練模型的基本流程&#xff1b;掌握使用pandas做數據分析的基本方法&#xff1b;掌握使用scikit-l…

三大常用集合

1.Set集合 在Java中&#xff0c;Set是一種集合類型&#xff0c;它是一種不允許包含重復元素的集合&#xff0c;每個元素在Set中是唯一的。Set接口的常用實現類有HashSet、TreeSet和LinkedHashSet。以下是關于Set集合的一些重要特點和用法&#xff1a; 特點&#xff1a; 不允…

什么是mysql的回表操作

MySQL中的“回表”操作是指在執行查詢時&#xff0c;由于索引結構的限制&#xff0c;數據庫系統需要從非聚集索引&#xff08;Secondary Index&#xff09;中找到主鍵值&#xff0c;然后使用這些主鍵值回溯到聚集索引&#xff08;Clustered Index&#xff09;中獲取完整的行數據…

珠江電纜,承載您夢想的每一度電

在現代社會&#xff0c;電力無處不在&#xff0c;它不僅是經濟發展的動力&#xff0c;更是每個人生活中不可或缺的能量來源。而在這個電力驅動的世界里&#xff0c;有一家企業默默地承載著千家萬戶的夢想&#xff0c;它就是珠江電纜。 連接夢想的每一度電 珠江電纜成立于2001…

使用Java實現單元測試:JUnit教程

使用Java實現單元測試&#xff1a;JUnit教程 大家好&#xff0c;我是微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01; 在軟件開發中&#xff0c;單元測試是保證代碼質量和功能正確性的重要手段之一。JUnit是Java語言中最流行…

絕區零國際服下載 一鍵下載絕區零國際服教程

絕區零是一款米哈游傾情打造的全新都市幻想動作角色扮演游戲。在游戲中&#xff0c;我們將扮演一名繩匠&#xff0c;這是為出于各種原因需要進入危險空洞的人提供指引的專業人士。您將與獨特的角色一起踏上冒險之旅&#xff0c;攜手探索空洞&#xff0c;對戰強大敵人&#xff0…

【狀態估計】線性高斯系統的狀態估計——離散時間的遞歸濾波

前兩篇文章介紹了離散時間的批量估計、離散時間的遞歸平滑&#xff0c;本文著重介紹離散時間的遞歸濾波。 前兩篇位置&#xff1a;【狀態估計】線性高斯系統的狀態估計——離散時間的批量估計、【狀態估計】線性高斯系統的狀態估計——離散時間的遞歸平滑。 離散時間的遞歸濾波…

ollama將模型永遠加載在顯存里

問題解析 我們在使用ollma部署大語言模型的時候,如果部署的模型尺寸較大,往往在第一次加載的時候需要花費大量的時間加載模型;等加載完成后,如果長時間不調用模型,我們會發現模型已經被釋放掉了,又要重新加載,導致體驗感極差. 這是為什么呢?因為在沒被調用時,ollama默認在顯…

Steam夏促怎么注冊 Steam夏促賬號注冊教程

隨著夏日的炙熱漸漸充斥著每一個角落&#xff0c;Steam平臺也趕來添熱鬧&#xff0c;推出了一系列讓人眼前一亮的夏季促銷活動。如果你也是游戲愛好者&#xff0c;我們肯定不能錯過這次的steam夏促。正直本次夏日促銷有著很多的游戲迎來史低和新史低&#xff0c;有各種各樣的游…