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/string.h>
9 #include <aws/common/thread.h>
10 #include <aws/testing/aws_test_harness.h>
11
12 #include <inttypes.h>
13 #include <stdarg.h>
14
15 #define TEST_FORMATTER_MAX_BUFFER_SIZE 4096
16
17 typedef int(log_formatter_test_fn)(struct aws_log_formatter *formatter, struct aws_string **output);
18
do_default_log_formatter_test(struct aws_allocator * allocator,log_formatter_test_fn * test_fn,const char * expected_user_output,enum aws_log_level log_level,enum aws_date_format date_format)19 int do_default_log_formatter_test(
20 struct aws_allocator *allocator,
21 log_formatter_test_fn *test_fn,
22 const char *expected_user_output,
23 enum aws_log_level log_level,
24 enum aws_date_format date_format) {
25 /* Initialize a default formatter*/
26 struct aws_log_formatter_standard_options options = {.date_format = date_format};
27
28 struct aws_log_formatter formatter;
29 aws_log_formatter_init_default(&formatter, allocator, &options);
30
31 struct aws_date_time test_time;
32 aws_date_time_init_now(&test_time);
33
34 /* Output something via the callback */
35 struct aws_string *output = NULL;
36 int result = (*test_fn)(&formatter, &output);
37
38 aws_log_formatter_clean_up(&formatter);
39
40 char buffer[TEST_FORMATTER_MAX_BUFFER_SIZE];
41 snprintf(buffer, TEST_FORMATTER_MAX_BUFFER_SIZE, "%s", aws_string_c_str(output));
42
43 aws_string_destroy(output);
44
45 /* Check that the format call was successful */
46 ASSERT_TRUE(result == AWS_OP_SUCCESS, "Formatting operation failed");
47
48 /*
49 * Make sure there's an endline as the final character(s).
50 * Move the end-of-string marker on top of the endline.
51 * Otherwise our failure outputs include the endline and become confusing.
52 */
53 size_t line_length = strlen(buffer);
54 ASSERT_TRUE(line_length >= 2, "Log line \"%s\" is too short", buffer);
55
56 ASSERT_TRUE(buffer[line_length - 1] == '\n', "Log line did not end with a newline");
57 buffer[line_length - 1] = 0;
58
59 /*
60 * Check that the log level appears properly
61 */
62 const char *log_level_start = strstr(buffer, "[");
63 ASSERT_TRUE(log_level_start != NULL, "Could not find start of log level in output line \"%s\"", buffer);
64
65 const char *level_string = NULL;
66 ASSERT_SUCCESS(
67 aws_log_level_to_string(log_level, &level_string), "Failed to convert log level %d to string", (int)log_level);
68 ASSERT_TRUE(
69 strncmp(log_level_start + 1, level_string, strlen(level_string)) == 0,
70 "Incorrect value for log level in output line \"%s\"",
71 buffer);
72
73 /**
74 * Find the timestamp substring.
75 */
76 const char *time_start = strstr(log_level_start + 1, "[");
77 ASSERT_TRUE(time_start != NULL, "Could not find start of timestamp in output line \"%s\"", buffer);
78 time_start += 1;
79
80 const char *time_end = strstr(time_start, "]");
81 ASSERT_TRUE(time_end != NULL, "Could not find end of timestamp in output line \"%s\"", buffer);
82
83 size_t time_length = time_end - time_start;
84
85 /*
86 * Fake a buffer pointing to the logged timestamp string; convert it to a date time
87 */
88 struct aws_byte_buf timestamp_buffer;
89 timestamp_buffer.allocator = formatter.allocator;
90 timestamp_buffer.buffer = (uint8_t *)time_start;
91 timestamp_buffer.capacity = time_length;
92 timestamp_buffer.len = time_length;
93
94 struct aws_date_time log_time;
95 ASSERT_SUCCESS(
96 aws_date_time_init_from_str(&log_time, ×tamp_buffer, date_format),
97 "Could not parse timestamp value starting at \"%s\"",
98 time_start);
99
100 /*
101 * Check that the timestamp, when converted back, is close to the current time.
102 */
103 time_t time_diff = aws_date_time_diff(&log_time, &test_time);
104 ASSERT_TRUE(time_diff <= 1, "Log timestamp deviated too far from test timestamp: %d seconds", (int)time_diff);
105
106 /*
107 * Find the thread id substring
108 */
109 const char *thread_id_start = strstr(time_end + 1, "[");
110 ASSERT_TRUE(thread_id_start != NULL, "Could not find start of thread id in output line \"%s\"", buffer);
111 thread_id_start += 1;
112
113 char *thread_id_end = strstr(thread_id_start, "]");
114 ASSERT_TRUE(thread_id_end != NULL, "Could not find end of thread id in output line \"%s\"", buffer);
115
116 ASSERT_TRUE((thread_id_end - thread_id_start + 1) == AWS_THREAD_ID_T_REPR_BUFSZ, "Unexpected thread id length");
117 aws_thread_id_t current_thread_id = aws_thread_current_thread_id();
118 char repr[AWS_THREAD_ID_T_REPR_BUFSZ];
119 ASSERT_SUCCESS(
120 aws_thread_id_t_to_string(current_thread_id, repr, AWS_THREAD_ID_T_REPR_BUFSZ),
121 "Could not convert aws_thread_id_t to string repr");
122 char logged_id[AWS_THREAD_ID_T_REPR_BUFSZ];
123 memcpy(logged_id, thread_id_start, AWS_THREAD_ID_T_REPR_BUFSZ - 1);
124 logged_id[AWS_THREAD_ID_T_REPR_BUFSZ - 1] = '\0';
125 ASSERT_SUCCESS(
126 strncmp(repr, logged_id, AWS_THREAD_ID_T_REPR_BUFSZ),
127 "Expected logged thread id to be \"%s\" but it was actually \"%s\"",
128 repr,
129 logged_id);
130
131 /*
132 * Check that the user content is what was expected
133 */
134 const char *separator = strstr(thread_id_end, " - ");
135 ASSERT_TRUE(separator != NULL, "");
136
137 const char *user_content = separator + 3;
138 size_t expected_user_content_length = strlen(expected_user_output);
139 ASSERT_SUCCESS(
140 strncmp(user_content, expected_user_output, expected_user_content_length),
141 "Expected user content \"%s\" but received \"%s\"",
142 expected_user_output,
143 user_content);
144
145 return AWS_OP_SUCCESS;
146 }
147
148 #define DEFINE_LOG_FORMATTER_TEST(test_function, log_level, date_format, expected_user_string) \
149 static int s_log_formatter_##test_function(struct aws_allocator *allocator, void *ctx) { \
150 (void)ctx; \
151 return do_default_log_formatter_test(allocator, test_function, expected_user_string, log_level, date_format); \
152 } \
153 AWS_TEST_CASE(test_log_formatter_##test_function, s_log_formatter_##test_function);
154
invoke_formatter(struct aws_log_formatter * formatter,struct aws_string ** output,enum aws_log_level log_level,const char * format,...)155 static int invoke_formatter(
156 struct aws_log_formatter *formatter,
157 struct aws_string **output,
158 enum aws_log_level log_level,
159 const char *format,
160 ...) {
161 va_list args;
162 va_start(args, format);
163
164 int result = formatter->vtable->format(formatter, output, log_level, AWS_LS_COMMON_GENERAL, format, args);
165
166 va_end(args);
167
168 return result;
169 }
170
171 /*
172 * Tests
173 */
174
175 /*
176 * Empty string case
177 */
s_formatter_empty_case(struct aws_log_formatter * formatter,struct aws_string ** output)178 static int s_formatter_empty_case(struct aws_log_formatter *formatter, struct aws_string **output) {
179 return invoke_formatter(formatter, output, AWS_LL_WARN, "");
180 }
181
182 DEFINE_LOG_FORMATTER_TEST(s_formatter_empty_case, AWS_LL_WARN, AWS_DATE_FORMAT_RFC822, "")
183
184 /*
185 * Simple string
186 */
s_formatter_simple_case(struct aws_log_formatter * formatter,struct aws_string ** output)187 static int s_formatter_simple_case(struct aws_log_formatter *formatter, struct aws_string **output) {
188 return invoke_formatter(formatter, output, AWS_LL_DEBUG, "Sample log output");
189 }
190
191 DEFINE_LOG_FORMATTER_TEST(s_formatter_simple_case, AWS_LL_DEBUG, AWS_DATE_FORMAT_ISO_8601, "Sample log output")
192
193 /*
194 * Format string with numbers
195 */
s_formatter_number_case(struct aws_log_formatter * formatter,struct aws_string ** output)196 static int s_formatter_number_case(struct aws_log_formatter *formatter, struct aws_string **output) {
197 return invoke_formatter(
198 formatter, output, AWS_LL_FATAL, "%d bottles of milk on the wall. Take %.4f bottles down.", 99, .9999f);
199 }
200
201 DEFINE_LOG_FORMATTER_TEST(
202 s_formatter_number_case,
203 AWS_LL_FATAL,
204 AWS_DATE_FORMAT_RFC822,
205 "99 bottles of milk on the wall. Take 0.9999 bottles down.")
206
207 /*
208 * Format string with strings
209 */
s_formatter_string_case(struct aws_log_formatter * formatter,struct aws_string ** output)210 static int s_formatter_string_case(struct aws_log_formatter *formatter, struct aws_string **output) {
211 return invoke_formatter(
212 formatter,
213 output,
214 AWS_LL_INFO,
215 "Once there was, if %s there was, and just perhaps there %s was",
216 "ever",
217 "never");
218 }
219
220 DEFINE_LOG_FORMATTER_TEST(
221 s_formatter_string_case,
222 AWS_LL_INFO,
223 AWS_DATE_FORMAT_ISO_8601,
224 "Once there was, if ever there was, and just perhaps there never was")
225
226 /*
227 * Format string with newlines
228 */
s_formatter_newline_case(struct aws_log_formatter * formatter,struct aws_string ** output)229 static int s_formatter_newline_case(struct aws_log_formatter *formatter, struct aws_string **output) {
230 return invoke_formatter(formatter, output, AWS_LL_TRACE, "\nMaking sure \nnewlines don't mess things\nup");
231 }
232
233 DEFINE_LOG_FORMATTER_TEST(
234 s_formatter_newline_case,
235 AWS_LL_TRACE,
236 AWS_DATE_FORMAT_RFC822,
237 "\nMaking sure \nnewlines don't mess things\nup")
238