How to build a watcher in Elasticsearch for generating OTRS ticket? - elasticsearch

I want to configure a elasticsearch webhook watcher , which will look for the keyword "error" in my indices and genarate an OTRS ticket, if found.
Right now I have following configuration :
{
"trigger": {
"schedule": {"interval": "1m"}
},
"input": {
"search": {
"request": {
"body": {
"size": 0,
"query": {"match_all": "Error"}
},
"indices": ["*"]
}
}
},
"condition": {
"compare": {
"ctx.payload.hits.total": {
"gte": 1
}
}
},
"actions" : {
"create_otrs" : {
"transform": {
"script": """{"Ticket":{"Queue":"EngineeringTeam","Priority":"P3","CustomerUser":"root#localhost","Title":"RESTCreateTest","State":"new","Type":"Incident"},"Article":{"ContentType":"text/plain;charset=utf8","Subject":"RestCreateTest","Body":"Thisisonlyatest"}}"""
},
"webhook" : {
"method" : "POST",
"host" : "http://myotrs.com/otrs/nph-genericinterface.pl/Webservice/GenericTicketConnectorREST/Ticket?UserLogin=<user>&Password=<pass>",
"port": 9200,
"body": "{{#toJson}}ctx.payload{{/toJson}}",
"auth" : {
"basic" : {
"username" : "elastic",
"password" : "<elasticsearch pass>"
}
}
}
}
}
}
This gives me Error saving watch : compile error and watcher will not simulate. There is no syntax error in the json by the way. What is wrong in the configuration? A curl operation successfully generates the OTRS ticket but I am getting a hard time configuring it with elasticsearch.

Tldr;
Your transform script is wrong.
As per the documentation:
The executed script may either return a valid model that is the equivalent of a Java™ Map or a JSON object (you will need to consult the documentation of the specific scripting language to find out what this construct is).
Solution
You can do something as simple as, converting your json into a string
{
"Ticket": {
"Queue": "EngineeringTeam",
"Priority": "P3",
"CustomerUser": "root#localhost",
"Title": "RESTCreateTest",
"State": "new",
"Type": "Incident"
},
"Article": {
"ContentType": "text/plain;charset=utf8",
"Subject": "RestCreateTest",
"Body": "Thisisonlyatest"
}
}
Becomes:
"{\"Ticket\":{\"Queue\":\"EngineeringTeam\",\"Priority\":\"P3\",\"CustomerUser\":\"root#localhost\",\"Title\":\"RESTCreateTest\",\"State\":\"new\",\"Type\":\"Incident\"},\"Article\":{\"ContentType\":\"text/plain;charset=utf8\",\"Subject\":\"RestCreateTest\",\"Body\":\"Thisisonlyatest\"}}"
And use the Json.load function to convert the string into a proper object.
Your watch will look like:
{
"watch" : {
"trigger": {
"schedule": {"interval": "1m"}
},
"input": {
"search": {
"request": {
"body": {
"size": 0,
"query": {"match_all": "Error"}
},
"indices": ["*"]
}
}
},
"condition": {
"compare": {
"ctx.payload.hits.total": {
"gte": 1
}
}
},
"actions" : {
"create_otrs" : {
"transform": {
"script": """return Json.load("{\"Ticket\":{\"Queue\":\"EngineeringTeam\",\"Priority\":\"P3\",\"CustomerUser\":\"root#localhost\",\"Title\":\"RESTCreateTest\",\"State\":\"new\",\"Type\":\"Incident\"},\"Article\":{\"ContentType\":\"text/plain;charset=utf8\",\"Subject\":\"RestCreateTest\",\"Body\":\"Thisisonlyatest\"}}");"""
},
"webhook" : {
"method" : "POST",
"host" : "http://myotrs.com/otrs/nph-genericinterface.pl/Webservice/GenericTicketConnectorREST/Ticket?UserLogin=<user>&Password=<pass>",
"port": 9200,
"body": "{{#toJson}}ctx.payload{{/toJson}}",
"auth" : {
"basic" : {
"username" : "elastic",
"password" : "<elasticsearch pass>"
}
}
}
}
}
}
}
Then another error you have in your watch is the query
{
"search": {
"request": {
"body": {
"size": 0,
"query": {"match_all": "Error"}
},
"indices": ["*"]
}
}
}
match_all should take an object such as {} so "Error" is not going to work.
So in the end the watcher looks like:
{
"watch" : {
"trigger": {
"schedule": {"interval": "1m"}
},
"input": {
"search": {
"request": {
"body": {
"size": 0,
"query": {"match_all": {}}
},
"indices": ["*"]
}
}
},
"condition": {
"compare": {
"ctx.payload.hits.total": {
"gte": 1
}
}
},
"actions" : {
"create_otrs" : {
"transform": {
"script": """return Json.load("{\"Ticket\":{\"Queue\":\"EngineeringTeam\",\"Priority\":\"P3\",\"CustomerUser\":\"root#localhost\",\"Title\":\"RESTCreateTest\",\"State\":\"new\",\"Type\":\"Incident\"},\"Article\":{\"ContentType\":\"text/plain;charset=utf8\",\"Subject\":\"RestCreateTest\",\"Body\":\"Thisisonlyatest\"}}");"""
},
"webhook" : {
"method" : "POST",
"host" : "http://myotrs.com/otrs/nph-genericinterface.pl/Webservice/GenericTicketConnectorREST/Ticket?UserLogin=<user>&Password=<pass>",
"port": 9200,
"body": "{{#toJson}}ctx.payload{{/toJson}}",
"auth" : {
"basic" : {
"username" : "elastic",
"password" : "<elasticsearch pass>"
}
}
}
}
}
}
}

