1 | /* |
2 | * Copyright 2010-2018 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/logging.h> |
17 | |
18 | #include <aws/common/string.h> |
19 | |
20 | #include <aws/common/log_channel.h> |
21 | #include <aws/common/log_formatter.h> |
22 | #include <aws/common/log_writer.h> |
23 | #include <aws/common/mutex.h> |
24 | |
25 | #include <errno.h> |
26 | #include <stdarg.h> |
27 | |
28 | #if _MSC_VER |
29 | # pragma warning(disable : 4204) /* non-constant aggregate initializer */ |
30 | # pragma warning(disable : 4996) /* Disable warnings about fopen() being insecure */ |
31 | #endif |
32 | |
33 | /* |
34 | * Null logger implementation |
35 | */ |
36 | static enum aws_log_level s_null_logger_get_log_level(struct aws_logger *logger, aws_log_subject_t subject) { |
37 | (void)logger; |
38 | (void)subject; |
39 | |
40 | return AWS_LL_NONE; |
41 | } |
42 | |
43 | static int s_null_logger_log( |
44 | struct aws_logger *logger, |
45 | enum aws_log_level log_level, |
46 | aws_log_subject_t subject, |
47 | const char *format, |
48 | ...) { |
49 | |
50 | (void)logger; |
51 | (void)log_level; |
52 | (void)subject; |
53 | (void)format; |
54 | |
55 | return AWS_OP_SUCCESS; |
56 | } |
57 | |
58 | static void s_null_logger_clean_up(struct aws_logger *logger) { |
59 | (void)logger; |
60 | } |
61 | |
62 | static struct aws_logger_vtable s_null_vtable = { |
63 | .get_log_level = s_null_logger_get_log_level, |
64 | .log = s_null_logger_log, |
65 | .clean_up = s_null_logger_clean_up, |
66 | }; |
67 | |
68 | static struct aws_logger s_null_logger = {.vtable = &s_null_vtable, .allocator = NULL, .p_impl = NULL}; |
69 | |
70 | /* |
71 | * Pipeline logger implementation |
72 | */ |
73 | static void s_aws_logger_pipeline_owned_clean_up(struct aws_logger *logger) { |
74 | struct aws_logger_pipeline *impl = logger->p_impl; |
75 | |
76 | AWS_ASSERT(impl->channel->vtable->clean_up != NULL); |
77 | (impl->channel->vtable->clean_up)(impl->channel); |
78 | |
79 | AWS_ASSERT(impl->formatter->vtable->clean_up != NULL); |
80 | (impl->formatter->vtable->clean_up)(impl->formatter); |
81 | |
82 | AWS_ASSERT(impl->writer->vtable->clean_up != NULL); |
83 | (impl->writer->vtable->clean_up)(impl->writer); |
84 | |
85 | aws_mem_release(impl->allocator, impl->channel); |
86 | aws_mem_release(impl->allocator, impl->formatter); |
87 | aws_mem_release(impl->allocator, impl->writer); |
88 | |
89 | aws_mem_release(impl->allocator, impl); |
90 | } |
91 | |
92 | /* |
93 | * Pipeline logger implementation |
94 | */ |
95 | static int s_aws_logger_pipeline_log( |
96 | struct aws_logger *logger, |
97 | enum aws_log_level log_level, |
98 | aws_log_subject_t subject, |
99 | const char *format, |
100 | ...) { |
101 | va_list format_args; |
102 | va_start(format_args, format); |
103 | |
104 | struct aws_logger_pipeline *impl = logger->p_impl; |
105 | struct aws_string *output = NULL; |
106 | |
107 | AWS_ASSERT(impl->formatter->vtable->format != NULL); |
108 | int result = (impl->formatter->vtable->format)(impl->formatter, &output, log_level, subject, format, format_args); |
109 | |
110 | va_end(format_args); |
111 | |
112 | if (result != AWS_OP_SUCCESS || output == NULL) { |
113 | return AWS_OP_ERR; |
114 | } |
115 | |
116 | AWS_ASSERT(impl->channel->vtable->send != NULL); |
117 | if ((impl->channel->vtable->send)(impl->channel, output)) { |
118 | /* |
119 | * failure to send implies failure to transfer ownership |
120 | */ |
121 | aws_string_destroy(output); |
122 | return AWS_OP_ERR; |
123 | } |
124 | |
125 | return AWS_OP_SUCCESS; |
126 | } |
127 | |
128 | static enum aws_log_level s_aws_logger_pipeline_get_log_level(struct aws_logger *logger, aws_log_subject_t subject) { |
129 | (void)subject; |
130 | |
131 | struct aws_logger_pipeline *impl = logger->p_impl; |
132 | |
133 | return impl->level; |
134 | } |
135 | |
136 | struct aws_logger_vtable g_pipeline_logger_owned_vtable = { |
137 | .get_log_level = s_aws_logger_pipeline_get_log_level, |
138 | .log = s_aws_logger_pipeline_log, |
139 | .clean_up = s_aws_logger_pipeline_owned_clean_up, |
140 | }; |
141 | |
142 | int aws_logger_init_standard( |
143 | struct aws_logger *logger, |
144 | struct aws_allocator *allocator, |
145 | struct aws_logger_standard_options *options) { |
146 | |
147 | struct aws_logger_pipeline *impl = aws_mem_calloc(allocator, 1, sizeof(struct aws_logger_pipeline)); |
148 | if (impl == NULL) { |
149 | return AWS_OP_ERR; |
150 | } |
151 | |
152 | struct aws_log_writer *writer = aws_mem_acquire(allocator, sizeof(struct aws_log_writer)); |
153 | if (writer == NULL) { |
154 | goto on_allocate_writer_failure; |
155 | } |
156 | |
157 | struct aws_log_writer_file_options file_writer_options = { |
158 | .filename = options->filename, |
159 | .file = options->file, |
160 | }; |
161 | |
162 | if (aws_log_writer_init_file(writer, allocator, &file_writer_options)) { |
163 | goto on_init_writer_failure; |
164 | } |
165 | |
166 | struct aws_log_formatter *formatter = aws_mem_acquire(allocator, sizeof(struct aws_log_formatter)); |
167 | if (formatter == NULL) { |
168 | goto on_allocate_formatter_failure; |
169 | } |
170 | |
171 | struct aws_log_formatter_standard_options formatter_options = {.date_format = AWS_DATE_FORMAT_ISO_8601}; |
172 | |
173 | if (aws_log_formatter_init_default(formatter, allocator, &formatter_options)) { |
174 | goto on_init_formatter_failure; |
175 | } |
176 | |
177 | struct aws_log_channel *channel = aws_mem_acquire(allocator, sizeof(struct aws_log_channel)); |
178 | if (channel == NULL) { |
179 | goto on_allocate_channel_failure; |
180 | } |
181 | |
182 | if (aws_log_channel_init_background(channel, allocator, writer) == AWS_OP_SUCCESS) { |
183 | impl->formatter = formatter; |
184 | impl->channel = channel; |
185 | impl->writer = writer; |
186 | impl->allocator = allocator; |
187 | impl->level = options->level; |
188 | |
189 | logger->vtable = &g_pipeline_logger_owned_vtable; |
190 | logger->allocator = allocator; |
191 | logger->p_impl = impl; |
192 | |
193 | return AWS_OP_SUCCESS; |
194 | } |
195 | |
196 | aws_mem_release(allocator, channel); |
197 | |
198 | on_allocate_channel_failure: |
199 | aws_log_formatter_clean_up(formatter); |
200 | |
201 | on_init_formatter_failure: |
202 | aws_mem_release(allocator, formatter); |
203 | |
204 | on_allocate_formatter_failure: |
205 | aws_log_writer_clean_up(writer); |
206 | |
207 | on_init_writer_failure: |
208 | aws_mem_release(allocator, writer); |
209 | |
210 | on_allocate_writer_failure: |
211 | aws_mem_release(allocator, impl); |
212 | |
213 | return AWS_OP_ERR; |
214 | } |
215 | |
216 | /* |
217 | * Pipeline logger implementation where all the components are externally owned. No clean up |
218 | * is done on the components. Useful for tests where components are on the stack and often mocked. |
219 | */ |
220 | static void s_aws_pipeline_logger_unowned_clean_up(struct aws_logger *logger) { |
221 | struct aws_logger_pipeline *impl = (struct aws_logger_pipeline *)logger->p_impl; |
222 | |
223 | aws_mem_release(impl->allocator, impl); |
224 | } |
225 | |
226 | static struct aws_logger_vtable s_pipeline_logger_unowned_vtable = { |
227 | .get_log_level = s_aws_logger_pipeline_get_log_level, |
228 | .log = s_aws_logger_pipeline_log, |
229 | .clean_up = s_aws_pipeline_logger_unowned_clean_up, |
230 | }; |
231 | |
232 | int aws_logger_init_from_external( |
233 | struct aws_logger *logger, |
234 | struct aws_allocator *allocator, |
235 | struct aws_log_formatter *formatter, |
236 | struct aws_log_channel *channel, |
237 | struct aws_log_writer *writer, |
238 | enum aws_log_level level) { |
239 | |
240 | struct aws_logger_pipeline *impl = aws_mem_acquire(allocator, sizeof(struct aws_logger_pipeline)); |
241 | |
242 | if (impl == NULL) { |
243 | return AWS_OP_ERR; |
244 | } |
245 | |
246 | impl->formatter = formatter; |
247 | impl->channel = channel; |
248 | impl->writer = writer; |
249 | impl->allocator = allocator; |
250 | impl->level = level; |
251 | |
252 | logger->vtable = &s_pipeline_logger_unowned_vtable; |
253 | logger->allocator = allocator; |
254 | logger->p_impl = impl; |
255 | |
256 | return AWS_OP_SUCCESS; |
257 | } |
258 | |
259 | /* |
260 | * Global API |
261 | */ |
262 | static struct aws_logger *s_root_logger_ptr = &s_null_logger; |
263 | |
264 | void aws_logger_set(struct aws_logger *logger) { |
265 | if (logger != NULL) { |
266 | s_root_logger_ptr = logger; |
267 | } else { |
268 | s_root_logger_ptr = &s_null_logger; |
269 | } |
270 | } |
271 | |
272 | struct aws_logger *aws_logger_get(void) { |
273 | return s_root_logger_ptr; |
274 | } |
275 | |
276 | void aws_logger_clean_up(struct aws_logger *logger) { |
277 | AWS_ASSERT(logger->vtable->clean_up != NULL); |
278 | |
279 | logger->vtable->clean_up(logger); |
280 | } |
281 | |
282 | static const char *s_log_level_strings[AWS_LL_COUNT] = {"NONE " , "FATAL" , "ERROR" , "WARN " , "INFO " , "DEBUG" , "TRACE" }; |
283 | |
284 | int aws_log_level_to_string(enum aws_log_level log_level, const char **level_string) { |
285 | AWS_ERROR_PRECONDITION(log_level < AWS_LL_COUNT); |
286 | |
287 | if (level_string != NULL) { |
288 | *level_string = s_log_level_strings[log_level]; |
289 | } |
290 | |
291 | return AWS_OP_SUCCESS; |
292 | } |
293 | |
294 | #ifndef AWS_MAX_LOG_SUBJECT_SLOTS |
295 | # define AWS_MAX_LOG_SUBJECT_SLOTS 16u |
296 | #endif |
297 | |
298 | static const uint32_t S_MAX_LOG_SUBJECT = AWS_LOG_SUBJECT_SPACE_SIZE * AWS_MAX_LOG_SUBJECT_SLOTS - 1; |
299 | |
300 | static const struct aws_log_subject_info_list *volatile s_log_subject_slots[AWS_MAX_LOG_SUBJECT_SLOTS] = {0}; |
301 | |
302 | static const struct aws_log_subject_info *s_get_log_subject_info_by_id(aws_log_subject_t subject) { |
303 | if (subject > S_MAX_LOG_SUBJECT) { |
304 | return NULL; |
305 | } |
306 | |
307 | uint32_t slot_index = subject >> AWS_LOG_SUBJECT_BIT_SPACE; |
308 | uint32_t subject_index = subject & AWS_LOG_SUBJECT_SPACE_MASK; |
309 | |
310 | const struct aws_log_subject_info_list *subject_slot = s_log_subject_slots[slot_index]; |
311 | |
312 | if (!subject_slot || subject_index >= subject_slot->count) { |
313 | return NULL; |
314 | } |
315 | |
316 | return &subject_slot->subject_list[subject_index]; |
317 | } |
318 | |
319 | const char *aws_log_subject_name(aws_log_subject_t subject) { |
320 | const struct aws_log_subject_info *subject_info = s_get_log_subject_info_by_id(subject); |
321 | |
322 | if (subject_info != NULL) { |
323 | return subject_info->subject_name; |
324 | } |
325 | |
326 | return "Unknown" ; |
327 | } |
328 | |
329 | void aws_register_log_subject_info_list(struct aws_log_subject_info_list *log_subject_list) { |
330 | /* |
331 | * We're not so worried about these asserts being removed in an NDEBUG build |
332 | * - we'll either segfault immediately (for the first two) or for the count |
333 | * assert, the registration will be ineffective. |
334 | */ |
335 | AWS_FATAL_ASSERT(log_subject_list); |
336 | AWS_FATAL_ASSERT(log_subject_list->subject_list); |
337 | AWS_FATAL_ASSERT(log_subject_list->count); |
338 | |
339 | const uint32_t min_range = log_subject_list->subject_list[0].subject_id; |
340 | const uint32_t slot_index = min_range >> AWS_LOG_SUBJECT_BIT_SPACE; |
341 | |
342 | if (slot_index >= AWS_MAX_LOG_SUBJECT_SLOTS) { |
343 | /* This is an NDEBUG build apparently. Kill the process rather than |
344 | * corrupting heap. */ |
345 | fprintf(stderr, "Bad log subject slot index 0x%016x\n" , slot_index); |
346 | abort(); |
347 | } |
348 | |
349 | s_log_subject_slots[slot_index] = log_subject_list; |
350 | } |
351 | |
352 | void aws_unregister_log_subject_info_list(struct aws_log_subject_info_list *log_subject_list) { |
353 | /* |
354 | * We're not so worried about these asserts being removed in an NDEBUG build |
355 | * - we'll either segfault immediately (for the first two) or for the count |
356 | * assert, the registration will be ineffective. |
357 | */ |
358 | AWS_FATAL_ASSERT(log_subject_list); |
359 | AWS_FATAL_ASSERT(log_subject_list->subject_list); |
360 | AWS_FATAL_ASSERT(log_subject_list->count); |
361 | |
362 | const uint32_t min_range = log_subject_list->subject_list[0].subject_id; |
363 | const uint32_t slot_index = min_range >> AWS_LOG_SUBJECT_BIT_SPACE; |
364 | |
365 | if (slot_index >= AWS_MAX_LOG_SUBJECT_SLOTS) { |
366 | /* This is an NDEBUG build apparently. Kill the process rather than |
367 | * corrupting heap. */ |
368 | fprintf(stderr, "Bad log subject slot index 0x%016x\n" , slot_index); |
369 | AWS_FATAL_ASSERT(false); |
370 | } |
371 | |
372 | s_log_subject_slots[slot_index] = NULL; |
373 | } |
374 | |
375 | /* |
376 | * no alloc implementation |
377 | */ |
378 | struct aws_logger_noalloc { |
379 | enum aws_log_level level; |
380 | FILE *file; |
381 | bool should_close; |
382 | struct aws_mutex lock; |
383 | }; |
384 | |
385 | static enum aws_log_level s_noalloc_stderr_logger_get_log_level(struct aws_logger *logger, aws_log_subject_t subject) { |
386 | (void)subject; |
387 | |
388 | struct aws_logger_noalloc *impl = logger->p_impl; |
389 | return impl->level; |
390 | } |
391 | |
392 | #define MAXIMUM_NO_ALLOC_LOG_LINE_SIZE 8192 |
393 | |
394 | static int s_noalloc_stderr_logger_log( |
395 | struct aws_logger *logger, |
396 | enum aws_log_level log_level, |
397 | aws_log_subject_t subject, |
398 | const char *format, |
399 | ...) { |
400 | |
401 | char format_buffer[MAXIMUM_NO_ALLOC_LOG_LINE_SIZE]; |
402 | |
403 | va_list format_args; |
404 | va_start(format_args, format); |
405 | |
406 | #if _MSC_VER |
407 | # pragma warning(push) |
408 | # pragma warning(disable : 4221) /* allow struct member to reference format_buffer */ |
409 | #endif |
410 | |
411 | struct aws_logging_standard_formatting_data format_data = { |
412 | .log_line_buffer = format_buffer, |
413 | .total_length = MAXIMUM_NO_ALLOC_LOG_LINE_SIZE, |
414 | .level = log_level, |
415 | .subject_name = aws_log_subject_name(subject), |
416 | .format = format, |
417 | .date_format = AWS_DATE_FORMAT_ISO_8601, |
418 | .allocator = logger->allocator, |
419 | .amount_written = 0, |
420 | }; |
421 | |
422 | #if _MSC_VER |
423 | # pragma warning(pop) /* disallow struct member to reference local value */ |
424 | #endif |
425 | |
426 | int result = aws_format_standard_log_line(&format_data, format_args); |
427 | |
428 | va_end(format_args); |
429 | |
430 | if (result == AWS_OP_ERR) { |
431 | return AWS_OP_ERR; |
432 | } |
433 | |
434 | struct aws_logger_noalloc *impl = logger->p_impl; |
435 | |
436 | aws_mutex_lock(&impl->lock); |
437 | |
438 | if (fwrite(format_buffer, 1, format_data.amount_written, impl->file) < format_data.amount_written) { |
439 | return aws_translate_and_raise_io_error(errno); |
440 | } |
441 | |
442 | aws_mutex_unlock(&impl->lock); |
443 | |
444 | return AWS_OP_SUCCESS; |
445 | } |
446 | |
447 | static void s_noalloc_stderr_logger_clean_up(struct aws_logger *logger) { |
448 | if (logger == NULL) { |
449 | return; |
450 | } |
451 | |
452 | struct aws_logger_noalloc *impl = logger->p_impl; |
453 | if (impl->should_close) { |
454 | fclose(impl->file); |
455 | } |
456 | |
457 | aws_mutex_clean_up(&impl->lock); |
458 | |
459 | aws_mem_release(logger->allocator, impl); |
460 | AWS_ZERO_STRUCT(*logger); |
461 | } |
462 | |
463 | static struct aws_logger_vtable s_noalloc_stderr_vtable = { |
464 | .get_log_level = s_noalloc_stderr_logger_get_log_level, |
465 | .log = s_noalloc_stderr_logger_log, |
466 | .clean_up = s_noalloc_stderr_logger_clean_up, |
467 | }; |
468 | |
469 | int aws_logger_init_noalloc( |
470 | struct aws_logger *logger, |
471 | struct aws_allocator *allocator, |
472 | struct aws_logger_standard_options *options) { |
473 | |
474 | struct aws_logger_noalloc *impl = aws_mem_calloc(allocator, 1, sizeof(struct aws_logger_noalloc)); |
475 | |
476 | if (impl == NULL) { |
477 | return AWS_OP_ERR; |
478 | } |
479 | |
480 | impl->level = options->level; |
481 | if (options->file != NULL) { |
482 | impl->file = options->file; |
483 | impl->should_close = false; |
484 | } else { /* _MSC_VER */ |
485 | impl->file = fopen(options->filename, "w" ); |
486 | impl->should_close = true; |
487 | } |
488 | |
489 | aws_mutex_init(&impl->lock); |
490 | |
491 | logger->vtable = &s_noalloc_stderr_vtable; |
492 | logger->allocator = allocator; |
493 | logger->p_impl = impl; |
494 | |
495 | return AWS_OP_SUCCESS; |
496 | } |
497 | |