Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: 1712 - Validate Instrument meta data (name, unit, description) #1713

Merged
merged 11 commits into from
Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions api/include/opentelemetry/common/macros.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@
# define OPENTELEMETRY_DEPRECATED_MESSAGE(msg)
#endif

// Regex support
#if (__GNUC__ == 4 && (__GNUC_MINOR__ == 8 || __GNUC_MINOR__ == 9))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: GNUC_MINOR >= 8?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex support is partially implemented for gcc 4.8 and gcc 4.9 only. So the existing condition looks good to me.

Copy link
Member Author

@lalitb lalitb Oct 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But yeah, even GNUC_MINOR >= 8 should work fine. But let it be explicit. This was already existing in predicate.h, I just moved it here at common place :)

# define HAVE_WORKING_REGEX 0
#else
# include <regex>
# define HAVE_WORKING_REGEX 1
#endif

/* clang-format off */

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#pragma once
#ifndef ENABLE_METRICS_PREVIEW
# include <string>
# include "opentelemetry/common/macros.h"
# include "opentelemetry/nostd/string_view.h"
# include "opentelemetry/version.h"

# if HAVE_WORKING_REGEX
# include <regex>
# endif

OPENTELEMETRY_BEGIN_NAMESPACE
namespace sdk
{
namespace metrics
{
class InstrumentMetaDataValidator
{
public:
InstrumentMetaDataValidator();
bool ValidateName(nostd::string_view name) const;
bool ValidateUnit(nostd::string_view unit) const;
bool ValidateDescription(nostd::string_view description) const;

private:
# if HAVE_WORKING_REGEX
const std::regex name_reg_key_;
const std::regex unit_reg_key_;
# endif
};

} // namespace metrics
} // namespace sdk
OPENTELEMETRY_END_NAMESPACE
#endif
19 changes: 19 additions & 0 deletions sdk/include/opentelemetry/sdk/metrics/meter.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
#ifndef ENABLE_METRICS_PREVIEW
# include <chrono>
# include "opentelemetry/metrics/meter.h"
# include "opentelemetry/metrics/noop.h"
# include "opentelemetry/sdk/instrumentationscope/instrumentation_scope.h"
# include "opentelemetry/sdk/metrics/instrument_metadata_validator.h"
# include "opentelemetry/sdk/metrics/instruments.h"
# include "opentelemetry/sdk/metrics/meter_context.h"
# include "opentelemetry/sdk/metrics/state/async_metric_storage.h"
Expand Down Expand Up @@ -121,6 +123,23 @@ class Meter final : public opentelemetry::metrics::Meter
std::unique_ptr<AsyncWritableMetricStorage> RegisterAsyncMetricStorage(
InstrumentDescriptor &instrument_descriptor);
opentelemetry::common::SpinLockMutex storage_lock_;
const InstrumentMetaDataValidator instrument_metadata_validator;

static nostd::shared_ptr<opentelemetry::metrics::ObservableInstrument>
GetNoopObservableInsrument()
{
static nostd::shared_ptr<opentelemetry::metrics::ObservableInstrument> noop_instrument(
new opentelemetry::metrics::NoopObservableInstrument("", "", ""));
return noop_instrument;
}
static bool ValidateInstrument(nostd::string_view name,
nostd::string_view description,
nostd::string_view unit)
{
const static InstrumentMetaDataValidator instrument_validator;
return instrument_validator.ValidateName(name) && instrument_validator.ValidateUnit(unit) &&
instrument_validator.ValidateDescription(description);
}
};
} // namespace metrics
} // namespace sdk
Expand Down
10 changes: 2 additions & 8 deletions sdk/include/opentelemetry/sdk/metrics/view/predicate.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,9 @@
#pragma once
#ifndef ENABLE_METRICS_PREVIEW
# include <vector>
# if (__GNUC__ == 4 && (__GNUC_MINOR__ == 8 || __GNUC_MINOR__ == 9))
# define HAVE_WORKING_REGEX 0
# include "opentelemetry/sdk/common/global_log_handler.h"
# else
# include <regex>
# define HAVE_WORKING_REGEX 1
# endif

# include "opentelemetry/common/macros.h"
# include "opentelemetry/nostd/string_view.h"
# include "opentelemetry/sdk/common/global_log_handler.h"

