001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.logging;
023:
024: import java.net.Socket;
025: import java.net.ServerSocket;
026: import java.net.InetAddress;
027:
028: import org.apache.log4j.LogManager;
029: import org.apache.log4j.MDC;
030: import org.apache.log4j.spi.LoggerRepository;
031: import org.apache.log4j.net.SocketNode;
032:
033: import org.jboss.system.ServiceMBeanSupport;
034: import org.jboss.system.MissingAttributeException;
035:
036: /**
037: * A Log4j SocketServer service. Listens for client connections on the
038: * specified port and creates a new thread and SocketNode to process the
039: * incoming client log messages.
040: *
041: * <p>
042: * The LoggerRepository can be changed based on the clients address
043: * by using a custom LoggerRepositoryFactory. The default factory
044: * will simply return the current repository.
045: *
046: * @jmx:mbean extends="org.jboss.system.ServiceMBean"
047: *
048: * @version <tt>$Revision: 57209 $</tt>
049: * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
050: */
051: public class Log4jSocketServer extends ServiceMBeanSupport implements
052: Log4jSocketServerMBean {
053: /** The port number where the server listens. */
054: protected int port = -1;
055:
056: /** The listen backlog count. */
057: protected int backlog = 50;
058:
059: /** The address to bind to. */
060: protected InetAddress bindAddress;
061:
062: /** True if the socket listener is enabled. */
063: protected boolean listenerEnabled = true;
064:
065: /** The socket listener thread. */
066: protected SocketListenerThread listenerThread;
067:
068: /** The server socket which the listener listens on. */
069: protected ServerSocket serverSocket;
070:
071: /** The factory to create LoggerRepository's for client connections. */
072: protected LoggerRepositoryFactory loggerRepositoryFactory;
073:
074: /**
075: * @jmx:managed-constructor
076: */
077: public Log4jSocketServer() {
078: super ();
079: }
080:
081: /**
082: * @jmx:managed-attribute
083: */
084: public void setPort(final int port) {
085: this .port = port;
086: }
087:
088: /**
089: * @jmx:managed-attribute
090: */
091: public int getPort() {
092: return port;
093: }
094:
095: /**
096: * @jmx:managed-attribute
097: */
098: public void setBacklog(final int backlog) {
099: this .backlog = backlog;
100: }
101:
102: /**
103: * @jmx:managed-attribute
104: */
105: public int getBacklog() {
106: return backlog;
107: }
108:
109: /**
110: * @jmx:managed-attribute
111: */
112: public void setBindAddress(final InetAddress addr) {
113: this .bindAddress = addr;
114: }
115:
116: /**
117: * @jmx:managed-attribute
118: */
119: public InetAddress getBindAddress() {
120: return bindAddress;
121: }
122:
123: /**
124: * @jmx:managed-attribute
125: */
126: public void setListenerEnabled(final boolean enabled) {
127: listenerEnabled = enabled;
128: }
129:
130: /**
131: * @jmx:managed-attribute
132: */
133: public boolean setListenerEnabled() {
134: return listenerEnabled;
135: }
136:
137: /**
138: * @jmx:managed-attribute
139: */
140: public void setLoggerRepositoryFactoryType(final Class type)
141: throws InstantiationException, IllegalAccessException,
142: ClassCastException {
143: this .loggerRepositoryFactory = (LoggerRepositoryFactory) type
144: .newInstance();
145: }
146:
147: /**
148: * @jmx:managed-attribute
149: */
150: public Class getLoggerRepositoryFactoryType() {
151: if (loggerRepositoryFactory == null)
152: return null;
153: return loggerRepositoryFactory.getClass();
154: }
155:
156: /**
157: * @jmx:managed-operation
158: */
159: public LoggerRepository getLoggerRepository(final InetAddress addr) {
160: return loggerRepositoryFactory.create(addr);
161: }
162:
163: ///////////////////////////////////////////////////////////////////////////
164: // Socket Listener //
165: ///////////////////////////////////////////////////////////////////////////
166:
167: protected class SocketListenerThread extends Thread {
168: protected Logger log = Logger
169: .getLogger(SocketListenerThread.class);
170: protected boolean enabled;
171: protected boolean shuttingDown;
172: protected Object lock = new Object();
173:
174: public SocketListenerThread(final boolean enabled) {
175: super ("SocketListenerThread");
176:
177: this .enabled = enabled;
178: }
179:
180: public void setEnabled(boolean enabled) {
181: this .enabled = enabled;
182:
183: synchronized (lock) {
184: lock.notifyAll();
185: }
186:
187: if (log.isDebugEnabled()) {
188: log.debug("Notified that enabled: " + enabled);
189: }
190: }
191:
192: public void shutdown() {
193: enabled = false;
194: shuttingDown = true;
195:
196: synchronized (lock) {
197: lock.notifyAll();
198: }
199:
200: if (log.isDebugEnabled()) {
201: log.debug("Notified to shutdown");
202: }
203: }
204:
205: public void run() {
206: while (!shuttingDown) {
207:
208: if (!enabled) {
209: try {
210: log.debug("Disabled, waiting for notification");
211: synchronized (lock) {
212: lock.wait();
213: }
214: } catch (InterruptedException ignore) {
215: }
216: }
217:
218: try {
219: doRun();
220: } catch (Throwable e) {
221: log
222: .error(
223: "Exception caught from main loop; ignoring",
224: e);
225: }
226: }
227: }
228:
229: protected void doRun() throws Exception {
230: while (enabled) {
231: Socket socket = serverSocket.accept();
232: InetAddress addr = socket.getInetAddress();
233: log.debug("Connected to client at " + addr);
234:
235: LoggerRepository repo = getLoggerRepository(addr);
236: log.debug("Using repository: " + repo);
237:
238: //
239: // jason: may want to expose socket node as an MBean for management
240: //
241:
242: log.debug("Starting new socket node");
243: SocketNode node = new SocketNode(socket, repo);
244: /* Create a thread with and MDC.host value set to the client
245: hostname to allow for distiguished output
246: */
247: String clientHost = addr.getHostName();
248: SocketThread thread = new SocketThread(node, clientHost);
249: thread.start();
250: log.debug("Socket node started");
251: }
252: }
253: }
254:
255: static class SocketThread extends Thread {
256: String host;
257:
258: SocketThread(Runnable target, String host) {
259: super (target, host + " LoggingEvent Thread");
260: this .host = host;
261: }
262:
263: public void run() {
264: MDC.put("host", host);
265: super .run();
266: }
267: }
268:
269: ///////////////////////////////////////////////////////////////////////////
270: // LoggerRepositoryFactory //
271: ///////////////////////////////////////////////////////////////////////////
272:
273: public static interface LoggerRepositoryFactory {
274: public LoggerRepository create(InetAddress addr);
275: }
276:
277: /**
278: * A simple LoggerRepository factory which simply returns
279: * the current repository from the LogManager.
280: */
281: public static class DefaultLoggerRepositoryFactory implements
282: LoggerRepositoryFactory {
283: private LoggerRepository repo;
284:
285: public LoggerRepository create(final InetAddress addr) {
286: if (repo == null)
287: repo = LogManager.getLoggerRepository();
288: return repo;
289: }
290: }
291:
292: ///////////////////////////////////////////////////////////////////////////
293: // Service Overrides //
294: ///////////////////////////////////////////////////////////////////////////
295:
296: protected void createService() throws Exception {
297: listenerThread = new SocketListenerThread(false);
298: listenerThread.setDaemon(true);
299: listenerThread.start();
300: log.debug("Socket listener thread started");
301:
302: if (loggerRepositoryFactory == null) {
303: log.debug("Using default logger repository factory");
304: loggerRepositoryFactory = new DefaultLoggerRepositoryFactory();
305: }
306: }
307:
308: protected void startService() throws Exception {
309: if (port == -1)
310: throw new MissingAttributeException("Port");
311:
312: // create a new server socket to handle port number changes
313: if (bindAddress == null) {
314: serverSocket = new ServerSocket(port, backlog);
315: } else {
316: serverSocket = new ServerSocket(port, backlog, bindAddress);
317: }
318:
319: log.info("Listening on " + serverSocket);
320:
321: listenerThread.setEnabled(listenerEnabled);
322: }
323:
324: protected void stopService() throws Exception {
325: listenerThread.setEnabled(false);
326: }
327:
328: protected void destroyService() throws Exception {
329: listenerThread.shutdown();
330: listenerThread = null;
331: serverSocket = null;
332: }
333: }
|