A C++ library for logging very fast and without allocating.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

248 lines
8.2 KiB

преди 2 години
преди 2 години
преди 2 години
преди 2 години
преди 2 години
  1. #pragma once
  2. #pragma once
  3. #ifdef __cplusplus
  4. #include <cstdint>
  5. #include <cstddef>
  6. #include <iterator>
  7. #else
  8. #include <stdint.h>
  9. #include <stddef.h>
  10. #endif
  11. struct sl_log_transaction;
  12. struct sl_log_transaction_internals;
  13. #ifdef __cplusplus
  14. namespace sl {
  15. using cstyle_log_transaction = sl_log_transaction;
  16. using cstyle_log_transaction_internals = sl_log_transaction_internals;
  17. /**
  18. * @brief This class represents a logging transaction
  19. * @details
  20. * This class has the following guarantees:
  21. * - Can be built on the stack safely
  22. * - This class never allocates using the system allocator
  23. * - This class does not perform any system call
  24. * - Finalization cannot happen twice from the public interface
  25. * - This class may pad the log by up to <tt>std::hardware_destructive_interference</tt> bytes
  26. * - This class is both thread-safe and multiprocessing-safe to finalize
  27. *
  28. * @details
  29. * This class expects the finalization to occur in the same thread that wrote any data to the log.
  30. * @pre
  31. * This class expects the logging buffer to be at least 2 binary orders of magnitude bigger than the largest log
  32. * message. It is recommended that it is at least 6 binary orders of magnitude bigger than the largest log.
  33. * @post
  34. * This class leaves the log in a valid state in accordance to the defined log strategies.
  35. */
  36. class log_transaction final {
  37. char* local_start;
  38. size_t local_size;
  39. char* buffer_start;
  40. size_t buffer_size;
  41. void* buffer_initiator_page;
  42. bool is_finalized = false;
  43. void finalize_impl();
  44. /**
  45. * @brief A forward iterator to the current log transaction.
  46. */
  47. class iterator final {
  48. using iterator_category = std::forward_iterator_tag;
  49. using difference_type = std::ptrdiff_t;
  50. using value_type = char;
  51. using pointer = char*;
  52. using reference = char&;
  53. log_transaction& owner_ref;
  54. size_t index;
  55. public:
  56. explicit iterator(log_transaction& parent, size_t position = 0)
  57. : owner_ref(parent)
  58. , index(((parent.buffer_start - parent.local_start) + position)%parent.buffer_size)
  59. {}
  60. iterator& operator++() {
  61. index+=1;
  62. index%=owner_ref.buffer_size;
  63. return *this;
  64. }
  65. iterator operator++(int) {
  66. auto cpy = *this;
  67. index+=1;
  68. index%=owner_ref.buffer_size;
  69. return cpy;
  70. }
  71. difference_type operator-(iterator other) const {
  72. size_t forward, backward;
  73. forward = owner_ref.buffer_size - index;
  74. forward += other.index;
  75. backward = other.index - index;
  76. return static_cast<difference_type>(std::min(forward, backward));
  77. }
  78. reference operator*() {
  79. return owner_ref.buffer_start[index];
  80. }
  81. };
  82. public:
  83. /**
  84. * @brief Starts a transaction. You have to then write your logs between the start and the end of the
  85. * transaction, then finalize it.
  86. * @details
  87. * Guarantees are as follow:
  88. * - This constructor never allocates using the system allocator
  89. * - This constructor does not perform any system call
  90. * - This constructor may pad the log by up to <tt>std::hardware_destructive_interference</tt> bytes
  91. * - This constructor is both thread-safe and multiprocessing-safe
  92. *
  93. * @pre
  94. * We expect the provided <tt>internal_id</tt> to be valid and registered, if it is not, the transaction may look for a
  95. * log named "UNHANDLED_ERRORS" and log there instead silently.
  96. * @post
  97. * A started transaction MUST be finalized. The log can, upon initiation of the transaction, no longer be
  98. * pushed to its sink past the current sequence point of the transaction.
  99. *
  100. * @post
  101. * This class will finalize an un-finalized transaction
  102. * @param internal_id the logger id where the transaction is to happen
  103. * @param log_size the amount of bytes to write in the transaction.
  104. */
  105. explicit log_transaction(int internal_id, size_t log_size);
  106. log_transaction(const log_transaction&) = delete;
  107. log_transaction(log_transaction&&) = delete;
  108. void operator=(const log_transaction&) = delete;
  109. void operator=(log_transaction&&) = delete;
  110. /**
  111. * @brief Unsafely accesses the underlying buffer
  112. * @param index The index to the buffer to access
  113. * @return a reference to an underlying <tt>char</tt>, undefined behaviour is the index is invalid
  114. */
  115. char& operator[](size_t index) {
  116. return buffer_start[((buffer_start - local_start) + index)%buffer_size];
  117. }
  118. /**
  119. * @brief Obtains a forward iterator to the start of the writable range
  120. * @return a non-descript iterator to the beginning of the range
  121. */
  122. iterator begin() {
  123. return iterator(*this, 0);
  124. }
  125. /**
  126. * @brief Obtains a forward iterator to the end of the writable range
  127. * @return a non-descript iterator to one-past-the-end of the range
  128. */
  129. iterator end() {
  130. return iterator(*this, local_size);
  131. }
  132. /**
  133. * @brief Finalizes the transaction
  134. * @details
  135. * Finalizes a transaction, makes access to the finalized buffer undefined behaviour. If <tt>NDEBUG</tt> is
  136. * defined, accesses will lead to a near-nullptr dereference.
  137. *
  138. * @details
  139. * You are NOT protected against erroneous double finalization in debug mode. You are protected against double
  140. * finalization out of debug mode. This protection and these erroneous states do not interact with the
  141. * destructor, which will finalize transactions if not finalized yet.
  142. */
  143. void finalize();
  144. /**
  145. * @see{void finalize()}
  146. */
  147. ~log_transaction(){
  148. if(!is_finalized) finalize_impl();
  149. }
  150. };
  151. }
  152. #else
  153. typedef struct sl_log_transaction log_transaction;
  154. typedef struct sl_log_transaction_internals log_transaction_internals;
  155. #endif
  156. /**
  157. * @brief Represents a transaction in the log. Must be finalized for proper operation of the log.
  158. */
  159. struct sl_log_transaction {
  160. char* begin; /**< The start of the given writable span */
  161. char* end; /**< 1 element past the end of the writable span */
  162. /**
  163. * @brief Internal, do not touch
  164. * @internal this is a pointer to a registry_slab, hence the need for reference stability/pointer stability
  165. */
  166. sl_log_transaction_internals* internals;
  167. };
  168. #ifdef __cplusplus
  169. extern "C" {
  170. #endif
  171. /**
  172. * @brief Starts a transaction. You have to then write your logs between the start and the end of the transaction,
  173. * then finalize it.
  174. * @details
  175. * Guarantees are as follow:
  176. * - This function never allocates using the system allocator
  177. * - This function does not perform any system call
  178. * - This function may need to pad the log to be operable by C easily
  179. * - This function is both thread-safe and multiprocessing-safe
  180. *
  181. * @pre
  182. * We expect the provided internal_id to be valid and registered, if it is not, the transaction may look for a log
  183. * named "UNHANDLED_ERRORS" and log there instead silently.
  184. * @post
  185. * A started transaction MUST be finalized. The log can, upon initiation of the transaction, no longer be pushed to
  186. * its sink past the current sequence point of the transaction.
  187. * @param internal_id the logger id where the transaction is to happen
  188. * @param log_size the amount of bytes to write in the transaction.
  189. * @return a transaction of at least log_size bytes
  190. */
  191. sl_log_transaction sl_start_log_transaction(int internal_id, size_t log_size);
  192. /**
  193. * @brief Finalizes a transaction.
  194. * @details
  195. * Be cautious. Finalizing a finalized transaction should NEVER be done. It may wait forever, screw up your log, and
  196. * clog your log queue forever.
  197. * @pre
  198. * The transaction to finalize must never have been finalized before. If it has been finalized before, the
  199. * finalization may never terminate.
  200. * @post
  201. * All data written in the transaction is thenceforth scheduled to be written. The transaction state is now moot
  202. * and should not be accessed further, and should possibly be cleared.
  203. * @param target the transaction to finalize.
  204. */
  205. void sl_finalize_log_transaction(sl_log_transaction target);
  206. /**
  207. * @brief Clears a transaction object. This is not required but may help prevent bugs.
  208. * @param to_clear
  209. */
  210. inline void sl_clear_transaction(sl_log_transaction* to_clear) {
  211. #ifdef NDEBUG
  212. do {} while(false);
  213. #else
  214. #ifdef __cplusplus
  215. to_clear->begin = nullptr;
  216. to_clear->end = nullptr;
  217. to_clear->internals = nullptr;
  218. #else
  219. to_clear->begin = NULL;
  220. to_clear->end = NULL;
  221. to_clear->internals = NULL;
  222. #endif
  223. #endif
  224. }
  225. #ifdef __cplusplus
  226. }
  227. #endif