OPENTELEMETRY_BEGIN_NAMESPACE
namespace sdk
Expand Down
1 change: 1 addition & 0 deletions sdk/src/metrics/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ add_library(
meter.cc
meter_context.cc
metric_reader.cc
instrument_metadata_validator.cc
export/periodic_exporting_metric_reader.cc
state/metric_collector.cc
state/observable_registry.cc
Expand Down
78 changes: 78 additions & 0 deletions sdk/src/metrics/instrument_metadata_validator.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

#ifndef ENABLE_METRICS_PREVIEW
# include "opentelemetry/sdk/metrics/instrument_metadata_validator.h"
# include "opentelemetry/common/macros.h"
# include "opentelemetry/nostd/string_view.h"

# include <algorithm>
# include <iostream>

OPENTELEMETRY_BEGIN_NAMESPACE
namespace sdk
{
namespace metrics
{
const size_t kMaxSize = 63;
// instrument-name = ALPHA 0*62 ("_" / "." / "-" / ALPHA / DIGIT)
const std::string kInstrumentNamePattern = "[a-zA-Z][-_.a-zA-Z0-9]{0,62}";
//
const std::string kInstrumentUnitPattern = "[\x01-\x7F]{0,63}";
// instrument-unit = It can have a maximum length of 63 ASCII chars

InstrumentMetaDataValidator::InstrumentMetaDataValidator()
# if HAVE_WORKING_REGEX
: name_reg_key_{kInstrumentNamePattern}, unit_reg_key_
{
kInstrumentUnitPattern
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this line formatted by clang-format? The format looks a little weird.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah clang-format behaved weirdly here. I have disable the clang-format for this block of code. Looks better now.
Thanks for noticing.

}
# endif
{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be put under #else block?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no it's fine actually. The part of code was somewhat confusing with clang-format. It should be more clear now.


bool InstrumentMetaDataValidator::ValidateName(nostd::string_view name) const
{
# if HAVE_WORKING_REGEX
return std::regex_match(name.data(), name_reg_key_);
# else
// size atmost 63 chars
if (name.size() > kMaxSize)
{
return false;
}
// first char should be alphanumeric
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First char should be alpha, not alphanumeric?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah good point. fixed the comment.

if (!isalpha(name[0]))
{
return false;
}
// subsequent chars should be either of alphabets, underscore, minus, dot
return !std::any_of(std::next(name.begin()), name.end(),
[](char c) { return !isalpha(c) && c != '-' && c != '_' && c != '.'; });
# endif
}

bool InstrumentMetaDataValidator::ValidateUnit(nostd::string_view unit) const
{
# if HAVE_WORKING_REGEX
return std::regex_match(unit.data(), unit_reg_key_);
# else
// length atmost 63 chars
if (unit.size() > kMaxSize)
{
return false;
}
// all should be ascii chars.
return !std::any_of(unit.begin(), unit.end(),
[](char c) { return static_cast<unsigned char>(c) > 127; });
# endif
}

bool InstrumentMetaDataValidator::ValidateDescription(nostd::string_view /*description*/) const
{
return true;
}

} // namespace metrics
} // namespace sdk
OPENTELEMETRY_END_NAMESPACE
#endif
91 changes: 91 additions & 0 deletions sdk/src/metrics/meter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# include "opentelemetry/sdk/metrics/state/observable_registry.h"
# include "opentelemetry/sdk/metrics/state/sync_metric_storage.h"

# include "opentelemetry/sdk/common/global_log_handler.h"
# include "opentelemetry/sdk/metrics/sync_instruments.h"
# include "opentelemetry/sdk_config.h"

Expand Down Expand Up @@ -39,6 +40,14 @@ nostd::unique_ptr<metrics::Counter<uint64_t>> Meter::CreateUInt64Counter(
nostd::string_view description,
nostd::string_view unit) noexcept
{
if (!ValidateInstrument(name, description, unit))
{
OTEL_INTERNAL_LOG_ERROR("Meter::CreateUInt64Counter - failed. Invalid parameters."
<< name << " " << description << " " << unit
<< ". Measurements won't be recorded.");
return nostd::unique_ptr<metrics::Counter<uint64_t>>(
new metrics::NoopCounter<uint64_t>(name, description, unit));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The calling instrumented application will not notice the error if returning a noop counter.

Not crashing now with a noop is not helping.
Instead, bugs will be filed later against opentelemetry-cpp about instrument that produce no data.

How about returning a nullptr, forcing the calling application to do something about it ?

Copy link
Member Author

@lalitb lalitb Oct 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good point. I too thought about it, but also felt that making critical application crash for some issue in telemetry configuration is not a good idea. We can also be more explicit in error message stating that measurements won't be recorded.

This is also inline with the general error handling guidelines -

Whenever API call returns values that is expected to be non-null value - in case of error in processing logic - SDK MUST return a "no-op" or any other "default" object that was (ideally) pre-allocated and readily available. This way API call sites will not crash on attempts to access methods and properties of a null objects.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the details. Ok with noop then.

}
InstrumentDescriptor instrument_descriptor = {
std::string{name.data(), name.size()}, std::string{description.data(), description.size()},
std::string{unit.data(), unit.size()}, InstrumentType::kCounter, InstrumentValueType::kLong};
Expand All @@ -52,6 +61,14 @@ nostd::unique_ptr<metrics::Counter<double>> Meter::CreateDoubleCounter(
nostd::string_view description,
nostd::string_view unit) noexcept
{
if (!ValidateInstrument(name, description, unit))
{
OTEL_INTERNAL_LOG_ERROR("Meter::CreateDoubleCounter - failed. Invalid parameters."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: probably use macro __func__ to avoid hard code function name in string liternal.

Copy link
Member Author

@lalitb lalitb Oct 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, probably we can modify OTEL_INTERNAL_* macros to automatically add class and method name to the logs. Will create a separate issue for this.

<< name << " " << description << " " << unit
<< ". Measurements won't be recorded.");
return nostd::unique_ptr<metrics::Counter<double>>(
new metrics::NoopCounter<double>(name, description, unit));
}
InstrumentDescriptor instrument_descriptor = {
std::string{name.data(), name.size()}, std::string{description.data(), description.size()},
std::string{unit.data(), unit.size()}, InstrumentType::kCounter,
Expand All @@ -66,6 +83,13 @@ nostd::shared_ptr<opentelemetry::metrics::ObservableInstrument> Meter::CreateInt
nostd::string_view description,
nostd::string_view unit) noexcept
{
if (!ValidateInstrument(name, description, unit))
{
OTEL_INTERNAL_LOG_ERROR("Meter::CreateInt64ObservableCounter - failed. Invalid parameters."
<< name << " " << description << " " << unit
<< ". Measurements won't be recorded.");
return GetNoopObservableInsrument();
}
InstrumentDescriptor instrument_descriptor = {
std::string{name.data(), name.size()}, std::string{description.data(), description.size()},
std::string{unit.data(), unit.size()}, InstrumentType::kObservableCounter,
Expand All @@ -80,6 +104,13 @@ Meter::CreateDoubleObservableCounter(nostd::string_view name,
nostd::string_view description,
nostd::string_view unit) noexcept
{
if (!ValidateInstrument(name, description, unit))
{
OTEL_INTERNAL_LOG_ERROR("Meter::CreateDoubleObservableCounter - failed. Invalid parameters."
<< name << " " << description << " " << unit
<< ". Measurements won't be recorded.");
return GetNoopObservableInsrument();
}
InstrumentDescriptor instrument_descriptor = {
std::string{name.data(), name.size()}, std::string{description.data(), description.size()},
std::string{unit.data(), unit.size()}, InstrumentType::kObservableCounter,
Expand All @@ -94,6 +125,14 @@ nostd::unique_ptr<metrics::Histogram<uint64_t>> Meter::CreateUInt64Histogram(
nostd::string_view description,
nostd::string_view unit) noexcept
{
if (!ValidateInstrument(name, description, unit))
{
OTEL_INTERNAL_LOG_ERROR("Meter::CreateUInt64Histogram - failed. Invalid parameters."
<< name << " " << description << " " << unit
<< ". Measurements won't be recorded.");
return nostd::unique_ptr<metrics::Histogram<uint64_t>>(
new metrics::NoopHistogram<uint64_t>(name, description, unit));
}
InstrumentDescriptor instrument_descriptor = {
std::string{name.data(), name.size()}, std::string{description.data(), description.size()},
std::string{unit.data(), unit.size()}, InstrumentType::kHistogram,
Expand All @@ -108,6 +147,14 @@ nostd::unique_ptr<metrics::Histogram<double>> Meter::CreateDoubleHistogram(
nostd::string_view description,
nostd::string_view unit) noexcept
{
if (!ValidateInstrument(name, description, unit))
{
OTEL_INTERNAL_LOG_ERROR("Meter::CreateDoubleHistogram - failed. Invalid parameters."
<< name << " " << description << " " << unit
<< ". Measurements won't be recorded.");
return nostd::unique_ptr<metrics::Histogram<double>>(
new metrics::NoopHistogram<double>(name, description, unit));
}
InstrumentDescriptor instrument_descriptor = {
std::string{name.data(), name.size()}, std::string{description.data(), description.size()},
std::string{unit.data(), unit.size()}, InstrumentType::kHistogram,
Expand All @@ -122,6 +169,13 @@ nostd::shared_ptr<opentelemetry::metrics::ObservableInstrument> Meter::CreateInt
nostd::string_view description,
nostd::string_view unit) noexcept
{
if (!ValidateInstrument(name, description, unit))
{
OTEL_INTERNAL_LOG_ERROR("Meter::CreateInt64ObservableGauge - failed. Invalid parameters."
<< name << " " << description << " " << unit
<< ". Measurements won't be recorded.");
return GetNoopObservableInsrument();
}
InstrumentDescriptor instrument_descriptor = {
std::string{name.data(), name.size()}, std::string{description.data(), description.size()},
std::string{unit.data(), unit.size()}, InstrumentType::kObservableGauge,
Expand All @@ -136,6 +190,13 @@ nostd::shared_ptr<opentelemetry::metrics::ObservableInstrument> Meter::CreateDou
nostd::string_view description,
nostd::string_view unit) noexcept
{
if (!ValidateInstrument(name, description, unit))
{
OTEL_INTERNAL_LOG_ERROR("Meter::CreateDoubleObservableGauge - failed. Invalid parameters."
<< name << " " << description << " " << unit
<< ". Measurements won't be recorded.");
return GetNoopObservableInsrument();
}
InstrumentDescriptor instrument_descriptor = {
std::string{name.data(), name.size()}, std::string{description.data(), description.size()},
std::string{unit.data(), unit.size()}, InstrumentType::kObservableGauge,
Expand All @@ -150,6 +211,14 @@ nostd::unique_ptr<metrics::UpDownCounter<int64_t>> Meter::CreateInt64UpDownCount
nostd::string_view description,
nostd::string_view unit) noexcept
{
if (!ValidateInstrument(name, description, unit))
{
OTEL_INTERNAL_LOG_ERROR("Meter::CreateInt64UpDownCounter - failed. Invalid parameters."
<< name << " " << description << " " << unit
<< ". Measurements won't be recorded.");
return nostd::unique_ptr<metrics::UpDownCounter<int64_t>>(
new metrics::NoopUpDownCounter<int64_t>(name, description, unit));
}
InstrumentDescriptor instrument_descriptor = {
std::string{name.data(), name.size()}, std::string{description.data(), description.size()},
std::string{unit.data(), unit.size()}, InstrumentType::kUpDownCounter,
Expand All @@ -164,6 +233,14 @@ nostd::unique_ptr<metrics::UpDownCounter<double>> Meter::CreateDoubleUpDownCount
nostd::string_view description,
nostd::string_view unit) noexcept
{
if (!ValidateInstrument(name, description, unit))
{
OTEL_INTERNAL_LOG_ERROR("Meter::CreateDoubleUpDownCounter - failed. Invalid parameters."
<< name << " " << description << " " << unit
<< ". Measurements won't be recorded.");
return nostd::unique_ptr<metrics::UpDownCounter<double>>(
new metrics::NoopUpDownCounter<double>(name, description, unit));
}
InstrumentDescriptor instrument_descriptor = {
std::string{name.data(), name.size()}, std::string{description.data(), description.size()},
std::string{unit.data(), unit.size()}, InstrumentType::kUpDownCounter,
Expand All @@ -178,6 +255,13 @@ Meter::CreateInt64ObservableUpDownCounter(nostd::string_view name,
nostd::string_view description,
nostd::string_view unit) noexcept
{
if (!ValidateInstrument(name, description, unit))
{
OTEL_INTERNAL_LOG_ERROR(
"Meter::CreateInt64ObservableUpDownCounter - failed. Invalid parameters -"
<< name << " " << description << " " << unit << ". Measurements won't be recorded.");
return GetNoopObservableInsrument();
}
InstrumentDescriptor instrument_descriptor = {
std::string{name.data(), name.size()}, std::string{description.data(), description.size()},
std::string{unit.data(), unit.size()}, InstrumentType::kObservableUpDownCounter,
Expand All @@ -192,6 +276,13 @@ Meter::CreateDoubleObservableUpDownCounter(nostd::string_view name,
nostd::string_view description,
nostd::string_view unit) noexcept
{
if (!ValidateInstrument(name, description, unit))
{
OTEL_INTERNAL_LOG_ERROR(
"Meter::CreateDoubleObservableUpDownCounter - failed. Invalid parameters."
<< name << " " << description << " " << unit << ". Measurements won't be recorded.");
return GetNoopObservableInsrument();
}
InstrumentDescriptor instrument_descriptor = {
std::string{name.data(), name.size()}, std::string{description.data(), description.size()},
std::string{unit.data(), unit.size()}, InstrumentType::kObservableUpDownCounter,
Expand Down
3 changes: 2 additions & 1 deletion sdk/test/metrics/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ foreach(
async_instruments_test
metric_reader_test
observable_registry_test
periodic_exporting_metric_reader_test)
periodic_exporting_metric_reader_test
instrument_metadata_validator_test)
add_executable(${testname} "${testname}.cc")
target_link_libraries(
${testname} ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}
Expand Down
Loading