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, &timestamp_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