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.log4j.net;
19
20 import java.net.DatagramSocket;
21 import java.net.InetAddress;
22 import java.net.DatagramPacket;
23 import java.net.UnknownHostException;
24 import java.net.SocketException;
25
26 import org.apache.log4j.helpers.OptionConverter;
27 import org.apache.log4j.AppenderSkeleton;
28 import org.apache.log4j.spi.LoggingEvent;
29 import org.apache.log4j.Category;
30 import org.apache.log4j.Priority;
31 import org.apache.log4j.Layout;
32
33 import org.apache.log4j.helpers.SingleLineTracerPrintWriter;
34 import org.apache.log4j.helpers.LogLog;
35 import org.apache.log4j.helpers.QuietWriter;
36
37
38 /**
39 Use DatagramStringAppender to send log messages to a remote daemon
40 which accepts Datagram (UDP) messages.
41 <p>
42 The benefits of UDP are that the client is guarunteed not to
43 slow down if the network or remote log daemon is slow, and that
44 no permanent TCP connection between client and server exists.
45 <p>
46 The disadvantages are that log messages can be lost if the network
47 or remote daemon are under excessive load.
48 <p>
49 This class builts the final message string <b>before</b> sending
50 the UDP packet, hence the "string" component in the class name. This
51 means that the receiving application can be written in any language.
52 The data is transmitted in whatever encoding is specified in the
53 configuration file; this may be an 8-bit encoding (eg ISO-8859-1, also
54 known as LATIN-1) or a larger encoding, eg UTF-16.
55 <p>
56 An alternative to building the message string within DatagramStringAppender
57 would be to serialize & send the complete logging event object (perhaps
58 such a class could be called a DatagramSerialAppender??). The
59 receiving end could then be configured with appropriate Layout objects
60 to generate the actual logged messages. This would ensure that the
61 logging of messages from different sources is done in a consistent
62 format, and give a central place to configure that format. It would ensure
63 (by transmitting messages as unicode) that the receiving end could control
64 the encoding in which the output is generated. It also would possibly allow
65 he receiving end to use the full log4j flexibility to pass the event to
66 different appenders at the receiving end, as the category information is
67 retained, etc. However, this does require that the receiving end is in
68 java, and that all clients of the logging daemon are java applications.
69 In contrast, this DatagramStringAppender can send mesages to a log daemon
70 that accepts messages from a variety of sources.
71
72 @author Simon Kitching
73 */
74 public class DatagramStringAppender extends AppenderSkeleton {
75
76 /**
77 A string constant used in naming the option for setting the destination
78 server for messages. Current value of this string constant is
79 <b>DatagramHost</b>. */
80 public static final String DATAGRAM_HOST_OPTION = "DatagramHost";
81
82 /**
83 A string constant used in naming the option for setting the destination
84 port for messages. Current value of this string constant is
85 <b>DatagramPort</b>. */
86 public static final String DATAGRAM_PORT_OPTION = "DatagramPort";
87
88 /**
89 A string constant used in naming the option for setting the character
90 encoding used when generating the log message. Current value of this
91 string constant is <b>DatagramEncoding</b>. */
92 public static final String DATAGRAM_ENCODING_OPTION = "DatagramEncoding";
93
94 /**
95 The default value for the "host" attribute, ie the machine to which
96 messages are sent. Current value of this string constant is
97 <b>localhost</b>. */
98 public static final String DEFAULT_HOST = "localhost";
99
100 /**
101 The default value for the "port" attribute, ie the UDP port to which
102 messages are sent. Current value of this integer constant is
103 <b>8200</b>. This value was chosen for no particular reason. */
104 public static final int DEFAULT_PORT = 8200;
105
106 /**
107 The default value for the "encoding" attribute, ie the way in which
108 unicode message strings are converted into a stream of bytes before
109 their transmission as a UDP packet. The current value of this constant
110 is <b>null</b>, which means that the default platform encoding will
111 be used. */
112 public static final String DEFAULT_ENCODING = null;
113
114 String host = DEFAULT_HOST;
115 int port = DEFAULT_PORT;
116 String encoding = DEFAULT_ENCODING;
117
118 SingleLineTracerPrintWriter stp;
119 QuietWriter qw;
120
121 public
122 DatagramStringAppender() {
123 this.setDestination(DEFAULT_HOST, DEFAULT_PORT, DEFAULT_ENCODING);
124 }
125
126 public
127 DatagramStringAppender(Layout layout) {
128 this.setLayout(layout);
129 this.setDestination(DEFAULT_HOST, DEFAULT_PORT, DEFAULT_ENCODING);
130 }
131
132 public
133 DatagramStringAppender(Layout layout, String host, int port) {
134 this.setLayout(layout);
135 this.setDestination(host, port, DEFAULT_ENCODING);
136 }
137
138 public
139 DatagramStringAppender(Layout layout, String host, int port, String encoding) {
140 this.setLayout(layout);
141 this.setDestination(host, port, encoding);
142 }
143
144 /**
145 Release any resources held by this Appender
146 */
147 public
148 void close() {
149 closed = true;
150 // A DatagramWriter is UDP based and needs no opening. Hence, it
151 // can't be closed. We just unset the variables here.
152 qw = null;
153 stp = null;
154 }
155
156 public
157 void append(LoggingEvent event) {
158 if(!isAsSevereAsThreshold(event.priority))
159 return;
160
161 // We must not attempt to append if qw is null.
162 if(qw == null) {
163 errorHandler.error(
164 "No host is set for DatagramStringAppender named \""
165 + this.name + "\".");
166 return;
167 }
168
169 String buffer = layout.format(event);
170 qw.write(buffer);
171
172 if(event.throwable != null)
173 event.throwable.printStackTrace(stp);
174 else if (event.throwableInformation != null) {
175 // we must be the receiver of a serialized/deserialized LoggingEvent;
176 // the event's throwable member is transient, ie becomes null when
177 // deserialized, but that's ok because throwableInformation should
178 // have the string equivalent of the same info (ie stack trace)
179 qw.write(event.throwableInformation);
180 }
181 }
182
183 /**
184 Activate the options set via the setOption method.
185
186 @see #setOption
187 */
188 public
189 void activateOptions() {
190 this.setDestination(this.host, this.port, this.encoding);
191 }
192
193 /**
194 Returns the option names for this component, namely the string
195 array consisting of {{@link #DATAGRAM_HOST_OPTION}, {@link
196 #DATAGRAM_PORT_OPTION}, {@link #DATAGRAM_ENCODING_OPTION} */
197 public
198 String[] getOptionStrings() {
199 return OptionConverter.concatanateArrays(super.getOptionStrings(),
200 new String[] {
201 DATAGRAM_HOST_OPTION,
202 DATAGRAM_PORT_OPTION,
203 DATAGRAM_ENCODING_OPTION});
204 }
205
206 /**
207 The DatagramStringAppender requires a layout. Hence, this method return
208 <code>true</code>.
209
210 @since 0.8.4 */
211 public
212 boolean requiresLayout() {
213 return true;
214 }
215
216 /**
217 Set DatagramStringAppender specific parameters.
218 <p>
219 The recognized options are <b>DatagramHost</b>, <b>DatagramPort</b> and
220 <b>DatagramEncoding</b>, i.e. the values of the string constants
221 {@link #DATAGRAM_HOST_OPTION}, {@link #DATAGRAM_PORT_OPTION} and
222 {@link #DATAGRAM_ENCODING_OPTION} respectively.
223 <p>
224 <dl>
225 <p>
226 <dt><b>DatagramHost</b>
227 <dd>
228 The name (or ip address) of the host machine where log output should go.
229 If the DatagramHost is not set, then this appender will default to
230 {@link #DEFAULT_HOST}.
231 <p>
232 <dt><b>DatagramPort</b>
233 <dd>
234 The UDP port number where log output should go. See {@link #DEFAULT_PORT}
235 <p>
236 <dt><b>DatagramEncoding</b>
237 <dd>
238 The ISO character encoding to be used when converting the Unicode
239 message to a sequence of bytes within a UDP packet. If not defined, then
240 the encoding defaults to the default platform encoding.
241 </dl>
242 */
243 public
244 void setOption(String option, String value) {
245 if(value == null) return;
246
247 super.setOption(option, value);
248
249 if(option.equals(DATAGRAM_HOST_OPTION))
250 {
251 this.host = value;
252 }
253 else if(option.equals(DATAGRAM_PORT_OPTION))
254 {
255 this.port = OptionConverter.toInt(value, DEFAULT_PORT);
256 }
257 else if(option.equals(DATAGRAM_ENCODING_OPTION))
258 {
259 this.encoding = value;
260 }
261 }
262
263 public
264 void setDestination(String host, int port, String encoding) {
265 if (host==null) {
266 LogLog.error("setDestination: host is null");
267 host = DEFAULT_HOST;
268 }
269
270 this.host = host;
271 this.port = port;
272 this.encoding = encoding;
273
274 this.qw = new QuietWriter(
275 new DatagramStringWriter(host, port, encoding),
276 errorHandler);
277 this.stp = new SingleLineTracerPrintWriter(qw);
278 }
279
280 public
281 void setLayout(Layout layout) {
282 this.layout = layout;
283 }
284 }