Line data Source code
1 : /**
2 : * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 : * SPDX-License-Identifier: Apache-2.0.
4 : */
5 :
6 : #include <aws/common/log_formatter.h>
7 :
8 : #include <aws/common/date_time.h>
9 : #include <aws/common/string.h>
10 : #include <aws/common/thread.h>
11 :
12 : #include <inttypes.h>
13 : #include <stdarg.h>
14 :
15 : /*
16 : * Default formatter implementation
17 : */
18 :
19 : #if _MSC_VER
20 : # pragma warning(disable : 4204) /* non-constant aggregate initializer */
21 : #endif
22 :
23 : /* (max) strlen of "[<LogLevel>]" */
24 0 : #define LOG_LEVEL_PREFIX_PADDING 7
25 :
26 : /* (max) strlen of "[<ThreadId>]" */
27 0 : #define THREAD_ID_PREFIX_PADDING 22
28 :
29 : /* strlen of (user-content separator) " - " + "\n" + spaces between prefix fields + brackets around timestamp + 1 +
30 : subject_name padding */
31 0 : #define MISC_PADDING 15
32 :
33 : #define MAX_LOG_LINE_PREFIX_SIZE \
34 0 : (LOG_LEVEL_PREFIX_PADDING + THREAD_ID_PREFIX_PADDING + MISC_PADDING + AWS_DATE_TIME_STR_MAX_LEN)
35 :
36 0 : static size_t s_advance_and_clamp_index(size_t current_index, int amount, size_t maximum) {
37 0 : size_t next_index = current_index + amount;
38 0 : if (next_index > maximum) {
39 0 : next_index = maximum;
40 0 : }
41 0 :
42 0 : return next_index;
43 0 : }
44 :
45 : /* Thread-local string representation of current thread id */
46 : AWS_THREAD_LOCAL struct {
47 : bool is_valid;
48 : char repr[AWS_THREAD_ID_T_REPR_BUFSZ];
49 : } tl_logging_thread_id = {.is_valid = false};
50 :
51 0 : int aws_format_standard_log_line(struct aws_logging_standard_formatting_data *formatting_data, va_list args) {
52 0 : size_t current_index = 0;
53 0 :
54 0 : /*
55 0 : * Begin the log line with "[<Log Level>] ["
56 0 : */
57 0 : const char *level_string = NULL;
58 0 : if (aws_log_level_to_string(formatting_data->level, &level_string)) {
59 0 : return AWS_OP_ERR;
60 0 : }
61 0 :
62 0 : if (formatting_data->total_length == 0) {
63 0 : return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
64 0 : }
65 0 :
66 0 : /*
67 0 : * Use this length for all but the last write, so we guarantee room for the newline even if we get truncated
68 0 : */
69 0 : size_t fake_total_length = formatting_data->total_length - 1;
70 0 :
71 0 : int log_level_length = snprintf(formatting_data->log_line_buffer, fake_total_length, "[%s] [", level_string);
72 0 : if (log_level_length < 0) {
73 0 : return AWS_OP_ERR;
74 0 : }
75 0 :
76 0 : current_index = s_advance_and_clamp_index(current_index, log_level_length, fake_total_length);
77 0 :
78 0 : if (current_index < fake_total_length) {
79 0 : /*
80 0 : * Add the timestamp. To avoid copies and allocations, do some byte buffer tomfoolery.
81 0 : *
82 0 : * First, make a byte_buf that points to the current position in the output string
83 0 : */
84 0 : struct aws_byte_buf timestamp_buffer = {
85 0 : .allocator = formatting_data->allocator,
86 0 : .buffer = (uint8_t *)formatting_data->log_line_buffer + current_index,
87 0 : .capacity = fake_total_length - current_index,
88 0 : .len = 0,
89 0 : };
90 0 :
91 0 : /*
92 0 : * Output the current time to the byte_buf
93 0 : */
94 0 : struct aws_date_time current_time;
95 0 : aws_date_time_init_now(¤t_time);
96 0 :
97 0 : int result = aws_date_time_to_utc_time_str(¤t_time, formatting_data->date_format, ×tamp_buffer);
98 0 : if (result != AWS_OP_SUCCESS) {
99 0 : return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
100 0 : }
101 0 :
102 0 : current_index = s_advance_and_clamp_index(current_index, (int)timestamp_buffer.len, fake_total_length);
103 0 : }
104 0 :
105 0 : if (current_index < fake_total_length) {
106 0 : /*
107 0 : * Add thread id and user content separator (" - ")
108 0 : */
109 0 : if (!tl_logging_thread_id.is_valid) {
110 0 : aws_thread_id_t current_thread_id = aws_thread_current_thread_id();
111 0 : if (aws_thread_id_t_to_string(current_thread_id, tl_logging_thread_id.repr, AWS_THREAD_ID_T_REPR_BUFSZ)) {
112 0 : return AWS_OP_ERR;
113 0 : }
114 0 : tl_logging_thread_id.is_valid = true;
115 0 : }
116 0 : int thread_id_written = snprintf(
117 0 : formatting_data->log_line_buffer + current_index,
118 0 : fake_total_length - current_index,
119 0 : "] [%s] ",
120 0 : tl_logging_thread_id.repr);
121 0 : if (thread_id_written < 0) {
122 0 : return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
123 0 : }
124 0 : current_index = s_advance_and_clamp_index(current_index, thread_id_written, fake_total_length);
125 0 : }
126 0 :
127 0 : if (current_index < fake_total_length) {
128 0 : /* output subject name */
129 0 : if (formatting_data->subject_name) {
130 0 : int subject_written = snprintf(
131 0 : formatting_data->log_line_buffer + current_index,
132 0 : fake_total_length - current_index,
133 0 : "[%s]",
134 0 : formatting_data->subject_name);
135 0 :
136 0 : if (subject_written < 0) {
137 0 : return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
138 0 : }
139 0 :
140 0 : current_index = s_advance_and_clamp_index(current_index, subject_written, fake_total_length);
141 0 : }
142 0 : }
143 0 :
144 0 : if (current_index < fake_total_length) {
145 0 : int separator_written =
146 0 : snprintf(formatting_data->log_line_buffer + current_index, fake_total_length - current_index, " - ");
147 0 : current_index = s_advance_and_clamp_index(current_index, separator_written, fake_total_length);
148 0 : }
149 0 :
150 0 : if (current_index < fake_total_length) {
151 0 : /*
152 0 : * Now write the actual data requested by the user
153 0 : */
154 : #ifdef _WIN32
155 : int written_count = vsnprintf_s(
156 : formatting_data->log_line_buffer + current_index,
157 : fake_total_length - current_index,
158 : _TRUNCATE,
159 : formatting_data->format,
160 : args);
161 : #else
162 : int written_count = vsnprintf(
163 0 : formatting_data->log_line_buffer + current_index,
164 0 : fake_total_length - current_index,
165 0 : formatting_data->format,
166 0 : args);
167 0 : #endif /* _WIN32 */
168 0 : if (written_count < 0) {
169 0 : return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
170 0 : }
171 0 :
172 0 : current_index = s_advance_and_clamp_index(current_index, written_count, fake_total_length);
173 0 : }
174 0 :
175 0 : /*
176 0 : * End with a newline.
177 0 : */
178 0 : int newline_written_count =
179 0 : snprintf(formatting_data->log_line_buffer + current_index, formatting_data->total_length - current_index, "\n");
180 0 : if (newline_written_count < 0) {
181 0 : return aws_raise_error(AWS_ERROR_UNKNOWN); /* we saved space, so this would be crazy */
182 0 : }
183 0 :
184 0 : formatting_data->amount_written = current_index + newline_written_count;
185 0 :
186 0 : return AWS_OP_SUCCESS;
187 0 : }
188 :
189 : struct aws_default_log_formatter_impl {
190 : enum aws_date_format date_format;
191 : };
192 :
193 : static int s_default_aws_log_formatter_format(
194 : struct aws_log_formatter *formatter,
195 : struct aws_string **formatted_output,
196 : enum aws_log_level level,
197 : aws_log_subject_t subject,
198 : const char *format,
199 0 : va_list args) {
200 0 :
201 0 : (void)subject;
202 0 :
203 0 : struct aws_default_log_formatter_impl *impl = formatter->impl;
204 0 :
205 0 : if (formatted_output == NULL) {
206 0 : return AWS_OP_ERR;
207 0 : }
208 0 :
209 0 : /*
210 0 : * Calculate how much room we'll need to build the full log line.
211 0 : * You cannot consume a va_list twice, so we have to copy it.
212 0 : */
213 0 : va_list tmp_args;
214 0 : va_copy(tmp_args, args);
215 : #ifdef _WIN32
216 : int required_length = _vscprintf(format, tmp_args) + 1;
217 : #else
218 : int required_length = vsnprintf(NULL, 0, format, tmp_args) + 1;
219 0 : #endif
220 0 : va_end(tmp_args);
221 0 :
222 0 : /*
223 0 : * Allocate enough room to hold the line. Then we'll (unsafely) do formatted IO directly into the aws_string
224 0 : * memory.
225 0 : */
226 0 : const char *subject_name = aws_log_subject_name(subject);
227 0 : int subject_name_len = 0;
228 0 :
229 0 : if (subject_name) {
230 0 : subject_name_len = (int)strlen(subject_name);
231 0 : }
232 0 :
233 0 : int total_length = required_length + MAX_LOG_LINE_PREFIX_SIZE + subject_name_len;
234 0 : struct aws_string *raw_string = aws_mem_calloc(formatter->allocator, 1, sizeof(struct aws_string) + total_length);
235 0 : if (raw_string == NULL) {
236 0 : goto error_clean_up;
237 0 : }
238 0 :
239 0 : struct aws_logging_standard_formatting_data format_data = {
240 0 : .log_line_buffer = (char *)raw_string->bytes,
241 0 : .total_length = total_length,
242 0 : .level = level,
243 0 : .subject_name = subject_name,
244 0 : .format = format,
245 0 : .date_format = impl->date_format,
246 0 : .allocator = formatter->allocator,
247 0 : .amount_written = 0,
248 0 : };
249 0 :
250 0 : if (aws_format_standard_log_line(&format_data, args)) {
251 0 : goto error_clean_up;
252 0 : }
253 0 :
254 0 : *(struct aws_allocator **)(&raw_string->allocator) = formatter->allocator;
255 0 : *(size_t *)(&raw_string->len) = format_data.amount_written;
256 0 :
257 0 : *formatted_output = raw_string;
258 0 :
259 0 : return AWS_OP_SUCCESS;
260 0 :
261 0 : error_clean_up:
262 0 :
263 0 : if (raw_string != NULL) {
264 0 : aws_mem_release(formatter->allocator, raw_string);
265 0 : }
266 0 :
267 0 : return AWS_OP_ERR;
268 0 : }
269 :
270 0 : static void s_default_aws_log_formatter_clean_up(struct aws_log_formatter *formatter) {
271 0 : aws_mem_release(formatter->allocator, formatter->impl);
272 0 : }
273 :
274 : static struct aws_log_formatter_vtable s_default_log_formatter_vtable = {
275 : .format = s_default_aws_log_formatter_format,
276 : .clean_up = s_default_aws_log_formatter_clean_up,
277 : };
278 :
279 : int aws_log_formatter_init_default(
280 : struct aws_log_formatter *formatter,
281 : struct aws_allocator *allocator,
282 0 : struct aws_log_formatter_standard_options *options) {
283 0 : struct aws_default_log_formatter_impl *impl =
284 0 : aws_mem_calloc(allocator, 1, sizeof(struct aws_default_log_formatter_impl));
285 0 : impl->date_format = options->date_format;
286 0 :
287 0 : formatter->vtable = &s_default_log_formatter_vtable;
288 0 : formatter->allocator = allocator;
289 0 : formatter->impl = impl;
290 0 :
291 0 : return AWS_OP_SUCCESS;
292 0 : }
293 :
294 0 : void aws_log_formatter_clean_up(struct aws_log_formatter *formatter) {
295 0 : AWS_ASSERT(formatter->vtable->clean_up);
296 0 : (formatter->vtable->clean_up)(formatter);
297 0 : }
|