diff --git a/lint-tasks.yml b/lint-tasks.yml index 90572f9a..96418f38 100644 --- a/lint-tasks.yml +++ b/lint-tasks.yml @@ -117,12 +117,15 @@ tasks: - "{{.CLP_FFI_PY_CPP_SRC_DIR}}/ExceptionFFI.hpp" - "{{.CLP_FFI_PY_CPP_SRC_DIR}}/ir/native/DeserializerBufferReader.cpp" - "{{.CLP_FFI_PY_CPP_SRC_DIR}}/ir/native/DeserializerBufferReader.hpp" + - "{{.CLP_FFI_PY_CPP_SRC_DIR}}/ir/native/LogEvent.hpp" - "{{.CLP_FFI_PY_CPP_SRC_DIR}}/ir/native/PyDeserializer.cpp" - "{{.CLP_FFI_PY_CPP_SRC_DIR}}/ir/native/PyDeserializer.hpp" - "{{.CLP_FFI_PY_CPP_SRC_DIR}}/ir/native/PyDeserializerBuffer.cpp" - "{{.CLP_FFI_PY_CPP_SRC_DIR}}/ir/native/PyDeserializerBuffer.hpp" - "{{.CLP_FFI_PY_CPP_SRC_DIR}}/ir/native/PyKeyValuePairLogEvent.cpp" - "{{.CLP_FFI_PY_CPP_SRC_DIR}}/ir/native/PyKeyValuePairLogEvent.hpp" + - "{{.CLP_FFI_PY_CPP_SRC_DIR}}/ir/native/PyLogEvent.cpp" + - "{{.CLP_FFI_PY_CPP_SRC_DIR}}/ir/native/PyLogEvent.hpp" - "{{.CLP_FFI_PY_CPP_SRC_DIR}}/ir/native/PyQuery.cpp" - "{{.CLP_FFI_PY_CPP_SRC_DIR}}/ir/native/PyQuery.hpp" - "{{.CLP_FFI_PY_CPP_SRC_DIR}}/ir/native/PySerializer.cpp" diff --git a/src/clp_ffi_py/ir/native/LogEvent.hpp b/src/clp_ffi_py/ir/native/LogEvent.hpp index 8175b5ec..b88d5d98 100644 --- a/src/clp_ffi_py/ir/native/LogEvent.hpp +++ b/src/clp_ffi_py/ir/native/LogEvent.hpp @@ -1,9 +1,12 @@ #ifndef CLP_FFI_PY_IR_NATIVE_LOG_EVENT_HPP #define CLP_FFI_PY_IR_NATIVE_LOG_EVENT_HPP +#include #include +#include +#include -#include +#include namespace clp_ffi_py::ir::native { /** diff --git a/src/clp_ffi_py/ir/native/PyLogEvent.cpp b/src/clp_ffi_py/ir/native/PyLogEvent.cpp index 3f17dc3d..447b5ed2 100644 --- a/src/clp_ffi_py/ir/native/PyLogEvent.cpp +++ b/src/clp_ffi_py/ir/native/PyLogEvent.cpp @@ -2,31 +2,228 @@ #include "PyLogEvent.hpp" +#include +#include #include +#include +#include +#include +#include +#include + +#include #include #include #include #include #include +#include #include namespace clp_ffi_py::ir::native { namespace { -extern "C" { /** - * Callback of PyLogEvent `__init__` method: - * __init__(log_message, timestamp, index=0, metadata=None) - * Keyword argument parsing is supported. - * Assumes `self` is uninitialized and will allocate the underlying memory. If `self` is already - * initialized this will result in memory leaks. + * Constant keys used to serialize/deserialize `PyLogEvent` objects through `__getstate__` and + * `__setstate__` methods. + */ +constexpr std::string_view cStateLogMessage{"log_message"}; +constexpr std::string_view cStateTimestamp{"timestamp"}; +constexpr std::string_view cStateFormattedTimestamp{"formatted_timestamp"}; +constexpr std::string_view cStateIndex{"index"}; + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) +PyDoc_STRVAR( + cPyLogEventDoc, + "This class represents a deserialzied log event and provides ways to access the underlying " + "log data, including the log message, the timestamp, and the log event index. " + "Normally, this class will be instantiated by the FFI IR deserialization methods.\n" + "However, with the `__init__` method provided below, direct instantiation is also " + "possible.\n\n" + "The signature of `__init__` method is shown as following:\n\n" + "__init__(self, log_message, timestamp, index=0, metadata=None)\n\n" + "Initializes an object that represents a log event. Notice that each object should be " + "strictly initialized only once. Double initialization will result in memory leaks.\n\n" + ":param log_message: The message content of the log event.\n" + ":param timestamp: The timestamp of the log event.\n" + ":param index: The message index (relative to the source CLP IR stream) of the log event.\n" + ":param metadata: The PyMetadata instance that represents the source CLP IR stream. " + "It is set to None by default.\n" +); +CLP_FFI_PY_METHOD auto PyLogEvent_init(PyLogEvent* self, PyObject* args, PyObject* keywords) -> int; + +/** + * Callback of `PyLogEvent` deallocator. * @param self - * @param args - * @param keywords - * @return 0 on success. - * @return -1 on failure with the relevant Python exception and error set. */ -auto PyLogEvent_init(PyLogEvent* self, PyObject* args, PyObject* keywords) -> int { +CLP_FFI_PY_METHOD auto PyLogEvent_dealloc(PyLogEvent* self) -> void; + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) +PyDoc_STRVAR( + cPyLogEventGetStateDoc, + "__getstate__(self)\n" + "--\n\n" + "Serializes the log event (should be called by the Python pickle module).\n\n" + ":return: Serialized log event in a Python dictionary.\n" +); +CLP_FFI_PY_METHOD auto PyLogEvent_getstate(PyLogEvent* self) -> PyObject*; + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) +PyDoc_STRVAR( + cPyLogEventSetStateDoc, + "__setstate__(self, state)\n" + "--\n\n" + "Deserializes the log event from a state dictionary.\n\n" + "Note: this function is exclusively designed for invocation by the Python pickle module. " + "Assumes `self` is uninitialized and will allocate the underlying memory. If" + "`self` is already initialized this will result in memory leaks.\n\n" + ":param state: Serialized log event represented by a Python dictionary. It is anticipated " + "to be the valid output of the `__getstate__` method.\n" + ":return: None\n" +); +CLP_FFI_PY_METHOD auto PyLogEvent_setstate(PyLogEvent* self, PyObject* state) -> PyObject*; + +/** + * Callback of `PyLogEvent`'s `__str__` method. + * @param self + * @return PyLogEvent::get_formatted_log_message + */ +CLP_FFI_PY_METHOD auto PyLogEvent_str(PyLogEvent* self) -> PyObject*; + +/** + * Callback of `PyLogEvent`'s `__repr__` method. + * @param self + * @return Python string representation of PyLogEvent state. + */ +CLP_FFI_PY_METHOD auto PyLogEvent_repr(PyLogEvent* self) -> PyObject*; + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) +PyDoc_STRVAR( + cPyLogEventGetLogMessageDoc, + "get_log_message(self)\n" + "--\n\n" + "Gets the log message of the log event.\n\n" + ":return: The log message.\n" +); +CLP_FFI_PY_METHOD auto PyLogEvent_get_log_message(PyLogEvent* self) -> PyObject*; + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) +PyDoc_STRVAR( + cPyLogEventGetTimestampDoc, + "get_timestamp(self)\n" + "--\n\n" + "Gets the Unix epoch timestamp in milliseconds of the log event.\n\n" + ":return: The timestamp in milliseconds.\n" +); +CLP_FFI_PY_METHOD auto PyLogEvent_get_timestamp(PyLogEvent* self) -> PyObject*; + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) +PyDoc_STRVAR( + cPyLogEventGetIndexDoc, + "get_index(self)\n" + "--\n\n" + "Gets the message index (relative to the source CLP IR stream) of the log event. This " + "index is set to 0 by default.\n\n" + ":return: The log event index.\n" +); +CLP_FFI_PY_METHOD auto PyLogEvent_get_index(PyLogEvent* self) -> PyObject*; + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) +PyDoc_STRVAR( + cPyLogEventMatchQueryDoc, + "match_query(self, query)\n" + "--\n\n" + "Matches the underlying log event against the given query. Refer to the documentation of " + "clp_ffi_py.Query for more details.\n\n" + ":param query: Input Query object.\n" + ":return: True if the log event matches the query, False otherwise.\n" +); +CLP_FFI_PY_METHOD auto PyLogEvent_match_query(PyLogEvent* self, PyObject* query) -> PyObject*; + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) +PyDoc_STRVAR( + cPyLogEventGetFormattedMessageDoc, + "get_formatted_message(self, timezone=None)\n" + "--\n\n" + "Gets the formatted log message of the log event.\n\n" + "If a specific timezone is provided, it will be used to format the timestamp. " + "Otherwise, the timestamp will be formatted using the timezone from the source CLP IR " + "stream.\n\n" + ":param timezone: Python tzinfo object that specifies a timezone.\n" + ":return: The formatted message.\n" +); +CLP_FFI_PY_METHOD auto +PyLogEvent_get_formatted_message(PyLogEvent* self, PyObject* args, PyObject* keywords) -> PyObject*; + +// NOLINTNEXTLINE(*-avoid-c-arrays, cppcoreguidelines-avoid-non-const-global-variables) +PyMethodDef PyLogEvent_method_table[]{ + {"get_log_message", + py_c_function_cast(PyLogEvent_get_log_message), + METH_NOARGS, + static_cast(cPyLogEventGetLogMessageDoc)}, + + {"get_timestamp", + py_c_function_cast(PyLogEvent_get_timestamp), + METH_NOARGS, + static_cast(cPyLogEventGetTimestampDoc)}, + + {"get_index", + py_c_function_cast(PyLogEvent_get_index), + METH_NOARGS, + static_cast(cPyLogEventGetIndexDoc)}, + + {"get_formatted_message", + py_c_function_cast(PyLogEvent_get_formatted_message), + METH_KEYWORDS | METH_VARARGS, + static_cast(cPyLogEventGetFormattedMessageDoc)}, + + {"match_query", + py_c_function_cast(PyLogEvent_match_query), + METH_O, + static_cast(cPyLogEventMatchQueryDoc)}, + + {"__getstate__", + py_c_function_cast(PyLogEvent_getstate), + METH_NOARGS, + static_cast(cPyLogEventGetStateDoc)}, + + {"__setstate__", + py_c_function_cast(PyLogEvent_setstate), + METH_O, + static_cast(cPyLogEventSetStateDoc)}, + + {nullptr} +}; + +// NOLINTBEGIN(cppcoreguidelines-pro-type-*-cast) +// NOLINTNEXTLINE(*-avoid-c-arrays, cppcoreguidelines-avoid-non-const-global-variables) +PyType_Slot PyLogEvent_slots[]{ + {Py_tp_alloc, reinterpret_cast(PyType_GenericAlloc)}, + {Py_tp_dealloc, reinterpret_cast(PyLogEvent_dealloc)}, + {Py_tp_new, reinterpret_cast(PyType_GenericNew)}, + {Py_tp_init, reinterpret_cast(PyLogEvent_init)}, + {Py_tp_str, reinterpret_cast(PyLogEvent_str)}, + {Py_tp_repr, reinterpret_cast(PyLogEvent_repr)}, + {Py_tp_methods, static_cast(PyLogEvent_method_table)}, + {Py_tp_doc, const_cast(static_cast(cPyLogEventDoc))}, + {0, nullptr} +}; +// NOLINTEND(cppcoreguidelines-pro-type-*-cast) + +/** + * PyLogEvent Python type specifications. + */ +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +PyType_Spec PyLogEvent_type_spec{ + "clp_ffi_py.ir.native.LogEvent", + sizeof(PyLogEvent), + 0, + Py_TPFLAGS_DEFAULT, + static_cast(PyLogEvent_slots) +}; + +CLP_FFI_PY_METHOD auto PyLogEvent_init(PyLogEvent* self, PyObject* args, PyObject* keywords) + -> int { static char keyword_message[]{"log_message"}; static char keyword_timestamp[]{"timestamp"}; static char keyword_message_idx[]{"index"}; @@ -86,40 +283,12 @@ auto PyLogEvent_init(PyLogEvent* self, PyObject* args, PyObject* keywords) -> in return 0; } -/** - * Callback of PyLogEvent deallocator. - * @param self - */ -auto PyLogEvent_dealloc(PyLogEvent* self) -> void { +CLP_FFI_PY_METHOD auto PyLogEvent_dealloc(PyLogEvent* self) -> void { self->clean(); PyObject_Del(self); } -/** - * Constant keys used to serialize/deserialize PyLogEvent objects through `__getstate__` and - * `__setstate__` methods. - */ -constexpr char const* const cStateLogMessage = "log_message"; -constexpr char const* const cStateTimestamp = "timestamp"; -constexpr char const* const cStateFormattedTimestamp = "formatted_timestamp"; -constexpr char const* const cStateIndex = "index"; - -// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) -PyDoc_STRVAR( - cPyLogEventGetStateDoc, - "__getstate__(self)\n" - "--\n\n" - "Serializes the log event (should be called by the Python pickle module).\n\n" - ":return: Serialized log event in a Python dictionary.\n" -); - -/** - * Callback of PyLogEvent `__getstate__` method. - * @param self - * @return Python dictionary that contains the serialized object. - * @return nullptr on failure with the relevant Python exception and error set. - */ -auto PyLogEvent_getstate(PyLogEvent* self) -> PyObject* { +CLP_FFI_PY_METHOD auto PyLogEvent_getstate(PyLogEvent* self) -> PyObject* { auto* log_event{self->get_log_event()}; if (false == log_event->has_formatted_timestamp()) { PyObjectPtr const formatted_timestamp_object{ @@ -141,42 +310,18 @@ auto PyLogEvent_getstate(PyLogEvent* self) -> PyObject* { return Py_BuildValue( "{sssssLsK}", - cStateLogMessage, + get_c_str_from_constexpr_string_view(cStateLogMessage), log_event->get_log_message().c_str(), - static_cast(cStateFormattedTimestamp), + get_c_str_from_constexpr_string_view(cStateFormattedTimestamp), log_event->get_formatted_timestamp().c_str(), - static_cast(cStateTimestamp), + get_c_str_from_constexpr_string_view(cStateTimestamp), log_event->get_timestamp(), - cStateIndex, + get_c_str_from_constexpr_string_view(cStateIndex), log_event->get_index() ); } -// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) -PyDoc_STRVAR( - cPyLogEventSetStateDoc, - "__setstate__(self, state)\n" - "--\n\n" - "Deserializes the log event from a state dictionary.\n\n" - "Note: this function is exclusively designed for invocation by the Python pickle module. " - "Assumes `self` is uninitialized and will allocate the underlying memory. If" - "`self` is already initialized this will result in memory leaks.\n\n" - ":param state: Serialized log event represented by a Python dictionary. It is anticipated " - "to be the valid output of the `__getstate__` method.\n" - ":return: None\n" -); - -/** - * Callback of PyLogEvent `__setstate__` method. - * Note: should only be used by the Python pickle module. - * Assumes `self` is uninitialized and will allocate the underlying memory. If `self` is already - * initialized this will result in memory leaks. - * @param self - * @param state Python dictionary that contains the serialized object info. - * @return Py_None on success - * @return nullptr on failure with the relevant Python exception and error set. - */ -auto PyLogEvent_setstate(PyLogEvent* self, PyObject* state) -> PyObject* { +CLP_FFI_PY_METHOD auto PyLogEvent_setstate(PyLogEvent* self, PyObject* state) -> PyObject* { self->default_init(); if (false == static_cast(PyDict_CheckExact(state))) { @@ -187,7 +332,9 @@ auto PyLogEvent_setstate(PyLogEvent* self, PyObject* state) -> PyObject* { return nullptr; } - auto* log_message_obj{PyDict_GetItemString(state, cStateLogMessage)}; + auto* log_message_obj{ + PyDict_GetItemString(state, get_c_str_from_constexpr_string_view(cStateLogMessage)) + }; if (nullptr == log_message_obj) { PyErr_Format( PyExc_KeyError, @@ -201,7 +348,10 @@ auto PyLogEvent_setstate(PyLogEvent* self, PyObject* state) -> PyObject* { return nullptr; } - auto* formatted_timestamp_obj{PyDict_GetItemString(state, cStateFormattedTimestamp)}; + auto* formatted_timestamp_obj{PyDict_GetItemString( + state, + get_c_str_from_constexpr_string_view(cStateFormattedTimestamp) + )}; if (nullptr == formatted_timestamp_obj) { PyErr_Format( PyExc_KeyError, @@ -215,7 +365,9 @@ auto PyLogEvent_setstate(PyLogEvent* self, PyObject* state) -> PyObject* { return nullptr; } - auto* timestamp_obj{PyDict_GetItemString(state, cStateTimestamp)}; + auto* timestamp_obj{ + PyDict_GetItemString(state, get_c_str_from_constexpr_string_view(cStateTimestamp)) + }; if (nullptr == timestamp_obj) { PyErr_Format( PyExc_KeyError, @@ -229,7 +381,7 @@ auto PyLogEvent_setstate(PyLogEvent* self, PyObject* state) -> PyObject* { return nullptr; } - auto* index_obj{PyDict_GetItemString(state, cStateIndex)}; + auto* index_obj{PyDict_GetItemString(state, get_c_str_from_constexpr_string_view(cStateIndex))}; if (nullptr == index_obj) { PyErr_Format( PyExc_KeyError, @@ -250,76 +402,27 @@ auto PyLogEvent_setstate(PyLogEvent* self, PyObject* state) -> PyObject* { Py_RETURN_NONE; } -/** - * Callback of PyLogEvent `__str__` method. - * @param self - * @return PyLogEvent::get_formatted_log_message - */ -auto PyLogEvent_str(PyLogEvent* self) -> PyObject* { +CLP_FFI_PY_METHOD auto PyLogEvent_str(PyLogEvent* self) -> PyObject* { return self->get_formatted_message(); } -/** - * Callback of PyLogEvent `__repr__` method. - * @param self - * @return Python string representation of PyLogEvent state. - */ -auto PyLogEvent_repr(PyLogEvent* self) -> PyObject* { +CLP_FFI_PY_METHOD auto PyLogEvent_repr(PyLogEvent* self) -> PyObject* { return PyObject_Repr(PyLogEvent_getstate(self)); } -// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) -PyDoc_STRVAR( - cPyLogEventGetLogMessageDoc, - "get_log_message(self)\n" - "--\n\n" - "Gets the log message of the log event.\n\n" - ":return: The log message.\n" -); - -auto PyLogEvent_get_log_message(PyLogEvent* self) -> PyObject* { +CLP_FFI_PY_METHOD auto PyLogEvent_get_log_message(PyLogEvent* self) -> PyObject* { return PyUnicode_FromString(self->get_log_event()->get_log_message().c_str()); } -// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) -PyDoc_STRVAR( - cPyLogEventGetTimestampDoc, - "get_timestamp(self)\n" - "--\n\n" - "Gets the Unix epoch timestamp in milliseconds of the log event.\n\n" - ":return: The timestamp in milliseconds.\n" -); - -auto PyLogEvent_get_timestamp(PyLogEvent* self) -> PyObject* { +CLP_FFI_PY_METHOD auto PyLogEvent_get_timestamp(PyLogEvent* self) -> PyObject* { return PyLong_FromLongLong(self->get_log_event()->get_timestamp()); } -// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) -PyDoc_STRVAR( - cPyLogEventGetIndexDoc, - "get_index(self)\n" - "--\n\n" - "Gets the message index (relative to the source CLP IR stream) of the log event. This " - "index is set to 0 by default.\n\n" - ":return: The log event index.\n" -); - -auto PyLogEvent_get_index(PyLogEvent* self) -> PyObject* { +CLP_FFI_PY_METHOD auto PyLogEvent_get_index(PyLogEvent* self) -> PyObject* { return PyLong_FromLongLong(static_cast(self->get_log_event()->get_index())); } -// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) -PyDoc_STRVAR( - cPyLogEventMatchQueryDoc, - "match_query(self, query)\n" - "--\n\n" - "Matches the underlying log event against the given query. Refer to the documentation of " - "clp_ffi_py.Query for more details.\n\n" - ":param query: Input Query object.\n" - ":return: True if the log event matches the query, False otherwise.\n" -); - -auto PyLogEvent_match_query(PyLogEvent* self, PyObject* query) -> PyObject* { +CLP_FFI_PY_METHOD auto PyLogEvent_match_query(PyLogEvent* self, PyObject* query) -> PyObject* { if (false == static_cast(PyObject_TypeCheck(query, PyQuery::get_py_type()))) { PyErr_SetString(PyExc_TypeError, get_c_str_from_constexpr_string_view(cPyTypeError)); return nullptr; @@ -328,20 +431,8 @@ auto PyLogEvent_match_query(PyLogEvent* self, PyObject* query) -> PyObject* { return get_py_bool(py_query->get_query()->matches(*self->get_log_event())); } -// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) -PyDoc_STRVAR( - cPyLogEventGetFormattedMessageDoc, - "get_formatted_message(self, timezone=None)\n" - "--\n\n" - "Gets the formatted log message of the log event.\n\n" - "If a specific timezone is provided, it will be used to format the timestamp. " - "Otherwise, the timestamp will be formatted using the timezone from the source CLP IR " - "stream.\n\n" - ":param timezone: Python tzinfo object that specifies a timezone.\n" - ":return: The formatted message.\n" -); - -auto PyLogEvent_get_formatted_message(PyLogEvent* self, PyObject* args, PyObject* keywords) +CLP_FFI_PY_METHOD auto +PyLogEvent_get_formatted_message(PyLogEvent* self, PyObject* args, PyObject* keywords) -> PyObject* { static char keyword_timezone[]{"timezone"}; static char* key_table[]{static_cast(keyword_timezone), nullptr}; @@ -361,92 +452,59 @@ auto PyLogEvent_get_formatted_message(PyLogEvent* self, PyObject* args, PyObject return self->get_formatted_message(timezone); } -} - -// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) -PyMethodDef PyLogEvent_method_table[]{ - {"get_log_message", - py_c_function_cast(PyLogEvent_get_log_message), - METH_NOARGS, - static_cast(cPyLogEventGetLogMessageDoc)}, - - {"get_timestamp", - py_c_function_cast(PyLogEvent_get_timestamp), - METH_NOARGS, - static_cast(cPyLogEventGetTimestampDoc)}, - - {"get_index", - py_c_function_cast(PyLogEvent_get_index), - METH_NOARGS, - static_cast(cPyLogEventGetIndexDoc)}, - - {"get_formatted_message", - py_c_function_cast(PyLogEvent_get_formatted_message), - METH_KEYWORDS | METH_VARARGS, - static_cast(cPyLogEventGetFormattedMessageDoc)}, - - {"match_query", - py_c_function_cast(PyLogEvent_match_query), - METH_O, - static_cast(cPyLogEventMatchQueryDoc)}, - - {"__getstate__", - py_c_function_cast(PyLogEvent_getstate), - METH_NOARGS, - static_cast(cPyLogEventGetStateDoc)}, - - {"__setstate__", - py_c_function_cast(PyLogEvent_setstate), - METH_O, - static_cast(cPyLogEventSetStateDoc)}, +} // namespace - {nullptr} -}; +auto PyLogEvent::get_py_type() -> PyTypeObject* { + return m_py_type.get(); +} -// NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) -PyDoc_STRVAR( - cPyLogEventDoc, - "This class represents a deserialzied log event and provides ways to access the underlying " - "log data, including the log message, the timestamp, and the log event index. " - "Normally, this class will be instantiated by the FFI IR deserialization methods.\n" - "However, with the `__init__` method provided below, direct instantiation is also " - "possible.\n\n" - "The signature of `__init__` method is shown as following:\n\n" - "__init__(self, log_message, timestamp, index=0, metadata=None)\n\n" - "Initializes an object that represents a log event. Notice that each object should be " - "strictly initialized only once. Double initialization will result in memory leaks.\n\n" - ":param log_message: The message content of the log event.\n" - ":param timestamp: The timestamp of the log event.\n" - ":param index: The message index (relative to the source CLP IR stream) of the log event.\n" - ":param metadata: The PyMetadata instance that represents the source CLP IR stream. " - "It is set to None by default.\n" -); +auto PyLogEvent::module_level_init(PyObject* py_module) -> bool { + static_assert(std::is_trivially_destructible()); + auto* type{py_reinterpret_cast(PyType_FromSpec(&PyLogEvent_type_spec))}; + m_py_type.reset(type); + if (nullptr == type) { + return false; + } + return add_python_type(get_py_type(), "LogEvent", py_module); +} -// NOLINTBEGIN(cppcoreguidelines-avoid-c-arrays, cppcoreguidelines-pro-type-*-cast) -PyType_Slot PyLogEvent_slots[]{ - {Py_tp_alloc, reinterpret_cast(PyType_GenericAlloc)}, - {Py_tp_dealloc, reinterpret_cast(PyLogEvent_dealloc)}, - {Py_tp_new, reinterpret_cast(PyType_GenericNew)}, - {Py_tp_init, reinterpret_cast(PyLogEvent_init)}, - {Py_tp_str, reinterpret_cast(PyLogEvent_str)}, - {Py_tp_repr, reinterpret_cast(PyLogEvent_repr)}, - {Py_tp_methods, static_cast(PyLogEvent_method_table)}, - {Py_tp_doc, const_cast(static_cast(cPyLogEventDoc))}, - {0, nullptr} -}; -// NOLINTEND(cppcoreguidelines-avoid-c-arrays, cppcoreguidelines-pro-type-*-cast) +auto PyLogEvent::create_new_log_event( + std::string_view log_message, + clp::ir::epoch_time_ms_t timestamp, + size_t index, + PyMetadata* metadata +) -> PyLogEvent* { + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) + PyLogEvent* self{PyObject_New(PyLogEvent, get_py_type())}; + if (nullptr == self) { + return nullptr; + } + self->default_init(); + if (false == self->init(log_message, timestamp, index, metadata)) { + return nullptr; + } + return self; +} -/** - * PyLogEvent Python type specifications. - */ -PyType_Spec PyLogEvent_type_spec{ - "clp_ffi_py.ir.native.LogEvent", - sizeof(PyLogEvent), - 0, - Py_TPFLAGS_DEFAULT, - static_cast(PyLogEvent_slots) -}; -} // namespace +auto PyLogEvent::init( + std::string_view log_message, + clp::ir::epoch_time_ms_t timestamp, + size_t index, + PyMetadata* metadata, + std::optional formatted_timestamp +) -> bool { + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + m_log_event = new (std::nothrow) LogEvent(log_message, timestamp, index, formatted_timestamp); + if (nullptr == m_log_event) { + PyErr_SetString( + PyExc_RuntimeError, + get_c_str_from_constexpr_string_view(clp_ffi_py::cOutOfMemoryError) + ); + return false; + } + set_metadata(metadata); + return true; +} auto PyLogEvent::get_formatted_message(PyObject* timezone) -> PyObject* { auto cache_formatted_timestamp{false}; @@ -487,58 +545,4 @@ auto PyLogEvent::get_formatted_message(PyObject* timezone) -> PyObject* { m_log_event->get_log_message().c_str() ); } - -auto PyLogEvent::init( - std::string_view log_message, - clp::ir::epoch_time_ms_t timestamp, - size_t index, - PyMetadata* metadata, - std::optional formatted_timestamp -) -> bool { - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - m_log_event = new (std::nothrow) LogEvent(log_message, timestamp, index, formatted_timestamp); - if (nullptr == m_log_event) { - PyErr_SetString( - PyExc_RuntimeError, - get_c_str_from_constexpr_string_view(clp_ffi_py::cOutOfMemoryError) - ); - return false; - } - set_metadata(metadata); - return true; -} - -PyObjectStaticPtr PyLogEvent::m_py_type{nullptr}; - -auto PyLogEvent::get_py_type() -> PyTypeObject* { - return m_py_type.get(); -} - -auto PyLogEvent::module_level_init(PyObject* py_module) -> bool { - static_assert(std::is_trivially_destructible()); - auto* type{py_reinterpret_cast(PyType_FromSpec(&PyLogEvent_type_spec))}; - m_py_type.reset(type); - if (nullptr == type) { - return false; - } - return add_python_type(get_py_type(), "LogEvent", py_module); -} - -auto PyLogEvent::create_new_log_event( - std::string_view log_message, - clp::ir::epoch_time_ms_t timestamp, - size_t index, - PyMetadata* metadata -) -> PyLogEvent* { - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) - PyLogEvent* self{PyObject_New(PyLogEvent, get_py_type())}; - if (nullptr == self) { - return nullptr; - } - self->default_init(); - if (false == self->init(log_message, timestamp, index, metadata)) { - return nullptr; - } - return self; -} } // namespace clp_ffi_py::ir::native diff --git a/src/clp_ffi_py/ir/native/PyLogEvent.hpp b/src/clp_ffi_py/ir/native/PyLogEvent.hpp index 13261d0b..42d53857 100644 --- a/src/clp_ffi_py/ir/native/PyLogEvent.hpp +++ b/src/clp_ffi_py/ir/native/PyLogEvent.hpp @@ -3,7 +3,11 @@ #include // Must be included before any other header files +#include #include +#include + +#include #include #include @@ -18,6 +22,53 @@ namespace clp_ffi_py::ir::native { */ class PyLogEvent { public: + // Static methods + /** + * Gets the PyTypeObject that represents PyLogEvent's Python type. This type is dynamically + * created and initialized during the execution of `PyLogEvent::module_level_init`. + * @return Python type object associated with PyLogEvent. + */ + [[nodiscard]] static auto get_py_type() -> PyTypeObject*; + + /** + * Creates and initializes PyLogEvent as a Python type, and then incorporates this type as a + * Python object into the py_module module. + * @param py_module This is the Python module where the initialized PyLogEvent will be + * incorporated. + * @return true on success. + * @return false on failure with the relevant Python exception and error set. + */ + [[nodiscard]] static auto module_level_init(PyObject* py_module) -> bool; + + /** + * Creates and initializes a new PyLogEvent using the given inputs. + * @param log_message + * @param timestamp + * @param index + * @param metadata A PyMetadata instance to bind with the log event (can be nullptr). + * @return a new reference of a PyLogEvent object that is initialized with the given inputs. + * @return nullptr on failure with the relevant Python exception and error set. + */ + [[nodiscard]] static auto create_new_log_event( + std::string_view log_message, + clp::ir::epoch_time_ms_t timestamp, + size_t index, + PyMetadata* metadata + ) -> PyLogEvent*; + + // Delete default constructor to disable direct instantiation. + PyLogEvent() = delete; + + // Delete copy & move constructors and assignment operators + PyLogEvent(PyLogEvent const&) = delete; + PyLogEvent(PyLogEvent&&) = delete; + auto operator=(PyLogEvent const&) -> PyLogEvent& = delete; + auto operator=(PyLogEvent&&) -> PyLogEvent& = delete; + + // Destructor + ~PyLogEvent() = default; + + // Methods /** * Initializes the underlying data with the given inputs. Since the memory allocation of * PyLogEvent is handled by CPython's allocator, cpp constructors will not be explicitly called. @@ -93,45 +144,12 @@ class PyLogEvent { [[nodiscard]] auto get_py_metadata() -> PyMetadata* { return m_py_metadata; } - /** - * Gets the PyTypeObject that represents PyLogEvent's Python type. This type is dynamically - * created and initialized during the execution of `PyLogEvent::module_level_init`. - * @return Python type object associated with PyLogEvent. - */ - [[nodiscard]] static auto get_py_type() -> PyTypeObject*; - - /** - * Creates and initializes PyLogEvent as a Python type, and then incorporates this type as a - * Python object into the py_module module. - * @param py_module This is the Python module where the initialized PyLogEvent will be - * incorporated. - * @return true on success. - * @return false on failure with the relevant Python exception and error set. - */ - [[nodiscard]] static auto module_level_init(PyObject* py_module) -> bool; - - /** - * Creates and initializes a new PyLogEvent using the given inputs. - * @param log_message - * @param timestamp - * @param index - * @param metadata A PyMetadata instance to bind with the log event (can be nullptr). - * @return a new reference of a PyLogEvent object that is initialized with the given inputs. - * @return nullptr on failure with the relevant Python exception and error set. - */ - [[nodiscard]] static auto create_new_log_event( - std::string_view log_message, - clp::ir::epoch_time_ms_t timestamp, - size_t index, - PyMetadata* metadata - ) -> PyLogEvent*; - private: + static inline PyObjectStaticPtr m_py_type{nullptr}; + PyObject_HEAD; LogEvent* m_log_event; PyMetadata* m_py_metadata; - - static PyObjectStaticPtr m_py_type; }; } // namespace clp_ffi_py::ir::native