/** * Logback: the reliable, generic, fast and flexible logging framework. * Copyright (C) 2006-2011, QOS.ch. All rights reserved. * * This program and the accompanying materials are dual-licensed under * either the terms of the Eclipse Public License v1.0 as published by * the Eclipse Foundation * * or (per the licensee's choosing) * * under the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation. */ package ch.qos.logback.audit.client.net; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.InetAddress; import java.net.Socket; import ch.qos.logback.audit.AuditEvent; import ch.qos.logback.audit.AuditException; import ch.qos.logback.audit.InternalAuditContants; import ch.qos.logback.audit.client.AuditAppenderBase; public class SocketAuditAppender extends AuditAppenderBase { static final String NO_HOST_URL = InternalAuditContants.CODES_URL + "#NO_HOST_URL"; static final String FAILIED_WRITE_URL = InternalAuditContants.CODES_URL + "#FAILIED_WRITE_URL"; /** * The default port number of remote logging server (9630). */ static final int DEFAULT_PORT = 9630; private String remoteHost; private InetAddress address; private int port = DEFAULT_PORT; private ObjectOutputStream oos; private ObjectInputStream ois; private int reconnectionDelay = 2000; protected int counter = 0; // reset the ObjectOutputStream every RESET_FREQUENCY calls private static final int RESET_FREQUENCY = 200; Connector connector; /** * Start this appender. */ public void start() { int errorCount = 0; if (port == 0) { errorCount++; addError("No port was configured for appender" + name); } if (address == null) { errorCount++; addError("No remote address was configured for appender " + name); } if (errorCount == 0) { connect(address, port); } } @Override public void stop() { super.stop(); cleanUp(); if (connector != null) { connector.setClosed(true); Thread connectorThread = connector.getThread(); try { connectorThread.join(5000); } catch (InterruptedException e) { addError("Failed to join connector thread", e); } connector = null; } } void connect(Socket socket) { try { // First, close the previous connection if any. cleanUp(); oos = new ObjectOutputStream(socket.getOutputStream()); ois = new ObjectInputStream(socket.getInputStream()); super.started = true; } catch (IOException e) { String msg = "Failed to open a stream on the socket for remmote logback server at [" + address.getHostName() + "] at port " + port; addError(msg, e); } } void connect(InetAddress address, int port) { try { // First, close the previous connection if any. cleanUp(); Socket socket = new Socket(address, port); connect(socket); } catch (IOException e) { String msg = "Could not connect to remote logback server at [" + address.getHostName() + "] at port " + port; addError(msg, e); if (reconnectionDelay > 0) { fireConnector(); } } } /** * Drop the connection to the remote host and release the underlying connector * thread if it has been created */ public void cleanUp() { if (oos != null) { try { oos.close(); } catch (IOException e) { addError("Could not close oos.", e); } oos = null; } if (ois != null) { try { ois.close(); } catch (IOException e) { addError("Could not close oos.", e); } ois = null; } } @Override protected void append(AuditEvent auditEvent) throws AuditException { if(!started) { return; } if (auditEvent == null) return; if (oos != null) { try { oos.writeObject(auditEvent); oos.flush(); if (++counter >= RESET_FREQUENCY) { counter = 0; // Failing to reset the object output stream every now and // then creates a serious memory leak. oos.reset(); } } catch (IOException e) { oos = null; this.started = false; fireConnector(); throw new AuditException("Failed sending audit event to host \"" + remoteHost + "\" down. For more information, please visit " + FAILIED_WRITE_URL, e); } } try { Object incoming = ois.readObject(); if (incoming instanceof Exception) { if (incoming instanceof AuditException) { AuditException ae = (AuditException) incoming; throw ae; } else { throw new AuditException("Server incurred an exception", (Exception) incoming); } } else if (incoming instanceof Boolean) { Boolean ack = (Boolean) incoming; if (ack.booleanValue()) { // System.out.println("ACKED"); } else { throw new AuditException("Acknowledgement failure"); } } else { throw new AuditException("Incoming object [" + incoming + "] outside of protocol"); } } catch (IOException e) { throw new AuditException("Failed reading acknowledgement", e); } catch (ClassNotFoundException e) { throw new AuditException("Unexpecteed object type while acknowledgement", e); } } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getRemoteHost() { return remoteHost; } protected static InetAddress getAddressByName(String host) { try { return InetAddress.getByName(host); } catch (Exception e) { // addError("Could not find address of [" + host + "].", e); return null; } } void fireConnector() { if (connector == null) { addInfo("Firing a connector thread"); connector = new Connector(this); connector.setContext(this.getContext()); Thread connectorThread = new Thread(connector); connectorThread.setDaemon(true); connectorThread.setName("SAA-ConnectorThread"); connectorThread.setPriority(Thread.MIN_PRIORITY); connector.setThread(connectorThread); connectorThread.start(); } } InetAddress getAddress() { return address; } public int getReconnectionDelay() { return reconnectionDelay; } public void setReconnectionDelay(int reconnectionDelay) { this.reconnectionDelay = reconnectionDelay; } /** * The <b>RemoteHost</b> option takes a string value which should be the host * name of the server where a {@link SocketNode} is running. */ public void setRemoteHost(String host) { address = getAddressByName(host); remoteHost = host; } }