From 7d12c1a52471b681a14ced0591e44013dd0e73f7 Mon Sep 17 00:00:00 2001 From: Sebastian Grund Date: Mon, 23 Sep 2024 10:50:08 +0200 Subject: [PATCH 1/3] Add tags functionality to `ElasticSearchWriter` --- lib/perfdata/elasticsearchwriter.cpp | 80 ++++++++++++++++++++++++++++ lib/perfdata/elasticsearchwriter.hpp | 4 ++ lib/perfdata/elasticsearchwriter.ti | 18 +++++++ 3 files changed, 102 insertions(+) diff --git a/lib/perfdata/elasticsearchwriter.cpp b/lib/perfdata/elasticsearchwriter.cpp index 9fb2aa90f..eaa19da59 100644 --- a/lib/perfdata/elasticsearchwriter.cpp +++ b/lib/perfdata/elasticsearchwriter.cpp @@ -5,6 +5,7 @@ #include "remote/url.hpp" #include "icinga/compatutility.hpp" #include "icinga/service.hpp" +#include "icinga/macroprocessor.hpp" #include "icinga/checkcommand.hpp" #include "base/application.hpp" #include "base/defer.hpp" @@ -131,6 +132,33 @@ void ElasticsearchWriter::Pause() ObjectImpl::Pause(); } +void ElasticsearchWriter::AddTemplateTags(const Dictionary::Ptr& fields, const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) +{ + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + Dictionary::Ptr tmpl = service ? GetServiceTagsTemplate() : GetHostTagsTemplate(); + + if (tmpl) { + MacroProcessor::ResolverList resolvers; + resolvers.emplace_back("host", host); + if (service) { + resolvers.emplace_back("service", service); + } + + ObjectLock olock(tmpl); + for (const Dictionary::Pair& pair : tmpl) { + String missingMacro; + Value value = MacroProcessor::ResolveMacros(pair.second, resolvers, cr, &missingMacro); + + if (missingMacro.IsEmpty()) { + fields->Set(pair.first, value); + } + } + } +} + void ElasticsearchWriter::AddCheckResult(const Dictionary::Ptr& fields, const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) { String prefix = "check_result."; @@ -257,6 +285,8 @@ void ElasticsearchWriter::InternalCheckResultHandler(const Checkable::Ptr& check ts = cr->GetExecutionEnd(); } + AddTemplateTags(fields, checkable, cr); + Enqueue(checkable, "checkresult", fields, ts); } @@ -307,6 +337,8 @@ void ElasticsearchWriter::StateChangeHandlerInternal(const Checkable::Ptr& check ts = cr->GetExecutionEnd(); } + AddTemplateTags(fields, checkable, cr); + Enqueue(checkable, "statechange", fields, ts); } @@ -377,6 +409,8 @@ void ElasticsearchWriter::NotificationSentToAllUsersHandlerInternal(const Notifi ts = cr->GetExecutionEnd(); } + AddTemplateTags(fields, checkable, cr); + Enqueue(checkable, "notification", fields, ts); } @@ -683,3 +717,49 @@ String ElasticsearchWriter::FormatTimestamp(double ts) return Utility::FormatDateTime("%Y-%m-%dT%H:%M:%S", ts) + "." + Convert::ToString(milliSeconds) + Utility::FormatDateTime("%z", ts); } + +void ElasticsearchWriter::ValidateHostTagsTemplate(const Lazy& lvalue, const ValidationUtils& utils) +{ + ObjectImpl::ValidateHostTagsTemplate(lvalue, utils); + + Dictionary::Ptr tags = lvalue(); + if (tags) { + ObjectLock olock(tags); + for (const Dictionary::Pair& pair : tags) { + if (pair.second.IsObjectType()) { + Array::Ptr arrObject = pair.second; + ObjectLock arrLock(arrObject); + for (const Value& arrValue : arrObject) { + if (!MacroProcessor::ValidateMacroString(arrValue)) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "host_tags_template", pair.first }, "Closing $ not found in macro format string '" + arrValue + "'.")); + } + } + } else if (!MacroProcessor::ValidateMacroString(pair.second)) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "host_tags_template", pair.first }, "Closing $ not found in macro format string '" + pair.second + "'.")); + } + } + } +} + +void ElasticsearchWriter::ValidateServiceTagsTemplate(const Lazy& lvalue, const ValidationUtils& utils) +{ + ObjectImpl::ValidateServiceTagsTemplate(lvalue, utils); + + Dictionary::Ptr tags = lvalue(); + if (tags) { + ObjectLock olock(tags); + for (const Dictionary::Pair& pair : tags) { + if (pair.second.IsObjectType()) { + Array::Ptr arrObject = pair.second; + ObjectLock arrLock(arrObject); + for (const Value& arrValue : arrObject) { + if (!MacroProcessor::ValidateMacroString(arrValue)) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "service_tags_template", pair.first }, "Closing $ not found in macro format string '" + arrValue + "'.")); + } + } + } else if (!MacroProcessor::ValidateMacroString(pair.second)) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "service_tags_template", pair.first }, "Closing $ not found in macro format string '" + pair.second + "'.")); + } + } + } +} diff --git a/lib/perfdata/elasticsearchwriter.hpp b/lib/perfdata/elasticsearchwriter.hpp index a988094d8..9ecd0fd76 100644 --- a/lib/perfdata/elasticsearchwriter.hpp +++ b/lib/perfdata/elasticsearchwriter.hpp @@ -23,6 +23,9 @@ public: static String FormatTimestamp(double ts); + void ValidateHostTagsTemplate(const Lazy &lvalue, const ValidationUtils &utils) override; + void ValidateServiceTagsTemplate(const Lazy &lvalue, const ValidationUtils &utils) override; + protected: void OnConfigLoaded() override; void Resume() override; @@ -37,6 +40,7 @@ private: std::mutex m_DataBufferMutex; void AddCheckResult(const Dictionary::Ptr& fields, const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); + void AddTemplateTags(const Dictionary::Ptr& fields, const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); void StateChangeHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, StateType type); void StateChangeHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, StateType type); diff --git a/lib/perfdata/elasticsearchwriter.ti b/lib/perfdata/elasticsearchwriter.ti index e3b8e27f5..ed9b24fb5 100644 --- a/lib/perfdata/elasticsearchwriter.ti +++ b/lib/perfdata/elasticsearchwriter.ti @@ -29,6 +29,9 @@ class ElasticsearchWriter : ConfigObject [config] bool enable_tls { default {{{ return false; }}} }; + + [config] Dictionary::Ptr host_tags_template; + [config] Dictionary::Ptr service_tags_template; [config] bool insecure_noverify { default {{{ return false; }}} }; @@ -47,4 +50,19 @@ class ElasticsearchWriter : ConfigObject }; }; +validator ElasticsearchWriter { + Dictionary host_tags_template { + String "*"; + Array "*" { + String "*"; + }; + }; + Dictionary service_tags_template { + String "*"; + Array "*" { + String "*"; + }; + }; +}; + } From a848d360ac19ff7b99e5b29170599bc8ba51f0de Mon Sep 17 00:00:00 2001 From: Sebastian Grund Date: Mon, 23 Sep 2024 10:54:47 +0200 Subject: [PATCH 2/3] Documentation for tags fuctionality in `ElasticSearchWriter` --- doc/09-object-types.md | 8 +++++++- doc/14-features.md | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/doc/09-object-types.md b/doc/09-object-types.md index c2d5e5eff..67735e986 100644 --- a/doc/09-object-types.md +++ b/doc/09-object-types.md @@ -1181,7 +1181,7 @@ Configuration Attributes: ### ElasticsearchWriter -Writes check result metrics and performance data to an Elasticsearch instance. +Writes check result metrics and performance data to an Elasticsearch or OpenSearch instance. This configuration object is available as [elasticsearch feature](14-features.md#elasticsearch-writer). Example: @@ -1194,6 +1194,10 @@ object ElasticsearchWriter "elasticsearch" { enable_send_perfdata = true + host_tags_template = { + os_name = "$host.vars.os$" + } + flush_threshold = 1024 flush_interval = 10 } @@ -1215,6 +1219,8 @@ Configuration Attributes: password | String | **Optional.** Basic auth password if Elasticsearch is hidden behind an HTTP proxy. enable\_tls | Boolean | **Optional.** Whether to use a TLS stream. Defaults to `false`. Requires an HTTP proxy. insecure\_noverify | Boolean | **Optional.** Disable TLS peer verification. + host\_tags\_template | Dictionary | **Optional.** Allows to apply additional tags to the Elasticsearch host entries. + service\_tags\_template | Dictionary | **Optional.** Allows to apply additional tags to the Elasticsearch service entries. ca\_path | String | **Optional.** Path to CA certificate to validate the remote host. Requires `enable_tls` set to `true`. cert\_path | String | **Optional.** Path to host certificate to present to the remote host for mutual verification. Requires `enable_tls` set to `true`. key\_path | String | **Optional.** Path to host key to accompany the cert\_path. Requires `enable_tls` set to `true`. diff --git a/doc/14-features.md b/doc/14-features.md index 6ad4d1095..f0981b350 100644 --- a/doc/14-features.md +++ b/doc/14-features.md @@ -398,6 +398,28 @@ check_result.perfdata..warn check_result.perfdata..crit ``` +Additionaly it is possible to configure custom tags that are applied to the metrics via `host_tags_template` or `service_tags_template`. +Depending on whether the write event was triggered on a service or host object, additional tags are added to the ElasticSearch entries. + +A host metrics entry configured with the following `host_tags_template`: + +``` +host_tags_template = { + + os_name = "$host.vars.os$" + custom_label = "A Custom Label" + list = [ "$host.groups$", "$host.vars.foo$" ] +} +``` + +Will in addition to the above mentioned lines also contain: + +``` +os_name = "Linux" +custom_label = "A Custom Label" +list = [ "group-A;linux-servers", "bar" ] +``` + #### Elasticsearch in Cluster HA Zones The Elasticsearch feature supports [high availability](06-distributed-monitoring.md#distributed-monitoring-high-availability-features) From 54cb29ec532f87d6a98615eb85e97d50cf328f50 Mon Sep 17 00:00:00 2001 From: Sebastian Grund Date: Wed, 25 Sep 2024 15:36:45 +0200 Subject: [PATCH 3/3] doc/14-features.md: correct Elasticsearch versions --- doc/14-features.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/doc/14-features.md b/doc/14-features.md index f0981b350..31c696619 100644 --- a/doc/14-features.md +++ b/doc/14-features.md @@ -335,16 +335,14 @@ More integrations: #### Elasticsearch Writer This feature forwards check results, state changes and notification events -to an [Elasticsearch](https://www.elastic.co/products/elasticsearch) installation over its HTTP API. +to an [Elasticsearch](https://www.elastic.co/products/elasticsearch) or an [OpenSearch](https://opensearch.org/) installation over its HTTP API. The check results include parsed performance data metrics if enabled. > **Note** > -> Elasticsearch 5.x or 6.x are required. This feature has been successfully tested with -> Elasticsearch 5.6.7 and 6.3.1. - - +> Elasticsearch 7.x, 8.x or Opensearch 2.12.x are required. This feature has been successfully tested with +> Elasticsearch 7.17.10, 8.8.1 and OpenSearch 2.13.0. Enable the feature and restart Icinga 2.