Coverage Report

Created: 2026-02-26 06:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/logging-log4cxx/src/main/cpp/telnetappender.cpp
Line
Count
Source
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
18
#include <log4cxx/net/telnetappender.h>
19
#include <log4cxx/helpers/loglog.h>
20
#include <log4cxx/helpers/optionconverter.h>
21
#include <log4cxx/helpers/stringhelper.h>
22
#include <log4cxx/helpers/serversocket.h>
23
#include <log4cxx/helpers/charsetencoder.h>
24
#include <log4cxx/helpers/bytebuffer.h>
25
#include <log4cxx/helpers/threadutility.h>
26
#include <log4cxx/private/appenderskeleton_priv.h>
27
#if LOG4CXX_ABI_VERSION <= 15
28
#include <log4cxx/private/aprsocket.h>
29
#endif
30
#include <mutex>
31
#include <thread>
32
#include <vector>
33
34
#if LOG4CXX_EVENTS_AT_EXIT
35
#include <log4cxx/private/atexitregistry.h>
36
#endif
37
38
using namespace LOG4CXX_NS;
39
using namespace LOG4CXX_NS::helpers;
40
using namespace LOG4CXX_NS::net;
41
42
struct Connection
43
{
44
  helpers::SocketPtr s;
45
  size_t sentCount;
46
};
47
LOG4CXX_LIST_DEF(ConnectionList, Connection);
48
49
IMPLEMENT_LOG4CXX_OBJECT(TelnetAppender)
50
51
struct TelnetAppender::TelnetAppenderPriv : public AppenderSkeletonPrivate
52
{
53
0
  TelnetAppenderPriv( int port, int maxConnections ) : AppenderSkeletonPrivate(),
54
0
    port(port),
55
0
    connections(maxConnections),
56
0
    encoding(LOG4CXX_STR("UTF-8")),
57
0
    encoder(CharsetEncoder::getUTF8Encoder()),
58
0
    activeConnections(0)
59
#if LOG4CXX_EVENTS_AT_EXIT
60
    , atExitRegistryRaii([this]{stopAcceptingConnections();})
61
#endif
62
0
  { }
63
64
  ~TelnetAppenderPriv()
65
0
  { stopAcceptingConnections(); }
66
67
  int port;
68
  LogString hostname;
69
  bool reuseAddress = false;
70
  bool nonBlocking = false;
71
  ConnectionList connections;
72
  LogString encoding;
73
  LOG4CXX_NS::helpers::CharsetEncoderPtr encoder;
74
  std::unique_ptr<helpers::ServerSocket> serverSocket;
75
  std::thread sh;
76
  size_t activeConnections;
77
  size_t eventCount{ 0 };
78
79
#if LOG4CXX_EVENTS_AT_EXIT
80
  helpers::AtExitRegistry::Raii atExitRegistryRaii;
81
#endif
82
83
  void stopAcceptingConnections()
84
0
  {
85
0
    {
86
0
      std::lock_guard<std::recursive_mutex> lock(this->mutex);
87
0
      if (!this->serverSocket || this->closed)
88
0
        return;
89
0
      this->closed = true;
90
0
    }
91
    // Interrupt accept()
92
0
    try
93
0
    {
94
0
      this->serverSocket->close();
95
0
    }
96
0
    catch (Exception&)
97
0
    {
98
0
    }
99
0
    if (this->sh.joinable())
100
0
      this->sh.join();
101
0
  }
102
};
103
104
0
#define _priv static_cast<TelnetAppenderPriv*>(m_priv.get())
105
106
/** The default telnet server port */
107
const int TelnetAppender::DEFAULT_PORT = 23;
108
109
/** The maximum number of concurrent connections */
110
const int TelnetAppender::MAX_CONNECTIONS = 20;
111
112
TelnetAppender::TelnetAppender()
113
0
  : AppenderSkeleton (std::make_unique<TelnetAppenderPriv>(DEFAULT_PORT, MAX_CONNECTIONS))
