From 9a74a6cb17717b0866e75d4bc10b30827b7a5634 Mon Sep 17 00:00:00 2001 From: Aurelien DARRAGON Date: Wed, 13 Sep 2023 11:52:31 +0200 Subject: [PATCH] MAJOR: log: introduce log backends Using "mode log" in a backend section turns the proxy in a log backend which can be used to log-balance logs between multiple log targets (udp or tcp servers) log backends can be used as regular log targets using the log directive with "backend@be_name" prefix, like so: | log backend@mybackend local0 A log backend will distribute log messages to servers according to the log load-balancing algorithm that can be set using the "log-balance" option from the log backend section. For now, only the roundrobin algorithm is supported and set by default. --- doc/configuration.txt | 58 +++++++- include/haproxy/backend-t.h | 5 + include/haproxy/backend.h | 1 + include/haproxy/log-t.h | 8 +- include/haproxy/server-t.h | 11 +- src/backend.c | 27 ++++ src/cfgparse-listen.c | 30 +++- src/cfgparse.c | 7 + src/log.c | 274 +++++++++++++++++++++++++++++++++++- src/proxy.c | 2 + src/server.c | 43 +++++- 11 files changed, 449 insertions(+), 17 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 782bdeb59d..584d604236 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -4426,6 +4426,7 @@ id - X X X ignore-persist - - X X load-server-state-from-file X - X X log (*) X X X X +log-balance X - X X log-format X X X - log-format-sd X X X - log-tag X X X X @@ -8692,6 +8693,12 @@ no log when used as a complement this can help troubleshooting by having the logs instantly available. + - A log backend in the form "backend@", which will send + log messages to the corresponding log backend responsible for + sending the message to the proper server according to the + backend's lb settings. A log backend is a backend section with + "mode log" set (see "mode" for more information). + - An explicit stream address prefix such as "tcp@","tcp6@", "tcp4@" or "uxst@" will allocate an implicit ring buffer with a stream forward server targeting the given address. @@ -8809,6 +8816,43 @@ no log # level and send in tcp log "${LOCAL_SYSLOG}:514" local0 notice # send to local server +log-balance [ ] + + Define the load balancing algorithm to be used in a log backend. + ("mode log" enabled) + + May be used in sections : defaults | frontend | listen | backend + yes | no | yes | yes + Arguments : + is the algorithm used to select a server when doing load + balancing. This only applies when no persistence information + is available, or when a connection is redispatched to another + server. may be one of the following : + + roundrobin Each server is used in turns. This is the smoothest and + fairest algorithm when the server's processing time remains + equally distributed. + + is an optional list of arguments which may be needed by some + algorithms. + + The load balancing algorithm of a log backend is set to roundrobin when + no other algorithm has been set. The algorithm may only be set once for each + log backend. The above algorithms support the "backup" server option and the + "allbackups" proxy option. However server "weight" is not supported and will + be ignored. + + Examples : + + global + log backend@mylog-rrb local0 # send all logs to mylog-rrb backend + + backend mylog-rrb + mode log + log-balance roundrobin + + server s1 udp@127.0.0.1:514 # will receive 50% of log messages + server s2 udp@127.0.0.1:514 log-format Specifies the log format string to use for traffic logs @@ -8923,7 +8967,7 @@ maxconn See also : "server", global section's "maxconn", "fullconn" -mode { tcp|http } +mode { tcp|http|log } Set the running mode or protocol of the instance May be used in sections : defaults | frontend | listen | backend yes | yes | yes | yes @@ -8939,6 +8983,16 @@ mode { tcp|http } processing and switching will be possible. This is the mode which brings HAProxy most of its value. + log When used in a backend section, it will turn the backend into a + log backend. Such backend can be used as a log destination for + any "log" directive by using the "backend@" syntax. Log + messages will be distributed to the servers from the backend + according to the lb settings which can be configured using the + "log-balance" keyword (in place of the "balance" keyword for TCP + and HTTP backends). Log backends support UDP servers by prefixing + the server's address with the "udp@" prefix. Common backend and + server features are supported, but not TCP or HTTP related ones. + When doing content switching, it is mandatory that the frontend and the backend are in the same mode (generally HTTP), otherwise the configuration will be refused. @@ -16082,7 +16136,7 @@ downinter log-proto The "log-proto" specifies the protocol used to forward event messages to - a server configured in a ring section. Possible values are "legacy" + a server configured in a log or ring section. Possible values are "legacy" and "octet-count" corresponding respectively to "Non-transparent-framing" and "Octet counting" in rfc6587. "legacy" is the default. diff --git a/include/haproxy/backend-t.h b/include/haproxy/backend-t.h index c06bdbe734..cce451a33f 100644 --- a/include/haproxy/backend-t.h +++ b/include/haproxy/backend-t.h @@ -145,6 +145,11 @@ struct lbprm { struct lb_fwlc fwlc; struct lb_chash chash; struct lb_fas fas; + struct { + struct server **srv; /* array containing in-use log servers */ + struct list avail; /* servers available for lb are registered in this list */ + uint32_t lastid; /* last relative id used */ + } log; /* used in log-balancing context (PR_MODE_SYSLOG backend) */ }; int algo; /* load balancing algorithm and variants: BE_LB_* */ int tot_wact, tot_wbck; /* total effective weights of active and backup servers */ diff --git a/include/haproxy/backend.h b/include/haproxy/backend.h index 986852c2a8..a5623494fd 100644 --- a/include/haproxy/backend.h +++ b/include/haproxy/backend.h @@ -45,6 +45,7 @@ void back_handle_st_cer(struct stream *s); const char *backend_lb_algo_str(int algo); int backend_parse_balance(const char **args, char **err, struct proxy *curproxy); +int backend_parse_log_balance(const char **args, char **err, struct proxy *curproxy); int tcp_persist_rdp_cookie(struct stream *s, struct channel *req, int an_bit); int be_downtime(struct proxy *px); diff --git a/include/haproxy/log-t.h b/include/haproxy/log-t.h index 36d9c19ec0..a0a25acf14 100644 --- a/include/haproxy/log-t.h +++ b/include/haproxy/log-t.h @@ -116,6 +116,7 @@ enum log_tgt { LOG_TARGET_DGRAM = 0, // datagram address (udp, unix socket) LOG_TARGET_FD, // file descriptor LOG_TARGET_BUFFER, // ring buffer + LOG_TARGET_BACKEND, // backend with SYSLOG mode }; /* lists of fields that can be logged, for logformat_node->type */ @@ -240,8 +241,11 @@ enum log_target_flags { struct log_target { struct sockaddr_storage *addr; union { - char *ring_name; /* type = BUFFER - preparsing */ - struct sink *sink; /* type = BUFFER - postparsing */ + char *ring_name; /* type = BUFFER - preparsing */ + struct sink *sink; /* type = BUFFER - postparsing */ + char *be_name; /* type = BACKEND - preparsing */ + struct proxy *be; /* type = BACKEND - postparsing */ + char *resolv_name; /* generic - preparsing */ }; enum log_tgt type; uint16_t flags; diff --git a/include/haproxy/server-t.h b/include/haproxy/server-t.h index 1175a470e2..fc08909e23 100644 --- a/include/haproxy/server-t.h +++ b/include/haproxy/server-t.h @@ -274,7 +274,8 @@ struct server { char *rdr_pfx; /* the redirection prefix */ struct proxy *proxy; /* the proxy this server belongs to */ - const struct mux_proto_list *mux_proto; /* the mux to use for all outgoing connections (specified by the "proto" keyword) */ + const struct mux_proto_list *mux_proto; /* the mux to use for all outgoing connections (specified by the "proto" keyword) */ + struct log_target *log_target; /* when 'mode log' is enabled, target facility used to transport log messages */ unsigned maxconn, minconn; /* max # of active sessions (0 = unlimited), min# for dynamic limit. */ struct srv_per_thread *per_thr; /* array of per-thread stuff such as connections lists */ struct srv_per_tgroup *per_tgrp; /* array of per-tgroup stuff such as idle conns */ @@ -331,7 +332,10 @@ struct server { THREAD_PAD(63); __decl_thread(HA_SPINLOCK_T lock); /* may enclose the proxy's lock, must not be taken under */ unsigned npos, lpos; /* next and last positions in the LB tree, protected by LB lock */ - struct eb32_node lb_node; /* node used for tree-based load balancing */ + union { + struct eb32_node lb_node; /* node used for tree-based load balancing */ + struct list lb_list; /* elem used for list-based load balancing */ + }; struct server *next_full; /* next server in the temporary full list */ /* usually atomically updated by any thread during parsing or on end of request */ @@ -374,7 +378,7 @@ struct server { char *hostname; /* server hostname */ struct sockaddr_storage init_addr; /* plain IP address specified on the init-addr line */ unsigned int init_addr_methods; /* initial address setting, 3-bit per method, ends at 0, enough to store 10 entries */ - enum srv_log_proto log_proto; /* used proto to emit messages on server lines from ring section */ + enum srv_log_proto log_proto; /* used proto to emit messages on server lines from log or ring section */ char *sni_expr; /* Temporary variable to store a sample expression for SNI */ struct { @@ -621,6 +625,7 @@ struct srv_kw_list { #define SRV_PARSE_PARSE_ADDR 0x08 /* required to parse the server address in the second argument */ #define SRV_PARSE_DYNAMIC 0x10 /* dynamic server created at runtime with cli */ #define SRV_PARSE_INITIAL_RESOLVE 0x20 /* resolve immediately the fqdn to an ip address */ +#define SRV_PARSE_IN_LOG_BE 0x40 /* keyword in log backend */ #endif /* _HAPROXY_SERVER_T_H */ diff --git a/src/backend.c b/src/backend.c index 7da5da873e..190e95ee12 100644 --- a/src/backend.c +++ b/src/backend.c @@ -2824,6 +2824,33 @@ int backend_parse_balance(const char **args, char **err, struct proxy *curproxy) return 0; } +/* This function parses a "balance" statement in a log backend section + * describing . It returns -1 if there is any error, otherwise zero. + * If it returns -1, it will write an error message into the buffer which + * will automatically be allocated and must be passed as NULL. The trailing '\n' + * will not be written. The function must be called with pointing to the + * first word after "balance". + */ +int backend_parse_log_balance(const char **args, char **err, struct proxy *curproxy) +{ + if (!*(args[0])) { + /* if no option is set, use round-robin by default */ + curproxy->lbprm.algo &= ~BE_LB_ALGO; + curproxy->lbprm.algo |= BE_LB_ALGO_RR; + return 0; + } + + if (strcmp(args[0], "roundrobin") == 0) { + curproxy->lbprm.algo &= ~BE_LB_ALGO; + curproxy->lbprm.algo |= BE_LB_ALGO_RR; + } + else { + memprintf(err, "only supports 'roundrobin' option"); + return -1; + } + return 0; +} + /************************************************************************/ /* All supported sample and ACL keywords must be declared here. */ diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c index d0bdca61d1..cddb9fc793 100644 --- a/src/cfgparse-listen.c +++ b/src/cfgparse-listen.c @@ -50,7 +50,7 @@ static const char *common_kw_list[] = { "use-server", "force-persist", "ignore-persist", "force-persist", "stick-table", "stick", "stats", "option", "default_backend", "http-reuse", "monitor", "transparent", "maxconn", "backlog", - "fullconn", "dispatch", "balance", "hash-type", + "fullconn", "dispatch", "balance", "log-balance", "hash-type", "hash-balance-factor", "unique-id-format", "unique-id-header", "log-format", "log-format-sd", "log-tag", "log", "source", "usesrc", "error-log-format", @@ -438,6 +438,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) if ((strcmp(args[0], "server") == 0)) { err_code |= parse_server(file, linenum, args, curproxy, curr_defproxy, + (curproxy->mode == PR_MODE_SYSLOG ? SRV_PARSE_IN_LOG_BE : 0) | SRV_PARSE_PARSE_ADDR); if (err_code & ERR_FATAL) @@ -446,6 +447,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) else if (strcmp(args[0], "default-server") == 0) { err_code |= parse_server(file, linenum, args, curproxy, curr_defproxy, + (curproxy->mode == PR_MODE_SYSLOG ? SRV_PARSE_IN_LOG_BE : 0) | SRV_PARSE_DEFAULT_SERVER); if (err_code & ERR_FATAL) @@ -454,6 +456,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) else if (strcmp(args[0], "server-template") == 0) { err_code |= parse_server(file, linenum, args, curproxy, curr_defproxy, + (curproxy->mode == PR_MODE_SYSLOG ? SRV_PARSE_IN_LOG_BE : 0) | SRV_PARSE_TEMPLATE|SRV_PARSE_PARSE_ADDR); if (err_code & ERR_FATAL) @@ -544,6 +547,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) if (strcmp(args[1], "http") == 0) curproxy->mode = PR_MODE_HTTP; else if (strcmp(args[1], "tcp") == 0) curproxy->mode = PR_MODE_TCP; + else if (strcmp(args[1], "log") == 0 && (curproxy->cap & PR_CAP_BE)) curproxy->mode = PR_MODE_SYSLOG; else if (strcmp(args[1], "health") == 0) { ha_alert("parsing [%s:%d] : 'mode health' doesn't exist anymore. Please use 'http-request return status 200' instead.\n", file, linenum); err_code |= ERR_ALERT | ERR_FATAL; @@ -554,6 +558,15 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm) err_code |= ERR_ALERT | ERR_FATAL; goto out; } + /* mode log shares lbprm struct with other modes, but makes a different use of it, + * thus, we must ensure that defproxy settings cannot persist between incompatibles + * modes at this point. + */ + if ((curr_defproxy->mode == PR_MODE_SYSLOG && curproxy->mode != PR_MODE_SYSLOG) || + (curr_defproxy->mode != PR_MODE_SYSLOG && curproxy->mode == PR_MODE_SYSLOG)) { + /* lbprm settings from incompatible defproxy, back to defaults */ + memset(&curproxy->lbprm, 0, sizeof(curproxy->lbprm)); + } } else if (strcmp(args[0], "id") == 0) { struct eb32_node *node; @@ -2539,6 +2552,21 @@ stats_error_parsing: goto out; } } + else if (strcmp(args[0], "log-balance") == 0) { /* set log-balancing with optional algorithm */ + if (warnifnotcap(curproxy, PR_CAP_BE, file, linenum, args[0], NULL)) + err_code |= ERR_WARN; + if (curproxy->mode != PR_MODE_SYSLOG) { + ha_alert("parsing [%s:%d] : %s %s\n", file, linenum, args[0], "only available for log backends"); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + + if (backend_parse_log_balance((const char **)args + 1, &errmsg, curproxy) < 0) { + ha_alert("parsing [%s:%d] : %s %s\n", file, linenum, args[0], errmsg); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + } else if (strcmp(args[0], "hash-type") == 0) { /* set hashing method */ /** * The syntax for hash-type config element is diff --git a/src/cfgparse.c b/src/cfgparse.c index ce3104d995..963894e0c4 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -3744,6 +3744,12 @@ out_uri_auth_compat: * on what LB algorithm was chosen. */ + if (curproxy->mode == PR_MODE_SYSLOG) { + /* log load-balancing requires special init that is performed + * during log-postparsing step + */ + goto skip_server_lb_init; + } curproxy->lbprm.algo &= ~(BE_LB_LKUP | BE_LB_PROP_DYN); switch (curproxy->lbprm.algo & BE_LB_KIND) { case BE_LB_KIND_RR: @@ -3783,6 +3789,7 @@ out_uri_auth_compat: } break; } + skip_server_lb_init: HA_RWLOCK_INIT(&curproxy->lbprm.lock); if (curproxy->options & PR_O_LOGASAP) diff --git a/src/log.c b/src/log.c index efe1d5b754..7f01ddf93a 100644 --- a/src/log.c +++ b/src/log.c @@ -740,14 +740,14 @@ static inline void init_log_target(struct log_target *target) target->type = 0; target->flags = LOG_TARGET_FL_NONE; target->addr = NULL; - target->ring_name = NULL; + target->resolv_name = NULL; } static void deinit_log_target(struct log_target *target) { ha_free(&target->addr); if (!(target->flags & LOG_TARGET_FL_RESOLVED)) - ha_free(&target->ring_name); + ha_free(&target->resolv_name); } /* returns 0 on failure and positive value on success */ @@ -761,9 +761,9 @@ static int dup_log_target(struct log_target *def, struct log_target *cpy) goto error; *cpy->addr = *def->addr; } - if (def->ring_name) { - cpy->ring_name = strdup(def->ring_name); - if (!cpy->ring_name) + if (def->resolv_name) { + cpy->resolv_name = strdup(def->resolv_name); + if (!cpy->resolv_name) goto error; } cpy->type = def->type; @@ -773,6 +773,172 @@ static int dup_log_target(struct log_target *def, struct log_target *cpy) return 0; } +/* must be called under the lbprm lock */ +static void _log_backend_srv_queue(struct server *srv) +{ + struct proxy *p = srv->proxy; + + /* queue the server in the proxy lb array to make it easily searcheable by + * log-balance algorithms. Here we use the srv array as a general server + * pool of in-use servers, lookup is done using a relative positional id + * (array is contiguous) + * + * We use the avail server list to get a quick hand on available servers + * (those that are UP) + */ + if (srv->flags & SRV_F_BACKUP) { + if (!p->srv_act) + p->lbprm.log.srv[p->srv_bck] = srv; + p->srv_bck++; + } + else { + if (!p->srv_act) { + /* we will be switching to act tree in LB logic, thus we need to + * reset the lastid + */ + HA_ATOMIC_STORE(&p->lbprm.log.lastid, 0); + } + p->lbprm.log.srv[p->srv_act] = srv; + p->srv_act++; + } + /* append the server to the list of available servers */ + LIST_APPEND(&p->lbprm.log.avail, &srv->lb_list); +} + +static void log_backend_srv_up(struct server *srv) +{ + struct proxy *p = srv->proxy; + + if (!srv_lb_status_changed(srv)) + return; /* nothing to do */ + if (srv_currently_usable(srv) || !srv_willbe_usable(srv)) + return; /* false alarm */ + + HA_RWLOCK_WRLOCK(LBPRM_LOCK, &p->lbprm.lock); + _log_backend_srv_queue(srv); + HA_RWLOCK_WRUNLOCK(LBPRM_LOCK, &p->lbprm.lock); +} + +/* must be called under lbprm lock */ +static void _log_backend_srv_recalc(struct proxy *p) +{ + unsigned int it = 0; + struct server *cur_srv; + + list_for_each_entry(cur_srv, &p->lbprm.log.avail, lb_list) { + uint8_t backup = cur_srv->flags & SRV_F_BACKUP; + + if ((!p->srv_act && backup) || + (p->srv_act && !backup)) + p->lbprm.log.srv[it++] = cur_srv; + } +} + +/* must be called under the lbprm lock */ +static void _log_backend_srv_dequeue(struct server *srv) +{ + struct proxy *p = srv->proxy; + + if (srv->flags & SRV_F_BACKUP) { + p->srv_bck--; + } + else { + p->srv_act--; + if (!p->srv_act) { + /* we will be switching to bck tree in LB logic, thus we need to + * reset the lastid + */ + HA_ATOMIC_STORE(&p->lbprm.log.lastid, 0); + } + } + + /* remove the srv from the list of available (UP) servers */ + LIST_DELETE(&srv->lb_list); + + /* reconstruct the array of usable servers */ + _log_backend_srv_recalc(p); +} + +static void log_backend_srv_down(struct server *srv) +{ + struct proxy *p = srv->proxy; + + if (!srv_lb_status_changed(srv)) + return; /* nothing to do */ + if (!srv_currently_usable(srv) || srv_willbe_usable(srv)) + return; /* false alarm */ + + HA_RWLOCK_WRLOCK(LBPRM_LOCK, &p->lbprm.lock); + _log_backend_srv_dequeue(srv); + HA_RWLOCK_WRUNLOCK(LBPRM_LOCK, &p->lbprm.lock); +} + +static int postcheck_log_backend(struct proxy *be) +{ + char *msg = NULL; + struct server *srv; + int err_code = ERR_NONE; + int target_type = -1; // -1 is unused in log_tgt enum + + if (be->mode != PR_MODE_SYSLOG || + (be->flags & (PR_FL_DISABLED|PR_FL_STOPPED))) + return ERR_NONE; /* nothing to do */ + + /* First time encoutering this log backend, perform some init + */ + be->lbprm.set_server_status_up = log_backend_srv_up; + be->lbprm.set_server_status_down = log_backend_srv_down; + be->lbprm.log.lastid = 0; /* initial value */ + LIST_INIT(&be->lbprm.log.avail); + + /* alloc srv array (it will be used for active and backup server lists in turn, + * so we ensure that the longest list will fit + */ + be->lbprm.log.srv = calloc(MAX(be->srv_act, be->srv_bck), sizeof(struct server *)); + + if (!be->lbprm.log.srv ) { + memprintf(&msg, "memory error when allocating server array (%d entries)", + MAX(be->srv_act, be->srv_bck)); + err_code |= ERR_ALERT | ERR_FATAL; + goto end; + } + + /* reinit srv counters, lbprm queueing will recount */ + be->srv_act = 0; + be->srv_bck = 0; + + /* finish the initialization of proxy's servers */ + srv = be->srv; + while (srv) { + if (target_type == -1) + target_type = srv->log_target->type; + if (target_type != srv->log_target->type) { + memprintf(&msg, "cannot mix server types within a log backend, '%s' srv's network type differs from previous server", srv->id); + err_code |= ERR_ALERT | ERR_FATAL; + goto end; + } + if (target_type == LOG_TARGET_BUFFER) { + srv->log_target->sink = sink_new_from_srv(srv, "log backend"); + if (!srv->log_target->sink) { + memprintf(&msg, "error when creating sink from '%s' log server", srv->id); + err_code |= ERR_ALERT | ERR_FATAL; + goto end; + } + } + srv->cur_eweight = 1; /* ignore weights, all servers have the same weight */ + _log_backend_srv_queue(srv); + srv = srv->next; + } + end: + if (err_code & ERR_CODE) { + ha_free(&be->lbprm.log.srv); /* free log servers array */ + ha_alert("log backend '%s': failed to initialize: %s.\n", be->id, msg); + ha_free(&msg); + } + + return err_code; +} + /* resolves a single logger entry (it is expected to be called * at postparsing stage) * @@ -791,7 +957,26 @@ int resolve_logger(struct logger *logger, char **msg) if (target->type == LOG_TARGET_BUFFER) err_code = sink_resolve_logger_buffer(logger, msg); + else if (target->type == LOG_TARGET_BACKEND) { + struct proxy *be; + /* special case */ + be = proxy_find_by_name(target->be_name, PR_CAP_BE, 0); + if (!be) { + memprintf(msg, "uses unknown log backend '%s'", target->be_name); + err_code |= ERR_ALERT | ERR_FATAL; + goto end; + } + else if (be->mode != PR_MODE_SYSLOG) { + memprintf(msg, "uses incompatible log backend '%s'", target->be_name); + err_code |= ERR_ALERT | ERR_FATAL; + goto end; + } + ha_free(&target->be_name); /* backend is resolved and will replace name hint */ + target->be = be; + } + + end: target->flags |= LOG_TARGET_FL_RESOLVED; return err_code; @@ -861,6 +1046,11 @@ static int parse_log_target(char *raw, struct log_target *target, char **err) target->ring_name = strdup(raw + 5); goto done; } + else if (strncmp(raw, "backend@", 8) == 0) { + target->type = LOG_TARGET_BACKEND; + target->be_name = strdup(raw + 8); + goto done; + } /* try to allocate log target addr */ target->addr = malloc(sizeof(*target->addr)); @@ -1876,6 +2066,69 @@ static inline void __do_send_log(struct log_target *target, struct log_header hd } } +/* does the same as __do_send_log() does for a single target, but here the log + * will be sent according to the log backend's lb settings. The function will + * leverage __do_send_log() function to actually send the log messages. + */ +static inline void __do_send_log_backend(struct proxy *be, struct log_header hdr, + int nblogger, size_t maxlen, + char *message, size_t size) +{ + struct server *srv; + uint32_t targetid = ~0; /* default value to check if it was explicitly assigned */ + uint32_t nb_srv; + + HA_RWLOCK_RDLOCK(LBPRM_LOCK, &be->lbprm.lock); + + if (be->srv_act) { + nb_srv = be->srv_act; + } + else if (be->srv_bck) { + /* no more active servers but backup ones are, switch to backup farm */ + nb_srv = be->srv_bck; + if (!(be->options & PR_O_USE_ALL_BK)) { + /* log balancing disabled on backup farm */ + targetid = 0; /* use first server */ + goto skip_lb; + } + } + else { + /* no srv available, can't log */ + goto drop; + } + + /* log-balancing logic: */ + + if ((be->lbprm.algo & BE_LB_ALGO) == BE_LB_ALGO_RR) { + /* Atomically load and update lastid since it's not protected + * by any write lock + * + * Wrapping is expected and could lead to unexpected ID reset in the + * middle of a cycle, but given that this only happens once in every + * 4 billions it is quite negligible + */ + targetid = HA_ATOMIC_FETCH_ADD(&be->lbprm.log.lastid, 1) % nb_srv; + } + + skip_lb: + + if (targetid == ~0) { + /* no target assigned, nothing to do */ + goto drop; + } + + /* find server based on targetid */ + srv = be->lbprm.log.srv[targetid]; + HA_RWLOCK_RDUNLOCK(LBPRM_LOCK, &be->lbprm.lock); + + __do_send_log(srv->log_target, hdr, nblogger, maxlen, message, size); + return; + + drop: + HA_RWLOCK_RDUNLOCK(LBPRM_LOCK, &be->lbprm.lock); + _HA_ATOMIC_INC(&dropped_logs); +} + /* * This function sends a syslog message. * It doesn't care about errors nor does it report them. @@ -1931,7 +2184,15 @@ void process_send_log(struct list *loggers, int level, int facility, hdr.facility = (facility == -1) ? logger->facility : facility; hdr.format = logger->format; hdr.metadata = metadata; - __do_send_log(&logger->target, hdr, ++nblogger, logger->maxlen, message, size); + + nblogger += 1; + if (logger->target.type == LOG_TARGET_BACKEND) { + __do_send_log_backend(logger->target.be, hdr, nblogger, logger->maxlen, message, size); + } + else { + /* normal target */ + __do_send_log(&logger->target, hdr, nblogger, logger->maxlen, message, size); + } } } } @@ -4181,6 +4442,7 @@ static int postresolve_loggers() /* config parsers for this section */ REGISTER_CONFIG_SECTION("log-forward", cfg_parse_log_forward, NULL); REGISTER_POST_CHECK(postresolve_loggers); +REGISTER_POST_PROXY_CHECK(postcheck_log_backend); REGISTER_PER_THREAD_ALLOC(init_log_buffers); REGISTER_PER_THREAD_FREE(deinit_log_buffers); diff --git a/src/proxy.c b/src/proxy.c index 999fc7f9e6..f8233fc78c 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -190,6 +190,8 @@ void free_proxy(struct proxy *p) free(p->conf.uif_file); if ((p->lbprm.algo & BE_LB_LKUP) == BE_LB_LKUP_MAP) free(p->lbprm.map.srv); + if (p->mode == PR_MODE_SYSLOG) + free(p->lbprm.log.srv); if (p->conf.logformat_sd_string != default_rfc5424_sd_log_format) free(p->conf.logformat_sd_string); diff --git a/src/server.c b/src/server.c index e9887c1c20..807dcc81bd 100644 --- a/src/server.c +++ b/src/server.c @@ -1901,7 +1901,7 @@ static struct srv_kw_list srv_kws = { "ALL", { }, { { "ws", srv_parse_ws, 1, 1, 1 }, /* websocket protocol */ { "id", srv_parse_id, 1, 0, 1 }, /* set id# of server */ { "init-addr", srv_parse_init_addr, 1, 1, 0 }, /* */ - { "log-proto", srv_parse_log_proto, 1, 1, 0 }, /* Set the protocol for event messages, only relevant in a ring section */ + { "log-proto", srv_parse_log_proto, 1, 1, 0 }, /* Set the protocol for event messages, only relevant in a log or ring section */ { "maxconn", srv_parse_maxconn, 1, 1, 1 }, /* Set the max number of concurrent connection */ { "maxqueue", srv_parse_maxqueue, 1, 1, 1 }, /* Set the max number of connection to put in queue */ { "max-reuse", srv_parse_max_reuse, 1, 1, 0 }, /* Set the max number of requests on a connection, -1 means unlimited */ @@ -2495,6 +2495,7 @@ void srv_free_params(struct server *srv) free(srv->resolvers_id); free(srv->addr_node.key); free(srv->lb_nodes); + free(srv->log_target); if (xprt_get(XPRT_SSL) && xprt_get(XPRT_SSL)->destroy_srv) xprt_get(XPRT_SSL)->destroy_srv(srv); @@ -2701,6 +2702,7 @@ static int _srv_parse_init(struct server **srv, char **args, int *cur_arg, const char *err = NULL; int err_code = 0; char *fqdn = NULL; + struct protocol *proto; int tmpl_range_low = 0, tmpl_range_high = 0; char *errmsg = NULL; @@ -2806,11 +2808,12 @@ static int _srv_parse_init(struct server **srv, char **args, int *cur_arg, if (!(parse_flags & SRV_PARSE_PARSE_ADDR)) goto skip_addr; - sk = str2sa_range(args[*cur_arg], &port, &port1, &port2, NULL, NULL, + sk = str2sa_range(args[*cur_arg], &port, &port1, &port2, NULL, &proto, &errmsg, NULL, &fqdn, + (parse_flags & SRV_PARSE_IN_LOG_BE ? PA_O_DGRAM : PA_O_CONNECT) | (parse_flags & SRV_PARSE_INITIAL_RESOLVE ? PA_O_RESOLVE : 0) | PA_O_PORT_OK | (parse_flags & SRV_PARSE_IN_PEER_SECTION ? PA_O_PORT_MAND : PA_O_PORT_OFS) | - PA_O_STREAM | PA_O_XPRT | PA_O_CONNECT); + PA_O_STREAM | PA_O_XPRT); if (!sk) { ha_alert("%s\n", errmsg); err_code |= ERR_ALERT | ERR_FATAL; @@ -2843,6 +2846,35 @@ static int _srv_parse_init(struct server **srv, char **args, int *cur_arg, } } + if ((parse_flags & SRV_PARSE_IN_LOG_BE) && proto) { + /* mode log enabled, and found proto: + * pre-resolve related log target from known infos + */ + newsrv->log_target = malloc(sizeof(*newsrv->log_target)); + if (!newsrv->log_target) { + ha_alert("memory error when allocating log server\n"); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + newsrv->log_target->addr = &newsrv->addr; + switch (proto->xprt_type) { + case PROTO_TYPE_DGRAM: + newsrv->log_target->type = LOG_TARGET_DGRAM; + break; + case PROTO_TYPE_STREAM: + /* for now BUFFER type only supports TCP server to it's almost + * explicit. This will require ring buffer creation during log + * postresolving step. + */ + newsrv->log_target->type = LOG_TARGET_BUFFER; + break; + default: + ha_alert("log server type not supported for log backend server.\n"); + err_code |= ERR_ALERT | ERR_FATAL; + break; + } + } + newsrv->addr = *sk; newsrv->svc_port = port; /* @@ -4863,6 +4895,11 @@ static int cli_parse_add_server(char **args, char *payload, struct appctx *appct return 1; } + if (be->mode == PR_MODE_SYSLOG) { + cli_err(appctx," Dynamic servers cannot be used with log backends."); + return 1; + } + /* At this point, some operations might not be thread-safe anymore. This * might be the case for parsing handlers which were designed to run * only at the starting stage on single-thread mode. -- 2.39.5