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