/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with VoltDB. If not, see <http://www.gnu.org/licenses/>. */ package org.voltdb.importclient.log4j; import java.io.BufferedInputStream; import java.io.EOFException; import java.io.IOException; import java.io.ObjectInputStream; import java.net.Socket; import java.net.URI; import java.util.ArrayList; import org.apache.log4j.spi.LoggingEvent; import org.voltcore.network.ReverseDNSCache; import org.voltdb.importer.AbstractImporter; import org.voltdb.importer.Invocation; /** * Log4j socket handler importer that listens on a specified port. */ public class Log4jSocketHandlerImporter extends AbstractImporter { private final ArrayList<SocketReader> m_connections = new ArrayList<SocketReader>(); private final Log4jSocketImporterConfig m_config; public Log4jSocketHandlerImporter(Log4jSocketImporterConfig config) { m_config = config; } @Override public URI getResourceID() { return m_config.getResourceID(); } @Override public String getName() { return "Log4jSocketHandlerImporter"; } @Override public void accept() { /* if (!hasTable(m_config.getTableName())) { printCreateTableError(); return; } */ try { while (shouldRun()) { Socket socket = m_config.getServerSocket().accept(); SocketReader reader = new SocketReader(socket); m_connections.add(reader); new Thread(reader).start(); } } catch (IOException e) { if (shouldRun()) { error(null, String.format("Unexpected error [%s] accepting connections on port [%d]", e.getMessage(), m_config.getPort())); } } finally { closeServerSocket(); } } @Override public void stop() { closeServerSocket(); for (SocketReader reader : m_connections) { reader.stop(); } } private void closeServerSocket() { try { m_config.getServerSocket().close(); } catch(IOException e) { // nothing to do other than log if (isDebugEnabled()) { debug(null, "Unexpected error closing log4j socket appender listener on " + m_config.getPort()); } } } private void printCreateTableError() { System.err.println("Log event table must exist before Log4j socket importer can be used"); System.err.println("Please create the table using the following ddl and use appropriate partition:"); System.err.println("CREATE TABLE " + m_config.getTableName() + "\n" + "(\n" + " log_event_host varchar(256) NOT NULL\n" + ", logger_name varchar(256) NOT NULL\n" + ", log_level varchar(25) NOT NULL\n" + ", logging_thread varchar(25) NOT NULL\n" + ", log_timestamp timestamp NOT NULL\n" + ", log_message varchar(1024)\n" + ", throwable_str_rep varchar(4096)\n" + ");\n" + "PARTITION TABLE " + m_config.getTableName() + " ON COLUMN log_event_host;"); } /** * Read log4j events from socket and persist into volt */ private class SocketReader implements Runnable { private final Socket m_socket; public SocketReader(Socket socket) { m_socket = socket; Log4jSocketHandlerImporter.this.info(null, "Connected to socket appender at " + socket.getRemoteSocketAddress()); } @Override public void run() { try { String hostname = ReverseDNSCache.hostnameOrAddress(m_socket.getInetAddress()); ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(m_socket.getInputStream())); while (true) { LoggingEvent event = (LoggingEvent) ois.readObject(); if (!Log4jSocketHandlerImporter.this.callProcedure(saveLog4jEventInvocation(hostname, event, m_config.getTableName()))) { Log4jSocketHandlerImporter.this.error(null, "Failed to insert log4j event"); } } } catch(EOFException e) { // normal exit condition Log4jSocketHandlerImporter.this.info(null, "Client disconnected from " + m_socket.getRemoteSocketAddress()); } catch (ClassNotFoundException | IOException e) { // assume that these are unrecoverable // errors and exit from thread Log4jSocketHandlerImporter.this.error(null, String.format("Unexpected error [%s] reading from %s", e.getMessage(), m_socket.getRemoteSocketAddress())); e.printStackTrace(); } finally { closeSocket(); } } public void stop() { closeSocket(); } private void closeSocket() { try { m_socket.close(); } catch(IOException e) { Log4jSocketHandlerImporter.this.error(null, "Could not close log4j event reader socket on " + m_socket.getLocalPort()); e.printStackTrace(); } } } private Invocation saveLog4jEventInvocation(String hostName, LoggingEvent loggingEvent, String tableName) { String throwRep = null; if (loggingEvent.getThrowableStrRep() != null && loggingEvent.getThrowableStrRep().length != 0) { StringBuffer sb = new StringBuffer(); for (String line : loggingEvent.getThrowableStrRep()) { sb.append(line + "\n"); } throwRep = sb.deleteCharAt(sb.length() - 1).toString(); } return new Invocation(tableName + ".insert", new Object[] { hostName, loggingEvent.getLoggerName(), loggingEvent.getLevel().toString(), loggingEvent.getThreadName(), loggingEvent.getTimeStamp()*1000, loggingEvent.getRenderedMessage(), throwRep} ); } }