blob: 0391c5f8ad1a57d28f1e281ac0c6442139271342 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
#include <log4cxx/net/telnetappender.h>
#include <log4cxx/helpers/loglog.h>
#include <log4cxx/helpers/optionconverter.h>
#include <log4cxx/helpers/stringhelper.h>
#include <log4cxx/helpers/serversocket.h>
#include <log4cxx/helpers/charsetencoder.h>
#include <log4cxx/helpers/bytebuffer.h>
#include <log4cxx/helpers/threadutility.h>
#include <log4cxx/private/appenderskeleton_priv.h>
#include <mutex>
#include <thread>
#include <vector>
#if LOG4CXX_EVENTS_AT_EXIT
#include <log4cxx/private/atexitregistry.h>
#endif
using namespace LOG4CXX_NS;
using namespace LOG4CXX_NS::helpers;
using namespace LOG4CXX_NS::net;
struct Connection
{
helpers::SocketPtr s;
size_t sentCount;
};
LOG4CXX_LIST_DEF(ConnectionList, Connection);
IMPLEMENT_LOG4CXX_OBJECT(TelnetAppender)
struct TelnetAppender::TelnetAppenderPriv : public AppenderSkeletonPrivate
{
TelnetAppenderPriv( int port, int maxConnections ) : AppenderSkeletonPrivate(),
port(port),
connections(maxConnections),
encoding(LOG4CXX_STR("UTF-8")),
encoder(CharsetEncoder::getUTF8Encoder()),
activeConnections(0)
#if LOG4CXX_EVENTS_AT_EXIT
, atExitRegistryRaii([this]
{
if (setClosed())
stopAcceptingConnections();
})
#endif
{ }
~TelnetAppenderPriv()
{ stopAcceptingConnections(); }
int port;
LogString hostname;
bool reuseAddress = false;
// Secure default: never let an unauthenticated peer that stops reading
// block the logging pipeline (see setNonBlocking for the opt-out).
bool nonBlocking = true;
ConnectionList connections;
LogString encoding;
LOG4CXX_NS::helpers::CharsetEncoderPtr encoder;
std::unique_ptr<helpers::ServerSocket> serverSocket;
std::thread sh;
size_t activeConnections;
size_t eventCount{ 0 };
#if LOG4CXX_EVENTS_AT_EXIT
helpers::AtExitRegistry::Raii atExitRegistryRaii;
#endif
void stopAcceptingConnections()
{
if (!this->serverSocket)
return;
// Interrupt accept()
try
{
this->serverSocket->close();
}
catch (Exception&)
{
}
if (this->sh.joinable())
this->sh.join();
}
void close();
};
#define _priv static_cast<TelnetAppenderPriv*>(m_priv.get())
/** The default telnet server port */
const int TelnetAppender::DEFAULT_PORT = 23;
/** The maximum number of concurrent connections */
const int TelnetAppender::MAX_CONNECTIONS = 20;
TelnetAppender::TelnetAppender()
: AppenderSkeleton (std::make_unique<TelnetAppenderPriv>(DEFAULT_PORT, MAX_CONNECTIONS))
{
}
TelnetAppender::~TelnetAppender()
{
if (_priv->setClosed())
_priv->close();
}
void TelnetAppender::activateOptions( )
{
if (_priv->serverSocket == NULL)
{
_priv->serverSocket = ServerSocket::create(_priv->port, _priv->reuseAddress, _priv->hostname);
_priv->serverSocket->setSoTimeout(1000);
}
if (!_priv->sh.joinable())
_priv->sh = ThreadUtility::instance()->createThread( LOG4CXX_STR("TelnetAppender"), &TelnetAppender::acceptConnections, this );
}
void TelnetAppender::setOption(const LogString& option,
const LogString& value)
{
if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("PORT"), LOG4CXX_STR("port")))
{
setPort(OptionConverter::toInt(value, DEFAULT_PORT));
}
else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("MAXCONNECTIONS"), LOG4CXX_STR("maxconnections")))
{
setMaxConnections(OptionConverter::toInt(value, MAX_CONNECTIONS));
}
else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("ENCODING"), LOG4CXX_STR("encoding")))
{
setEncoding(value);
}
else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("NONBLOCKING"), LOG4CXX_STR("nonblocking")))
{
setNonBlocking(OptionConverter::toBoolean(value, true));
}
else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("REUSEADDRESS"), LOG4CXX_STR("reuseaddress")))
{
setReuseAddress(OptionConverter::toBoolean(value, true));
}
else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("HOSTNAME"), LOG4CXX_STR("hostname")))
{
setHostname(value);
}
else
{
AppenderSkeleton::setOption(option, value);
}
}
LogString TelnetAppender::getEncoding() const
{
std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
return _priv->encoding;
}
void TelnetAppender::setEncoding(const LogString& value)
{
std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
_priv->encoder = CharsetEncoder::getEncoder(value);
_priv->encoding = value;
}
void TelnetAppender::close()
{
if (_priv->setClosed())
_priv->close();
}
void TelnetAppender::TelnetAppenderPriv::close()
{
this->stopAcceptingConnections();
std::lock_guard<std::recursive_mutex> lock(this->mutex);
if (this->eventCount && helpers::LogLog::isDebugEnabled())
{
LogString msg = LOG4CXX_STR("TelnetAppender eventCount ");
helpers::StringHelper::toString(this->eventCount, msg);
helpers::LogLog::debug(msg);
}
SocketPtr nullSocket;
int connectionNumber{ 0 };
for (auto& item : this->connections)
{
++connectionNumber;
if (item.s)
{
item.s->close();
if (this->eventCount && helpers::LogLog::isDebugEnabled())
{
LogString msg = LOG4CXX_STR("TelnetAppender connection ");
helpers::StringHelper::toString(connectionNumber, msg);
msg += LOG4CXX_STR(" sentCount ");
helpers::StringHelper::toString(item.sentCount, msg);
helpers::LogLog::debug(msg);
}
item = Connection{ nullSocket, 0 };
}
}
this->activeConnections = 0;
}
void TelnetAppender::write(ByteBuffer& buf)
{
int connectionNumber{ 0 };
for (auto& item :_priv->connections)
{
++connectionNumber;
if (item.s)
{
try
{
ByteBuffer b(buf.current(), buf.remaining());
item.s->write(b);
++item.sentCount;
}
catch (const Exception& e)
{
if (helpers::LogLog::isDebugEnabled())
{
LogString msg(LOG4CXX_STR("TelnetAppender connection "));
helpers::StringHelper::toString(connectionNumber, msg);
msg += LOG4CXX_STR(" sentCount ");
helpers::StringHelper::toString(item.sentCount, msg);
msg += LOG4CXX_STR("/");
helpers::StringHelper::toString(_priv->eventCount, msg);
helpers::LogLog::warn(msg, e);
}
item.s.reset();
_priv->activeConnections--;
}
}
}
}
void TelnetAppender::writeStatus(const SocketPtr& socket, const LogString& msg, Pool& p)
{
size_t bytesSize = msg.size() * 2 + 10;
char* bytes = p.pstralloc(bytesSize);
LogString::const_iterator msgIter(msg.begin());
ByteBuffer buf(bytes, bytesSize);
while (msgIter != msg.end())
{
CharsetEncoder::encode(_priv->encoder, msg, msgIter, buf);
buf.flip();
socket->write(buf);
buf.clear();
}
}
void TelnetAppender::append( const spi::LoggingEventPtr& event )
{
++_priv->eventCount;
if (0 < _priv->activeConnections)
{
helpers::Pool tempPool;
LogString msg;
if (_priv->layout)
_priv->layout->format(msg, event);
else
msg = event->getRenderedMessage();
msg.append(LOG4CXX_STR("\r\n"));
size_t bytesSize = msg.size() * 2 + 10;
char* bytes = tempPool.pstralloc(bytesSize);
LogString::const_iterator msgIter(msg.begin());
ByteBuffer buf(bytes, bytesSize);
std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
while (msgIter != msg.end())
{
CharsetEncoder::encode(_priv->encoder, msg, msgIter, buf);
buf.flip();
write(buf);
buf.clear();
}
}
}
void TelnetAppender::acceptConnections()
{
// main loop; is left when This->closed is != 0 after an accept()
while (true)
{
try
{
SocketPtr newClient = _priv->serverSocket->accept();
newClient->setNonBlocking(_priv->nonBlocking);
bool done = _priv->closed;
if (done)
{
Pool p;
writeStatus(newClient, LOG4CXX_STR("Log closed.\r\n"), p);
newClient->close();
break;
}
size_t count = _priv->activeConnections;
if (count >= _priv->connections.size())
{
Pool p;
writeStatus(newClient, LOG4CXX_STR("Too many connections.\r\n"), p);
newClient->close();
}
else
{
//
// find unoccupied connection
//
std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
int connectionNumber{ 0 };
for (auto& item : _priv->connections)
{
++connectionNumber;
if (!item.s)
{
item = Connection{ newClient, 0 };
_priv->activeConnections++;
if (helpers::LogLog::isDebugEnabled())
{
LogString msg = LOG4CXX_STR("TelnetAppender new connection ");
helpers::StringHelper::toString(connectionNumber, msg);
msg += LOG4CXX_STR("/");
helpers::StringHelper::toString(_priv->activeConnections, msg);
helpers::LogLog::debug(msg);
}
break;
}
}
Pool p;
LogString oss(LOG4CXX_STR("TelnetAppender v1.0 ("));
StringHelper::toString((int) count + 1, oss);
oss += LOG4CXX_STR(" active connections)\r\n\r\n");
writeStatus(newClient, oss, p);
}
}
catch (InterruptedIOException&)
{
if (_priv->closed)
{
break;
}
}
catch (Exception& e)
{
if (!_priv->closed)
{
LogLog::error(LOG4CXX_STR("Encountered error while in SocketHandler loop."), e);
}
else
{
break;
}
}
}
}
int TelnetAppender::getPort() const
{
return _priv->port;
}
void TelnetAppender::setPort(int port1)
{
_priv->port = port1;
}
LogString TelnetAppender::getHostname() const
{
return _priv->hostname;
}
void TelnetAppender::setHostname(const LogString& hostname)
{
_priv->hostname = hostname;
}
int TelnetAppender::getMaxConnections() const
{
return static_cast<int>(_priv->connections.size());
}
void TelnetAppender::setMaxConnections(int newValue)
{
if (newValue < 0)
{
LogLog::warn(LOG4CXX_STR("TelnetAppender MaxConnections must be non-negative. Keeping the previous value."));
return;
}
std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
const size_t newSize = static_cast<size_t>(newValue);
if (_priv->connections.size() < newSize)
_priv->connections.resize(newSize);
else while (newSize < _priv->connections.size())
{
auto item = _priv->connections.back();
_priv->connections.pop_back();
if (item.s)
{
item.s->close();
--_priv->activeConnections;
}
}
}
void TelnetAppender::setNonBlocking(bool newValue)
{
_priv->nonBlocking = newValue;
}
void TelnetAppender::setReuseAddress(bool reuseAddress)
{
_priv->reuseAddress = reuseAddress;
}
bool TelnetAppender::requiresLayout() const
{
return false;
}