В Logstash, как мне ограничить глубину свойств JSON в моих журналах, которые превращаются в поля индекса в Elasticsearch? - PullRequest
0 голосов
/ 07 сентября 2018

Я довольно новичок в Elastic Stack. Я использую Logstash 6.4.0 для загрузки данных журнала JSON из Filebeat 6.4.0 в Elasticsearch 6.4.0 .. Я обнаружил, что получаю слишком много свойств JSON, преобразованных в поля, как только я начинаю использовать Kibana 6.4.0 ,

Я знаю это, потому что, когда я перехожу к Kibana Discover и добавляю свой индекс logstash-*, я получаю сообщение об ошибке, которое гласит:

Обнаружение: попытка получить слишком много полей docvalue_fields. Должно быть меньше или равно: [100], но было [106]. Этот предел можно установить, изменив настройку уровня индекса [index.max_docvalue_fields_search].

Если перейти к Management > Kibana > Index Patterns, я увижу 940 полей. Похоже, что каждое дочернее свойство моего корневого объекта JSON (и многие из этих дочерних свойств имеют объекты JSON в качестве значений и т. Д.) Автоматически анализируется и используется для создания полей в моем Elasticsearch logstash-* index.

Итак, вот мой вопрос - как я могу ограничить это автоматическое создание? Можно ли это сделать по глубине свойства? Можно ли сделать это другим способом?

Вот моя конфигурация Filebeat (без комментариев):

