/*
* Lilith - a log event viewer.
* Copyright (C) 2007-2015 Joern Huxhorn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Copyright 2007-2015 Joern Huxhorn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.huxhorn.lilith.logback.appender;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import de.huxhorn.lilith.sender.HeartbeatRunnable;
import de.huxhorn.lilith.sender.MessageWriteByteStrategy;
import de.huxhorn.lilith.sender.MultiplexSendBytesService;
import de.huxhorn.lilith.sender.WriteByteStrategy;
import de.huxhorn.sulky.codec.Encoder;
import de.huxhorn.sulky.io.IOUtilities;
import de.huxhorn.sulky.ulid.ULID;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
public abstract class MultiplexSocketAppenderBase<E>
extends UnsynchronizedAppenderBase<E>
{
private static final int DEFAULT_QUEUE_SIZE = 1000;
private Encoder<E> encoder;
private int port;
private final List<String> remoteHostsList;
private String applicationIdentifier;
private Thread heartbeatThread;
private long reconnectionDelay;
private WriteByteStrategy writeByteStrategy;
private int queueSize;
private MultiplexSendBytesService multiplexSendBytes;
private boolean debug;
private boolean creatingUUID=true;
private String uuid;
public MultiplexSocketAppenderBase()
{
this(new MessageWriteByteStrategy());
}
public MultiplexSocketAppenderBase(WriteByteStrategy writeByteStrategy)
{
this(writeByteStrategy, DEFAULT_QUEUE_SIZE);
}
public MultiplexSocketAppenderBase(WriteByteStrategy writeByteStrategy, int queueSize)
{
this.writeByteStrategy = writeByteStrategy;
setQueueSize(queueSize);
remoteHostsList=new ArrayList<>();
}
public boolean isCreatingUUID()
{
return creatingUUID;
}
public void setCreatingUUID(boolean creatingUUID)
{
this.creatingUUID = creatingUUID;
}
public String getUUID()
{
return uuid;
}
private void setUUID(String uuid)
{
this.uuid = uuid;
uuidChanged();
}
protected abstract void uuidChanged();
public boolean isDebug()
{
return debug;
}
public void setDebug(boolean debug)
{
this.debug = debug;
}
public int getQueueSize()
{
return queueSize;
}
public void setQueueSize(int queueSize)
{
this.queueSize = queueSize;
}
public String getApplicationIdentifier()
{
return applicationIdentifier;
}
public void setApplicationIdentifier(String applicationIdentifier)
{
this.applicationIdentifier = applicationIdentifier;
applicationIdentifierChanged();
}
protected abstract void applicationIdentifierChanged();
public long getReconnectionDelay()
{
return reconnectionDelay;
}
public void setReconnectionDelay(long reconnectionDelay)
{
this.reconnectionDelay = reconnectionDelay;
}
public int getPort()
{
return port;
}
public void setPort(int port)
{
this.port = port;
}
public List<String> getRemoteHostsList()
{
return new ArrayList<>(remoteHostsList);
}
/**
* Sets the remote host list by splitting the string remoteHosts. It is expected to be comma-separated.
*
* @param remoteHosts comma-seperated list of hosts.
*/
public void setRemoteHosts(String remoteHosts)
{
StringTokenizer tok = new StringTokenizer(remoteHosts, ",", false);
List<String> hosts = new ArrayList<>();
while(tok.hasMoreTokens())
{
String current = tok.nextToken();
current = current.trim();
if(!"".equals(current) && !hosts.contains(current))
{
hosts.add(current);
}
}
setRemoteHostsList(hosts);
}
/**
* Sets the list of remote hosts.
*
* This method should also be called setRemoteHosts but Joran explodes if it has the same name as the String version.
*
* @param remoteHostsList the list of remote hosts.
*/
public void setRemoteHostsList(List<String> remoteHostsList)
{
if(debug)
{
System.err.println("RemoteHosts: " + remoteHostsList);
}
this.remoteHostsList.clear();
this.remoteHostsList.addAll(remoteHostsList);
}
public void addRemoteHost(String remoteHost)
{
remoteHost=remoteHost.trim();
if(!"".equals(remoteHost) && !this.remoteHostsList.contains(remoteHost))
{
this.remoteHostsList.add(remoteHost);
}
}
/**
* Start this appender.
*/
public void start()
{
if(!started)
{
int errorCount = 0;
if(port == 0)
{
errorCount++;
addError("No port was configured for appender" + name + ".");
}
if(remoteHostsList == null || remoteHostsList.size() == 0)
{
errorCount++;
addError("No remote addresses were configured for appender" + name + ".");
}
if(queueSize < 1)
{
errorCount++;
addError("Invalid queue size configured for appender" + name + ". Queue size must be at least 1!");
}
if(creatingUUID)
{
//setUUID(UUID.randomUUID().toString());
setUUID(ULIDHolder.ULID.nextULID());
}
else
{
setUUID(null);
}
if(errorCount == 0)
{
initialize();
this.started = true;
addInfo("Waiting 1s to establish connections.");
try
{
Thread.sleep(1000);
}
catch(InterruptedException e)
{
IOUtilities.interruptIfNecessary(e);
}
addInfo("Started " + this);
}
}
}
private void initialize()
{
if(multiplexSendBytes != null)
{
multiplexSendBytes.shutDown();
}
multiplexSendBytes = new MultiplexSendBytesService(name, remoteHostsList, port, writeByteStrategy, reconnectionDelay, queueSize);
multiplexSendBytes.setDebug(debug);
multiplexSendBytes.startUp();
// TODO: add support for ip.ip.ip.ip:port
if(heartbeatThread != null)
{
heartbeatThread.interrupt();
try
{
heartbeatThread.join();
}
catch(InterruptedException e)
{
// this is ok.
}
}
heartbeatThread = new Thread(new HeartbeatRunnable(multiplexSendBytes), name + " Heartbeat");
heartbeatThread.setDaemon(true);
heartbeatThread.start();
}
/**
* Stop this appender.
*
* This will mark the appender as closed and calls the {@link #cleanUp}
* method.
*/
@Override
public void stop()
{
if(!isStarted())
{
return;
}
this.started = false;
cleanUp();
}
private void cleanUp()
{
addInfo("Cleaning up " + this + ".");
heartbeatThread.interrupt();
try
{
heartbeatThread.join();
}
catch(InterruptedException e)
{
// this is ok.
}
heartbeatThread = null;
multiplexSendBytes.shutDown();
multiplexSendBytes = null;
}
protected void sendBytes(byte[] bytes)
{
if(multiplexSendBytes != null)
{
multiplexSendBytes.sendBytes(bytes);
}
}
protected void append(E e)
{
if(encoder != null)
{
preProcess(e);
byte[] serialized = encoder.encode(e);
if(serialized != null)
{
sendBytes(serialized);
}
}
}
protected abstract void preProcess(E e);
protected Encoder<E> getEncoder()
{
return encoder;
}
protected void setEncoder(Encoder<E> encoder)
{
this.encoder = encoder;
}
// lazily initialized ULID instance
private static class ULIDHolder
{
static final ULID ULID = new ULID();
}
}