#include #include #include #include #include #include #include #include #include #include #include #include #include /* A global buffer used to store all startup alerts/warnings. It will then be * retrieve on the CLI. */ static struct ring *startup_logs = NULL; /* A thread local buffer used to store all alerts/warnings. It can be used to * retrieve them for CLI commands after startup. */ #define USER_MESSAGES_BUFSIZE 1024 static THREAD_LOCAL struct buffer usermsgs_buf = BUF_NULL; /* A thread local context used for stderr output via ha_alert/warning/notice/diag. */ #define USERMSGS_CTX_BUFSIZE PATH_MAX static THREAD_LOCAL struct usermsgs_ctx usermsgs_ctx = { .str = BUF_NULL, }; /* Put msg in usermsgs_buf. * * The message should not be terminated by a newline because this function * manually insert it. * * If there is not enough room in the buffer, the message is silently discarded. * Do not forget to frequently clear the buffer. */ static void usermsgs_put(const struct ist *msg) { /* Allocate the buffer if not already done. */ if (unlikely(b_is_null(&usermsgs_buf))) { usermsgs_buf.area = malloc(USER_MESSAGES_BUFSIZE * sizeof(char)); usermsgs_buf.size = USER_MESSAGES_BUFSIZE; } if (likely(!b_is_null(&usermsgs_buf))) { if (b_room(&usermsgs_buf) >= msg->len + 2) { /* Insert the message + newline. */ b_putblk(&usermsgs_buf, msg->ptr, msg->len); b_putchr(&usermsgs_buf, '\n'); /* Insert NUL outside of the buffer. */ *b_tail(&usermsgs_buf) = '\0'; } } } /* Clear the user messages log buffer. * * will set the local-thread context appended to every output * following this call. It can be NULL if not necessary. */ void usermsgs_clr(const char *prefix) { if (likely(!b_is_null(&usermsgs_buf))) { b_reset(&usermsgs_buf); usermsgs_buf.area[0] = '\0'; } usermsgs_ctx.prefix = prefix; } /* Check if the user messages buffer is empty. */ int usermsgs_empty(void) { return !!(b_is_null(&usermsgs_buf) || !b_data(&usermsgs_buf)); } /* Return the messages log buffer content. */ const char *usermsgs_str(void) { if (unlikely(b_is_null(&usermsgs_buf))) return ""; return b_head(&usermsgs_buf); } /* Set thread-local context infos to prefix forthcoming stderr output during * configuration parsing. * * and specify the location of the parsed configuration. * * can be of various types. If not NULL, the string prefix generated will * depend on its type. */ void set_usermsgs_ctx(const char *file, int line, enum obj_type *obj) { usermsgs_ctx.file = file; usermsgs_ctx.line = line; usermsgs_ctx.obj = obj; } /* Set thread-local context infos to prefix forthcoming stderr output. It will * be set as a complement to possibly already defined file/line. * * can be of various types. If not NULL, the string prefix generated will * depend on its type. */ void register_parsing_obj(enum obj_type *obj) { usermsgs_ctx.obj = obj; } /* Reset thread-local context infos for stderr output. */ void reset_usermsgs_ctx(void) { usermsgs_ctx.file = NULL; usermsgs_ctx.line = 0; usermsgs_ctx.obj = NULL; } static void generate_usermsgs_ctx_str(void) { struct usermsgs_ctx *ctx = &usermsgs_ctx; void *area; int ret; if (unlikely(b_is_null(&ctx->str))) { area = calloc(USERMSGS_CTX_BUFSIZE, sizeof(*area)); if (area) ctx->str = b_make(area, USERMSGS_CTX_BUFSIZE, 0, 0); } if (likely(!b_is_null(&ctx->str))) { b_reset(&ctx->str); if (ctx->prefix) { ret = snprintf(b_tail(&ctx->str), b_room(&ctx->str), "%s : ", ctx->prefix); b_add(&ctx->str, MIN(ret, b_room(&ctx->str))); } if (ctx->file) { ret = snprintf(b_tail(&ctx->str), b_room(&ctx->str), "[%s:%d] : ", ctx->file, ctx->line); b_add(&ctx->str, MIN(ret, b_room(&ctx->str))); } switch (obj_type(ctx->obj)) { case OBJ_TYPE_SERVER: ret = snprintf(b_tail(&ctx->str), b_room(&ctx->str), "'server %s/%s' : ", __objt_server(ctx->obj)->proxy->id, __objt_server(ctx->obj)->id); b_add(&ctx->str, MIN(ret, b_room(&ctx->str))); break; case OBJ_TYPE_NONE: default: break; } if (!b_data(&ctx->str)) snprintf(b_tail(&ctx->str), b_room(&ctx->str), "%s", ""); } } /* Generic function to display messages prefixed by a label */ static void print_message(int use_usermsgs_ctx, const char *label, const char *fmt, va_list argp) { struct ist msg_ist = IST_NULL; char *head, *parsing_str, *msg; char prefix[11]; // '[' + 8 chars + ']' + 0. *prefix = '['; strncpy(prefix + 1, label, sizeof(prefix) - 2); msg = prefix + strlen(prefix); *msg++ = ']'; while (msg < prefix + sizeof(prefix) - 1) *msg++ = ' '; *msg = 0; head = parsing_str = msg = NULL; memprintf(&head, "%s (%u) : ", prefix, (uint)getpid()); memvprintf(&msg, fmt, argp); /* trim the trailing '\n' */ msg_ist = ist(msg); if (msg_ist.len > 0 && msg_ist.ptr[msg_ist.len - 1] == '\n') msg_ist.len--; if (use_usermsgs_ctx) { generate_usermsgs_ctx_str(); parsing_str = b_head(&usermsgs_ctx.str); reset_usermsgs_ctx(); } else { parsing_str = ""; } if (global.mode & MODE_STARTING) { if (unlikely(!startup_logs)) startup_logs = ring_new(STARTUP_LOG_SIZE); if (likely(startup_logs)) { struct ist m[3]; m[0] = ist(head); m[1] = ist(parsing_str); m[2] = msg_ist; ring_write(startup_logs, ~0, 0, 0, m, 3); } } else { usermsgs_put(&msg_ist); } fprintf(stderr, "%s%s%s", head, parsing_str, msg); fflush(stderr); free(head); free(msg); } static void print_message_args(int use_usermsgs_ctx, const char *label, const char *fmt, ...) { va_list argp; va_start(argp, fmt); print_message(use_usermsgs_ctx, label, fmt, argp); va_end(argp); } /* * Displays the message on stderr with the date and pid. Overrides the quiet * mode during startup. */ void ha_alert(const char *fmt, ...) { va_list argp; if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE) || !(global.mode & MODE_STARTING)) { if (!(warned & WARN_EXEC_PATH) && (global.mode & MODE_STARTING)) { const char *path = get_exec_path(); warned |= WARN_EXEC_PATH; print_message_args(0, "NOTICE", "haproxy version is %s\n", haproxy_version); if (path) print_message_args(0, "NOTICE", "path to executable is %s\n", path); } va_start(argp, fmt); print_message(1, "ALERT", fmt, argp); va_end(argp); } } /* * Displays the message on stderr with the date and pid. */ void ha_warning(const char *fmt, ...) { va_list argp; warned |= WARN_ANY; if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE) || !(global.mode & MODE_STARTING)) { if (!(warned & WARN_EXEC_PATH) && (global.mode & MODE_STARTING)) { const char *path = get_exec_path(); warned |= WARN_EXEC_PATH; print_message_args(0, "NOTICE", "haproxy version is %s\n", haproxy_version); if (path) print_message_args(0, "NOTICE", "path to executable is %s\n", path); } va_start(argp, fmt); print_message(1, "WARNING", fmt, argp); va_end(argp); } } /* * Variant of _ha_diag_warning with va_list. * Use it only if MODE_DIAG has been previously checked. */ void _ha_vdiag_warning(const char *fmt, va_list argp) { print_message(1, "DIAG", fmt, argp); } /* * Output a diagnostic warning. * Use it only if MODE_DIAG has been previously checked. */ void _ha_diag_warning(const char *fmt, ...) { va_list argp; va_start(argp, fmt); _ha_vdiag_warning(fmt, argp); va_end(argp); } /* * Output a diagnostic warning. Do nothing of MODE_DIAG is not on. */ void ha_diag_warning(const char *fmt, ...) { va_list argp; if (global.mode & MODE_DIAG) { va_start(argp, fmt); _ha_vdiag_warning(fmt, argp); va_end(argp); } } /* * Displays the message on stderr with the date and pid. */ void ha_notice(const char *fmt, ...) { va_list argp; if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE) || !(global.mode & MODE_STARTING)) { va_start(argp, fmt); print_message(1, "NOTICE", fmt, argp); va_end(argp); } } /* * Displays the message on only if quiet mode is not set. */ void qfprintf(FILE *out, const char *fmt, ...) { va_list argp; if (!(global.mode & MODE_QUIET) || (global.mode & MODE_VERBOSE)) { va_start(argp, fmt); vfprintf(out, fmt, argp); fflush(out); va_end(argp); } } /* parse the "show startup-logs" command, returns 1 if a message is returned, otherwise zero */ static int cli_parse_show_startup_logs(char **args, char *payload, struct appctx *appctx, void *private) { if (!cli_has_level(appctx, ACCESS_LVL_OPER)) return 1; if (!startup_logs) return cli_msg(appctx, LOG_INFO, "\n"); // nothing to print return ring_attach_cli(startup_logs, appctx, 0); } /* register cli keywords */ static struct cli_kw_list cli_kws = {{ },{ { { "show", "startup-logs", NULL }, "show startup-logs : report logs emitted during HAProxy startup", cli_parse_show_startup_logs, NULL, NULL }, {{},} }}; INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws); static void deinit_errors_buffers() { ring_free(_HA_ATOMIC_XCHG(&startup_logs, NULL)); ha_free(&usermsgs_buf.area); ha_free(&usermsgs_ctx.str.area); } /* errors might be used in threads and even before forking, thus 2 deinit */ REGISTER_PER_THREAD_FREE(deinit_errors_buffers); REGISTER_POST_DEINIT(deinit_errors_buffers);