Related

ElasticSearch Aggregation Filter (not nested) Array

I have mapping like that:
PUT myindex1/_mapping
{
"properties": {
"program":{
"properties":{
"rounds" : {
"properties" : {
"id" : {
"type" : "keyword"
},
"name" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
}
}
And my example docs:
POST myindex1/_doc
{
"program": {
"rounds":[
{"id":"00000000-0000-0000-0000-000000000000", "name":"Test1"},
{"id":"00000000-0000-0000-0000-000000000001", "name":"Fact2"}
]
}
}
POST myindex1/_doc
{
"program": {
"rounds":[
{"id":"00000000-0000-0000-0000-000000000002", "name":"Test3"},
{"id":"00000000-0000-0000-0000-000000000003", "name":"Fact4"}
]
}
}
POST myindex1/_doc
{
"program": {
"rounds":[
{"id":"00000000-0000-0000-0000-000000000004", "name":"Test5"},
{"id":"00000000-0000-0000-0000-000000000005", "name":"Fact6"}
]
}
}
Purpose: get only names of rounds that filtered as wildcard by user.
Aggregation query:
GET myindex1/_search
{
"aggs": {
"result": {
"aggs": {
"names": {
"terms": {
"field": "program.rounds.name.keyword",
"size": 10000,
"order": {
"_key": "asc"
}
}
}
},
"filter": {
"bool": {
"must":[
{
"wildcard": {
"program.rounds.name": "*test*"
}
}
]
}
}
}
},
"size": 0
}
This aggregation returns all 6 names, but I need only Test1,Test3,Test5. Also tried include": "/tes.*/i" regex pattern for terms, but ignore case does not work.
Note: I'm note sure abount nested type, because I don't interested in association between Id and Name (at least for now).
ElasticSearch version: 7.7.0
If you want to only aggregate specific rounds based on a condition on the name field, then you need to make rounds nested, otherwise all name values end up in the same field.
Your mapping needs to be changed to this:
PUT myindex1/
{
"mappings": {
"properties": {
"program": {
"properties": {
"rounds": {
"type": "nested", <--- add this
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
}
}
}
And then your query needs to change to this:
GET myindex1/_search
{
"size": 0,
"query": {
"nested": {
"path": "program.rounds",
"query": {
"bool": {
"must": [
{
"wildcard": {
"program.rounds.name": "*Test*"
}
}
]
}
}
}
},
"aggs": {
"rounds": {
"nested": {
"path": "program.rounds"
},
"aggs": {
"name_filter": {
"filter": {
"wildcard": {
"program.rounds.name": "*Test*"
}
},
"aggs": {
"names": {
"terms": {
"field": "program.rounds.name.keyword",
"size": 10000,
"order": {
"_key": "asc"
}
}
}
}
}
}
}
}
}
And the result will be:
"aggregations" : {
"rounds" : {
"doc_count" : 6,
"name_filter" : {
"doc_count" : 3,
"names" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "Test1",
"doc_count" : 1
},
{
"key" : "Test3",
"doc_count" : 1
},
{
"key" : "Test5",
"doc_count" : 1
}
]
}
}
}
}
UPDATE:
Actually, you can achieve what you want without introducing nested types with the following query. You were close, but the include pattern was wrong
GET myindex1/_search
{
"aggs": {
"result": {
"aggs": {
"names": {
"terms": {
"field": "program.rounds.name.keyword",
"size": 10000,
"include": "[Tt]est.*",
"order": {
"_key": "asc"
}
}
}
},
"filter": {
"bool": {
"must": [
{
"wildcard": {
"program.rounds.name": "*Test*"
}
}
]
}
}
}
},
"size": 0
}

Range #Timestamp is not giving results in Watcher Kibana

I am using below watcher json.
{
"trigger": {
"schedule": {
"interval": "2m"
}
},
"input": {
"search": {
"request": {
"search_type": "query_then_fetch",
"indices": [
"<log-abc.upr-dev-{now/d}>"
],
"types": [],
"body": {
"size": 20,
"query": {
"bool": {
"must": [
{
"match": {
"trailer_message": "SUCCESS"
}
},
{
"range": {
"#timestamp": {
"gte": "now-50m"
}
}
}
]
}
}
}
}
}
},
"condition": {
"compare": {
"ctx.payload.hits.total": {
"gt": 0
}
}
},
"actions": {
"notify-pagerduty": {
"webhook": {
"scheme": "https",
"host": "********",
"port": 443,
"method": "post",
"path": "******",
"params": {},
"headers": {
"Content-Type": "application/json"
},
"body": "{\r\n \"payload\": {\r\n \"summary\": \"{{ctx.payload.hits.total}} success \",\r\n \"source\": \"TEST TEST\",\r\n \"severity\": \"error\"\r\n },\r\n \"routing_key\": \"*******************\",\r\n \"event\": \"function\",\r\n \"client\": \"Watcher\"\r\n}"
}
}
}
}
My logs has Success values but after using Range i am not getting any results.
If i remove range it produces results from that day's logs.
I want to use range as well but it is not working.
Please let me know where is the problem.
I was able to solve this problem.
#timestamp was not the field being used in my logs.
there was a different index sessiontime. Once i pointed my Watcher to use sessiontime it started to work.

Send all the aggregations as text with Watcher (ElasticSearch)

I'm configuring right now Watcher to search in the access logs and see how many error is so far and send it to a slack account.
Well, the problem that I have is because I can't know how many aggregations I will have when the query is done and in my configurations is something like "hardcoded" to send just like 5 at maximum , but if the result is grather than 5 not works.
I'm searching for 404 status code in the query and filter only for one server, then I just need send all bucket results as notification as:
Total: Total-number-of-its
Logs:
log1: number-of-results
log2: number-of-results
log3: number-of-results
log4: number-of-results
log5: number-of-results
log6: number-of-results
Here my configuration:
"trigger" : {
"schedule" : { "interval" : "1h" }
},
"input" : {
"search" : {
"request": {
"body": {
"query": {
"bool": {
"must": [
{ "range": {
"#timestamp": {
"gte": "now-1h",
"lte": "now"
}
}
},
{
"match": {
"beat.hostname": "someserver"
}
}
],
"filter": {
"term": {
"response": "404"
}
}
}
},
"aggs": {
"host": {
"terms": {
"field": "beat.hostname",
"size": 1
}
},
"logs_list": {
"terms": {
"field": "source",
"size": 10
}
}
}
}
}
}
},
"condition": {
"compare" : { "ctx.payload.hits.total" : { "gt" : 0 }}
},
"actions" : {
"notify-slack" : {
"throttle_period" : "30m",
"slack" : {
"message" : {
"from": "Watcher",
"to" : [ "somechannel" ],
"attachments" : [
{
"title" : "400 code status found",
"text" : "Encountered: {{ctx.payload.hits.total}} in the last hour on {{ctx.payload.aggregations.host.buckets.0.key}} \n Files: \n {{ctx.payload.aggregations.logs_list.buckets.0.key}}: {{ctx.payload.aggregations.logs_list.buckets.0.doc_count}} \n {{ctx.payload.aggregations.logs_list.buckets.1.key}}: {{ctx.payload.aggregations.logs_list.buckets.1.doc_count}} \n {{ctx.payload.aggregations.logs_list.buckets.2.key}}: {{ctx.payload.aggregations.logs_list.buckets.2.doc_count}} \n {{ctx.payload.aggregations.logs_list.buckets.3.key}}: {{ctx.payload.aggregations.logs_list.buckets.3.doc_count}} \n {{ctx.payload.aggregations.logs_list.buckets.4.key}}: {{ctx.payload.aggregations.logs_list.buckets.4.doc_count}} \n {{ctx.payload.aggregations.logs_list.buckets.5.key}}: {{ctx.payload.aggregations.logs_list.buckets.5.doc_count}}",
"color" : "danger"
}
]
}
}
}
}
I don't know how should I send the "text" in actions, any ideas how should I pass all buckets result?
Thanks in advance, i'm using xpack, ELK and logstash.
If I understand your question correctly, you want to loop over your aggregation in the action. Try this:
{{#ctx.payload.aggregations.myAggName.buckets}}
{{key}}: {{doc_count}}
{{/ctx.payload.aggregations.myAggName.buckets}}

Watcher alert if no records matching filter in x minutes

I need to get ElasticSearch watcher to alert if there is no record matching a pattern inserted into the index in a time frame, it needs to be able to do this whilst grouping on another pair of field.
i.e. the records will be of the pattern:
Date Timestamp Level Message Client Site
It needs to check that Message matches "is running" for each Client's site(s) (i.e. Google Maps and Bing Maps have the same site of Maps). I tihnk the best(?) way to do this right now is to run a wacher per client site.
Sofar I have this, assume the task should write is running into the log every 20 minutes :
{
"trigger" : {
"schedule" : {
"interval" : "25m"
}
},
"input" : {
"search" : {
"request" : {
"search_type" : "count",
"indices" : "<logstash-{now/d}>",
"body" : {
"filtered" : {
"query" : {
"match_phrase" : { "Message" : "Is running" }
},
"filter" : {
"match" : { "Client" : "Example" } ,
"match" : { "Site" : "SomeSite" }
}
}
}
}
}
},
"condition" : {
"script" : "return ctx.payload.hits.total < 1"
},
"actions" : {
},
"email_administrator" : {
"email" : {
"to" : "me#host.tld",
"subject" : "Tasks are not running for {{ctx.payload.client}} on their site {{ctx.payload.site}}",
"body" : "Too many error in the system, see attached data",
"attach_data" : true,
"priority" : "high"
}
}
}
}
For anyone looking how to do this in the future, a few things need nesting in query as part of filter and match becomes term. Fun!...
{
"trigger": {
"schedule": {
"interval": "25m"
}
},
"input": {
"search": {
"request": {
"search_type": "count",
"indices": "<logstash-{now/d}>",
"body": {
"query": {
"filtered": {
"query": {
"match_phrase": {
"Message": "Its running"
}
},
"filter": {
"query": {
"term": {
"Client": "Example"
}
},
"query": {
"term": {
"Site": "SomeSite"
}
},
"query": {
"range": {
"event_timestamp": {
"gte": "now-25m",
"lte": "now"
}
}
}
}
}
}
}
}
}
},
"condition": {
"compare": {
"ctx.payload.hits.total": {
"lte": 1
}
}
},
"actions": {
"email_administrator": {
"email": {
"to": "me#host.tld",
"subject": "Tasks are not running for {{ctx.payload.client}} on their site {{ctx.payload.site}}",
"body": "Tasks are not running for {{ctx.payload.client}} on their site {{ctx.payload.site}}",
"attach_data": true,
"priority": "high"
}
}
}
}
You have to change your condition,It support json format:
"condition" : {
"script" : "return ctx.payload.hits.total : 1"
}
Please refer below link,
https://www.elastic.co/guide/en/watcher/current/condition.html

Using a custom_score to sort by a nested child's timestamp

I'm pretty new to elasticsearch and have been banging my head trying to get this sorting to work. The general idea is to search email message threads with nested messages and nested participants. The goal is to display search results at the thread level, sorting by the participant who is doing the search and either the last_received_at or last_sent_at column depending on which mailbox they are in.
My understanding is that you can't sort by a single child's value among many nested children. So in order to do this I saw a couple of suggestions for using a custom_score with a script, then sorting on the score. My plan is to dynamically change the sort column and then run a nested custom_score query that will return the date of one of the participants as the score. I've been noticing some issues with both the score format being strange (eg. always has 4 zeros at the end) and it may not be returning the date that I was expecting.
Below are simplified versions of the index and the query in question. If anyone has any suggestions, I'd be very grateful. (FYI - I am using elasticsearch version 0.20.6.)
Index:
mappings: {
message_thread: {
properties: {
id: {
type: long
}
subject: {
dynamic: true
properties: {
id: {
type: long
}
name: {
type: string
}
}
}
participants: {
dynamic: true
properties: {
id: {
type: long
}
name: {
type: string
}
last_sent_at: {
format: dateOptionalTime
type: date
}
last_received_at: {
format: dateOptionalTime
type: date
}
}
}
messages: {
dynamic: true
properties: {
sender: {
dynamic: true
properties: {
id: {
type: long
}
}
}
id: {
type: long
}
body: {
type: string
}
created_at: {
format: dateOptionalTime
type: date
}
recipient: {
dynamic: true
properties: {
id: {
type: long
}
}
}
}
}
version: {
type: long
}
}
}
}
Query:
{
"query": {
"bool": {
"must": [
{
"term": { "participants.id": 3785 }
},
{
"custom_score": {
"query": {
"filtered": {
"query": { "match_all": {} },
"filter": {
"term": { "participants.id": 3785 }
}
}
},
"params": { "sort_column": "participants.last_received_at" },
"script": "doc[sort_column].value"
}
}
]
}
},
"filter": {
"bool": {
"must": [
{
"term": { "messages.recipient.id": 3785 }
}
]
}
},
"sort": [ "_score" ]
}
Solution:
Thanks to #imotov, here is the final result. The participants were not properly nested in the index (while the messages didn't need to be). In addition, include_in_root was used for the participants to simplify the query (participants are small records and not a real size issue, although #imotov also provided an example without it). He then restructured the JSON request to use a dis_max query.
curl -XDELETE "localhost:9200/test-idx"
curl -XPUT "localhost:9200/test-idx" -d '{
"mappings": {
"message_thread": {
"properties": {
"id": {
"type": "long"
},
"messages": {
"properties": {
"body": {
"type": "string",
"analyzer": "standard"
},
"created_at": {
"type": "date",
"format": "yyyy-MM-dd'\''T'\''HH:mm:ss'\''Z'\''"
},
"id": {
"type": "long"
},
"recipient": {
"dynamic": "true",
"properties": {
"id": {
"type": "long"
}
}
},
"sender": {
"dynamic": "true",
"properties": {
"id": {
"type": "long"
}
}
}
}
},
"messages_count": {
"type": "long"
},
"participants": {
"type": "nested",
"include_in_root": true,
"properties": {
"id": {
"type": "long"
},
"last_received_at": {
"type": "date",
"format": "yyyy-MM-dd'\''T'\''HH:mm:ss'\''Z'\''"
},
"last_sent_at": {
"type": "date",
"format": "yyyy-MM-dd'\''T'\''HH:mm:ss'\''Z'\''"
},
"name": {
"type": "string",
"analyzer": "standard"
}
}
},
"subject": {
"properties": {
"id": {
"type": "long"
},
"name": {
"type": "string"
}
}
}
}
}
}
}'
curl -XPUT "localhost:9200/test-idx/message_thread/1" -d '{
"id" : 1,
"subject" : {"name": "Test Thread"},
"participants" : [
{"id" : 87793, "name" : "John Smith", "last_received_at" : null, "last_sent_at" : "2010-10-27T17:26:58Z"},
{"id" : 3785, "name" : "David Jones", "last_received_at" : "2010-10-27T17:26:58Z", "last_sent_at" : null}
],
"messages" : [{
"id" : 1,
"body" : "This is a test.",
"sender" : { "id" : 87793 },
"recipient" : { "id" : 3785},
"created_at" : "2010-10-27T17:26:58Z"
}]
}'
curl -XPUT "localhost:9200/test-idx/message_thread/2" -d '{
"id" : 2,
"subject" : {"name": "Elastic"},
"participants" : [
{"id" : 57834, "name" : "Paul Johnson", "last_received_at" : "2010-11-25T17:26:58Z", "last_sent_at" : "2010-10-25T17:26:58Z"},
{"id" : 3785, "name" : "David Jones", "last_received_at" : "2010-10-25T17:26:58Z", "last_sent_at" : "2010-11-25T17:26:58Z"}
],
"messages" : [{
"id" : 2,
"body" : "More testing of elasticsearch.",
"sender" : { "id" : 57834 },
"recipient" : { "id" : 3785},
"created_at" : "2010-10-25T17:26:58Z"
},{
"id" : 3,
"body" : "Reply message.",
"sender" : { "id" : 3785 },
"recipient" : { "id" : 57834},
"created_at" : "2010-11-25T17:26:58Z"
}]
}'
curl -XPOST localhost:9200/test-idx/_refresh
echo
# Using include in root
curl "localhost:9200/test-idx/message_thread/_search?pretty=true" -d '{
"query": {
"filtered": {
"query": {
"nested": {
"path": "participants",
"score_mode": "max",
"query": {
"custom_score": {
"query": {
"filtered": {
"query": {
"match_all": {}
},
"filter": {
"term": {
"participants.id": 3785
}
}
}
},
"params": {
"sort_column": "participants.last_received_at"
},
"script": "doc[sort_column].value"
}
}
}
},
"filter": {
"query": {
"multi_match": {
"query": "test",
"fields": ["subject.name", "participants.name", "messages.body"],
"operator": "and",
"use_dis_max": true
}
}
}
}
},
"sort": ["_score"],
"fields": []
}
'
# Not using include in root
curl "localhost:9200/test-idx/message_thread/_search?pretty=true" -d '{
"query": {
"filtered": {
"query": {
"nested": {
"path": "participants",
"score_mode": "max",
"query": {
"custom_score": {
"query": {
"filtered": {
"query": {
"match_all": {}
},
"filter": {
"term": {
"participants.id": 3785
}
}
}
},
"params": {
"sort_column": "participants.last_received_at"
},
"script": "doc[sort_column].value"
}
}
}
},
"filter": {
"query": {
"bool": {
"should": [{
"match": {
"subject.name":"test"
}
}, {
"nested" : {
"path": "participants",
"query": {
"match": {
"name":"test"
}
}
}
}, {
"match": {
"messages.body":"test"
}
}
]
}
}
}
}
},
"sort": ["_score"],
"fields": []
}
'
There are a couple of issues here. You are asking about nested objects, but participants are not defined in your mapping as nested objects. The second possible issue is that score has type float, so it might not have enough precision to represent timestamp as is. If you can figure out how to fit this value into float, you can take a look at this example: Elastic search - tagging strength (nested/child document boosting). However, if you are developing a new system, it might be prudent to upgrade to 0.90.0.Beta1, which supports sorting on nested fields.

Resources