diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..7301d07 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.23) +project(SnugLog) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +add_subdirectory(LibSnugLog) + +#add_subdirectory(Tests) diff --git a/LibSnugLog/CMakeLists.txt b/LibSnugLog/CMakeLists.txt new file mode 100644 index 0000000..1ff12e8 --- /dev/null +++ b/LibSnugLog/CMakeLists.txt @@ -0,0 +1,8 @@ + +include_directories(./public_include) +include_directories(./include) + + +add_library(LibSnugLog + include/disruptor.h + source/disruptor.cpp include/sink.h include/registry.h include/source.h public_include/sl/strategies.h public_include/sl/register.h public_include/sl/transaction.h) \ No newline at end of file diff --git a/LibSnugLog/include/disruptor.h b/LibSnugLog/include/disruptor.h new file mode 100644 index 0000000..3f59c93 --- /dev/null +++ b/LibSnugLog/include/disruptor.h @@ -0,0 +1,2 @@ +#pragma once + diff --git a/LibSnugLog/include/registry.h b/LibSnugLog/include/registry.h new file mode 100644 index 0000000..6f70f09 --- /dev/null +++ b/LibSnugLog/include/registry.h @@ -0,0 +1 @@ +#pragma once diff --git a/LibSnugLog/include/sink.h b/LibSnugLog/include/sink.h new file mode 100644 index 0000000..6f70f09 --- /dev/null +++ b/LibSnugLog/include/sink.h @@ -0,0 +1 @@ +#pragma once diff --git a/LibSnugLog/include/source.h b/LibSnugLog/include/source.h new file mode 100644 index 0000000..6f70f09 --- /dev/null +++ b/LibSnugLog/include/source.h @@ -0,0 +1 @@ +#pragma once diff --git a/LibSnugLog/public_include/sl/register.h b/LibSnugLog/public_include/sl/register.h new file mode 100644 index 0000000..5aea7ec --- /dev/null +++ b/LibSnugLog/public_include/sl/register.h @@ -0,0 +1,77 @@ +#pragma once + +#ifdef __cplusplus +#include +#include +#else +#include +#include +#endif + + +#include "sl/strategies.h" + +#ifdef __cplusplus +struct sl_buffer_strategy; +struct sl_sink_strategy; +struct sl_overflow_strategy; +struct sl_output_strategy; + +namespace sl { + using buffer_strategy = sl_buffer_strategy; + using sink_strategy = sl_sink_strategy; + using overflow_strategy = sl_overflow_strategy; + using output_strategy = sl_output_strategy; +} + +#else +typedef struct sl_buffer_strategy buffer_strategy; +typedef struct sl_sink_strategy sink_strategy; +typedef struct sl_overflow_strategy overflow_strategy; +typedef struct sl_output_strategy output_strategy; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + /** + * @brief Constructs a buffer strategy from C + * @param strategy The type of strategy + * @param filename A file describing the strategy, ignored if SL_BUFFER_TYPE is SL_INTERNAL, optional if SL_BUFFER_TYPE is SL_SHARED + * @return a strategy token for building a logger + */ + sl_buffer_strategy* sl_create_buffer_strategy(SL_BUFFER_TYPE strategy, char *filename); + + /** + * @brief Constructs a sink strategy for how to handle IO in the logger + * @param strategy The type of strategy + * @return a strategy token to build a logger + */ + sl_sink_strategy* sl_create_sink_strategy(SL_SINK_IO_TYPE strategy); + + /** + * @brief Constructs an overflow strategy for how to handle the edge case of a full buffer + * @param strategy The type of strategy + * @return a strategy token to build a logger + */ + sl_overflow_strategy* sl_create_overflow_strategy(SL_ON_SINK_FULL strategy); + + /** + * @brief Constructs a strategy that will direct where data is logged and how it will be split + * @param strategy The strategy to use + * @param strategy_parameter Either the amount of hours to keep in the same file, or the file size in kilobytes + * @param output_directory The directory where the logs will be written + * @return a strategy token to build a logger + */ + sl_output_strategy* sl_create_output_strategy(SL_ROLL_LOGS strategy, uint64_t strategy_parameter, char* output_directory); + + /** + * @brief Constructs and registers a logger, readily available immediately + * @param internal_id The ID with which you want to refer to the logger to access it as fast as possible + * @param external_id The ID which will be part of the logs filename + * @param strategies Collect the 4 tokens and reunite them into the hero's sword that will slay every other logger + */ + void sl_register_logger(int internal_id, char* external_id, sl_buffer_strategy*, sl_sink_strategy*, sl_overflow_strategy*, sl_output_strategy*); +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/LibSnugLog/public_include/sl/strategies.h b/LibSnugLog/public_include/sl/strategies.h new file mode 100644 index 0000000..2f50e15 --- /dev/null +++ b/LibSnugLog/public_include/sl/strategies.h @@ -0,0 +1,37 @@ +#pragma once + +/** + * @brief Describes what should happen if the allocated buffer is full and no logs can be written + */ +enum SL_ON_SINK_FULL { + SL_OVERFLOW = 1, /**< When the sink is full, we want to drop the oldest data even if it is not written yet */ + SL_WAIT = 2 /**< When the sink is full, we want to wait for the next write. Do that if log keeping is critical */ +}; + +/** + * @brief Describes what type of IO should be used to log your data + */ +enum SL_SINK_IO_TYPE { + SL_DIRECT_IO = 1, /**< Use IO that ensure data is written on disk as soon as possible. Useful for debugging. */ + SL_MMAPED_IO = 2, /**< Use IO that ensures that logging data will survive a software crash by using a memory mapped file as buffer. You can later expunge that buffer to get the useful information out. */ + SL_FASTEST_IO = 3, /**< Write with the highest throughput. */ + SL_EXTERNAL_IO = 4 /**< Do not handle any IO. Writes to a memory mapped file and let another process handle the IO. */ +}; + +/** + * @brief What type of buffer should we use? + */ +enum SL_BUFFER_TYPE { + SL_EXTERNAL = 1, /**< Use a provided memory mapped non-temporary file */ + SL_SHARED = 2, /**< Use an automatically generated temporary file */ + SL_INTERNAL = 3 /**< Use internal memory. Compatible only with the SL_SINK_IO_TYPE: SL_DIRECT_IO and SL_FASTEST_IO. */ +}; + +/** + * @brief On what basis do we generate new files for logs? + */ +enum SL_ROLL_LOGS { + SL_BY_TIME = 1, /**< Will create a new log every n-hours */ + SL_BY_SIZE = 2, /**< Will create a new log every n-KB */ + SL_NEVER = 0 /**< Will never split the log. Use when doing external logging. */ +}; \ No newline at end of file diff --git a/LibSnugLog/public_include/sl/transaction.h b/LibSnugLog/public_include/sl/transaction.h new file mode 100644 index 0000000..8d6e3fc --- /dev/null +++ b/LibSnugLog/public_include/sl/transaction.h @@ -0,0 +1,245 @@ +#pragma once + +#pragma once + +#ifdef __cplusplus +#include +#include +#include + +#else +#include +#include +#endif + + + +#ifdef __cplusplus +struct sl_log_transaction; +struct sl_log_transaction_internals; + +namespace sl { + using cstyle_log_transaction = sl_log_transaction; + using cstyle_log_transaction_internals = sl_log_transaction_internals; + + /** + * @brief This class represents a logging transaction + * @details + * This class has the following guarantees: + * - Can be built on the stack safely + * - This class never allocates using the system allocator + * - This class does not perform any system call + * - Finalization cannot happen twice from the public interface + * - This class may pad the log by up to std::hardware_destructive_interference bytes + * - This class is both thread-safe and multiprocessing-safe to finalize + * + * @details + * This class expects the finalization to occur in the same thread that wrote any data to the log. + * @pre + * This class expects the logging buffer to be at least 2 binary orders of magnitude bigger than the largest log + * message. It is recommended that it is at least 6 binary orders of magnitude bigger than the largest log. + * @post + * This class leaves the log in a valid state in accordance to the defined log strategies. + */ + class log_transaction { + char* local_start; + size_t local_size; + char* buffer_start; + size_t buffer_size; + void* buffer_initiator_page; + bool is_finalized = false; + + void finalize_impl(); + + /** + * @brief A forward iterator to the current log transaction. + */ + class iterator { + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = char; + using pointer = char*; + using reference = char&; + + log_transaction& owner_ref; + size_t index; + public: + explicit iterator(log_transaction& parent, size_t position = 0) + : owner_ref(parent) + , index(((parent.buffer_start - parent.local_start) + position)%parent.buffer_size) + {} + iterator& operator++() { + index+=1; + index%=owner_ref.buffer_size; + return *this; + } + iterator operator++(int) { + auto cpy = *this; + index+=1; + index%=owner_ref.buffer_size; + return cpy; + } + difference_type operator-(iterator other) const { + size_t forward, backward; + forward = owner_ref.buffer_size - index; + forward += other.index; + backward = other.index - index; + return static_cast(std::min(forward, backward)); + } + reference operator*() { + return owner_ref.buffer_start[index]; + } + }; + public: + /** + * @brief Starts a transaction. You have to then write your logs between the start and the end of the + * transaction, then finalize it. + * @details + * Guarantees are as follow: + * - This constructor never allocates using the system allocator + * - This constructor does not perform any system call + * - This constructor may pad the log by up to std::hardware_destructive_interference bytes + * - This constructor is both thread-safe and multiprocessing-safe + * + * @pre + * We expect the provided internal_id to be valid and registered, if it is not, the transaction may look for a + * log named "UNHANDLED_ERRORS" and log there instead silently. + * @post + * A started transaction MUST be finalized. The log can, upon initiation of the transaction, no longer be + * pushed to its sink past the current sequence point of the transaction. + * + * @post + * This class will finalize an un-finalized transaction + * @param internal_id the logger id where the transaction is to happen + * @param log_size the amount of bytes to write in the transaction. + */ + explicit log_transaction(int internal_id, size_t log_size); + + log_transaction(const log_transaction&) = delete; + log_transaction(log_transaction&&) = delete; + void operator=(const log_transaction&) = delete; + void operator=(log_transaction&&) = delete; + + /** + * @brief Unsafely accesses the underlying buffer + * @param index The index to the buffer to access + * @return a reference to an underlying char, undefined behaviour is the index is invalid + */ + char& operator[](size_t index) { + return buffer_start[((buffer_start - local_start) + index)%buffer_size]; + } + + /** + * @brief Obtains a forward iterator to the start of the writable range + * @return a non-descript iterator to the beginning of the range + */ + iterator begin() { + return iterator(*this, 0); + } + + /** + * @brief Obtains a forward iterator to the end of the writable range + * @return a non-descript iterator to one-past-the-end of the range + */ + iterator end() { + return iterator(*this, local_size); + } + + /** + * @brief Finalizes the transaction + * @details + * Finalizes a transaction, makes access to the finalized buffer undefined behaviour. If NDEBUG is + * defined, accesses will lead to a near-nullptr dereference. + * + * @details + * You are NOT protected against erroneous double finalization in debug mode. You are protected against double + * finalization out of debug mode. This protection and these erroneous states do not interact with the + * destructor, which will finalize transactions if not finalized yet. + */ + void finalize(); + + /** + * @see{void finalize()} + */ + ~log_transaction(){ + if(!is_finalized) finalize_impl(); + } + }; +} + +#else +typedef struct sl_log_transaction log_transaction; +typedef struct sl_log_transaction_internals log_transaction_internals; +#endif + +/** + * @brief Represents a transaction in the log. Must be finalized for proper operation of the log. + */ +struct sl_log_transaction { + char* begin; /**< The start of the given writable span */ + char* end; /**< 1 element past the end of the writable span */ + sl_log_transaction_internals* internals; /** Internal, do not touch */ +}; + +#ifdef __cplusplus +extern "C" { +#endif +/** + * @brief Starts a transaction. You have to then write your logs between the start and the end of the transaction, + * then finalize it. + * @details + * Guarantees are as follow: + * - This function never allocates using the system allocator + * - This function does not perform any system call + * - This function may need to pad the log to be operable by C easily + * - This function is both thread-safe and multiprocessing-safe + * + * @pre + * We expect the provided internal_id to be valid and registered, if it is not, the transaction may look for a log + * named "UNHANDLED_ERRORS" and log there instead silently. + * @post + * A started transaction MUST be finalized. The log can, upon initiation of the transaction, no longer be pushed to + * its sink past the current sequence point of the transaction. + * @param internal_id the logger id where the transaction is to happen + * @param log_size the amount of bytes to write in the transaction. + * @return a transaction of at least log_size bytes + */ +sl_log_transaction sl_start_log_transaction(int internal_id, size_t log_size); + +/** + * @brief Finalizes a transaction. + * @details + * Be cautious. Finalizing a finalized transaction should NEVER be done. It may wait forever, screw up your log, and + * clog your log queue forever. + * @pre + * The transaction to finalize must never have been finalized before. If it has been finalized before, the + * finalization may never terminate. + * @post + * All data written in the transaction is thenceforth scheduled to be written. The transaction state is now moot + * and should not be accessed further, and should possibly be cleared. + * @param target the transaction to finalize. + */ +void sl_finalize_log_transaction(sl_log_transaction target); + +/** + * @brief Clears a transaction object. This is not required but may help prevent bugs. + * @param to_clear + */ +inline void sl_clear_transaction(sl_log_transaction* to_clear) { +#ifdef NDEBUG + do {} while(false); +#else + #ifdef __cplusplus + to_clear->begin = nullptr; + to_clear->end = nullptr; + to_clear->internals = nullptr; + #else + to_clear->begin = NULL; + to_clear->end = NULL; + to_clear->internals = NULL; + #endif +#endif +} +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/LibSnugLog/source/disruptor.cpp b/LibSnugLog/source/disruptor.cpp new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/LibSnugLog/source/disruptor.cpp @@ -0,0 +1 @@ + diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt new file mode 100644 index 0000000..e69de29