log_formatter.c 9.5 KB

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