├── main.cc └── varidicLogging.pro /main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct BaseLogFormatter 7 | { 8 | virtual ~BaseLogFormatter() = default; 9 | virtual std::ostringstream& Evaluate(const char* data, std::ostringstream&) const = 0; 10 | }; 11 | 12 | // Knows how to format stored arguments, writing to an output stream 13 | // Each instance of this class maps to a unique sequence of log arguments 14 | template 15 | struct LogFormatter : BaseLogFormatter 16 | { 17 | LogFormatter(const char* formatString) : mFormatString(formatString) {} 18 | const char* mFormatString; 19 | 20 | template 21 | const char* FormatArg(std::ostringstream& outputStream, const char* argsData) const 22 | { 23 | const Arg* arg = reinterpret_cast(argsData); 24 | outputStream << *arg; 25 | return argsData + sizeof(Arg); 26 | } 27 | 28 | template 29 | typename std::enable_if::type 30 | Format(std::ostringstream& outputStream, const char* formatString, const char*) const 31 | { 32 | outputStream << formatString; 33 | } 34 | 35 | template 36 | void Format(auto& outputStream, const char* formatString, const char* argsData) const 37 | { 38 | const char* firstPlaceholder = std::strstr(formatString, "%"); 39 | // write from format to first placeholder 40 | outputStream.write(formatString, firstPlaceholder - formatString); 41 | 42 | // write corresponding argument 43 | argsData = FormatArg(outputStream, argsData); 44 | 45 | // Move to the next data item 46 | Format(outputStream, firstPlaceholder + 1, argsData); 47 | } 48 | 49 | // given input data, write to stream 50 | std::ostringstream& Evaluate(const char* argsData, std::ostringstream& outputStream) const override 51 | { 52 | Format(outputStream, mFormatString, argsData); 53 | return outputStream; 54 | } 55 | }; 56 | 57 | // Knows how to write log data to a memory stream, given the static data 58 | struct LogWriter 59 | { 60 | struct Header 61 | { 62 | // the log data (i.e. args) 63 | char mBuffer[128]; 64 | 65 | // the object which knows how to format this data 66 | const BaseLogFormatter* mLogFormatter; 67 | }; 68 | 69 | static LogWriter& GetLogWriter() 70 | { 71 | static LogWriter logWriter; 72 | return logWriter; 73 | } 74 | 75 | // return a new object which knows how to store data 76 | template 77 | T* CreateLogFormatter(Args&&... args) 78 | { 79 | return new T(std::forward(args)...); 80 | } 81 | 82 | // front end to write all arguments to a buffer 83 | template 84 | void Write(const BaseLogFormatter& logFormatter, const Args... args) 85 | { 86 | size_t argSize = GetArgsSize(args...); 87 | char* buffer = GetLogBuffer(logFormatter, argSize); 88 | CopyArgs(buffer, args...); 89 | } 90 | 91 | Header& GetNextHeader() 92 | { 93 | // incomplete 94 | static Header sHeader; 95 | return sHeader; 96 | } 97 | 98 | char* GetLogBuffer(const BaseLogFormatter& logFormatter, size_t sizeRequired) 99 | { 100 | // incomplete 101 | Header& header = GetNextHeader(); 102 | header.mLogFormatter = &logFormatter; 103 | return header.mBuffer; 104 | } 105 | 106 | // Note: we could have an option for non-trivially copyable args (SFINAE) 107 | template 108 | char* CopyArg(char* buffer, T arg) 109 | { 110 | memcpy(buffer, &arg, sizeof(arg)); 111 | return buffer + sizeof(arg); 112 | } 113 | 114 | // base case for the format string 115 | inline char* CopyArgs(char* buffer) 116 | { 117 | return buffer; // nothing to copy here 118 | } 119 | 120 | // write a single arg to the buffer and continue with the tail 121 | template 122 | char* CopyArgs(char* buffer, const Arg& arg, const Args&... args) 123 | { 124 | buffer = CopyArg(buffer, arg); 125 | return CopyArgs(buffer, args...); 126 | } 127 | 128 | inline size_t GetArgsSize() 129 | { 130 | return 0; 131 | } 132 | 133 | template 134 | size_t GetArgSize(const Arg& arg) 135 | { 136 | return sizeof(arg); 137 | } 138 | 139 | template 140 | size_t GetArgsSize(const Arg& arg, const Args... args) 141 | { 142 | return GetArgSize(arg) + GetArgsSize(args...); 143 | } 144 | }; 145 | 146 | // front-end method to write a log entry 147 | template 148 | void WriteLog(BaseLogFormatter** logFormatter, const char* formatString, const Args&... args) 149 | { 150 | LogWriter logWriter = LogWriter::GetLogWriter(); 151 | 152 | // find the object that knows how to write the args to the log buffer 153 | if (*logFormatter == nullptr) 154 | *logFormatter = logWriter.CreateLogFormatter>(formatString); 155 | 156 | // write the args to the log buffer 157 | logWriter.Write(**logFormatter, args...); 158 | } 159 | 160 | // Knows how to consume a record from the producer, given the static data 161 | struct LogConsumer 162 | { 163 | static LogConsumer& GetLogConsumer() 164 | { 165 | static LogConsumer logConsumer; 166 | return logConsumer; 167 | } 168 | 169 | void Consume(const LogWriter::Header& header, std::ostringstream& outputStream) 170 | { 171 | header.mLogFormatter->Evaluate(header.mBuffer, outputStream); 172 | } 173 | }; 174 | 175 | // util 176 | template 177 | constexpr unsigned sizeof_args(Types&&...) 178 | { 179 | return sizeof...(Types); 180 | } 181 | 182 | // util 183 | constexpr size_t CountPlaceholders(const char* formatString) 184 | { 185 | if (formatString[0] == '\0') 186 | return 0; 187 | return (formatString[0] == '%' ? 1u : 0u) + CountPlaceholders(formatString + 1); 188 | } 189 | 190 | // API 191 | #define LOG(formatString, ...) \ 192 | static_assert(CountPlaceholders(formatString) == sizeof_args(__VA_ARGS__), "Number of arguments mismatch"); \ 193 | static BaseLogFormatter* sLogFormatter = nullptr; \ 194 | WriteLog(&sLogFormatter, formatString, ##__VA_ARGS__); 195 | 196 | int main() 197 | { 198 | LOG("Hello int=% char=% float=%", 1, 'a', 42.3); 199 | 200 | std::ostringstream outputStream; 201 | LogConsumer::GetLogConsumer().Consume(LogWriter::GetLogWriter().GetNextHeader(), outputStream); 202 | std::cout << outputStream.str() << std::endl; 203 | } 204 | -------------------------------------------------------------------------------- /varidicLogging.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | CONFIG += console c++14 3 | CONFIG -= app_bundle 4 | CONFIG -= qt 5 | 6 | SOURCES += main.cc 7 | 8 | 9 | --------------------------------------------------------------------------------