]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MAJOR: log: introduce log backends
authorAurelien DARRAGON <adarragon@haproxy.com>
Wed, 13 Sep 2023 09:52:31 +0000 (11:52 +0200)
committerChristopher Faulet <cfaulet@haproxy.com>
Fri, 13 Oct 2023 08:05:06 +0000 (10:05 +0200)
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
include/haproxy/backend-t.h
include/haproxy/backend.h
include/haproxy/log-t.h
include/haproxy/server-t.h
src/backend.c
src/cfgparse-listen.c
src/cfgparse.c
src/log.c
src/proxy.c
src/server.c

index 782bdeb59d0afc31ea2f9c24113c98573367f673..584d604236dac700dc33a8141f21b3dfbd90b807 100644 (file)
@@ -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@<name>", 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 <algorithm> [ <arguments> ]
+
+  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 :
+    <algorithm> 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. <algorithm> 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.
+
+    <arguments> 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 <string>
   Specifies the log format string to use for traffic logs
@@ -8923,7 +8967,7 @@ maxconn <conns>
   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@<name>" 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 <delay>
 
 log-proto <logproto>
   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.
 
index c06bdbe7346ebc7103d8d12a4da488cf35bc1651..cce451a33f0ec66faaa91b14f52b1539ab4771df 100644 (file)
@@ -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 */
index 986852c2a81f7e4adbe8925d0d57ca01bd6c47ad..a5623494fd2d1fe4bf5485a8a1131dd49be4b2a6 100644 (file)
@@ -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);
index 36d9c19ec0949bceb09cb27b3d76febd65ecee88..a0a25acf14f7db59e2ef38ba87da8e7b8c8c1e96 100644 (file)
@@ -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;
index 1175a470e201d81e67faa1292c988758b761ce0c..fc08909e235203b75570378e0bf97ee29c1a6ae7 100644 (file)
@@ -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 */
 
index 7da5da873ef3f9bc3c23314bfa5d6aef4209a4f4..190e95ee12186ec28ce2bcc616e4a905fa3f7a36 100644 (file)
@@ -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 <curproxy>. It returns -1 if there is any error, otherwise zero.
+ * If it returns -1, it will write an error message into the <err> 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 <args> 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.    */
index d0bdca61d13eb22696848d560bc11b7fa239c22f..cddb9fc7930bc280ea7a4aef802526a80e35ad15 100644 (file)
@@ -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
index ce3104d9952c382877a037bf02510d46eb5e3c3b..963894e0c4780774d3e10de05b7fb40dfa0dd3e5 100644 (file)
@@ -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)
index efe1d5b754dea5d99cd5ce68fcbaaff84651570e..7f01ddf93ae2f139f6b8dde37b98f46c47444966 100644 (file)
--- 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);
index 999fc7f9e6e12dc56c6f3db8514511b1ab280248..f8233fc78c382679c664a21da01d87c7985169bd 100644 (file)
@@ -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);
index e9887c1c2087e7f5311935aeb20471bf9e8fbd61..807dcc81bdf9ce8a0e5cd79ca3efb44d427a2571 100644 (file)
@@ -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.