-
Notifications
You must be signed in to change notification settings - Fork 455
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
[API] Add user facing Logging API and Benchmarks #2094
Changes from 12 commits
60fb322
a613863
4f6b360
9c5080b
6ab98fe
ca51896
d2a1a45
fc92d60
c6e2296
8e82391
11ced70
8e9c884
b9846bf
d6e96af
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// Copyright The OpenTelemetry Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
#pragma once | ||
|
||
#ifdef ENABLE_LOGS_PREVIEW | ||
|
||
# include "opentelemetry/nostd/unique_ptr.h" | ||
# include "opentelemetry/version.h" | ||
|
||
OPENTELEMETRY_BEGIN_NAMESPACE | ||
namespace logs | ||
{ | ||
|
||
/** | ||
* EventId class which acts the Id of the event with an optional name. | ||
*/ | ||
class EventId | ||
{ | ||
public: | ||
EventId(int64_t id, nostd::string_view name) noexcept | ||
{ | ||
id_ = id; | ||
name_ = nostd::unique_ptr<char[]>{new char[name.length() + 1]}; | ||
std::copy(name.begin(), name.end(), name_.get()); | ||
name_.get()[name.length()] = 0; | ||
} | ||
|
||
public: | ||
int64_t id_; | ||
nostd::unique_ptr<char[]> name_; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the explain, on the other hand, should we set There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that makes sense. Set the last element of |
||
}; | ||
|
||
} // namespace logs | ||
OPENTELEMETRY_END_NAMESPACE | ||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
#ifdef ENABLE_LOGS_PREVIEW | ||
|
||
# include "opentelemetry/common/key_value_iterable.h" | ||
# include "opentelemetry/logs/event_id.h" | ||
# include "opentelemetry/logs/log_record.h" | ||
# include "opentelemetry/logs/logger_type_traits.h" | ||
# include "opentelemetry/logs/severity.h" | ||
|
@@ -246,10 +247,225 @@ class Logger | |
this->EmitLogRecord(Severity::kFatal, std::forward<ArgumentType>(args)...); | ||
} | ||
|
||
// | ||
// OpenTelemetry C++ user-facing Logs API | ||
// | ||
|
||
inline bool Enabled(Severity severity, const EventId &event_id) const noexcept | ||
{ | ||
OPENTELEMETRY_LIKELY_IF(Enabled(severity) == false) { return false; } | ||
return EnabledImplementation(severity, event_id); | ||
} | ||
|
||
inline bool Enabled(Severity severity, int64_t event_id) const noexcept | ||
{ | ||
OPENTELEMETRY_LIKELY_IF(Enabled(severity) == false) { return false; } | ||
return EnabledImplementation(severity, event_id); | ||
} | ||
|
||
inline bool Enabled(Severity severity) const noexcept | ||
{ | ||
return static_cast<uint8_t>(severity) >= OPENTELEMETRY_ATOMIC_READ_8(&minimum_severity_); | ||
} | ||
|
||
/** | ||
* Log an event | ||
* | ||
* @severity severity of the log | ||
* @event_id event identifier of the log | ||
* @format an utf-8 string following https://messagetemplates.org/ | ||
* @attributes key value pairs of the log | ||
*/ | ||
virtual void Log(Severity severity, | ||
owent marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const EventId &event_id, | ||
nostd::string_view format, | ||
const common::KeyValueIterable &attributes) noexcept | ||
{ | ||
this->EmitLogRecord(severity, event_id, format, attributes); | ||
} | ||
|
||
virtual void Log(Severity severity, | ||
int64_t event_id, | ||
nostd::string_view format, | ||
const common::KeyValueIterable &attributes) noexcept | ||
{ | ||
Comment on lines
+287
to
+291
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need each of these helpers to be virtual functions ? A simple inline function seem sufficient. |
||
this->EmitLogRecord(severity, event_id, format, attributes); | ||
} | ||
|
||
virtual void Log(Severity severity, | ||
nostd::string_view format, | ||
const common::KeyValueIterable &attributes) noexcept | ||
{ | ||
this->EmitLogRecord(severity, format, attributes); | ||
} | ||
|
||
virtual void Log(Severity severity, nostd::string_view message) noexcept | ||
{ | ||
this->EmitLogRecord(severity, message); | ||
} | ||
|
||
// Convenient wrappers based on virtual methods Log(). | ||
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#field-severitynumber | ||
|
||
inline void Trace(const EventId &event_id, | ||
nostd::string_view format, | ||
const common::KeyValueIterable &attributes) noexcept | ||
{ | ||
this->Log(Severity::kTrace, event_id, format, attributes); | ||
} | ||
Comment on lines
+310
to
+315
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am confused about the intended usage for this helper: Is the caller supposed to check Enabled for each call, as in:
or is Trace() responsible to check the severity flag itself, as in:
The benchmarks shows both usage, is this code writing un wanted traces in the second case ? Beside, if the whole point of Logger::minimum_severity_ is to check the flags in the API before making a virtual function call, then why making calls here to virtual functions Log() and EmitLogRecord() ? Suggested changes:
For example:
Edit: If
where This will add clarity:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the suggestion @marcalff .
logger->Trace("some message template", common::MakeAttributes({"some_value_takes_time_calculate", long_time_query()})); If we are not going to call Please let me know if anything is missed here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just want to have a discuss. #define WLOGDEBUG(logger, ...) \
if(logger.check_level(log_level::DEBUG)) {
logger->log(log_level::DEBUG, __VA_ARGS__);
} And when we use Is there any way we can achieve a similar effect? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps we can define similar macro to help the user reduce boilerplate code, but it can avoid the logging cost. Based on the logging statement you shared, the the variable WLOGDEBUG(logger, "Debug message: {}", protobuf_message.DebugString()); While providing macros could help, the pointer in our API is make There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's continue the discussion about Enabled() in the next patch, for the SDK part. The fact that the caller MAY use Enabled() to optimize paths that evaluate complex arguments is good. Still we can't force that the caller MUST use Enabled() all the time, so the implementation will need to check it again at some point. |
||
|
||
inline void Trace(int64_t event_id, | ||
nostd::string_view format, | ||
const common::KeyValueIterable &attributes) noexcept | ||
{ | ||
this->Log(Severity::kTrace, event_id, format, attributes); | ||
} | ||
|
||
inline void Trace(nostd::string_view format, const common::KeyValueIterable &attributes) noexcept | ||
{ | ||
this->Log(Severity::kTrace, format, attributes); | ||
} | ||
|
||
inline void Trace(nostd::string_view message) noexcept { this->Log(Severity::kTrace, message); } | ||
|
||
inline void Debug(const EventId &event_id, | ||
nostd::string_view format, | ||
const common::KeyValueIterable &attributes) noexcept | ||
{ | ||
this->Log(Severity::kDebug, event_id, format, attributes); | ||
} | ||
|
||
inline void Debug(int64_t event_id, | ||
nostd::string_view format, | ||
const common::KeyValueIterable &attributes) noexcept | ||
{ | ||
this->Log(Severity::kDebug, event_id, format, attributes); | ||
} | ||
|
||
inline void Debug(nostd::string_view format, const common::KeyValueIterable &attributes) noexcept | ||
{ | ||
this->Log(Severity::kDebug, format, attributes); | ||
} | ||
|
||
inline void Debug(nostd::string_view message) noexcept { this->Log(Severity::kDebug, message); } | ||
|
||
inline void Info(const EventId &event_id, | ||
nostd::string_view format, | ||
const common::KeyValueIterable &attributes) noexcept | ||
{ | ||
this->Log(Severity::kInfo, event_id, format, attributes); | ||
} | ||
|
||
inline void Info(int64_t event_id, | ||
nostd::string_view format, | ||
const common::KeyValueIterable &attributes) noexcept | ||
{ | ||
this->Log(Severity::kInfo, event_id, format, attributes); | ||
} | ||
|
||
inline void Info(nostd::string_view format, const common::KeyValueIterable &attributes) noexcept | ||
{ | ||
this->Log(Severity::kInfo, format, attributes); | ||
} | ||
|
||
inline void Info(nostd::string_view message) noexcept { this->Log(Severity::kInfo, message); } | ||
|
||
inline void Warn(const EventId &event_id, | ||
nostd::string_view format, | ||
const common::KeyValueIterable &attributes) noexcept | ||
{ | ||
this->Log(Severity::kWarn, event_id, format, attributes); | ||
} | ||
|
||
inline void Warn(int64_t event_id, | ||
nostd::string_view format, | ||
const common::KeyValueIterable &attributes) noexcept | ||
{ | ||
this->Log(Severity::kWarn, event_id, format, attributes); | ||
} | ||
|
||
inline void Warn(nostd::string_view format, const common::KeyValueIterable &attributes) noexcept | ||
{ | ||
this->Log(Severity::kWarn, format, attributes); | ||
} | ||
|
||
inline void Warn(nostd::string_view message) noexcept { this->Log(Severity::kWarn, message); } | ||
|
||
inline void Error(const EventId &event_id, | ||
nostd::string_view format, | ||
const common::KeyValueIterable &attributes) noexcept | ||
{ | ||
this->Log(Severity::kError, event_id, format, attributes); | ||
} | ||
|
||
inline void Error(int64_t event_id, | ||
nostd::string_view format, | ||
const common::KeyValueIterable &attributes) noexcept | ||
{ | ||
this->Log(Severity::kError, event_id, format, attributes); | ||
} | ||
|
||
inline void Error(nostd::string_view format, const common::KeyValueIterable &attributes) noexcept | ||
{ | ||
this->Log(Severity::kError, format, attributes); | ||
} | ||
|
||
inline void Error(nostd::string_view message) noexcept { this->Log(Severity::kError, message); } | ||
|
||
inline void Fatal(const EventId &event_id, | ||
nostd::string_view format, | ||
const common::KeyValueIterable &attributes) noexcept | ||
{ | ||
this->Log(Severity::kFatal, event_id, format, attributes); | ||
} | ||
|
||
inline void Fatal(int64_t event_id, | ||
nostd::string_view format, | ||
const common::KeyValueIterable &attributes) noexcept | ||
{ | ||
this->Log(Severity::kFatal, event_id, format, attributes); | ||
} | ||
|
||
inline void Fatal(nostd::string_view format, const common::KeyValueIterable &attributes) noexcept | ||
{ | ||
this->Log(Severity::kFatal, format, attributes); | ||
} | ||
|
||
inline void Fatal(nostd::string_view message) noexcept { this->Log(Severity::kFatal, message); } | ||
|
||
// | ||
// End of OpenTelemetry C++ user-facing Log API. | ||
// | ||
|
||
protected: | ||
// TODO: discuss with community about naming for internal methods. | ||
virtual bool EnabledImplementation(Severity /*severity*/, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of having a function which child classes can derive from, I wonder if it makes sense to use a function pointer (similar to the severity level field which can be set by the implementations). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Assume this is for internal implementation so doesn't block the PR. The virtual function here looks a restricted function pointer which is stored in the virtual table. If we store the pointer here like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I actually consider the patch as a bless than a curse. (and I don't see why virtual functions cannot be patched) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Virtual function tables are in read only area of memory in modern operation system which cannot be patched normally. But I think the idea still applies to provider a mechanism for any related part to register its own There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. About the name, Using a function callback in a class member instead of a virtual method is asking for too much trouble in my opinion:
In short, the current proposal (virtual method) looks good to me, apart from the name to discuss later. |
||
const EventId & /*event_id*/) const noexcept | ||
{ | ||
return false; | ||
} | ||
|
||
virtual bool EnabledImplementation(Severity /*severity*/, int64_t /*event_id*/) const noexcept | ||
{ | ||
return false; | ||
} | ||
|
||
void SetMinimumSeverity(uint8_t severity_or_max) noexcept | ||
{ | ||
OPENTELEMETRY_ATOMIC_WRITE_8(&minimum_severity_, severity_or_max); | ||
} | ||
|
||
private: | ||
template <class... ValueType> | ||
void IgnoreTraitResult(ValueType &&...) | ||
{} | ||
|
||
// | ||
// minimum_severity_ can be updated concurrently by multiple threads/cores, so race condition on | ||
// read/write should be handled. And std::atomic can not be used here because it is not ABI | ||
// compatible for OpenTelemetry C++ API. | ||
// | ||
mutable uint8_t minimum_severity_{kMaxSeverity}; | ||
}; | ||
} // namespace logs | ||
OPENTELEMETRY_END_NAMESPACE | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This goes against the standard practice of not doing any memory copy at the API/SDK surface. The recordable interface design ensures that we can directly serialize the data at exporter, without creating any intermediate copies at the API/SDK level. One solution to avoid this copy operation can be to get rid of EventId class, and pass the event-id (as int64_t) and event-name (as notstd::string_view) directly to Log methods. Also we have only used primitive, or non-owning
nostd
types as API arguments. Even though we can debate that passing reference/pointer to a user created class mayn't break the ABI, in general good to avoid if we can.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No memory will be allocated in our logging APIs. The
EventId
class is supposed to be created by the user with sort of global lifetime, it so copies the string instead of using anystring_view
on existingstring
because it then requires the user to manage the original string object explicitly. The allocation and free of such object is not in the scope of logging API as it only takes const reference on such object.