114
0
{
115
0
}
Unexecuted instantiation: log4cxx::net::TelnetAppender::TelnetAppender()
Unexecuted instantiation: log4cxx::net::TelnetAppender::TelnetAppender()
116
117
TelnetAppender::~TelnetAppender()
118
0
{
119
0
  finalize();
120
0
}
121
122
void TelnetAppender::activateOptions(Pool& /* p */)
123
0
{
124
0
  if (_priv->serverSocket == NULL)
125
0
  {
126
0
    _priv->serverSocket = ServerSocket::create(_priv->port, _priv->reuseAddress, _priv->hostname);
127
0
    _priv->serverSocket->setSoTimeout(1000);
128
0
  }
129
130
0
  if (!_priv->sh.joinable())
131
0
    _priv->sh = ThreadUtility::instance()->createThread( LOG4CXX_STR("TelnetAppender"), &TelnetAppender::acceptConnections, this );
132
0
}
133
134
void TelnetAppender::setOption(const LogString& option,
135
  const LogString& value)
136
0
{
137
0
  if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("PORT"), LOG4CXX_STR("port")))
138
0
  {
139
0
    setPort(OptionConverter::toInt(value, DEFAULT_PORT));
140
0
  }
141
0
  else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("MAXCONNECTIONS"), LOG4CXX_STR("maxconnections")))
142
0
  {
143
0
    setMaxConnections(OptionConverter::toInt(value, MAX_CONNECTIONS));
144
0
  }
145
0
  else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("ENCODING"), LOG4CXX_STR("encoding")))
146
0
  {
147
0
    setEncoding(value);
148
0
  }
149
0
  else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("NONBLOCKING"), LOG4CXX_STR("nonblocking")))
150
0
  {
151
0
    setNonBlocking(OptionConverter::toBoolean(value, true));
152
0
  }
153
0
  else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("REUSEADDRESS"), LOG4CXX_STR("reuseaddress")))
154
0
  {
155
0
    setReuseAddress(OptionConverter::toBoolean(value, true));
156
0
  }
157
0
  else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("HOSTNAME"), LOG4CXX_STR("hostname")))
158
0
  {
159
0
    setHostname(value);
160
0
  }
161
0
  else
162
0
  {
163
0
    AppenderSkeleton::setOption(option, value);
164
0
  }
