LogIt++
Header-only, macro-first C++ logging library
Loading...
Searching...
No Matches
OtlpJsonSerializer.hpp
Go to the documentation of this file.
1#pragma once
2#ifndef LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_OTLP_OTLPJSONSERIALIZER_HPP_INCLUDED
3#define LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_OTLP_OTLPJSONSERIALIZER_HPP_INCLUDED
4
7
10#include <cctype>
11#include <cstdint>
12#include <cmath>
13#include <iomanip>
14#include <limits>
15#include <sstream>
16#include <string>
17#include <unordered_map>
18#include <vector>
19
20namespace logit {
21
24 struct OtlpLogItem {
26 std::string message;
27 };
28
32 inline std::string otlp_json_escape(const std::string& value) {
33 std::string out;
34 out.reserve(value.size() + 16);
35
36 for (size_t i = 0; i < value.size(); ++i) {
37 const unsigned char c = static_cast<unsigned char>(value[i]);
38
39 switch (c) {
40 case '"': out += "\\\""; break;
41 case '\\': out += "\\\\"; break;
42 case '\b': out += "\\b"; break;
43 case '\f': out += "\\f"; break;
44 case '\n': out += "\\n"; break;
45 case '\r': out += "\\r"; break;
46 case '\t': out += "\\t"; break;
47 default:
48 if (c < 0x20) {
49 static const char* hex = "0123456789abcdef";
50 out += "\\u00";
51 out += hex[(c >> 4) & 0x0F];
52 out += hex[c & 0x0F];
53 } else {
54 out += static_cast<char>(c);
55 }
56 break;
57 }
58 }
59
60 return out;
61 }
62
66 inline int otlp_severity_number(LogLevel level) {
67 switch (level) {
68 case LogLevel::LOG_LVL_TRACE: return 1;
69 case LogLevel::LOG_LVL_DEBUG: return 5;
70 case LogLevel::LOG_LVL_INFO: return 9;
71 case LogLevel::LOG_LVL_WARN: return 13;
72 case LogLevel::LOG_LVL_ERROR: return 17;
73 case LogLevel::LOG_LVL_FATAL: return 21;
74 default: return 0;
75 }
76 }
77
81 inline void otlp_write_comma_if_needed(std::ostringstream& os, bool& first) {
82 if (!first) {
83 os << ',';
84 }
85 first = false;
86 }
87
92 inline void otlp_write_string_attr(std::ostringstream& os, const std::string& key, const std::string& value) {
93 os << "{\"key\":\"" << otlp_json_escape(key)
94 << "\",\"value\":{\"stringValue\":\"" << otlp_json_escape(value) << "\"}}";
95 }
96
101 inline void otlp_write_int_attr(std::ostringstream& os, const std::string& key, int64_t value) {
102 os << "{\"key\":\"" << otlp_json_escape(key)
103 << "\",\"value\":{\"intValue\":\"" << value << "\"}}";
104 }
105
110 inline void otlp_write_bool_attr(std::ostringstream& os, const std::string& key, bool value) {
111 os << "{\"key\":\"" << otlp_json_escape(key)
112 << "\",\"value\":{\"boolValue\":" << (value ? "true" : "false") << "}}";
113 }
114
119 inline void otlp_write_double_attr(std::ostringstream& os, const std::string& key, double value) {
120 if (std::isfinite(value)) {
121 os << "{\"key\":\"" << otlp_json_escape(key)
122 << "\",\"value\":{\"doubleValue\":"
123 << std::setprecision(std::numeric_limits<double>::max_digits10)
124 << value << "}}";
125 } else {
126 os << "{\"key\":\"" << otlp_json_escape(key)
127 << "\",\"value\":{\"stringValue\":\"" << otlp_json_escape(std::to_string(value)) << "\"}}";
128 }
129 }
130
135 inline void otlp_write_uint_attr(std::ostringstream& os, const std::string& key, uint64_t value) {
136 if (value <= static_cast<uint64_t>((std::numeric_limits<int64_t>::max)())) {
137 otlp_write_int_attr(os, key, static_cast<int64_t>(value));
138 } else {
139 os << "{\"key\":\"" << otlp_json_escape(key)
140 << "\",\"value\":{\"stringValue\":\"" << value << "\"}}";
141 }
142 }
143
147 inline std::string sanitize_otlp_key(const std::string& name) {
148 std::string result = name;
149 for (char& c : result) {
150 if (!std::isalnum(static_cast<unsigned char>(c)) && c != '_' && c != '.' && c != '-') {
151 c = '_';
152 }
153 }
154 return result;
155 }
156
162 std::ostringstream& os,
163 const OtlpLogItem& item,
164 const OtlpJsonFormatConfig& config) {
165 const OtlpRecordSnapshot& r = item.record;
166 const int64_t time_unix_nano = r.timestamp_ms * 1000000LL;
167
168 os << '{';
169 os << "\"timeUnixNano\":\"" << time_unix_nano << "\",";
170 os << "\"severityText\":\"" << otlp_json_escape(to_c_str(r.log_level)) << "\",";
171 os << "\"severityNumber\":" << otlp_severity_number(r.log_level) << ',';
172 os << "\"body\":{\"stringValue\":\"" << otlp_json_escape(item.message) << "\"},";
173 os << "\"attributes\":[";
174
175 bool first = true;
176
177 if (config.include_source) {
179 otlp_write_string_attr(os, "code.file.path", r.file);
180
182 otlp_write_int_attr(os, "code.line.number", r.line);
183
185 otlp_write_string_attr(os, "code.function.name", r.function);
186 }
187
188 if (config.include_thread_id) {
190 otlp_write_string_attr(os, "thread.id", r.thread_id);
191 }
192
193 if (config.include_format) {
195 otlp_write_string_attr(os, "logit.format", r.format);
196 }
197
198 if (config.include_arg_names && !r.arg_names.empty()) {
200 otlp_write_string_attr(os, "logit.arg_names", r.arg_names);
201 }
202
203 if (config.include_args && !r.args_array.empty()) {
204 std::unordered_map<std::string, std::size_t> key_count;
205
206 for (std::size_t i = 0; i < r.args_array.size(); ++i) {
207 const VariableValue& arg = r.args_array[i];
208
209 std::string sanitized = sanitize_otlp_key(arg.name);
210
211 bool all_underscore = true;
212 for (std::size_t j = 0; j < sanitized.size(); ++j) {
213 if (sanitized[j] != '_') {
214 all_underscore = false;
215 break;
216 }
217 }
218
219 std::string key;
220 if (sanitized.empty() || all_underscore) {
221 key = config.args_prefix + std::to_string(i);
222 } else {
223 key = config.args_prefix + sanitized;
224 }
225
226 auto it = key_count.find(key);
227 if (it != key_count.end()) {
228 ++(it->second);
229 key += "." + std::to_string(it->second);
230 }
231 key_count[key] = 0;
232
234
235 switch (arg.type) {
238 break;
240 otlp_write_int_attr(os, key, static_cast<int64_t>(arg.pod_value.int8_value));
241 break;
243 otlp_write_int_attr(os, key, static_cast<int64_t>(arg.pod_value.int16_value));
244 break;
246 otlp_write_int_attr(os, key, static_cast<int64_t>(arg.pod_value.int32_value));
247 break;
250 break;
252 otlp_write_uint_attr(os, key, static_cast<uint64_t>(arg.pod_value.uint8_value));
253 break;
255 otlp_write_uint_attr(os, key, static_cast<uint64_t>(arg.pod_value.uint16_value));
256 break;
258 otlp_write_uint_attr(os, key, static_cast<uint64_t>(arg.pod_value.uint32_value));
259 break;
262 break;
264 otlp_write_double_attr(os, key, static_cast<double>(arg.pod_value.float_value));
265 break;
268 break;
270 otlp_write_double_attr(os, key, static_cast<double>(arg.pod_value.long_double_value));
271 break;
272 default:
273 otlp_write_string_attr(os, key, arg.to_string());
274 break;
275 }
276 }
277 }
278
280 otlp_write_int_attr(os, "logit.logger_index", r.logger_index);
281
283 otlp_write_bool_attr(os, "logit.raw_mode", r.raw_mode);
284
286 otlp_write_bool_attr(os, "logit.print_mode", r.print_mode);
287
289 otlp_write_bool_attr(os, "logit.fmt_mode", r.fmt_mode);
290
291 os << ']';
292 os << '}';
293 }
294
300 const std::vector<OtlpLogItem>& batch,
301 const OtlpJsonFormatConfig& config) {
302 std::ostringstream os;
303
304 os << '{';
305 os << "\"resourceLogs\":[{";
306 os << "\"resource\":{\"attributes\":[";
307
308 bool resource_first = true;
309 otlp_write_comma_if_needed(os, resource_first);
310 otlp_write_string_attr(os, "service.name", config.service_name);
311
312 if (!config.service_namespace.empty()) {
313 otlp_write_comma_if_needed(os, resource_first);
314 otlp_write_string_attr(os, "service.namespace", config.service_namespace);
315 }
316
317 if (!config.service_instance_id.empty()) {
318 otlp_write_comma_if_needed(os, resource_first);
319 otlp_write_string_attr(os, "service.instance.id", config.service_instance_id);
320 }
321
322 if (!config.deployment_environment.empty()) {
323 otlp_write_comma_if_needed(os, resource_first);
324 otlp_write_string_attr(os, "deployment.environment.name", config.deployment_environment);
325 }
326
327 os << "]},";
328 os << "\"scopeLogs\":[{";
329 os << "\"scope\":{\"name\":\"logit-cpp\"},";
330 os << "\"logRecords\":[";
331
332 for (size_t i = 0; i < batch.size(); ++i) {
333 if (i != 0) {
334 os << ',';
335 }
336 otlp_write_log_record_json(os, batch[i], config);
337 }
338
339 os << ']';
340 os << "}]";
341 os << "}]";
342 os << '}';
343
344 return os.str();
345 }
346
347} // namespace logit
348
349#endif // LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_OTLP_OTLPJSONSERIALIZER_HPP_INCLUDED
Defines serialization-related configuration shared by OTLP loggers.
Defines a stable snapshot of LogRecord data for asynchronous OTLP export.
The primary namespace for the LogIt++ library.
void otlp_write_uint_attr(std::ostringstream &os, const std::string &key, uint64_t value)
Writes a uint64 OTLP attribute.
void otlp_write_double_attr(std::ostringstream &os, const std::string &key, double value)
Writes a double OTLP attribute.
void otlp_write_string_attr(std::ostringstream &os, const std::string &key, const std::string &value)
Writes a string OTLP attribute.
LogLevel
Logging levels.
Definition enums.hpp:15
@ LOG_LVL_TRACE
Trace level logging.
@ LOG_LVL_ERROR
Error level logging.
@ LOG_LVL_INFO
Information level logging.
@ LOG_LVL_FATAL
Fatal level logging.
@ LOG_LVL_DEBUG
Debug level logging.
@ LOG_LVL_WARN
Warning level logging.
void otlp_write_comma_if_needed(std::ostringstream &os, bool &first)
Writes a comma before the next JSON value when needed.
std::string sanitize_otlp_key(const std::string &name)
Sanitizes an attribute key for OTLP.
std::string otlp_json_escape(const std::string &value)
Escapes a string for JSON output.
void otlp_write_bool_attr(std::ostringstream &os, const std::string &key, bool value)
Writes a bool OTLP attribute.
void otlp_write_log_record_json(std::ostringstream &os, const OtlpLogItem &item, const OtlpJsonFormatConfig &config)
Writes one OTLP JSON log record.
std::string build_otlp_logs_json_payload(const std::vector< OtlpLogItem > &batch, const OtlpJsonFormatConfig &config)
Builds an OTLP ExportLogsServiceRequest JSON payload.
void otlp_write_int_attr(std::ostringstream &os, const std::string &key, int64_t value)
Writes an int OTLP attribute.
const char * to_c_str(LogLevel level, int mode=0)
Convert LogLevel to a C-style string representation.
Definition enums.hpp:77
int otlp_severity_number(LogLevel level)
Maps LogIt++ levels to OpenTelemetry severity numbers.
Serialization settings for OTLP JSON payload construction.
bool include_thread_id
Export thread id attribute.
bool include_format
Export original format string as logit.format.
bool include_source
Export source file, line, and function attributes.
std::string args_prefix
Key prefix for structured arg attributes.
bool include_arg_names
Export original argument names as logit.arg_names (legacy).
std::string service_namespace
Optional service.namespace resource attribute.
std::string service_instance_id
Optional service.instance.id resource attribute.
bool include_args
Export structured typed arg attributes.
std::string deployment_environment
Optional deployment.environment.name resource attribute.
std::string service_name
service.name resource attribute.
Owns one formatted log message and its structured metadata.
OtlpRecordSnapshot record
Structured log metadata snapshot.
std::string message
Formatted message body.
Owns LogRecord fields required after original logging call returns.
std::string format
Original message or format string.
LogLevel log_level
Log severity level.
int64_t timestamp_ms
Timestamp in milliseconds.
int line
Source line number.
std::string thread_id
Stringified std::thread::id.
std::vector< VariableValue > args_array
Structured argument values.
std::string file
Source file path.
int logger_index
Target logger index, or -1 for all.
std::string arg_names
Original argument names.
std::string function
Source function name.
bool fmt_mode
fmt formatting mode flag.
bool raw_mode
Raw log mode flag.
bool print_mode
Raw argument print mode flag.
Structure for storing values of various types, including enumerations.
long double long_double_value
std::string name
Variable name.
std::string to_string() const
Method to get the value as a string.
enum logit::VariableValue::ValueType type
Specifies the type of the stored value in the VariableValue structure.
@ BOOL_VAL
Value of type bool.
@ FLOAT_VAL
Value of type float (single-precision floating point).
@ INT16_VAL
Value of type int16_t (signed 16-bit integer).
@ UINT64_VAL
Value of type uint64_t (unsigned 64-bit integer).
@ UINT32_VAL
Value of type uint32_t (unsigned 32-bit integer).
@ INT64_VAL
Value of type int64_t (signed 64-bit integer).
@ DOUBLE_VAL
Value of type double (double-precision floating point).
@ INT32_VAL
Value of type int32_t (signed 32-bit integer).
@ UINT8_VAL
Value of type uint8_t (unsigned 8-bit integer).
@ LONG_DOUBLE_VAL
Value of type long double (extended-precision floating point).
@ INT8_VAL
Value of type int8_t (signed 8-bit integer).
@ UINT16_VAL
Value of type uint16_t (unsigned 16-bit integer).
union logit::VariableValue::@0 pod_value
Union to store POD types.