3 * Copyright (c) 2012-2020 Samsung Electronics Co., Ltd.
5 * Licensed under the Apache License, Version 2.0 (the License);
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
26 #include <logcommon.h>
29 * @addtogroup SHARED_FUNCTIONS
33 #define FILTERINFO_PID_NONE -1
34 #define FILTERINFO_TID_NONE -1
36 struct dlogutil_filter_options {
38 log_priority global_pri;
39 bool exact_global_pri;
40 bool need_apply_default;
44 * @brief Allocate filter info
45 * @details Allocates filter info with given parameters
46 * @param[in] tag The filtering tag
47 * @param[in] pri The filtering priority
48 * @param[in] exactPri The filtering priority is exact
49 * @param[in] prefix The filtering tag is a prefix
50 * @return The new structure (or NULL if allocation failed).
51 * @see filterinfo_free
53 static FilterInfo *filterinfo_new(const char *tag, log_priority pri, bool prefix, bool exactPri, pid_t pid, pthread_t tid)
55 FilterInfo *p_ret = (FilterInfo *)calloc(1, sizeof(FilterInfo));
60 p_ret->type = FILTER_TAG_AND_PRIO;
61 p_ret->tnp.tag = strdup(tag);
62 if (!p_ret->tnp.tag) {
66 p_ret->tnp.tagLength = strlen(p_ret->tnp.tag);
68 p_ret->tnp.exactPri = exactPri;
69 p_ret->tnp.prefix = prefix;
70 } else if (pid != FILTERINFO_PID_NONE) {
71 p_ret->type = FILTER_PID;
73 } else if (tid != FILTERINFO_TID_NONE) {
74 p_ret->type = FILTER_TID;
77 assert(false); // LCOV_EXCL_LINE
83 static FilterInfo *filterinfo_clone(FilterInfo *p_info)
87 switch (p_info->type) {
88 case FILTER_TAG_AND_PRIO:
89 return filterinfo_new(p_info->tnp.tag, p_info->tnp.pri, p_info->tnp.prefix, p_info->tnp.exactPri, FILTERINFO_PID_NONE, FILTERINFO_TID_NONE);
91 return filterinfo_new(NULL, 0, false, false, p_info->pid, FILTERINFO_TID_NONE);
93 return filterinfo_new(NULL, 0, false, false, FILTERINFO_PID_NONE, p_info->tid);
95 default: assert(false); // LCOV_EXCL_LINE
100 * @brief Deallocate filter info
101 * @details Deallocates the entire filter info structure
102 * @param[in] p_info The structure to deallocate
103 * @see filterinfo_new
105 static void filterinfo_free(FilterInfo *p_info)
109 if (p_info->type == FILTER_TAG_AND_PRIO) {
110 free(p_info->tnp.tag);
111 p_info->tnp.tag = NULL;
117 // apply_cb versions of two previous functions
118 elem_value filterinfo_clone_cb(elem_value p_info, void *_)
120 return filterinfo_clone(p_info);
122 static void filterinfo_free_cb(elem_value p_info, void *_)
124 filterinfo_free(p_info);
127 #define DLOG_ERROR_NOTAG "DLOG_ERROR_NOTAG"
130 * @brief Filter a line
131 * @details Passes a line through filters to discover whether it should be logged or not
132 * @param[in] p_format The format to work with
133 * @param[in] entry The log entry to filter
134 * @return True if the line should be printed, else false
136 bool log_should_print_line(dlogutil_filter_options_s *p_filter, const dlogutil_entry_s *entry)
142 bool matched = false;
146 /* mark empty-tagged messages and make it easy to catch an application that does that */
147 if (dlogutil_entry_get_tag(entry, &tag) == TIZEN_ERROR_NO_DATA || !tag || !strlen(tag))
148 tag = DLOG_ERROR_NOTAG;
150 prio = (log_priority)entry->priority;
152 // Sadly, we can't use list_foreach, because it doesn't let us exit early
153 for (list_head p_curFilter_iter = p_filter->filters; p_curFilter_iter != NULL; list_next(&p_curFilter_iter)) {
154 FilterInfo *p_curFilter = list_at(p_curFilter_iter);
156 switch (p_curFilter->type) {
157 case FILTER_TAG_AND_PRIO:
158 prefix = p_curFilter->tnp.prefix;
159 if ((prefix && (0 == strncmp(tag, p_curFilter->tnp.tag, p_curFilter->tnp.tagLength))) ||
160 (!prefix && (0 == strcmp(tag, p_curFilter->tnp.tag)))) {
162 if (p_curFilter->tnp.exactPri) {
163 if (prio == p_curFilter->tnp.pri)
166 if (prio >= p_curFilter->tnp.pri)
173 if (entry->pid == p_curFilter->pid)
178 if (entry->tid == p_curFilter->tid)
182 default: assert(false); // LCOV_EXCL_LINE
189 if (p_filter->exact_global_pri)
190 return (prio == p_filter->global_pri);
192 return (prio >= p_filter->global_pri);
195 bool log_filter_need_apply_default(dlogutil_filter_options_s *p_filter)
198 return p_filter->need_apply_default;
202 * @brief Allocate log format
203 * @details Allocates a log format structure
204 * @return The new structure (or NULL if allocation failed).
205 * @see log_filter_free
207 dlogutil_filter_options_s *log_filter_new(void)
209 dlogutil_filter_options_s *p_ret;
211 p_ret = calloc(1, sizeof(dlogutil_filter_options_s));
215 p_ret->global_pri = DLOG_SILENT;
216 p_ret->exact_global_pri = false;
217 p_ret->need_apply_default = true;
223 * @brief Copy a log format
224 * @details Allocates a copy of the log format
225 * @param[in] p_format The log format to copy
226 * @return The new structure (or NULL if allocation failed)
227 * @see log_filter_free
229 dlogutil_filter_options_s *log_filter_from_filter(const dlogutil_filter_options_s *p_filter)
231 dlogutil_filter_options_s *p_ret;
233 if (!(p_ret = log_filter_new()))
236 p_ret->global_pri = p_filter->global_pri;
237 p_ret->exact_global_pri = p_filter->exact_global_pri;
238 p_ret->need_apply_default = p_filter->need_apply_default;
240 if (p_filter->filters) {
241 p_ret->filters = list_map(p_filter->filters, NULL, filterinfo_clone_cb, filterinfo_free_cb);
245 log_filter_free(p_ret);
254 * @brief Move a log format
255 * @details Moves a log format (like C++ std::move)
256 * @param[in] p_format The log format to move
257 * @return The new structure (or NULL if allocation failed)
258 * @see log_filter_free
260 dlogutil_filter_options_s *log_filter_move(dlogutil_filter_options_s *p_filter)
262 dlogutil_filter_options_s *const ret = malloc(sizeof *ret);
267 p_filter->filters = NULL;
273 * @brief Deallocate log format
274 * @details Deallocates the entire log format structure
275 * @param[in] p_format The structure to deallocate
277 void log_filter_free(dlogutil_filter_options_s *p_filter)
280 log_filter_clear(p_filter);
286 void log_filter_clear(dlogutil_filter_options_s *p_filter)
290 list_clear_custom(&p_filter->filters, NULL, filterinfo_free_cb);
291 p_filter->global_pri = DLOG_SILENT;
292 p_filter->exact_global_pri = false;
295 list_head log_filter_get_list(dlogutil_filter_options_s *p_filter)
299 return p_filter->filters;
302 log_priority log_filter_get_global_priority(dlogutil_filter_options_s *p_filter, bool *is_exact)
307 *is_exact = p_filter->exact_global_pri;
308 return p_filter->global_pri;
312 * @brief Print format from string
313 * @details Converts a string to print format
314 * @param[in] formatString The string representation to convert
315 * @return The integer print format (on invalid string: FORMAT_OFF)
317 log_print_format log_format_from_string(const char *formatString)
319 static const struct {
321 log_print_format format;
323 {"brief" , FORMAT_BRIEF },
324 {"process" , FORMAT_PROCESS },
325 {"tag" , FORMAT_TAG },
326 {"thread" , FORMAT_THREAD },
327 {"raw" , FORMAT_RAW },
328 {"time" , FORMAT_TIME },
329 {"threadtime" , FORMAT_THREADTIME },
330 {"kerneltime" , FORMAT_KERNELTIME },
331 {"recv_realtime", FORMAT_RECV_REALTIME},
332 {"rwtime" , FORMAT_RWTIME },
333 {"long" , FORMAT_LONG },
334 {"json" , FORMAT_JSON },
337 for (size_t i = 0; i < NELEMS(formats); ++i)
338 if (!strcmp(formatString, formats[i].string))
339 return formats[i].format;
345 * @brief Add a filter rule
346 * @details Adds a tag/priority filtering rule
347 * @param[in] p_format The log format to add the filter rule to
348 * @param[in] filterExpression A filter expression ("tag:prio")
349 * @return An error code
350 * @retval TIZEN_ERROR_NONE Success
351 * @retval TIZEN_ERROR_INVALID_PARAMETER Invalid syntax of the filterspec
352 * @retval TIZEN_ERROR_OUT_OF_MEMORY Couldn't allocate enough memory
353 * @remarks Assumes single threaded execution
355 int log_add_filter_rule(dlogutil_filter_options_s *p_filter,
356 const char *filterExpression)
358 size_t tagNameLength, priPosition;
360 bool pattern = false;
361 log_priority pri = DLOG_DEFAULT;
363 tagNameLength = strcspn(filterExpression, ":");
365 if (tagNameLength == 0)
366 return TIZEN_ERROR_INVALID_PARAMETER;
368 if (filterExpression[tagNameLength] == ':') {
369 if ((strlen(filterExpression + (tagNameLength + 1)) > 1)
370 && (filterExpression[tagNameLength + 1] == '=')) {
371 priPosition = tagNameLength + 2;
374 priPosition = tagNameLength + 1;
377 pri = filter_char_to_pri(filterExpression[priPosition]);
379 if (pri == DLOG_UNKNOWN)
380 return TIZEN_ERROR_INVALID_PARAMETER;
383 if (0 == strncmp("*", filterExpression, tagNameLength)) {
384 /* This filter expression refers to the global filter
385 * The default level for this is DEBUG if the priority
388 if (pri == DLOG_DEFAULT)
391 p_filter->global_pri = pri;
392 p_filter->exact_global_pri = exact;
394 /* for filter expressions that don't refer to the global
395 * filter, the default is verbose if the priority is unspecified
397 if (pri == DLOG_DEFAULT)
400 if (filterExpression[tagNameLength - 1] == '*') {
402 tagNameLength = tagNameLength - 1;
406 char *tagName = strndup(filterExpression, tagNameLength);
408 return TIZEN_ERROR_OUT_OF_MEMORY;
410 FilterInfo *p_fi = filterinfo_new(tagName, pri, pattern, exact, FILTERINFO_PID_NONE, FILTERINFO_TID_NONE);
413 return TIZEN_ERROR_OUT_OF_MEMORY;
415 if (!list_add(&p_filter->filters, p_fi))
416 return TIZEN_ERROR_OUT_OF_MEMORY;
419 return TIZEN_ERROR_NONE;
423 * @brief Add filter string
424 * @details Adds multiple tag/priority filtering rule from a string
425 * @param[in] p_format The log format to add the filter rules to
426 * @param[in] filterString A filter string containing multiple filter rules delimited by spaces, tabs or commas
427 * @return An error code
428 * @retval TIZEN_ERROR_NONE Success
429 * @retval TIZEN_ERROR_INVALID_PARAMETER Invalid syntax of the filterspec
430 * @retval TIZEN_ERROR_INVALID_PARAMETER One of the pointers was NULL
431 * @retval TIZEN_ERROR_OUT_OF_MEMORY Couldn't allocate enough memory
432 * @remarks Assumes single threaded execution
434 LIBDLOGUTIL_EXPORT_API int dlogutil_filter_options_set_filterspec(dlogutil_filter_options_s *p_filter,
435 const char *filterString)
437 CHECK_PARAM(p_filter);
438 CHECK_PARAM(filterString);
440 char *filterStringCopy = strdup(filterString);
441 if (!filterStringCopy)
442 return TIZEN_ERROR_OUT_OF_MEMORY;
444 char *p_cur = filterStringCopy;
448 while (NULL != (p_ret = strsep(&p_cur, " \t,"))) {
449 /* ignore whitespace-only entries */
450 if (p_ret[0] != '\0') {
451 err = log_add_filter_rule(p_filter, p_ret);
454 free(filterStringCopy);
460 p_filter->need_apply_default = false;
462 free(filterStringCopy);
463 return TIZEN_ERROR_NONE;
467 * @brief Add pid to filter by
468 * @details Adds a single pid filtering rule
469 * @param[in] p_format The log format to add the filter rules to
470 * @param[in] pid A pid to filter by
471 * @return An error code
472 * @retval TIZEN_ERROR_NONE Success
473 * @retval TIZEN_ERROR_INVALID_PARAMETER One of the pointers was NULL
474 * @retval TIZEN_ERROR_OUT_OF_MEMORY Couldn't allocate enough memory
475 * @remarks Assumes single threaded execution
477 LIBDLOGUTIL_EXPORT_API int dlogutil_filter_options_set_pid(dlogutil_filter_options_s *p_filter, pid_t pid)
479 CHECK_PARAM(p_filter);
481 FilterInfo *p_fi = filterinfo_new(NULL, 0, false, false, pid, FILTERINFO_TID_NONE);
483 return TIZEN_ERROR_OUT_OF_MEMORY;
485 return list_add(&p_filter->filters, p_fi) ? TIZEN_ERROR_NONE : TIZEN_ERROR_OUT_OF_MEMORY;
489 * @brief Add tid to filter by
490 * @details Adds a single tid filtering rule
491 * @param[in] p_format The log format to add the filter rules to
492 * @param[in] tid A tid to filter by
493 * @return An error code
494 * @retval TIZEN_ERROR_NONE Success
495 * @retval TIZEN_ERROR_INVALID_PARAMETER One of the pointers was NULL
496 * @retval TIZEN_ERROR_OUT_OF_MEMORY Couldn't allocate enough memory
497 * @remarks Assumes single threaded execution
499 LIBDLOGUTIL_EXPORT_API int dlogutil_filter_options_set_tid(dlogutil_filter_options_s *p_filter, pid_t tid)
501 CHECK_PARAM(p_filter);
503 FilterInfo *p_fi = filterinfo_new(NULL, 0, false, false, FILTERINFO_PID_NONE, tid);
505 return TIZEN_ERROR_OUT_OF_MEMORY;
507 return list_add(&p_filter->filters, p_fi) ? TIZEN_ERROR_NONE : TIZEN_ERROR_OUT_OF_MEMORY;
510 dlogutil_sorting_order_e get_format_sorting(log_print_format format)
519 return DLOGUTIL_SORT_DEFAULT;
521 case FORMAT_KERNELTIME:
522 return DLOGUTIL_SORT_SENT_MONO;
525 case FORMAT_THREADTIME:
527 return DLOGUTIL_SORT_SENT_REAL;
530 case FORMAT_RECV_REALTIME:
531 return DLOGUTIL_SORT_RECV_REAL;
533 default: assert(false); // LCOV_EXCL_LINE
537 #define COLOR_PURPLE "\033[35;1m"
538 #define COLOR_RED "\033[31;1m"
539 #define COLOR_YELLOW "\033[33;1m"
540 #define COLOR_RESET "\033[0m"
542 static const char *get_pre_color(log_priority pri)
556 static const char *get_post_color(log_priority pri)
568 static char to_hex_digit(int digit)
575 return digit + 'A' - 10;
578 char *json_escape_string(const char *in)
581 size_t len = strlen(in);
582 /* One input string character will be encoded by max 6 characters in the output string.
583 * We use unsigned chars for easy comparisions. */
584 unsigned char *buf = malloc(6 * len + 1);
588 /* So, RFC 7159 says that JSON output can be in one of three encodings: UTF-{8,16,32}.
589 * In particular, in UTF-8, the multibyte characters are allowed. This means that, assuming
590 * that our input is valid UTF-8, we can just write them directly, simplifying the implementation.
591 * Correctly encoding arbitrary binary data would require something like base64, this, however,
592 * would make it very difficult to read by humans. Almost all logs are UTF-8 anyway. */
594 for (size_t i = 0; i < len; ++i) {
595 unsigned char c = in[i];
597 /* These cases can get nicer encodings than the usual "\uxxxx", even though we technically
598 * dont have to do so. Also, we don't encode '/', because there is no reason to. */
628 /* We don't use a range case, because we would have to remove the already done characters from it.
629 * We would end up with three distinct cases for the {0, ..., 31} interval. */
635 buf[targ++] = to_hex_digit(c / 16);
636 buf[targ++] = to_hex_digit(c % 16);
645 char *ret = realloc(buf, targ);
651 const char *json_priority_name(log_priority prio)
653 static const char *const json_priorities_map[] = {
654 [DLOG_VERBOSE] = "verbose",
655 [DLOG_DEBUG] = "debug",
656 [DLOG_INFO] = "info",
657 [DLOG_WARN] = "warning",
658 [DLOG_ERROR] = "error",
659 [DLOG_FATAL] = "fatal",
660 [DLOG_SILENT] = "silent",
663 const char *ret = NULL;
664 if (prio < NELEMS(json_priorities_map))
665 ret = json_priorities_map[prio];
666 if (!ret) // Either out of the map, or in between its elements
671 char *log_format_json(
672 char *default_buffer,
673 size_t default_buffer_size,
674 const dlogutil_entry_s *entry,
679 const char *prio_name = json_priority_name(entry->priority);
681 __attribute__((cleanup(free_ptr))) char *tag_esc = json_escape_string(tag);
685 __attribute__((cleanup(free_ptr))) char *msg_esc = json_escape_string(msg);
689 char recv_real[64], recv_mono[64], sent_real[64], sent_mono[64];
694 if (entry->nsec_recv_real == INVALID_NSEC)
697 time_t_temp = entry->sec_recv_real;
698 ptm = localtime_r(&time_t_temp, &tmBuf);
701 strftime(recv_real, sizeof recv_real, "\"recv_real\":\"%FT%T%z\",", ptm);
704 if (entry->nsec_sent_real == INVALID_NSEC)
707 time_t_temp = entry->sec_sent_real;
708 ptm = localtime_r(&time_t_temp, &tmBuf);
711 strftime(sent_real, sizeof sent_real, "\"sent_real\":\"%FT%T%z\",", ptm);
714 if (entry->nsec_recv_mono == INVALID_NSEC)
717 snprintf(recv_mono, sizeof recv_mono, "\"recv_mono\":%d.%.9d,", entry->sec_recv_mono, entry->nsec_recv_mono);
719 if (entry->nsec_sent_mono == INVALID_NSEC)
722 snprintf(sent_mono, sizeof sent_mono, "\"sent_mono\":%d.%.9d,", entry->sec_sent_mono, entry->nsec_sent_mono);
725 "\"priority\":\"%s\"," \
731 "}\n", prio_name, entry->pid, entry->tid, \
732 recv_real, recv_mono, sent_real, sent_mono, \
735 int len = snprintf(NULL, 0, ARGS);
740 else if (default_buffer_size >= len + 1) {
741 ret = default_buffer;
743 ret = (char *)malloc(len + 1);
749 int res = snprintf(ret, len + 1, ARGS);
754 if (ret != default_buffer)
763 static bool entry_get_ts_inner(const dlogutil_entry_s *entry, bool sent, bool mono, struct timespec *target)
766 ? (mono ? entry->sec_sent_mono : entry->sec_sent_real)
767 : (mono ? entry->sec_recv_mono : entry->sec_recv_real);
769 ? (mono ? entry->nsec_sent_mono : entry->nsec_sent_real)
770 : (mono ? entry->nsec_recv_mono : entry->nsec_recv_real);
772 if (nsec == INVALID_NSEC)
775 target->tv_sec = sec;
776 target->tv_nsec = nsec;
780 struct timespec entry_get_ts(const dlogutil_entry_s *entry, bool sent, bool mono)
782 /* The returned timestamp should match the format (as in, the printf specifier) used below.
783 * In particular, monotonic timestamps should be the ones represented by the 'raw' (%u)
784 * format (currently used solely by the KERNELTIME format) while realtime timestamps should
785 * be represented by strftime date-formatted strings.
786 * However, we decided that some timing data is better than none. Therefore, if the needed timestamp
787 * is missing, we use one of the others, even though it probably is at least slightly wrong.
788 * A better solution will be possible after the planned refactor of the format system.
789 * In particular, we allow using the realtime stamp in the monotonic field, as well as using sending
790 * and receiving timestamps interchangeably. */
792 struct timespec ret = {0};
794 #define CHECK_AND_CHANGE_SENT \
795 if (entry_get_ts_inner(entry, sent, mono, &ret)) \
799 CHECK_AND_CHANGE_SENT
800 CHECK_AND_CHANGE_SENT
804 CHECK_AND_CHANGE_SENT
805 CHECK_AND_CHANGE_SENT
808 #undef CHECK_AND_CHANGE_SENT
813 static int resolve_write(int fd, struct log_write_buffer *wrbuf, const char *realbuf, size_t realsize)
815 if (realbuf != wrbuf->data + wrbuf->position) {
816 if (wrbuf->position > 0) {
817 int res = write(fd, wrbuf->data, wrbuf->position);
823 if (realsize < wrbuf->size) {
824 memcpy(wrbuf->data, realbuf, realsize);
825 clock_gettime(CLOCK_MONOTONIC, &wrbuf->oldest_log);
826 res = wrbuf->position = realsize;
828 res = write(fd, realbuf, realsize);
831 return res > 0 ? res : 0;
833 if (wrbuf->position == 0)
834 clock_gettime(CLOCK_MONOTONIC, &wrbuf->oldest_log);
835 wrbuf->position += realsize;
840 static int log_print_line_format_json(int fd, const dlogutil_entry_s *entry, struct log_write_buffer *wrbuf)
843 // TODO: This could really use an improvement
844 char *const buf = log_format_json(wrbuf->data + wrbuf->position, wrbuf->size - wrbuf->position, entry, &buflen, entry->msg, entry->msg + entry->tag_len + 1);
847 bool to_free = buf != (wrbuf->data + wrbuf->position);
849 int res = resolve_write(fd, wrbuf, buf, buflen);
855 #define METADATA_MAX_LEN 128
856 #define COLORED_METADATA_MAX_LEN (METADATA_MAX_LEN + sizeof COLOR_PURPLE + sizeof COLOR_RESET)
858 #define FORMAT_STR_PRIORITY "%c"
859 #define FORMAT_STR_TAG "%s"
860 #define FORMAT_STR_PID "%5u"
861 #define FORMAT_STR_TID "%5u"
862 #define FORMAT_STR_KERNELTIME "%05ld.%03ld"
864 #define MILISEC_SIZE (sizeof(".012")-1)
865 #define NSEC_TO_MSEC(t) ((t) / 1000000)
867 static void buf_add_milisec(char *buf, int32_t nsec)
870 snprintf(buf, MILISEC_SIZE, "%03d", NSEC_TO_MSEC(nsec));
873 static char *buf_add_time(char *buf, size_t max_len, const dlogutil_entry_s *entry,
874 bool add_ms, bool add_tz, bool sent_time)
877 struct timespec t = entry_get_ts(entry, sent_time, false);
878 struct tm *const ptm = localtime_r(&t.tv_sec, &tmBuf);
882 size_t written = strftime(buf, max_len, "%m-%d %H:%M:%S", ptm);
884 if (add_ms && max_len - written > MILISEC_SIZE) {
885 buf_add_milisec(buf + written, t.tv_nsec);
886 written += MILISEC_SIZE; // not counting terminating null byte
889 if (add_tz && sent_time)
890 written += strftime(buf + written, max_len - written, "%z", ptm);
892 return buf + written;
895 static char entry_get_priority_string(const dlogutil_entry_s *entry)
897 return filter_pri_to_char(entry->priority);
900 static const char *entry_get_tag_string(const dlogutil_entry_s *entry)
903 int r = dlogutil_entry_get_tag(entry, &ret);
904 if (r == TIZEN_ERROR_NO_DATA || strlen(ret) == 0)
905 return DLOG_ERROR_NOTAG;
910 static const char *entry_get_message_string(const dlogutil_entry_s *entry)
913 int r = dlogutil_entry_get_message(entry, &ret);
914 if (r == TIZEN_ERROR_NO_DATA || strlen(ret) == 0)
915 return "DLOG_ERROR_NOMSG";
920 typedef int (*format_function)(const dlogutil_entry_s *, char *, char *);
922 static int format_metadata_brief(const dlogutil_entry_s *entry, char *prefix, char *suffix)
924 snprintf(prefix, METADATA_MAX_LEN,
925 FORMAT_STR_PRIORITY "/" FORMAT_STR_TAG "(" FORMAT_STR_PID "): ",
926 entry_get_priority_string(entry),
927 entry_get_tag_string(entry),
932 static int format_metadata_process(const dlogutil_entry_s *entry, char *prefix, char *suffix)
934 snprintf(prefix, METADATA_MAX_LEN,
935 FORMAT_STR_PRIORITY "(" FORMAT_STR_PID ") ",
936 entry_get_priority_string(entry),
939 snprintf(suffix, METADATA_MAX_LEN,
940 " (" FORMAT_STR_TAG ")\n",
941 entry_get_tag_string(entry));
945 static int format_metadata_tag(const dlogutil_entry_s *entry, char *prefix, char *suffix)
947 snprintf(prefix, METADATA_MAX_LEN,
948 FORMAT_STR_PRIORITY "/" FORMAT_STR_TAG ": ",
949 entry_get_priority_string(entry),
950 entry_get_tag_string(entry));
954 static int format_metadata_thread(const dlogutil_entry_s *entry, char *prefix, char *suffix)
956 snprintf(prefix, METADATA_MAX_LEN,
957 FORMAT_STR_PRIORITY "(P" FORMAT_STR_PID ", T" FORMAT_STR_TID ") ",
958 entry_get_priority_string(entry),
964 static int format_metadata_time(const dlogutil_entry_s *entry, char *prefix, char *suffix)
966 char *const buf_start = prefix;
967 prefix = buf_add_time(prefix, METADATA_MAX_LEN, entry, true, true, true);
971 snprintf(prefix, METADATA_MAX_LEN - (prefix - buf_start),
972 " " FORMAT_STR_PRIORITY "/" FORMAT_STR_TAG "(" FORMAT_STR_PID "): ",
973 entry_get_priority_string(entry),
974 entry_get_tag_string(entry),
979 static int format_metadata_threadtime(const dlogutil_entry_s *entry, char *prefix, char *suffix)
981 char *const buf_start = prefix;
982 prefix = buf_add_time(prefix, METADATA_MAX_LEN, entry, true, true, true);
986 snprintf(prefix, METADATA_MAX_LEN - (prefix - buf_start),
987 " " FORMAT_STR_PRIORITY "/" FORMAT_STR_TAG "(P" FORMAT_STR_PID ", T" FORMAT_STR_TID "): ",
988 entry_get_priority_string(entry),
989 entry_get_tag_string(entry),
995 static int format_metadata_kerneltime(const dlogutil_entry_s *entry, char *prefix, char *suffix)
997 struct timespec t = entry_get_ts(entry, true, true);
998 snprintf(prefix, METADATA_MAX_LEN,
999 FORMAT_STR_KERNELTIME " " FORMAT_STR_PRIORITY "/" FORMAT_STR_TAG "(P" FORMAT_STR_PID ", T" FORMAT_STR_TID "): ",
1001 NSEC_TO_MSEC(t.tv_nsec),
1002 entry_get_priority_string(entry),
1003 entry_get_tag_string(entry),
1009 static int format_metadata_recv_realtime(const dlogutil_entry_s *entry, char *prefix, char *suffix)
1011 char *buf_start = prefix;
1012 prefix = buf_add_time(prefix, METADATA_MAX_LEN, entry, true, false, false);
1016 snprintf(prefix, METADATA_MAX_LEN - (prefix - buf_start),
1017 " " FORMAT_STR_PRIORITY "/" FORMAT_STR_TAG "(P" FORMAT_STR_PID ", T" FORMAT_STR_TID "): ",
1018 entry_get_priority_string(entry),
1019 entry_get_tag_string(entry),
1025 static int format_metadata_rwtime(const dlogutil_entry_s *entry, char *prefix, char *suffix)
1027 struct timespec t = entry_get_ts(entry, true, true);
1028 char *const buf_start = prefix;
1029 prefix = buf_add_time(prefix, METADATA_MAX_LEN, entry, false, false, false);
1033 snprintf(prefix, METADATA_MAX_LEN - (prefix - buf_start),
1034 " [" FORMAT_STR_KERNELTIME "] " FORMAT_STR_PRIORITY "/" FORMAT_STR_TAG "(P" FORMAT_STR_PID ", T" FORMAT_STR_TID "): ",
1036 NSEC_TO_MSEC(t.tv_nsec),
1037 entry_get_priority_string(entry),
1038 entry_get_tag_string(entry),
1044 static int format_metadata_long(const dlogutil_entry_s *entry, char *prefix, char *suffix)
1046 char time_str[sizeof "03-14 23:22:51.451"];
1047 buf_add_time(time_str, sizeof time_str, entry, true, false, true);
1051 snprintf(prefix, METADATA_MAX_LEN,
1052 "[ %s " FORMAT_STR_PRIORITY "/" FORMAT_STR_TAG " P" FORMAT_STR_PID ", T" FORMAT_STR_TID "]\n",
1054 entry_get_priority_string(entry),
1055 entry_get_tag_string(entry),
1061 #define STATIC_BUFFER_SIZE 512
1067 const char *no_color_suffix;
1068 size_t allocated_space;
1071 size_t no_color_suffix_len;
1072 char prefix[COLORED_METADATA_MAX_LEN];
1073 char suffix[COLORED_METADATA_MAX_LEN];
1076 static int print_buffer_init(print_buffer *buffer, size_t message_len, size_t number_of_lines, struct log_write_buffer *wrbuf)
1080 size_t needed_space = message_len + number_of_lines * (strlen(buffer->prefix) + strlen(buffer->suffix) + strlen(buffer->no_color_suffix)) + 1;
1082 if (needed_space <= wrbuf->size - wrbuf->position) {
1083 buffer->buf_start = wrbuf->data + wrbuf->position;
1084 buffer->buf_created = false;
1086 buffer->buf_start = malloc(needed_space);
1087 if (!buffer->buf_start)
1089 buffer->buf_created = true;
1092 buffer->allocated_space = needed_space;
1093 buffer->buf_position = buffer->buf_start;
1094 buffer->prefix_len = strlen(buffer->prefix);
1095 buffer->suffix_len = strlen(buffer->suffix);
1096 buffer->no_color_suffix_len = strlen(buffer->no_color_suffix);
1100 static void print_buffer_destroy(print_buffer *buffer)
1102 if (buffer->buf_created)
1103 free(buffer->buf_start);
1104 buffer->buf_created = false;
1107 static size_t print_buffer_remaining_space(print_buffer *buffer)
1109 return buffer->allocated_space - (buffer->buf_position - buffer->buf_start);
1112 // string_limit has to be less or equal than the actual length of the string
1113 static int print_buffer_add_string(print_buffer *buffer, const char *string, size_t string_limit)
1115 size_t len = print_buffer_remaining_space(buffer);
1116 if (string_limit < len)
1119 memcpy(buffer->buf_position, string, len);
1120 buffer->buf_position += len;
1124 static int print_buffer_add_string_line(print_buffer *buffer, const char *string, size_t string_limit, bool stop_on_eol)
1127 return print_buffer_add_string(buffer, string, string_limit);
1129 char *const buffer_end = buffer->buf_start + buffer->allocated_space;
1131 while (buffer->buf_position + i != buffer_end && i < string_limit && string[i] != 0 && string[i] != '\n') {
1132 buffer->buf_position[i] = string[i];
1135 buffer->buf_position += i;
1139 // returns how much of the line it used
1140 static size_t print_buffer_add_line(print_buffer *buffer, const char *line, size_t line_limit, bool stop_on_eol)
1142 print_buffer_add_string(buffer, buffer->prefix, buffer->prefix_len);
1144 size_t const offset = print_buffer_add_string_line(buffer, line, line_limit, stop_on_eol) + 1;
1146 print_buffer_add_string(buffer, buffer->suffix, buffer->suffix_len);
1147 print_buffer_add_string(buffer, buffer->no_color_suffix, buffer->no_color_suffix_len);
1152 static void add_colors(char *buf, uint8_t priority)
1154 char temporary_buf[METADATA_MAX_LEN];
1156 if (*buf == 0) // empty string has no colors
1159 strncpy(temporary_buf, buf, sizeof temporary_buf);
1161 snprintf(buf, COLORED_METADATA_MAX_LEN, "%s%s%s",
1162 get_pre_color(priority),
1164 get_post_color(priority));
1167 static void print_buffer_add_colors(print_buffer *buffer, uint8_t priority)
1169 add_colors(buffer->prefix, priority);
1170 add_colors(buffer->suffix, priority);
1173 static size_t scan_lines(const char *message, bool split_on_eol)
1182 while (*message != 0) {
1183 if (*message == '\n')
1193 * @brief Print log line
1194 * @details Prints the given log entry to the supplied file descriptor
1195 * @param[in] p_format The format to use
1196 * @param[in] fd The file descriptor to write to
1197 * @param[in] entry The entry to print
1198 * @param[in,out] buf The write buffer to use
1199 * @returns On success, the number of bytes written; else a specific value
1200 * @retval -1 Memory allocation failure
1201 * @retval 0 Write failed
1203 int log_print_log_line(
1204 struct log_format p_format,
1206 const dlogutil_entry_s *entry,
1207 struct log_write_buffer *wrbuf)
1212 /* TODO: Return values are super iffy now.
1213 * The issue is that we sometimes don't write the log, but we still might want to return
1214 * a positive value. Right now we do some weird voodoo with the return values,
1215 * which means that the positive random are kinda random. */
1217 if (wrbuf->data == NULL) {
1218 const size_t size = 512;
1219 wrbuf->data = alloca(size);
1220 wrbuf->position = 0;
1223 int r = log_print_log_line(p_format, fd, entry, wrbuf);
1227 if (r > 0 && wrbuf->position != 0) {
1228 r = write(fd, wrbuf->data, wrbuf->position);
1234 wrbuf->position = 0;
1239 if (p_format.format == FORMAT_JSON)
1240 return log_print_line_format_json(fd, entry, wrbuf);
1242 const struct format_info {
1243 format_function format_metadata;
1245 char *no_color_suffix;
1247 [FORMAT_OFF] = { NULL , false, "" },
1248 [FORMAT_BRIEF] = { format_metadata_brief , false, "\n" },
1249 [FORMAT_PROCESS] = { format_metadata_process , true , "" },
1250 [FORMAT_TAG] = { format_metadata_tag , false, "\n" },
1251 [FORMAT_THREAD] = { format_metadata_thread , false, "\n" },
1252 [FORMAT_RAW] = { NULL , false, "\n" },
1253 [FORMAT_TIME] = { format_metadata_time , false, "\n" },
1254 [FORMAT_THREADTIME] = { format_metadata_threadtime , false, "\n" },
1255 [FORMAT_KERNELTIME] = { format_metadata_kerneltime , true , "\n" },
1256 [FORMAT_RECV_REALTIME] = { format_metadata_recv_realtime, true , "\n" },
1257 [FORMAT_RWTIME] = { format_metadata_rwtime , false, "\n" },
1258 [FORMAT_LONG] = { format_metadata_long , false, "\n\n"},
1261 if (p_format.format < 0 || p_format.format >= NELEMS(format_info))
1264 __attribute__ ((cleanup(print_buffer_destroy))) print_buffer buffer = {0};
1266 const struct format_info *const format = &format_info[p_format.format];
1267 assert(format->no_color_suffix);
1269 if (format->format_metadata
1270 && format->format_metadata(entry, buffer.prefix, buffer.suffix) != 0)
1274 print_buffer_add_colors(&buffer, entry->priority);
1276 buffer.no_color_suffix = format->no_color_suffix;
1278 const char *message = entry_get_message_string(entry);
1279 size_t message_len = strlen(message);
1280 size_t number_of_lines = scan_lines(message, format->split_lines);
1282 if (message_len > 0 && message[message_len-1] == '\n')
1284 const char *message_end = message + message_len;
1286 if (print_buffer_init(&buffer, message_len, number_of_lines, wrbuf) != 0)
1289 while (message < message_end)
1290 message += print_buffer_add_line(&buffer, message, message_end - message, format->split_lines);
1292 return resolve_write(fd, wrbuf, buffer.buf_start, buffer.buf_position - buffer.buf_start);