View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
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.
16   */
17  
18  package org.apache.logging.log4j.core.appender.mom.jeromq;
19  
20  import java.io.Serializable;
21  import java.util.ArrayList;
22  import java.util.List;
23  import java.util.concurrent.TimeUnit;
24  
25  import org.apache.logging.log4j.core.Appender;
26  import org.apache.logging.log4j.core.Filter;
27  import org.apache.logging.log4j.core.Layout;
28  import org.apache.logging.log4j.core.LogEvent;
29  import org.apache.logging.log4j.core.appender.AbstractAppender;
30  import org.apache.logging.log4j.core.config.Node;
31  import org.apache.logging.log4j.core.config.Property;
32  import org.apache.logging.log4j.core.config.plugins.Plugin;
33  import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
34  import org.apache.logging.log4j.core.config.plugins.PluginElement;
35  import org.apache.logging.log4j.core.config.plugins.PluginFactory;
36  import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
37  import org.apache.logging.log4j.core.layout.PatternLayout;
38  import org.apache.logging.log4j.util.Strings;
39  
40  /**
41   * Sends log events to one or more ZeroMQ (JeroMQ) endpoints.
42   * <p>
43   * Requires the JeroMQ jar (LGPL as of 0.3.5)
44   * </p>
45   */
46  // TODO
47  // Some methods are synchronized because a ZMQ.Socket is not thread-safe
48  // Using a ThreadLocal for the publisher hangs tests on shutdown. There must be
49  // some issue on threads owning certain resources as opposed to others.
50  @Plugin(name = "JeroMQ", category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true)
51  public final class JeroMqAppender extends AbstractAppender {
52  
53      private static final int DEFAULT_BACKLOG = 100;
54  
55      private static final int DEFAULT_IVL = 100;
56  
57      private static final int DEFAULT_RCV_HWM = 1000;
58  
59      private static final int DEFAULT_SND_HWM = 1000;
60  
61      private final JeroMqManager manager;
62      private final List<String> endpoints;
63      private int sendRcFalse;
64      private int sendRcTrue;
65  
66      private JeroMqAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout,
67              final boolean ignoreExceptions, final List<String> endpoints, final long affinity, final long backlog,
68              final boolean delayAttachOnConnect, final byte[] identity, final boolean ipv4Only, final long linger,
69              final long maxMsgSize, final long rcvHwm, final long receiveBufferSize, final int receiveTimeOut,
70              final long reconnectIVL, final long reconnectIVLMax, final long sendBufferSize, final int sendTimeOut,
71              final long sndHWM, final int tcpKeepAlive, final long tcpKeepAliveCount, final long tcpKeepAliveIdle,
72              final long tcpKeepAliveInterval, final boolean xpubVerbose, final Property[] properties) {
73          super(name, filter, layout, ignoreExceptions, properties);
74          this.manager = JeroMqManager.getJeroMqManager(name, affinity, backlog, delayAttachOnConnect, identity, ipv4Only,
75              linger, maxMsgSize, rcvHwm, receiveBufferSize, receiveTimeOut, reconnectIVL, reconnectIVLMax,
76              sendBufferSize, sendTimeOut, sndHWM, tcpKeepAlive, tcpKeepAliveCount, tcpKeepAliveIdle,
77              tcpKeepAliveInterval, xpubVerbose, endpoints);
78          this.endpoints = endpoints;
79      }
80  
81      // The ZMQ.Socket class has other set methods that we do not cover because
82      // they throw unsupported operation exceptions.
83      @PluginFactory
84      public static JeroMqAppender createAppender(
85              // @formatter:off
86              @Required(message = "No name provided for JeroMqAppender") @PluginAttribute("name") final String name,
87              @PluginElement("Layout") Layout<?> layout,
88              @PluginElement("Filter") final Filter filter,
89              @PluginElement("Properties") final Property[] properties,
90              // Super attributes
91              @PluginAttribute("ignoreExceptions") final boolean ignoreExceptions,
92              // ZMQ attributes; defaults picked from zmq.Options.
93              @PluginAttribute(value = "affinity", defaultLong = 0) final long affinity,
94              @PluginAttribute(value = "backlog", defaultLong = DEFAULT_BACKLOG) final long backlog,
95              @PluginAttribute(value = "delayAttachOnConnect") final boolean delayAttachOnConnect,
96              @PluginAttribute(value = "identity") final byte[] identity,
97              @PluginAttribute(value = "ipv4Only", defaultBoolean = true) final boolean ipv4Only,
98              @PluginAttribute(value = "linger", defaultLong = -1) final long linger,
99              @PluginAttribute(value = "maxMsgSize", defaultLong = -1) final long maxMsgSize,
100             @PluginAttribute(value = "rcvHwm", defaultLong = DEFAULT_RCV_HWM) final long rcvHwm,
101             @PluginAttribute(value = "receiveBufferSize", defaultLong = 0) final long receiveBufferSize,
102             @PluginAttribute(value = "receiveTimeOut", defaultLong = -1) final int receiveTimeOut,
103             @PluginAttribute(value = "reconnectIVL", defaultLong = DEFAULT_IVL) final long reconnectIVL,
104             @PluginAttribute(value = "reconnectIVLMax", defaultLong = 0) final long reconnectIVLMax,
105             @PluginAttribute(value = "sendBufferSize", defaultLong = 0) final long sendBufferSize,
106             @PluginAttribute(value = "sendTimeOut", defaultLong = -1) final int sendTimeOut,
107             @PluginAttribute(value = "sndHwm", defaultLong = DEFAULT_SND_HWM) final long sndHwm,
108             @PluginAttribute(value = "tcpKeepAlive", defaultInt = -1) final int tcpKeepAlive,
109             @PluginAttribute(value = "tcpKeepAliveCount", defaultLong = -1) final long tcpKeepAliveCount,
110             @PluginAttribute(value = "tcpKeepAliveIdle", defaultLong = -1) final long tcpKeepAliveIdle,
111             @PluginAttribute(value = "tcpKeepAliveInterval", defaultLong = -1) final long tcpKeepAliveInterval,
112             @PluginAttribute(value = "xpubVerbose") final boolean xpubVerbose
113             // @formatter:on
114     ) {
115         if (layout == null) {
116             layout = PatternLayout.createDefaultLayout();
117         }
118         List<String> endpoints;
119         if (properties == null) {
120             endpoints = new ArrayList<>(0);
121         } else {
122             endpoints = new ArrayList<>(properties.length);
123             for (final Property property : properties) {
124                 if ("endpoint".equalsIgnoreCase(property.getName())) {
125                     final String value = property.getValue();
126                     if (Strings.isNotEmpty(value)) {
127                         endpoints.add(value);
128                     }
129                 }
130             }
131         }
132         LOGGER.debug("Creating JeroMqAppender with name={}, filter={}, layout={}, ignoreExceptions={}, endpoints={}",
133                 name, filter, layout, ignoreExceptions, endpoints);
134         return new JeroMqAppender(name, filter, layout, ignoreExceptions, endpoints, affinity, backlog,
135                 delayAttachOnConnect, identity, ipv4Only, linger, maxMsgSize, rcvHwm, receiveBufferSize,
136                 receiveTimeOut, reconnectIVL, reconnectIVLMax, sendBufferSize, sendTimeOut, sndHwm, tcpKeepAlive,
137                 tcpKeepAliveCount, tcpKeepAliveIdle, tcpKeepAliveInterval, xpubVerbose, null);
138     }
139 
140     @Override
141     public synchronized void append(final LogEvent event) {
142         final Layout<? extends Serializable> layout = getLayout();
143         final byte[] formattedMessage = layout.toByteArray(event);
144         if (manager.send(getLayout().toByteArray(event))) {
145             sendRcTrue++;
146         } else {
147             sendRcFalse++;
148             LOGGER.error("Appender {} could not send message {} to JeroMQ {}", getName(), sendRcFalse, formattedMessage);
149         }
150     }
151 
152     @Override
153     public boolean stop(final long timeout, final TimeUnit timeUnit) {
154         setStopping();
155         boolean stopped = super.stop(timeout, timeUnit, false);
156         stopped &= manager.stop(timeout, timeUnit);
157         setStopped();
158         return stopped;
159     }
160 
161     // not public, handy for testing
162     int getSendRcFalse() {
163         return sendRcFalse;
164     }
165 
166     // not public, handy for testing
167     int getSendRcTrue() {
168         return sendRcTrue;
169     }
170 
171     // not public, handy for testing
172     void resetSendRcs() {
173         sendRcTrue = sendRcFalse = 0;
174     }
175 
176     @Override
177     public String toString() {
178         return "JeroMqAppender{" +
179             "name=" + getName() +
180             ", state=" + getState() +
181             ", manager=" + manager +
182             ", endpoints=" + endpoints +
183             '}';
184     }
185 }