A C++ library for logging very fast and without allocating.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 

249 lignes
8.2 KiB

#pragma once
#pragma once
#ifdef __cplusplus
#include <cstdint>
#include <cstddef>
#include <iterator>
#else
#include <stdint.h>
#include <stddef.h>
#endif
struct sl_log_transaction;
struct sl_log_transaction_internals;
#ifdef __cplusplus
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 <tt>std::hardware_destructive_interference</tt> 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 final {
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 final {
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<difference_type>(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 <tt>std::hardware_destructive_interference</tt> bytes
* - This constructor is both thread-safe and multiprocessing-safe
*
* @pre
* We expect the provided <tt>internal_id</tt> 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 <tt>char</tt>, 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 <tt>NDEBUG</tt> 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 */
/**
* @brief Internal, do not touch
* @internal this is a pointer to a registry_slab, hence the need for reference stability/pointer stability
*/
sl_log_transaction_internals* internals;
};
#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