|
#pragma once
|
|
|
|
|
|
#ifdef __cplusplus
|
|
|
|
#include <vector>
|
|
#include <chrono>
|
|
|
|
#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<typename T>
|
|
concept OverflowStrategy = requires (T strategy) {
|
|
{T::on_overflow} -> std::same_as<const overflow_response_t&>;
|
|
{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<typename T>
|
|
concept BufferLike = requires (T buffer) {
|
|
{buffer.data()} -> std::same_as<char*>;
|
|
{buffer.size()} -> std::same_as<size_t>;
|
|
};
|
|
|
|
/**
|
|
* @brief This concept maps a buffer strategy.
|
|
*/
|
|
template<typename T>
|
|
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<typename T::buffer_type>;
|
|
};
|
|
|
|
/**
|
|
* @brief This concept maps a sink strategy.
|
|
*/
|
|
template<typename T>
|
|
concept SinkStrategy = requires (T strategy, int fd, std::string_view data) {
|
|
{strategy.write(fd, data)};
|
|
};
|
|
|
|
/**
|
|
* @brief This concept maps an output strategy.
|
|
*/
|
|
template<typename T>
|
|
concept OutputStrategy = requires (T strategy, std::string_view source, int fd) {
|
|
{strategy.chunk(source)} -> std::same_as<std::pair<std::string_view, std::string_view>>;
|
|
{strategy.on_write_completed_event(source, fd)} -> std::same_as<int>;
|
|
};
|
|
#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<char>;
|
|
static_assert(BufferLike<buffer_type>);
|
|
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>);
|
|
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>);
|
|
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<std::chrono::time_point<std::chrono::file_clock> > last_change = std::nullopt;
|
|
|
|
std::pair<std::string_view, std::string_view> 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<std::string_view, std::string_view> 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<std::string_view, std::string_view> chunk(std::string_view);
|
|
int on_write_completed_event(std::string_view, int);
|
|
};
|
|
#endif
|