#pragma once #ifdef __cplusplus #include #include #endif /** * @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. */ }; #ifndef __cplusplus typedef enum SL_ON_SINK_FULL SL_ON_SINK_FULL; typedef enum SL_SINK_IO_TYPE SL_SINK_IO_TYPE; typedef enum SL_BUFFER_TYPE SL_BUFFER_TYPE; typedef enum SL_ROLL_LOGS SL_ROLL_LOGS; #endif #ifdef __cpp_concepts /** * @brief Represents the behaviour if the buffer would be overflowing. */ enum class overflow_response_t { must_wait = 0, /**< Represents that on overflow, the writer should wait using the strategy provided wait function. */ must_overflow = 1, /**< Represents that on overflow, the writer should push the fence and keep on writing immediately, even if the log would get corrupt. */ must_drop = must_wait /**< UNUSED, may be used in the future to mean that the current write should be dropped. */ }; /** * @brief This concept maps an overflow strategy. */ template concept OverflowStrategy = requires (T strategy) { {T::on_overflow} -> std::same_as; {strategy.wait()}; }; /** * @brief This concept represents something that should be writable and mapped to valid memory within the software. * * @details This is used for mapping allocated buffers made from buffer strategies. */ template concept BufferLike = requires (T buffer) { {buffer.data()} -> std::same_as; {buffer.size()} -> std::same_as; }; /** * @brief This concept maps a buffer strategy. */ template concept BufferStrategy = requires (T strategy, std::string_view filename, size_t size) { {strategy.build_buffer(filename, size)} -> BufferLike; {strategy.build_buffer(filename, size)} -> std::same_as; }; /** * @brief This concept maps a sink strategy. */ template concept SinkStrategy = requires (T strategy, int fd, std::string_view data) { {strategy.write(fd, data)}; }; /** * @brief This concept maps an output strategy. */ template concept OutputStrategy = requires (T strategy, std::string_view source, int fd) { {strategy.chunk(source)} -> std::same_as>; {strategy.on_write_completed_event(source, fd)} -> std::same_as; }; #endif #ifdef __cplusplus #ifndef __cpp_concepts #define OverflowStrategy typename #define BufferStrategy typename #define SinkStrategy typename #define OutputStrategy typename #endif /** * @brief Represent a mapped buffer. You should generally not touch this as it is internal representation for strategies. */ struct mapped_buffer { char* _data; size_t _size; /** * @return the data pointer of the buffer */ [[nodiscard]] char* data() const { return _data; } /** * @return the size of the allocated buffer */ [[nodiscard]] size_t size() const { return _size; } }; /** * @brief a strategy type directing how buffers are allocated. This directs the buffer strategy to use a standard container to allocate. */ struct BufferStrategyInternal { using buffer_type = std::vector; static_assert(BufferLike); buffer_type build_buffer(std::string_view, size_t); }; /** * @brief a strategy that employs a memory mapped buffer to manage its memory. Said buffer is expected to be read from the same process. */ struct BufferStrategyShared { using buffer_type = mapped_buffer; static_assert(BufferLike); buffer_type build_buffer(std::string_view, size_t); }; /** * @brief a strategy that employs a memory mapped buffer to manage its memory. Said buffer is expected to be read from a different process. */ struct BufferStrategyExternal { using buffer_type = mapped_buffer; static_assert(BufferLike); buffer_type build_buffer(std::string_view, size_t); }; /* * ... * */ /** * @brief a strategy that makes the sink flush at every write, making the IO to disk as soon as possible. */ struct SinkStrategyDirect { void write(int fd, std::string_view data); }; /** * @brief a strategy that makes the sink write with the settings that have the highest throughput. */ struct SinkStrategyFastest { void write(int fd, std::string_view data); }; /** * @brief a strategy that makes the sink write using memory mapped IO, making the IO always commit to system pages before returning. */ struct SinkStrategyMmaped { void write(int fd, std::string_view data); }; /** * @brief a strategy that makes the sink do nothing, expecting another process to handle the process. */ struct SinkStrategyExternal { void write(int fd, std::string_view data); }; /* * ... * */ /** * @brief a strategy that makes overflowing waiting. The waiting is handled by exponential backoff of factor 1.5 */ struct OverflowStrategyWait { static constexpr overflow_response_t on_overflow = overflow_response_t::must_wait; void wait(); }; /** * @brief a strategy that makes overflowing overwrite the data, possibly corrupting the generated log, without waiting. */ struct OverflowStrategyContinue { static constexpr overflow_response_t on_overflow = overflow_response_t::must_overflow; void wait(); }; /* * ... * */ /** * @brief a strategy that controls how often new log files are generated. Logs are spaced by making them writable for only a certain amount of time. */ struct OutputStrategyTimed { std::chrono::seconds interval; std::string_view directory; std::optional > last_change = std::nullopt; std::pair chunk(std::string_view); int on_write_completed_event(std::string_view, int); }; /** * @brief a strategy that controls how often new log files are generated. Logs are spaced by making them at most one line longer that the amount of bytes specified. */ struct OutputStrategySized { uint64_t interval; std::string_view directory; uint64_t written_bytes = 0; std::pair chunk(std::string_view); int on_write_completed_event(std::string_view, int); }; /** * @brief a strategy that makes the logs be generated in the same file all the time. */ struct OutputStrategySimple { std::pair chunk(std::string_view); int on_write_completed_event(std::string_view, int); }; #endif