Branch data Line data Source code
1 : : // Copyright 2026 HPActor Contributors
2 : : //
3 : : // Licensed under the Apache License, Version 2.0 (the "License");
4 : : // you may not use this file except in compliance with the License.
5 : : // You may obtain a copy of the License at
6 : : //
7 : : // http://www.apache.org/licenses/LICENSE-2.0
8 : : //
9 : : // Unless required by applicable law or agreed to in writing, software
10 : : // distributed under the License is distributed on an "AS IS" BASIS,
11 : : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : : // See the License for the specific language governing permissions and
13 : : // limitations under the License.
14 : :
15 : : #include <hpactor/log/log_formatter.hpp>
16 : :
17 : : #include <hpactor/log/log_category.hpp>
18 : : #include <hpactor/log/log_event.hpp>
19 : : #include <hpactor/log/log_field.hpp>
20 : : #include <hpactor/log/log_level.hpp>
21 : :
22 : : #include <chrono>
23 : : #include <cstdio>
24 : : #include <cstring>
25 : : #include <ctime>
26 : :
27 : : namespace hpactor::log {
28 : :
29 : : namespace {
30 : :
31 : : // ---------------------------------------------------------------------------
32 : : // Format Unix epoch nanoseconds as ISO 8601 (e.g.
33 : : // "2026-05-09T12:34:56.789123456Z")
34 : : // ---------------------------------------------------------------------------
35 : 576 : void format_timestamp(uint64_t ns, char* buf, size_t bufsz) {
36 : : auto secs = std::chrono::seconds(
37 : 576 : static_cast<std::chrono::seconds::rep>(ns / 1'000'000'000));
38 : 576 : auto sub_ns = static_cast<unsigned long>(ns % 1'000'000'000);
39 : 576 : auto tp = std::chrono::system_clock::time_point(secs);
40 : 576 : auto t = std::chrono::system_clock::to_time_t(tp);
41 : : std::tm gm;
42 : 576 : gmtime_r(&t, &gm);
43 : 576 : int pos = std::snprintf(buf, bufsz, "%04d-%02d-%02dT%02d:%02d:%02d.",
44 : 576 : gm.tm_year + 1900, gm.tm_mon + 1, gm.tm_mday,
45 : : gm.tm_hour, gm.tm_min, gm.tm_sec);
46 : 576 : if (pos > 0 && static_cast<std::size_t>(pos) < bufsz) {
47 : 576 : std::snprintf(buf + pos, bufsz - static_cast<std::size_t>(pos),
48 : : "%09luZ", sub_ns);
49 : : }
50 : 576 : }
51 : :
52 : : // ---------------------------------------------------------------------------
53 : : // Append a JSON-escaped string value to `out`.
54 : : // Escapes ", \, \n, \r, \t, and control characters (U+0000..U+001F).
55 : : // ---------------------------------------------------------------------------
56 : 690 : void json_escape_string(std::string& out, const char* s) {
57 : 690 : if (!s) {
58 : 0 : return;
59 : : }
60 : 16103 : for (; *s; ++s) {
61 : 15413 : unsigned char c = static_cast<unsigned char>(*s);
62 : 15413 : switch (c) {
63 : 3 : case '"':
64 : 3 : out += "\\\"";
65 : 3 : break;
66 : 1 : case '\\':
67 : 1 : out += "\\\\";
68 : 1 : break;
69 : 1 : case '\n':
70 : 1 : out += "\\n";
71 : 1 : break;
72 : 0 : case '\r':
73 : 0 : out += "\\r";
74 : 0 : break;
75 : 0 : case '\t':
76 : 0 : out += "\\t";
77 : 0 : break;
78 : 15408 : default:
79 : 15408 : if (c < 0x20) {
80 : : char esc[8];
81 : 0 : std::snprintf(esc, sizeof(esc), "\\u%04x",
82 : : static_cast<unsigned>(c));
83 : 0 : out += esc;
84 : : } else {
85 : 15408 : out += static_cast<char>(c);
86 : : }
87 : 15408 : break;
88 : : }
89 : : }
90 : : }
91 : :
92 : : // ---------------------------------------------------------------------------
93 : : // Append the value of a LogField in text (key=value) form to `out`.
94 : : // ---------------------------------------------------------------------------
95 : 2 : void append_text_field_value(const LogField& field, std::string& out) {
96 : : char buf[128];
97 : 2 : switch (field.type) {
98 : 0 : case LogFieldType::kInt64:
99 : 0 : std::snprintf(buf, sizeof(buf), "%ld",
100 : 0 : static_cast<long>(field.value.i64));
101 : 0 : out += buf;
102 : 0 : break;
103 : 2 : case LogFieldType::kUInt64:
104 : 2 : std::snprintf(buf, sizeof(buf), "%lu",
105 : 2 : static_cast<unsigned long>(field.value.u64));
106 : 2 : out += buf;
107 : 2 : break;
108 : 0 : case LogFieldType::kDouble:
109 : 0 : std::snprintf(buf, sizeof(buf), "%g", field.value.f64);
110 : 0 : out += buf;
111 : 0 : break;
112 : 0 : case LogFieldType::kBool:
113 : 0 : out += field.value.boolean ? "true" : "false";
114 : 0 : break;
115 : 0 : case LogFieldType::kStringLiteral:
116 : 0 : if (field.value.str) {
117 : 0 : out += field.value.str;
118 : : }
119 : 0 : break;
120 : 0 : case LogFieldType::kPointer:
121 : 0 : std::snprintf(buf, sizeof(buf), "%p", field.value.ptr);
122 : 0 : out += buf;
123 : 0 : break;
124 : : }
125 : 2 : }
126 : :
127 : : // ---------------------------------------------------------------------------
128 : : // Append the value of a LogField in JSON form to `out`.
129 : : // ---------------------------------------------------------------------------
130 : 530 : void append_json_field_value(const LogField& field, std::string& out) {
131 : : char buf[128];
132 : 530 : switch (field.type) {
133 : 0 : case LogFieldType::kInt64:
134 : 0 : std::snprintf(buf, sizeof(buf), "%ld",
135 : 0 : static_cast<long>(field.value.i64));
136 : 0 : out += buf;
137 : 0 : break;
138 : 414 : case LogFieldType::kUInt64:
139 : 414 : std::snprintf(buf, sizeof(buf), "%lu",
140 : 414 : static_cast<unsigned long>(field.value.u64));
141 : 414 : out += buf;
142 : 414 : break;
143 : 0 : case LogFieldType::kDouble:
144 : 0 : std::snprintf(buf, sizeof(buf), "%g", field.value.f64);
145 : 0 : out += buf;
146 : 0 : break;
147 : 0 : case LogFieldType::kBool:
148 : 0 : out += field.value.boolean ? "true" : "false";
149 : 0 : break;
150 : 116 : case LogFieldType::kStringLiteral:
151 : 116 : out += '"';
152 : 116 : json_escape_string(out, field.value.str ? field.value.str : "");
153 : 116 : out += '"';
154 : 116 : break;
155 : 0 : case LogFieldType::kPointer:
156 : 0 : out += '"';
157 : 0 : std::snprintf(buf, sizeof(buf), "%p", field.value.ptr);
158 : 0 : out += buf;
159 : 0 : out += '"';
160 : 0 : break;
161 : : }
162 : 530 : }
163 : :
164 : : } // anonymous namespace
165 : :
166 : : // ---------------------------------------------------------------------------
167 : : // TextLogFormatter
168 : : // ---------------------------------------------------------------------------
169 : 2 : void TextLogFormatter::format(const LogEvent& event, std::string& out) {
170 : : char ts_buf[64];
171 : 2 : format_timestamp(event.timestamp_ns, ts_buf, sizeof(ts_buf));
172 : 2 : out = ts_buf;
173 : :
174 : 2 : out += ' ';
175 : 2 : out += to_string(event.level);
176 : :
177 : 2 : out += ' ';
178 : 2 : out += to_string(event.category);
179 : :
180 : 2 : if (event.actor_id.value() != 0) {
181 : : char buf[32];
182 : 1 : int n = std::snprintf(buf, sizeof(buf), " actor=%lu",
183 : : static_cast<unsigned long>(event.actor_id.value()));
184 : 1 : if (n > 0) {
185 : 1 : out.append(buf, static_cast<std::size_t>(n));
186 : : }
187 : : }
188 : :
189 : 2 : out += " event=";
190 : 2 : out += to_string(static_cast<LogEventId>(event.event_id));
191 : :
192 : 2 : if (event.message) {
193 : 2 : out += ' ';
194 : 2 : out += event.message;
195 : : }
196 : :
197 : 4 : for (uint8_t i = 0; i < event.field_count; ++i) {
198 : 2 : out += ' ';
199 : 2 : out += event.fields[i].name;
200 : 2 : out += '=';
201 : 2 : append_text_field_value(event.fields[i], out);
202 : : }
203 : 2 : }
204 : :
205 : : // ---------------------------------------------------------------------------
206 : : // JsonLogFormatter
207 : : // ---------------------------------------------------------------------------
208 : 574 : void JsonLogFormatter::format(const LogEvent& event, std::string& out) {
209 : : char ts_buf[64];
210 : 574 : format_timestamp(event.timestamp_ns, ts_buf, sizeof(ts_buf));
211 : :
212 : 574 : out = R"({"ts":")";
213 : 574 : out += ts_buf;
214 : 574 : out += R"(","level":")";
215 : 574 : out += to_string(event.level);
216 : 574 : out += R"(","category":")";
217 : 574 : out += to_string(event.category);
218 : 574 : out += '"';
219 : :
220 : 574 : if (event.actor_id.value() != 0) {
221 : : char buf[32];
222 : 131 : std::snprintf(buf, sizeof(buf), "%lu",
223 : : static_cast<unsigned long>(event.actor_id.value()));
224 : 131 : out += ",\"actor_id\":";
225 : 131 : out += buf;
226 : : }
227 : :
228 : : {
229 : : char buf[32];
230 : 574 : std::snprintf(buf, sizeof(buf), "%u", event.event_id);
231 : 574 : out += ",\"event_id\":";
232 : 574 : out += buf;
233 : : }
234 : :
235 : 574 : out += R"(,"message":")";
236 : 574 : json_escape_string(out, event.message ? event.message : "");
237 : 574 : out += '"';
238 : :
239 : 1104 : for (uint8_t i = 0; i < event.field_count; ++i) {
240 : 530 : out += ",\"";
241 : 530 : out += event.fields[i].name;
242 : 530 : out += "\":";
243 : 530 : append_json_field_value(event.fields[i], out);
244 : : }
245 : :
246 : 574 : out += '}';
247 : 574 : }
248 : :
249 : : } // namespace hpactor::log
|