Skip to content

Commit 4a9699b

Browse files
committed
Cleanup code after the first round of comments.
General improvements to code and documentation. Fixing comments by: - Mattia Codato - Johannes Schmidt
1 parent 5dee72e commit 4a9699b

File tree

7 files changed

+99
-89
lines changed

7 files changed

+99
-89
lines changed

doc/14-features.md

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -440,9 +440,16 @@ where you either have the Elasticsearch HTTP API, or a TLS secured HTTP proxy,
440440
or Logstash for additional filtering.
441441

442442

443-
#### Elasticsearch Writer <a id="elasticsearch-writer"></a>
443+
#### Elasticsearch Datastream Writer <a id="elasticsearch-datastream-writer"></a>
444+
445+
> **Note**
446+
>
447+
> This is a newer alternative to the Elasticsearch Writer above. The Elasticsearch Datastream Writer uses
448+
> Elasticsearch's datastream feature and follows the Elastic Common Schema (ECS), providing better performance
449+
> and data organization. Use this writer for new installations. The original Elasticsearch Writer is still
450+
> available for backward compatibility.
444451
445-
This feature sends check results with performance data to to an [Elasticsearch](https://www.elastic.co/products/elasticsearch) instance or cluster.
452+
This feature sends check results with performance data to an [Elasticsearch](https://www.elastic.co/products/elasticsearch) instance or cluster.
446453

447454
> **Note**
448455
>
@@ -455,7 +462,7 @@ icinga2 feature enable elasticsearchdatastream
455462
```
456463

457464
The default configuration expects an Elasticsearch instance running on `localhost` on port `9200`
458-
and writes to an index called `icinga2`.
465+
and writes to datastreams with the pattern `metrics-icinga2.<check>-<namespace>`.
459466

460467
More configuration details can be found [here](09-object-types.md#objecttype-elasticsearchdatastreamwriter).
461468

@@ -470,26 +477,6 @@ configuration parameter to further separate documents, e.g. by environment like
470477
The `datastream_namespace` can also be used to separate documents e.g. by hostgroups or zones, by using the
471478
`filter` function to filter the check results and use several writers with different namespaces.
472479

473-
Metric values are stored like this:
474-
475-
```
476-
check_result.perfdata.<perfdata-label>.value
477-
```
478-
479-
The following characters are escaped in perfdata labels:
480-
481-
Character | Escaped character
482-
------------|--------------------------
483-
whitespace | _
484-
\ | _
485-
/ | _
486-
:: | .
487-
488-
Note that perfdata labels may contain dots (`.`) allowing to
489-
add more subsequent levels inside the tree.
490-
`::` adds support for [multi performance labels](https://github.com/flackem/check_multi/blob/next/doc/configuration/performance.md)
491-
and is therefore replaced by `.`.
492-
493480
Icinga 2 automatically adds the following threshold metrics
494481
if existing:
495482

@@ -545,10 +532,10 @@ object ElasticsearchDatastreamWriter "elasticsearchdatastream" {
545532
}
546533
```
547534

548-
#### Elasticsearch in Cluster HA Zones <a id="elasticsearch-writer-cluster-ha"></a>
535+
#### Elasticsearch Datastream Writer in Cluster HA Zones <a id="elasticsearch-datastream-writer-cluster-ha"></a>
549536

550-
The Elasticsearch feature supports [high availability](06-distributed-monitoring.md#distributed-monitoring-high-availability-features)
551-
in cluster zones since 2.11.
537+
The Elasticsearch Datastream Writer feature supports [high availability](06-distributed-monitoring.md#distributed-monitoring-high-availability-features)
538+
in cluster zones.
552539

553540
By default, all endpoints in a zone will activate the feature and start
554541
writing events to the Elasticsearch HTTP API. In HA enabled scenarios,

lib/perfdata/elasticsearchdatastreamwriter.cpp

Lines changed: 56 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <boost/algorithm/string.hpp>
44
#include <boost/asio/ssl/context.hpp>
5+
#include <boost/asio/ssl/stream.hpp>
56
#include <boost/beast/core/flat_buffer.hpp>
67
#include <boost/beast/http/field.hpp>
78
#include <boost/beast/http/message.hpp>
@@ -43,6 +44,11 @@ using namespace icinga;
4344
namespace beast = boost::beast;
4445
namespace http = beast::http;
4546

47+
static void ExceptionHandler(const boost::exception_ptr& exp);
48+
static Array::Ptr ExtractTemplateTags(const MacroProcessor::ResolverList& resolvers, const Array::Ptr& tags_tmpl,
49+
const Checkable::Ptr& checkable, const CheckResult::Ptr& cr);
50+
static Dictionary::Ptr ExtractTemplateLabels(const MacroProcessor::ResolverList& resolvers, const Dictionary::Ptr& labels_tmpl,
51+
const Checkable::Ptr& checkable, const CheckResult::Ptr& cr);
4652

4753
REGISTER_TYPE(ElasticsearchDatastreamWriter);
4854

@@ -87,7 +93,7 @@ void ElasticsearchDatastreamWriter::Resume()
8793
Log(LogInformation, "ElasticsearchDatastreamWriter")
8894
<< "'" << GetName() << "' resumed.";
8995
m_Paused = false;
90-
m_WorkQueue.SetExceptionCallback([this](boost::exception_ptr exp) { ExceptionHandler(std::move(exp)); });
96+
m_WorkQueue.SetExceptionCallback([](boost::exception_ptr exp) { ExceptionHandler(exp); });
9197

9298
if (GetManageIndexTemplate()) {
9399
// Ensure index template exists/is updated as the first item on the work queue.
@@ -167,9 +173,9 @@ void ElasticsearchDatastreamWriter::ManageIndexTemplate() {
167173
Dictionary::Ptr jsonResponse = TrySend(component_template_url, "{\"template\":{}}");
168174
Log(LogInformation, "ElasticsearchDatastreamWriter")
169175
<< "Successfully installed component template 'icinga2@custom'.";
170-
} catch (const StatusCodeException es) {
176+
} catch (const StatusCodeException& es) {
171177
int status_code = es.GetStatusCode();
172-
if (status_code == 400) {
178+
if (status_code == static_cast<int>(http::status::bad_request)) {
173179
Log(LogInformation, "ElasticsearchDatastreamWriter")
174180
<< "Component template 'metrics-icinga2@custom' already exists, skipping creation.";
175181
// Continue to install/update index template
@@ -207,7 +213,7 @@ void ElasticsearchDatastreamWriter::ManageIndexTemplate() {
207213
}
208214
}
209215

210-
Dictionary::Ptr ElasticsearchDatastreamWriter::ExtractPerfData(const Checkable::Ptr checkable, const Array::Ptr& perfdata) {
216+
Dictionary::Ptr ElasticsearchDatastreamWriter::ExtractPerfData(const Checkable::Ptr& checkable, const Array::Ptr& perfdata) {
211217
Dictionary::Ptr pd_fields = new Dictionary();
212218
if (!perfdata)
213219
return pd_fields;
@@ -231,7 +237,7 @@ Dictionary::Ptr ElasticsearchDatastreamWriter::ExtractPerfData(const Checkable::
231237

232238
Dictionary::Ptr metric = new Dictionary();
233239
metric->Set("value", pdv->GetValue());
234-
metric->Set("counter", pdv->GetCounter());
240+
if (pdv->GetCounter()) metric->Set("counter", pdv->GetCounter());
235241

236242
if (!pdv->GetMin().IsEmpty()) metric->Set("min", pdv->GetMin());
237243
if (!pdv->GetMax().IsEmpty()) metric->Set("max", pdv->GetMax());
@@ -249,8 +255,8 @@ Dictionary::Ptr ElasticsearchDatastreamWriter::ExtractPerfData(const Checkable::
249255
}
250256

251257
// user-defined tags, as specified in the ECS specification: https://www.elastic.co/docs/reference/ecs/ecs-base#field-tags
252-
Array::Ptr ElasticsearchDatastreamWriter::ExtractTemplateTags(const MacroProcessor::ResolverList resolvers,
253-
const Array::Ptr tag_tmpl, const Checkable::Ptr& checkable, const CheckResult::Ptr& cr)
258+
static Array::Ptr ExtractTemplateTags(const MacroProcessor::ResolverList& resolvers,
259+
const Array::Ptr& tag_tmpl, const Checkable::Ptr& checkable, const CheckResult::Ptr& cr)
254260
{
255261
if (tag_tmpl == nullptr) {
256262
return nullptr;
@@ -275,8 +281,8 @@ Array::Ptr ElasticsearchDatastreamWriter::ExtractTemplateTags(const MacroProcess
275281
}
276282

277283
// user-defined labels, as specified in the ECS specification: https://www.elastic.co/docs/reference/ecs/ecs-base#field-labels
278-
Dictionary::Ptr ElasticsearchDatastreamWriter::ExtractTemplateLabels(const MacroProcessor::ResolverList resolvers,
279-
const Dictionary::Ptr labels_tmpl, const Checkable::Ptr& checkable, const CheckResult::Ptr& cr)
284+
static Dictionary::Ptr ExtractTemplateLabels(const MacroProcessor::ResolverList& resolvers,
285+
const Dictionary::Ptr& labels_tmpl, const Checkable::Ptr& checkable, const CheckResult::Ptr& cr)
280286
{
281287
if (labels_tmpl == nullptr) {
282288
return nullptr;
@@ -301,7 +307,7 @@ Dictionary::Ptr ElasticsearchDatastreamWriter::ExtractTemplateLabels(const Macro
301307
return labels;
302308
}
303309

304-
String ElasticsearchDatastreamWriter::ExtractDatastreamNamespace(MacroProcessor::ResolverList resolvers,
310+
String ElasticsearchDatastreamWriter::ExtractDatastreamNamespace(const MacroProcessor::ResolverList& resolvers,
305311
const Checkable::Ptr& checkable, const CheckResult::Ptr& cr)
306312
{
307313
String namespace_tmpl = GetDatastreamNamespace();
@@ -365,20 +371,21 @@ void ElasticsearchDatastreamWriter::CheckResultHandler(const Checkable::Ptr& che
365371
{ "version", "8.0.0" }
366372
});
367373

368-
String datastream_name = "metrics-icinga2." + checkable->GetCheckCommandRaw() + "-" + GetDatastreamNamespace();
374+
String datastream_namespace = ExtractDatastreamNamespace(resolvers, checkable, cr);
375+
String datastream_name = "metrics-icinga2." + checkable->GetCheckCommandRaw() + "-" + datastream_namespace;
369376
Dictionary::Ptr data_stream = new Dictionary({
370377
{"type", "metrics"},
371378
{"dataset", "icinga2." + checkable->GetCheckCommandRaw()},
372-
{"namespace", ExtractDatastreamNamespace(resolvers, checkable, cr)}
379+
{"namespace", datastream_namespace}
373380
});
374381

375382
Dictionary::Ptr ecs_host = new Dictionary({
376383
{"name", host->GetDisplayName()},
377384
{"hostname", host->GetName()},
378-
{"zone", host->GetZone()->GetZoneName()},
379385
{"soft_state", host->GetState()},
380386
{"hard_state", host->GetLastHardState()}
381387
});
388+
if (!host->GetZoneName().IsEmpty()) ecs_host->Set("zone", host->GetZoneName());
382389

383390
char _addr[16];
384391
if (!host->GetAddress().IsEmpty() && inet_pton(AF_INET, host->GetAddress().CStr(), _addr) == 1) {
@@ -394,10 +401,10 @@ void ElasticsearchDatastreamWriter::CheckResultHandler(const Checkable::Ptr& che
394401
ecs_service = new Dictionary({
395402
{"name", service->GetName()},
396403
{"display_name", service->GetDisplayName()},
397-
{"zone", host->GetZone()->GetZoneName()},
398404
{"soft_state", service->GetState()},
399405
{"hard_state", service->GetLastHardState()}
400406
});
407+
if (!service->GetZoneName().IsEmpty()) ecs_service->Set("zone", service->GetZoneName());
401408
}
402409

403410
Dictionary::Ptr ecs_agent = new Dictionary({
@@ -407,10 +414,10 @@ void ElasticsearchDatastreamWriter::CheckResultHandler(const Checkable::Ptr& che
407414
Endpoint::Ptr endpoint = checkable->GetCommandEndpoint();
408415
if (endpoint) {
409416
ecs_agent->Set("id", endpoint->GetName());
410-
ecs_agent->Set("version", FormatIcingaVersion(endpoint->GetIcingaVersion()));
417+
ecs_agent->Set("version", endpoint->GetIcingaVersionString());
411418
} else {
412419
ecs_agent->Set("id", IcingaApplication::GetInstance()->GetName());
413-
ecs_agent->Set("version", FormatIcingaVersion((unsigned long) IcingaApplication::GetInstance()->GetVersion()));
420+
ecs_agent->Set("version", IcingaApplication::GetAppSpecVersion());
414421
}
415422

416423
Dictionary::Ptr ecs_event = new Dictionary({
@@ -432,25 +439,38 @@ void ElasticsearchDatastreamWriter::CheckResultHandler(const Checkable::Ptr& che
432439
{"active", cr->GetActive()}
433440
});
434441

435-
Dictionary::Ptr perf_data = ExtractPerfData(checkable, cr->GetPerformanceData());
436-
437442
Dictionary::Ptr document = new Dictionary({
438443
{"@timestamp", FormatTimestamp(cr->GetScheduleEnd())},
439444
{ "ecs", ecs_metadata },
440445
{ "data_stream", data_stream },
441446
{ "host", ecs_host },
442-
{ "service", ecs_service },
443447
{ "agent", ecs_agent },
444448
{ "event", ecs_event },
445449
{ "check", check_result },
446450
{ "message", cr->GetOutput() },
447-
{ "perf_data", perf_data },
448-
{ "tags", ExtractTemplateTags(
449-
resolvers, service ? GetServiceTagsTemplate() : GetHostTagsTemplate(), checkable, cr) },
450-
{ "labels", ExtractTemplateLabels(
451-
resolvers, service ? GetServiceLabelsTemplate() : GetHostLabelsTemplate(), checkable, cr) }
452451
});
453452

453+
Dictionary::Ptr perfdata = ExtractPerfData(checkable, cr->GetPerformanceData());
454+
if (perfdata->GetLength() != 0) {
455+
document->Set("perfdata", perfdata);
456+
}
457+
458+
Array::Ptr ecs_tags = ExtractTemplateTags(
459+
resolvers, service ? GetServiceTagsTemplate() : GetHostTagsTemplate(), checkable, cr);
460+
if (ecs_tags && ecs_tags->GetLength() != 0 ) {
461+
document->Set("tags", ecs_tags);
462+
}
463+
464+
Dictionary::Ptr ecs_labels = ExtractTemplateLabels(
465+
resolvers, service ? GetServiceLabelsTemplate() : GetHostLabelsTemplate(), checkable, cr);
466+
if (ecs_labels && ecs_labels->GetLength() != 0 ) {
467+
document->Set("labels", ecs_labels);
468+
}
469+
470+
if (ecs_service && ecs_service->GetLength() != 0 ) {
471+
document->Set("service", ecs_service);
472+
}
473+
454474
EcsDocument::Ptr workqueue_document = new EcsDocument(
455475
datastream_name,
456476
document
@@ -550,7 +570,7 @@ Value ElasticsearchDatastreamWriter::TrySend(Url::Ptr url, String body) {
550570
m_FlushTimer->Reschedule(-1);
551571

552572
http::request<http::string_body> request (http::verb::post, std::string(url->Format(true)), 10);
553-
request.set(http::field::user_agent, "Icinga/" + Application::GetAppVersion());
573+
request.set(http::field::user_agent, "Icinga/" + Application::GetAppSpecVersion());
554574
request.set(http::field::host, url->GetHost() + ":" + url->GetPort());
555575

556576
/* Specify required headers by Elasticsearch. */
@@ -676,7 +696,7 @@ OptionalTlsStream ElasticsearchDatastreamWriter::Connect()
676696
auto& tlsStream (stream.first->next_layer());
677697

678698
try {
679-
tlsStream.handshake(tlsStream.client);
699+
tlsStream.handshake(boost::asio::ssl::stream_base::client);
680700
} catch (const std::exception&) {
681701
Log(LogWarning, "ElasticsearchDatastreamWriter")
682702
<< "TLS handshake with host '" << GetHost() << "' on port " << GetPort() << " failed.";
@@ -705,12 +725,12 @@ void ElasticsearchDatastreamWriter::AssertOnWorkQueue()
705725
ASSERT(m_WorkQueue.IsWorkerThread());
706726
}
707727

708-
void ElasticsearchDatastreamWriter::ExceptionHandler(boost::exception_ptr exp)
728+
static void ExceptionHandler(const boost::exception_ptr& exp)
709729
{
710730
Log(LogCritical, "ElasticsearchDatastreamWriter", "Exception during Elastic operation: Verify that your backend is operational!");
711731

712732
Log(LogDebug, "ElasticsearchDatastreamWriter")
713-
<< "Exception during Elasticsearch operation: " << DiagnosticInformation(std::move(exp));
733+
<< "Exception during Elasticsearch operation: " << DiagnosticInformation(exp);
714734
}
715735

716736
String ElasticsearchDatastreamWriter::FormatTimestamp(double ts)
@@ -731,16 +751,7 @@ String ElasticsearchDatastreamWriter::FormatTimestamp(double ts)
731751
return Utility::FormatDateTime("%Y-%m-%dT%H:%M:%S", ts) + "." + Convert::ToString(milliSeconds) + Utility::FormatDateTime("%z", ts);
732752
}
733753

734-
String ElasticsearchDatastreamWriter::FormatIcingaVersion(unsigned long version) {
735-
auto bugfix = version % 100;
736-
version /= 100;
737-
auto minor = version % 100;
738-
auto major = version / 100;
739-
return String() + std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(bugfix);
740-
}
741-
742-
743-
void ElasticsearchDatastreamWriter::ValidateTagsTemplate(Array::Ptr tags) {
754+
void ElasticsearchDatastreamWriter::ValidateTagsTemplate(const Array::Ptr& tags) {
744755
ObjectLock olock(tags);
745756
for (const Value& tag : tags) {
746757
if (!MacroProcessor::ValidateMacroString(tag)) {
@@ -752,7 +763,7 @@ void ElasticsearchDatastreamWriter::ValidateTagsTemplate(Array::Ptr tags) {
752763
void ElasticsearchDatastreamWriter::ValidateHostTagsTemplate(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils)
753764
{
754765
ObjectImpl<ElasticsearchDatastreamWriter>::ValidateHostTagsTemplate(lvalue, utils);
755-
Array::Ptr tags = lvalue();
766+
auto& tags = lvalue();
756767
if (tags) {
757768
ValidateTagsTemplate(tags);
758769
}
@@ -761,13 +772,13 @@ void ElasticsearchDatastreamWriter::ValidateHostTagsTemplate(const Lazy<Array::P
761772
void ElasticsearchDatastreamWriter::ValidateServiceTagsTemplate(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils)
762773
{
763774
ObjectImpl<ElasticsearchDatastreamWriter>::ValidateServiceTagsTemplate(lvalue, utils);
764-
Array::Ptr tags = lvalue();
775+
auto& tags = lvalue();
765776
if (tags) {
766777
ValidateTagsTemplate(tags);
767778
}
768779
}
769780

770-
void ElasticsearchDatastreamWriter::ValidateLabelsTemplate(Dictionary::Ptr labels) {
781+
void ElasticsearchDatastreamWriter::ValidateLabelsTemplate(const Dictionary::Ptr& labels) {
771782
ObjectLock olock(labels);
772783
for (const Dictionary::Pair& kv : labels) {
773784
if (!MacroProcessor::ValidateMacroString(kv.second)) {
@@ -779,7 +790,7 @@ void ElasticsearchDatastreamWriter::ValidateLabelsTemplate(Dictionary::Ptr label
779790
void ElasticsearchDatastreamWriter::ValidateHostLabelsTemplate(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils)
780791
{
781792
ObjectImpl<ElasticsearchDatastreamWriter>::ValidateHostLabelsTemplate(lvalue, utils);
782-
Dictionary::Ptr labels = lvalue();
793+
auto& labels = lvalue();
783794
if (labels) {
784795
ValidateLabelsTemplate(labels);
785796
}
@@ -788,7 +799,7 @@ void ElasticsearchDatastreamWriter::ValidateHostLabelsTemplate(const Lazy<Dictio
788799
void ElasticsearchDatastreamWriter::ValidateServiceLabelsTemplate(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils)
789800
{
790801
ObjectImpl<ElasticsearchDatastreamWriter>::ValidateServiceLabelsTemplate(lvalue, utils);
791-
Dictionary::Ptr labels = lvalue();
802+
auto& labels = lvalue();
792803
if (labels) {
793804
ValidateLabelsTemplate(labels);
794805
}
@@ -816,7 +827,6 @@ void ElasticsearchDatastreamWriter::ValidateFilter(const Lazy<Value> &lvalue, co
816827
m_CompiledFilter = std::move(fexpr);
817828
}
818829

819-
EcsDocument::EcsDocument(String index, Dictionary::Ptr document) {
820-
SetIndex(index, true);
821-
SetDocument(document, true);
830+
EcsDocument::EcsDocument(String index, Dictionary::Ptr document)
831+
: m_Index(std::move(index)), m_Document(std::move(document)) {
822832
}

0 commit comments

Comments
 (0)