-
Notifications
You must be signed in to change notification settings - Fork 7
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
feat!: Add support for hierarchical and auto-generated keys for log filtering. #62
Changes from 11 commits
bcd3b69
872b69d
dd923c9
a6b70c5
bbf7340
165a890
4618e0c
2a02ff8
483079a
4dc0ab8
9d5395e
62d83bd
4555da7
d5de853
2093972
65837c9
b8c9cf6
f12cf04
f46af70
c364896
03bd5b9
c0120ec
973966d
15211e2
eff990b
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 |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
#include <cstddef> | ||
#include <iterator> | ||
#include <memory> | ||
#include <optional> | ||
#include <string> | ||
#include <string_view> | ||
#include <type_utils.hpp> | ||
|
@@ -14,6 +15,7 @@ | |
#include <clp/ffi/KeyValuePairLogEvent.hpp> | ||
#include <clp/ffi/SchemaTree.hpp> | ||
#include <clp/ffi/Value.hpp> | ||
#include <clp/ir/EncodedTextAst.hpp> | ||
#include <clp/ir/types.hpp> | ||
#include <clp/time_types.hpp> | ||
#include <emscripten/val.h> | ||
|
@@ -28,11 +30,23 @@ namespace { | |
* Parses a string to determine the corresponding `LogLevel` enum value. | ||
* @param str | ||
* @return `LogLevel` enum corresponding to `str` if `str` matches a string in `cLogLevelNames`. | ||
* @return `LogLevel::NONE` otherwise. | ||
* @return std::nullopt otherwise. | ||
LinZhihao-723 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*/ | ||
auto parse_log_level(std::string_view str) -> LogLevel; | ||
[[nodiscard]] auto parse_log_level(std::string_view str) -> std::optional<LogLevel>; | ||
|
||
auto parse_log_level(std::string_view str) -> LogLevel { | ||
/** | ||
* Parses the log level from the given value. | ||
* @param value | ||
* @return The parsed log level forwarded from `parse_log_level`. | ||
* @return std::nullopt on failures: | ||
* - The given value's type cannot be decoded as a string. | ||
* - Forwards `clp::ir::EncodedTextAst::decode_and_unparse`'s return values. | ||
* - Forwards `parse_log_level`'s return values. | ||
LinZhihao-723 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
*/ | ||
[[nodiscard]] auto parse_log_level_from_value(clp::ffi::Value const& value | ||
) -> std::optional<LogLevel>; | ||
|
||
auto parse_log_level(std::string_view str) -> std::optional<LogLevel> { | ||
// Convert the string to uppercase. | ||
std::string log_level_name_upper_case{str}; | ||
std::ranges::transform( | ||
|
@@ -48,18 +62,88 @@ auto parse_log_level(std::string_view str) -> LogLevel { | |
log_level_name_upper_case | ||
); | ||
if (it == cLogLevelNames.end()) { | ||
return LogLevel::NONE; | ||
return std::nullopt; | ||
} | ||
|
||
return static_cast<LogLevel>(std::distance(cLogLevelNames.begin(), it)); | ||
} | ||
|
||
auto parse_log_level_from_value(clp::ffi::Value const& value) -> std::optional<LogLevel> { | ||
if (value.is<std::string>()) { | ||
return parse_log_level(value.get_immutable_view<std::string>()); | ||
} | ||
|
||
if (value.is<clp::ir::FourByteEncodedTextAst>()) { | ||
auto const optional_log_level | ||
= value.get_immutable_view<clp::ir::FourByteEncodedTextAst>().decode_and_unparse(); | ||
if (false == optional_log_level.has_value()) { | ||
return std::nullopt; | ||
} | ||
return parse_log_level(optional_log_level.value()); | ||
} | ||
|
||
if (value.is<clp::ir::EightByteEncodedTextAst>()) { | ||
auto const optional_log_level | ||
= value.get_immutable_view<clp::ir::EightByteEncodedTextAst>().decode_and_unparse(); | ||
if (false == optional_log_level.has_value()) { | ||
return std::nullopt; | ||
} | ||
return parse_log_level(optional_log_level.value()); | ||
} | ||
davemarco marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
SPDLOG_ERROR("Protocol Error: The log level value must be a valid string-convertible type."); | ||
return std::nullopt; | ||
} | ||
} // namespace | ||
|
||
auto StructuredIrUnitHandler::SchemaTreeFullBranch::match( | ||
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. This function had a bug and now it's been fixed. |
||
clp::ffi::SchemaTree const& schema_tree, | ||
clp::ffi::SchemaTree::NodeLocator const& leaf_locator | ||
) const -> bool { | ||
if (leaf_locator.get_type() != m_leaf_type) { | ||
return false; | ||
} | ||
|
||
auto optional_node_id{schema_tree.try_get_node_id(leaf_locator)}; | ||
size_t matched_depth{0}; | ||
for (auto const& key : m_leaf_to_root_path) { | ||
if (false == optional_node_id.has_value()) { | ||
break; | ||
} | ||
auto const& node{schema_tree.get_node(optional_node_id.value())}; | ||
if (node.get_key_name() != key) { | ||
return false; | ||
} | ||
++matched_depth; | ||
optional_node_id = node.get_parent_id(); | ||
} | ||
|
||
if (matched_depth != m_leaf_to_root_path.size()) { | ||
// The given leaf-to-root path is shorter than the expected one. | ||
return false; | ||
} | ||
|
||
if (optional_node_id.has_value()) { | ||
// The root is not reached yet. | ||
// The given leaf-to-root path is longer than the expected one. | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
auto StructuredIrUnitHandler::handle_log_event(StructuredLogEvent&& log_event | ||
) -> clp::ffi::ir_stream::IRErrorCode { | ||
auto const& id_value_pairs{log_event.get_user_gen_node_id_value_pairs()}; | ||
auto const timestamp = get_timestamp(id_value_pairs); | ||
auto const log_level = get_log_level(id_value_pairs); | ||
auto const timestamp = get_timestamp( | ||
m_timestamp_full_branch.is_auto_generated() | ||
? log_event.get_auto_gen_node_id_value_pairs() | ||
: log_event.get_user_gen_node_id_value_pairs() | ||
); | ||
LinZhihao-723 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
auto const log_level = get_log_level( | ||
m_log_level_full_branch.is_auto_generated() | ||
? log_event.get_auto_gen_node_id_value_pairs() | ||
: log_event.get_user_gen_node_id_value_pairs() | ||
); | ||
|
||
m_deserialized_log_events->emplace_back(std::move(log_event), log_level, timestamp); | ||
|
||
|
@@ -76,20 +160,29 @@ auto StructuredIrUnitHandler::handle_utc_offset_change( | |
|
||
auto StructuredIrUnitHandler::handle_schema_tree_node_insertion( | ||
bool is_auto_generated, | ||
clp::ffi::SchemaTree::NodeLocator schema_tree_node_locator | ||
clp::ffi::SchemaTree::NodeLocator schema_tree_node_locator, | ||
std::shared_ptr<clp::ffi::SchemaTree const> const& schema_tree | ||
) -> clp::ffi::ir_stream::IRErrorCode { | ||
if (is_auto_generated) { | ||
// TODO: Currently, all auto-generated keys are ignored. | ||
return clp::ffi::ir_stream::IRErrorCode::IRErrorCode_Success; | ||
auto const optional_inserted_node_id{schema_tree->try_get_node_id(schema_tree_node_locator)}; | ||
if (false == optional_inserted_node_id.has_value()) { | ||
return clp::ffi::ir_stream::IRErrorCode_Corrupted_IR; | ||
} | ||
auto const inserted_node_id{optional_inserted_node_id.value()}; | ||
davemarco marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
++m_current_node_id; | ||
if (false == m_optional_log_level_node_id.has_value() | ||
&& is_auto_generated == m_log_level_full_branch.is_auto_generated()) | ||
{ | ||
if (m_log_level_full_branch.match(*schema_tree, schema_tree_node_locator)) { | ||
m_optional_log_level_node_id.emplace(inserted_node_id); | ||
} | ||
} | ||
|
||
auto const& key_name{schema_tree_node_locator.get_key_name()}; | ||
if (key_name == m_log_level_key) { | ||
m_log_level_node_id.emplace(m_current_node_id); | ||
} else if (key_name == m_timestamp_key) { | ||
m_timestamp_node_id.emplace(m_current_node_id); | ||
if (false == m_optional_timestamp_node_id.has_value() | ||
&& is_auto_generated == m_timestamp_full_branch.is_auto_generated()) | ||
{ | ||
if (m_timestamp_full_branch.match(*schema_tree, schema_tree_node_locator)) { | ||
m_optional_timestamp_node_id.emplace(inserted_node_id); | ||
} | ||
} | ||
|
||
return clp::ffi::ir_stream::IRErrorCode::IRErrorCode_Success; | ||
|
@@ -102,65 +195,59 @@ auto StructuredIrUnitHandler::handle_end_of_stream() -> clp::ffi::ir_stream::IRE | |
auto StructuredIrUnitHandler::get_log_level( | ||
StructuredLogEvent::NodeIdValuePairs const& id_value_pairs | ||
) const -> LogLevel { | ||
LogLevel log_level{LogLevel::NONE}; | ||
constexpr LogLevel cDefaultLogLevel{LogLevel::NONE}; | ||
|
||
if (false == m_log_level_node_id.has_value()) { | ||
return log_level; | ||
if (false == m_optional_log_level_node_id.has_value()) { | ||
return cDefaultLogLevel; | ||
} | ||
auto const& optional_log_level_value{id_value_pairs.at(m_log_level_node_id.value())}; | ||
|
||
auto const log_level_node_id = m_optional_log_level_node_id.value(); | ||
if (false == id_value_pairs.contains(log_level_node_id)) { | ||
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. This check was missing before this PR. 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. This is good catch |
||
return cDefaultLogLevel; | ||
} | ||
|
||
auto const& optional_log_level_value = id_value_pairs.at(log_level_node_id); | ||
if (false == optional_log_level_value.has_value()) { | ||
return log_level; | ||
} | ||
auto const log_level_value = optional_log_level_value.value(); | ||
|
||
if (log_level_value.is<std::string>()) { | ||
auto const& log_level_str = log_level_value.get_immutable_view<std::string>(); | ||
log_level = parse_log_level(log_level_str); | ||
} else if (log_level_value.is<clp::ffi::value_int_t>()) { | ||
auto const& log_level_int = log_level_value.get_immutable_view<clp::ffi::value_int_t>(); | ||
if (log_level_int >= clp::enum_to_underlying_type(cValidLogLevelsBeginIdx) | ||
&& log_level_int < clp::enum_to_underlying_type(LogLevel::LENGTH)) | ||
{ | ||
log_level = static_cast<LogLevel>(log_level_int); | ||
} | ||
} else { | ||
auto log_event_idx = m_deserialized_log_events->size(); | ||
SPDLOG_ERROR( | ||
"Authoritative log level's value is not an int or string for log event index {}", | ||
log_event_idx | ||
); | ||
SPDLOG_ERROR("Protocol error: The log level cannot be an empty value."); | ||
LinZhihao-723 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return cDefaultLogLevel; | ||
} | ||
|
||
auto const optional_log_level = parse_log_level_from_value(optional_log_level_value.value()); | ||
if (false == optional_log_level.has_value()) { | ||
auto const log_event_idx = m_deserialized_log_events->size(); | ||
SPDLOG_INFO("Failed to parse log level for log event index {}", log_event_idx); | ||
LinZhihao-723 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return cDefaultLogLevel; | ||
} | ||
|
||
return log_level; | ||
return optional_log_level.value(); | ||
} | ||
|
||
auto StructuredIrUnitHandler::get_timestamp( | ||
StructuredLogEvent::NodeIdValuePairs const& id_value_pairs | ||
) const -> clp::ir::epoch_time_ms_t { | ||
clp::ir::epoch_time_ms_t timestamp{0}; | ||
|
||
if (false == m_timestamp_node_id.has_value()) { | ||
return timestamp; | ||
constexpr clp::ir::epoch_time_ms_t cDefaultTimestamp{0}; | ||
if (false == m_optional_timestamp_node_id.has_value()) { | ||
return cDefaultTimestamp; | ||
} | ||
auto const& optional_timestamp_value{id_value_pairs.at(m_timestamp_node_id.value())}; | ||
if (false == optional_timestamp_value.has_value()) { | ||
return timestamp; | ||
|
||
auto const timestamp_node_id = m_optional_timestamp_node_id.value(); | ||
if (false == id_value_pairs.contains(timestamp_node_id)) { | ||
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. Same. |
||
return cDefaultTimestamp; | ||
} | ||
auto const timestamp_value = optional_timestamp_value.value(); | ||
|
||
if (timestamp_value.is<clp::ffi::value_int_t>()) { | ||
timestamp = static_cast<clp::ir::epoch_time_ms_t>( | ||
timestamp_value.get_immutable_view<clp::ffi::value_int_t>() | ||
); | ||
} else { | ||
// TODO: Add support for parsing string-type timestamp values. | ||
auto log_event_idx = m_deserialized_log_events->size(); | ||
SPDLOG_ERROR( | ||
"Authoritative timestamp's value is not an int for log event index {}", | ||
log_event_idx | ||
); | ||
auto const& optional_ts = id_value_pairs.at(timestamp_node_id); | ||
if (false == optional_ts.has_value()) { | ||
davemarco marked this conversation as resolved.
Show resolved
Hide resolved
|
||
SPDLOG_ERROR("Protocol error: The timestamp cannot be an empty value."); | ||
LinZhihao-723 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return cDefaultTimestamp; | ||
} | ||
|
||
return timestamp; | ||
auto const& timestamp{optional_ts.value()}; | ||
davemarco marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (false == timestamp.is<clp::ffi::value_int_t>()) { | ||
davemarco marked this conversation as resolved.
Show resolved
Hide resolved
|
||
SPDLOG_ERROR("Protocol error: The timestamp value must be a valid integer."); | ||
LinZhihao-723 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return cDefaultTimestamp; | ||
} | ||
return static_cast<clp::ir::epoch_time_ms_t>( | ||
timestamp.get_immutable_view<clp::ffi::value_int_t>() | ||
davemarco marked this conversation as resolved.
Show resolved
Hide resolved
|
||
); | ||
} | ||
} // namespace clp_ffi_js::ir |
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.
I don't see any documentation directly related to this JS type. If there does exist such doc, we need to update them accordingly.
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.
The code has not been reviewed but will look something like this (subtype here)
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.
{
logLevelKey:{hasAutoPrefix: boolean; splitKey: string[]},
timestampKey:{hasAutoPrefix: boolean; splitKey: string[]}
} however, may change pending review...
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.
We might want to have an agreement on these namings first.
hasAutoPrefix
, I'd prefer to explicitly call itisAutoGenerated
splitKey
, I don't have any comments@kirkrodrigues @junhaoliao what do u think?
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.
isAutoGenerated
splitKey
sounds like some string we use to split another string up. How aboutparts
(akin to pathlib's path parts)?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.
I see. I would still rather that
StructuredIrReaderOptions
usedisAutoGenerated
to keep the IR interface logical. I thinkParsedKey
can still usehasAutoPrefix
. So in effect, I'm saying:StructuredIrReaderOptions
to use that will use the nameisAutoGenerated
.hasAutoPrefix
toisAutoGenerated
when constructingStructuredIrReaderOptions
.Is that possible?
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.
I agree. Using
hasAutoPrefix
together withparts
could be confusing since the path (the first key in the parts) should already have the auto-prefix stripped.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.
okay isAutoGenerated is good
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.
@LinZhihao-723, you might need to update this now, but maybe the array indexes will still work. idk
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.
Done. Pushed in the latest commit. Notice that I also updated the code to support
null
input, so that the reader options are not all required.