filebeat.inputs:
- type: log
  enabled: true
  paths:
  - d:/clients/company-here/rpms/logs/rpmsdev/*.json
  json.keys_under_root: true
  json.add_error_key: true

filebeat.config.modules:
  path: ${path.config}/modules.d/*.yml
  reload.enabled: false

setup.template.settings:
  index.number_of_shards: 3

setup.kibana:

output.logstash:
  hosts: ["localhost:5044"]

Вот моя текущая конфигурация конвейера Logstash:

input {
    beats {
        port => "5044"
    }
}
filter {
    date {
        match => [ "@timestamp" , "ISO8601"]
    }
}
output {
    stdout { 
        #codec => rubydebug 
    }
    elasticsearch {
        hosts => [ "localhost:9200" ]
    }
}

Вот пример одного сообщения журнала, которое я отправляю (одна строка моего файла журнала) - обратите внимание, что JSON является полностью динамическим и может меняться в зависимости от того, что регистрируется:

{
    "@timestamp": "2018-09-06T14:29:32.128",
    "level": "ERROR",
    "logger": "RPMS.WebAPI.Filters.LogExceptionAttribute",
    "message": "Log Exception: RPMS.WebAPI.Entities.LogAction",
    "eventProperties": {
        "logAction": {
            "logActionId": 26268916,
            "performedByUserId": "b36778be-6181-4b69-a0fe-e3a975ddcdd7",
            "performedByUserName": "test.sga.danny@domain.net",
            "performedByFullName": "Mike Manley",
            "controller": "RpmsToMainframeOperations",
            "action": "UpdateStoreItemPricing",
            "actionDescription": "Exception while updating store item pricing for store item with storeItemId: 146926. An error occurred while sending the request. InnerException: Unable to connect to the remote server InnerException: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond 10.1.1.133:8800",
            "url": "http://localhost:49399/api/RpmsToMainframeOperations/UpdateStoreItemPricing/146926",
            "verb": "PUT",
            "statusCode": 500,
            "status": "Internal Server Error - Exception",
            "request": {
                "itemId": 648,
                "storeId": 13,
                "storeItemId": 146926,
                "changeType": "price",
                "book": "C",
                "srpCode": "",
                "multi": 0,
                "price": "1.27",
                "percent": 40,
                "keepPercent": false,
                "keepSrp": false
            },
            "response": {
                "exception": {
                    "ClassName": "System.Net.Http.HttpRequestException",
                    "Message": "An error occurred while sending the request.",
                    "Data": null,
                    "InnerException": {
                        "ClassName": "System.Net.WebException",
                        "Message": "Unable to connect to the remote server",
                        "Data": null,
                        "InnerException": {
                            "NativeErrorCode": 10060,
                            "ClassName": "System.Net.Sockets.SocketException",
                            "Message": "A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond",
                            "Data": null,
                            "InnerException": null,
                            "HelpURL": null,
                            "StackTraceString": "   at System.Net.Sockets.Socket.InternalEndConnect(IAsyncResult asyncResult)\r\n   at System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult)\r\n   at System.Net.ServicePoint.ConnectSocketInternal(Boolean connectFailure, Socket s4, Socket s6, Socket& socket, IPAddress& address, ConnectSocketState state, IAsyncResult asyncResult, Exception& exception)",
                            "RemoteStackTraceString": null,
                            "RemoteStackIndex": 0,
                            "ExceptionMethod": "8\nInternalEndConnect\nSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\nSystem.Net.Sockets.Socket\nVoid InternalEndConnect(System.IAsyncResult)",
                            "HResult": -2147467259,
                            "Source": "System",
                            "WatsonBuckets": null
                        },
                        "HelpURL": null,
                        "StackTraceString": "   at System.Net.HttpWebRequest.EndGetRequestStream(IAsyncResult asyncResult, TransportContext& context)\r\n   at System.Net.Http.HttpClientHandler.GetRequestStreamCallback(IAsyncResult ar)",
                        "RemoteStackTraceString": null,
                        "RemoteStackIndex": 0,
                        "ExceptionMethod": "8\nEndGetRequestStream\nSystem, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\nSystem.Net.HttpWebRequest\nSystem.IO.Stream EndGetRequestStream(System.IAsyncResult, System.Net.TransportContext ByRef)",
                        "HResult": -2146233079,
                        "Source": "System",
                        "WatsonBuckets": null
                    },
                    "HelpURL": null,
                    "StackTraceString": "   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\r\n   at RPMS.WebAPI.Infrastructure.RpmsToMainframe.RpmsToMainframeOperationsManager.<PerformOperationInternalAsync>d__14.MoveNext() in D:\\Century\\Clients\\PigglyWiggly\\RPMS\\PWADC.RPMS\\RPMSDEV\\RPMS.WebAPI\\Infrastructure\\RpmsToMainframe\\RpmsToMainframeOperationsManager.cs:line 114\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\r\n   at RPMS.WebAPI.Infrastructure.RpmsToMainframe.RpmsToMainframeOperationsManager.<PerformOperationAsync>d__13.MoveNext() in D:\\Century\\Clients\\PigglyWiggly\\RPMS\\PWADC.RPMS\\RPMSDEV\\RPMS.WebAPI\\Infrastructure\\RpmsToMainframe\\RpmsToMainframeOperationsManager.cs:line 96\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\r\n   at RPMS.WebAPI.Controllers.RpmsToMainframe.RpmsToMainframeOperationsController.<UpdateStoreItemPricing>d__43.MoveNext() in D:\\Century\\Clients\\PigglyWiggly\\RPMS\\PWADC.RPMS\\RPMSDEV\\RPMS.WebAPI\\Controllers\\RpmsToMainframe\\RpmsToMainframeOperationsController.cs:line 537\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Threading.Tasks.TaskHelpersExtensions.<CastToObject>d__1`1.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__1.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Filters.ActionFilterAttribute.<CallOnActionExecutedAsync>d__6.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Web.Http.Filters.ActionFilterAttribute.<CallOnActionExecutedAsync>d__6.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Filters.ActionFilterAttribute.<ExecuteActionFilterAsyncCore>d__5.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Filters.ActionFilterAttribute.<CallOnActionExecutedAsync>d__6.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Web.Http.Filters.ActionFilterAttribute.<CallOnActionExecutedAsync>d__6.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Filters.ActionFilterAttribute.<ExecuteActionFilterAsyncCore>d__5.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__5.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Filters.AuthorizationFilterAttribute.<ExecuteAuthorizationFilterAsyncCore>d__3.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Controllers.AuthenticationFilterResult.<ExecuteAsync>d__5.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   at System.Web.Http.Controllers.ExceptionFilterResult.<ExecuteAsync>d__6.MoveNext()",
                    "RemoteStackTraceString": null,
                    "RemoteStackIndex": 0,
                    "ExceptionMethod": "8\nThrowForNonSuccess\nmscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089\nSystem.Runtime.CompilerServices.TaskAwaiter\nVoid ThrowForNonSuccess(System.Threading.Tasks.Task)",
                    "HResult": -2146233088,
                    "Source": "mscorlib",
                    "WatsonBuckets": null,
                    "SafeSerializationManager": {
                        "m_serializedStates": [{

                        }]
                    },
                    "CLR_SafeSerializationManager_RealType": "System.Net.Http.HttpRequestException, System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
                }
            },
            "performedAt": "2018-09-06T14:29:32.1195316-05:00"
        }
    },
    "logAction": "RPMS.WebAPI.Entities.LogAction"
}

1 Ответ

0 голосов
/ 14 сентября 2018

Я так и не нашел способ ограничить глубину автоматического создания поля. Я также разместил свой вопрос на форумах Elastic и так и не получил ответа. Между моментом моего поста и сейчас я узнал намного больше о Logstash.

Мое окончательное решение состояло в том, чтобы извлечь свойства JSON, которые мне были нужны, в качестве полей, а затем я использовал шаблон GREEDYDATA в фильтре grok, чтобы поместить остальные свойства в поле unextractedJson, чтобы я мог запрос значений в этом поле в Elasticsearch.

Вот моя новая конфигурация Filebeat (без комментариев):

filebeat.inputs:
- type: log
  enabled: true
  paths:
  - d:/clients/company-here/rpms/logs/rpmsdev/*.json
  #json.keys_under_root: true
  json.add_error_key: true

filebeat.config.modules:
  path: ${path.config}/modules.d/*.yml
  reload.enabled: false

setup.template.settings:
  index.number_of_shards: 3

setup.kibana:

output.logstash:
  hosts: ["localhost:5044"]

Обратите внимание, что я прокомментировал настройку json.keys_under_root, которая указывает Filebeat помещать запись журнала в формате JSON в поле json, которое отправляется в Logstash.

Вот фрагмент моей новой конфигурации конвейера Logstash:

#...

filter {

    ###########################################################################
    # common date time extraction
    date {
        match => ["[json][time]", "ISO8601"]
        remove_field => ["[json][time]"]
    }

    ###########################################################################
    # configuration for the actions log
    if [source] =~ /actionsCurrent.json/ {

        if ("" in [json][eventProperties][logAction][performedByUserName]) {
            mutate {
                add_field => {
                    "performedByUserName" => "%{[json][eventProperties][logAction][performedByUserName]}"
                    "performedByFullName" => "%{[json][eventProperties][logAction][performedByFullName]}"
                }
                remove_field => [
                    "[json][eventProperties][logAction][performedByUserName]", 
                    "[json][eventProperties][logAction][performedByFullName]"]
            }
        }

        mutate {
            add_field => {
                "logFile" => "actions"
                "logger" => "%{[json][logger]}"
                "level" => "%{[json][level]}"
                "performedAt" => "%{[json][eventProperties][logAction][performedAt]}"
                "verb" => "%{[json][eventProperties][logAction][verb]}"
                "url" => "%{[json][eventProperties][logAction][url]}"
                "controller" => "%{[json][eventProperties][logAction][controller]}"
                "action" => "%{[json][eventProperties][logAction][action]}"
                "actionDescription" => "%{[json][eventProperties][logAction][actionDescription]}"
                "statusCode" => "%{[json][eventProperties][logAction][statusCode]}"
                "status" => "%{[json][eventProperties][logAction][status]}"
            }
            remove_field => [
                "[json][logger]",
                "[json][level]",
                "[json][eventProperties][logAction][performedAt]",
                "[json][eventProperties][logAction][verb]",
                "[json][eventProperties][logAction][url]",
                "[json][eventProperties][logAction][controller]",
                "[json][eventProperties][logAction][action]",
                "[json][eventProperties][logAction][actionDescription]",
                "[json][eventProperties][logAction][statusCode]",
                "[json][eventProperties][logAction][status]",
                "[json][logAction]",
                "[json][message]"
            ]
        }

        mutate {
            convert => {
                "statusCode" => "integer"
            }
        }

        grok {
            match => { "json" => "%{GREEDYDATA:unextractedJson}" }
            remove_field => ["json"]
        }

    }

# ...

Обратите внимание на параметры конфигурации add_field в командах mutate, которые извлекают свойства в именованные поля, за которыми следуют параметры конфигурации remove_field, которые удаляют эти свойства из JSON. В конце фрагмента фильтра обратите внимание на команду grok, которая поглощает остальную часть JSON и помещает ее в поле unextractedJson. Наконец, и, что важно, я удаляю поле json, предоставленное Filebeat. Этот последний бит спасает меня от предоставления всех этих данных JSON Elasticsearch / Kibana.

Это решение принимает записи журнала, которые выглядят следующим образом:

{ "time": "2018-09-13T13:36:45.376", "level": "DEBUG", "logger": "RPMS.WebAPI.Filters.LogActionAttribute", "message": "Log Action: RPMS.WebAPI.Entities.LogAction", "eventProperties": {"logAction": {"logActionId":26270372,"performedByUserId":"83fa1d72-fac2-4184-867e-8c2935a262e6","performedByUserName":"rpmsadmin@domain.net","performedByFullName":"Super Admin","clientIpAddress":"::1","controller":"Account","action":"Logout","actionDescription":"Logout.","url":"http://localhost:49399/api/Account/Logout","verb":"POST","statusCode":200,"status":"OK","request":null,"response":null,"performedAt":"2018-09-13T13:36:45.3707739-05:00"}}, "logAction": "RPMS.WebAPI.Entities.LogAction" }

И превращает их в индексы Elasticsearch, которые выглядят так:

{
  "_index": "actions-2018.09.13",
  "_type": "doc",
  "_id": "xvA41GUBIzzhuC5epTZG",
  "_version": 1,
  "_score": null,
  "_source": {
    "level": "DEBUG",
    "tags": [
      "beats_input_raw_event"
    ],
    "@timestamp": "2018-09-13T18:36:45.376Z",
    "status": "OK",
    "unextractedJson": "{\"eventProperties\"=>{\"logAction\"=>{\"performedByUserId\"=>\"83fa1d72-fac2-4184-867e-8c2935a262e6\", \"logActionId\"=>26270372, \"clientIpAddress\"=>\"::1\"}}}",
    "action": "Logout",
    "source": "d:\\path\\actionsCurrent.json",
    "actionDescription": "Logout.",
    "offset": 136120,
    "@version": "1",
    "verb": "POST",
    "statusCode": 200,
    "controller": "Account",
    "performedByFullName": "Super Admin",
    "logger": "RPMS.WebAPI.Filters.LogActionAttribute",
    "input": {
      "type": "log"
    },
    "url": "http://localhost:49399/api/Account/Logout",
    "logFile": "actions",
    "host": {
      "name": "Development5"
    },
    "prospector": {
      "type": "log"
    },
    "performedAt": "2018-09-13T13:36:45.3707739-05:00",
    "beat": {
      "name": "Development5",
      "hostname": "Development5",
      "version": "6.4.0"
    },
    "performedByUserName": "rpmsadmin@domain.net"
  },
  "fields": {
    "@timestamp": [
      "2018-09-13T18:36:45.376Z"
    ],
    "performedAt": [
      "2018-09-13T18:36:45.370Z"
    ]
  },
  "sort": [
    1536863805376
  ]
}
...