165
0
}
166
167
LogString TelnetAppender::getEncoding() const
168
0
{
169
0
  std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
170
0
  return _priv->encoding;
171
0
}
172
173
void TelnetAppender::setEncoding(const LogString& value)
174
0
{
175
0
  std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
176
0
  _priv->encoder = CharsetEncoder::getEncoder(value);
177
0
  _priv->encoding = value;
178
0
}
179
180
181
void TelnetAppender::close()
182
0
{
183
0
  _priv->stopAcceptingConnections();
184
0
  std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
185
0
  if (_priv->eventCount && helpers::LogLog::isDebugEnabled())
186
0
  {
187
0
    Pool p;
188
0
    LogString msg = LOG4CXX_STR("TelnetAppender eventCount ");
189
0
    helpers::StringHelper::toString(_priv->eventCount, p, msg);
190
0
    helpers::LogLog::debug(msg);
191
0
  }
192
0
  SocketPtr nullSocket;
193
0
  int connectionNumber{ 0 };
194
0
  for (auto& item : _priv->connections)
195
0
  {
196
0
    ++connectionNumber;
197
0
    if (item.s)
198
0
    {
199
0
      item.s->close();
200
0
      if (_priv->eventCount && helpers::LogLog::isDebugEnabled())
201
0
      {
202
0
        Pool p;
203
0
        LogString msg = LOG4CXX_STR("TelnetAppender connection ");
204
0
        helpers::StringHelper::toString(connectionNumber, p, msg);
205
0
        msg += LOG4CXX_STR(" sentCount ");
206
0
        helpers::StringHelper::toString(item.sentCount, p, msg);
207
0
        helpers::LogLog::debug(msg);
208
0
      }
209
0
      item = Connection{ nullSocket, 0 };
210
0
    }
211
0
  }
212
0
  _priv->activeConnections = 0;
213
0
}
214
215
216
void TelnetAppender::write(ByteBuffer& buf)
217
0
{
218
0
  int connectionNumber{ 0 };
219
0
  for (auto& item :_priv->connections)
220
0
  {
221
0
    ++connectionNumber;
222
0
    if (item.s)
223
0
    {
224
0
      try
225
0
      {
226
0
        ByteBuffer b(buf.current(), buf.remaining());
227
0
        item.s->write(b);
228
0
        ++item.sentCount;
229
0
      }
230
0
      catch (const Exception& e)
231
0
      {
232
0
        if (helpers::LogLog::isDebugEnabled())
233
0
        {
234
0
          Pool p;
235
0
          LogString msg(LOG4CXX_STR("TelnetAppender connection "));
236
0
          helpers::StringHelper::toString(connectionNumber, p, msg);
237
0
          msg += LOG4CXX_STR(" sentCount ");
238
0
          helpers::StringHelper::toString(item.sentCount, p, msg);
239
0
          msg += LOG4CXX_STR("/");
240
0
          helpers::StringHelper::toString(_priv->eventCount, p, msg);
241
0
          helpers::LogLog::warn(msg, e);
242
0
        }
243
0
        item.s.reset();
244
0
        _priv->activeConnections--;
245
0
      }
246
0
    }
247
0
  }
248
0
}
249
250
void TelnetAppender::writeStatus(const SocketPtr& socket, const LogString& msg, Pool& p)
251
0
{
252
0
  size_t bytesSize = msg.size() * 2;
253
0
  char* bytes = p.pstralloc(bytesSize);
254
255
0
  LogString::const_iterator msgIter(msg.begin());
256
0
  ByteBuffer buf(bytes, bytesSize);
257
258
0
  while (msgIter != msg.end())
259
0
  {
260
0
    _priv->encoder->encode(msg, msgIter, buf);
261
0
    buf.flip();
262
0
    socket->write(buf);
263
0
    buf.clear();
264
0
  }
265
0
}
266
267
void TelnetAppender::append(const spi::LoggingEventPtr& event, Pool& p)
268
0
{
269
0
  ++_priv->eventCount;
270
0
  if (0 < _priv->activeConnections)
271
0
  {
272
0
    LogString msg;
273
0
    if (_priv->layout)
274
0
      _priv->layout->format(msg, event, p);
275
0
    else
276
0
      msg = event->getRenderedMessage();
277
0
    msg.append(LOG4CXX_STR("\r\n"));
278
0
    size_t bytesSize = msg.size() * 2;
279
0
    char* bytes = p.pstralloc(bytesSize);
280
281
0
    LogString::const_iterator msgIter(msg.begin());
282
0
    ByteBuffer buf(bytes, bytesSize);
283
284
0
    std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
285
286
0
    while (msgIter != msg.end())
287
0
    {
288
0
      log4cxx_status_t stat = _priv->encoder->encode(msg, msgIter, buf);
289
0
      buf.flip();
290
0
      write(buf);
291
0
      buf.clear();
292
293
0
      if (CharsetEncoder::isError(stat))
294
0
      {
295
0
        LogString unrepresented(1, 0x3F /* '?' */);
296
0
        LogString::const_iterator unrepresentedIter(unrepresented.begin());
297
0
        stat = _priv->encoder->encode(unrepresented, unrepresentedIter, buf);
298
0
        buf.flip();
299
0
        write(buf);
300
0
        buf.clear();
301
0
        msgIter++;
302
0
      }
303
0
    }
304
0
  }
305
0
}
306
307
void TelnetAppender::acceptConnections()
308
0
{
309
310
  // main loop; is left when This->closed is != 0 after an accept()
311
0
  while (true)
312
0
  {
313
0
    try
314
0
    {
315
0
      SocketPtr newClient = _priv->serverSocket->accept();
316
#if 15 < LOG4CXX_ABI_VERSION
317
      newClient->setNonBlocking(_priv->nonBlocking);
318
#else
319
0
      if (auto p = dynamic_cast<APRSocket*>(newClient.get()))
320
0
        p->setNonBlocking(_priv->nonBlocking);
321
0
#endif
322
0
      bool done = _priv->closed;
323
324
0
      if (done)
325
0
      {
326
0
        Pool p;
327
0
        writeStatus(newClient, LOG4CXX_STR("Log closed.\r\n"), p);
328
0
        newClient->close();
329
330
0
        break;
331
0
      }
332
333
0
      size_t count = _priv->activeConnections;
334
335
0
      if (count >= _priv->connections.size())
336
0
      {
337
0
        Pool p;
338
0
        writeStatus(newClient, LOG4CXX_STR("Too many connections.\r\n"), p);
339
0
        newClient->close();
340
0
      }
341
0
      else
342
0
      {
343
        //
344
        //   find unoccupied connection
345
        //
346
0
        std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
347
348
0
        int connectionNumber{ 0 };
349
0
        for (auto& item : _priv->connections)
350
0
        {
351
0
          ++connectionNumber;
352
0
          if (!item.s)
353
0
          {
354
0
            item = Connection{ newClient, 0 };
355
0
            _priv->activeConnections++;
356
0
            if (helpers::LogLog::isDebugEnabled())
357
0
            {
358
0
              Pool p;
359
0
              LogString msg = LOG4CXX_STR("TelnetAppender new connection ");
360
0
              helpers::StringHelper::toString(connectionNumber, p, msg);
361
0
              msg += LOG4CXX_STR("/");
362
0
              helpers::StringHelper::toString(_priv->activeConnections, p, msg);
363
0
              helpers::LogLog::debug(msg);
364
0
            }
365
366
0
            break;
367
0
          }
368
0
        }
369
370
0
        Pool p;
371
0
        LogString oss(LOG4CXX_STR("TelnetAppender v1.0 ("));
372
0
        StringHelper::toString((int) count + 1, p, oss);
373
0
        oss += LOG4CXX_STR(" active connections)\r\n\r\n");
374
0
        writeStatus(newClient, oss, p);
375
0
      }
376
0
    }
377
0
    catch (InterruptedIOException&)
378
0
    {
379
0
      if (_priv->closed)
380
0
      {
381
0
        break;
382
0
      }
383
0
    }
384
0
    catch (Exception& e)
385
0
    {
386
0
      if (!_priv->closed)
387
0
      {
388
0
        LogLog::error(LOG4CXX_STR("Encountered error while in SocketHandler loop."), e);
389
0
      }
390
0
      else
391
0
      {
392
0
        break;
393
0
      }
394
0
    }
395
0
  }
396
397
0
}
398
399
int TelnetAppender::getPort() const
400
0
{
401
0
  return _priv->port;
402
0
}
403
404
void TelnetAppender::setPort(int port1)
405
0
{
406
0
  _priv->port = port1;
407
0
}
408
409
LogString TelnetAppender::getHostname() const
410
0
{
411
0
  return _priv->hostname;
412
0
}
413
414
void TelnetAppender::setHostname(const LogString& hostname)
415
0
{
416
0
  _priv->hostname = hostname;
417
0
}
418
419
int TelnetAppender::getMaxConnections() const
420
0
{
421
0
  return static_cast<int>(_priv->connections.size());
422
0
}
423
424
void TelnetAppender::setMaxConnections(int newValue)
425
0
{
426
0
  std::lock_guard<std::recursive_mutex> lock(_priv->mutex);
427
0
  if (_priv->connections.size() < newValue)
428
0
    _priv->connections.resize(newValue);
429
0
  else while (newValue < _priv->connections.size())
430
0
  {
431
0
    auto item = _priv->connections.back();
432
0
    _priv->connections.pop_back();
433
0
    if (item.s)
434
0
    {
435
0
      item.s->close();
436
0
      --_priv->activeConnections;
437
0
    }
438
0
  }
439
0
}
440
441
void TelnetAppender::setNonBlocking(bool newValue)
442
0
{
443
0
  _priv->nonBlocking = newValue;
444
0
}
445
446
void TelnetAppender::setReuseAddress(bool reuseAddress)
447
0
{
448
0
  _priv->reuseAddress = reuseAddress;
449
0
}
450
451
bool TelnetAppender::requiresLayout() const
452
0
{
453
0
  return false;
454
0
}