Coverage Report

Created: 2026-06-15 06:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/logging-log4cxx/src/main/cpp/jsonlayout.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/logstring.h>
19
#include <log4cxx/jsonlayout.h>
20
#include <log4cxx/spi/loggingevent.h>
21
#include <log4cxx/level.h>
22
#include <log4cxx/helpers/optionconverter.h>
23
#include <log4cxx/helpers/cacheddateformat.h>
24
#include <log4cxx/helpers/simpledateformat.h>
25
#include <log4cxx/helpers/stringhelper.h>
26
#include <log4cxx/helpers/transcoder.h>
27
#include <log4cxx/private/layout_priv.h>
28
29
#include <string.h>
30
31
using namespace LOG4CXX_NS;
32
using namespace LOG4CXX_NS::helpers;
33
using namespace LOG4CXX_NS::spi;
34
35
IMPLEMENT_LOG4CXX_OBJECT(JSONLayout)
36
37
struct JSONLayout::JSONLayoutPrivate
38
{
39
  JSONLayoutPrivate() :
40
0
    locationInfo(false),
41
0
    prettyPrint(false),
42
0
    ppIndentL1(LOG4CXX_STR("  ")),
43
0
    ppIndentL2(LOG4CXX_STR("    ")),
44
0
    expectedPatternLength(100),
45
0
    threadInfo(false) {}
46
47
  // Print no location info by default
48
  bool locationInfo; //= false
49
  bool prettyPrint; //= false
50
51
  pattern::CachedDateFormat dateFormat
52
    { std::make_shared<helpers::SimpleDateFormat>(LOG4CXX_STR("yyyy-MM-dd HH:mm:ss,SSS"))
53
    , pattern::CachedDateFormat::getMaximumCacheValidity(LOG4CXX_STR("yyyy-MM-dd HH:mm:ss,SSS"))
54
    };
55
56
  LogString ppIndentL1;
57
  LogString ppIndentL2;
58
59
  // Expected length of a formatted event excluding the message text
60
  size_t expectedPatternLength;
61
62
  // Thread info is not included by default
63
  bool threadInfo; //= false
64
};
65
66
JSONLayout::JSONLayout() :
67
0
  m_priv(std::make_unique<JSONLayoutPrivate>())
68
0
{
69
0
}
Unexecuted instantiation: log4cxx::JSONLayout::JSONLayout()
Unexecuted instantiation: log4cxx::JSONLayout::JSONLayout()
70
71
0
JSONLayout::~JSONLayout(){}
72
73
void JSONLayout::setLocationInfo(bool locationInfoFlag)
74
0
{
75
0
  m_priv->locationInfo = locationInfoFlag;
76
0
}
77
78
bool JSONLayout::getLocationInfo() const
79
0
{
80
0
  return m_priv->locationInfo;
81
0
}
82
83
void JSONLayout::setPrettyPrint(bool prettyPrintFlag)
84
0
{
85
0
  m_priv->prettyPrint = prettyPrintFlag;
86
0
}
87
88
bool JSONLayout::getPrettyPrint() const
89
0
{
90
0
  return m_priv->prettyPrint;
91
0
}
92
93
void JSONLayout::setThreadInfo(bool newValue)
94
0
{
95
0
  m_priv->threadInfo = newValue;
96
0
}
97
98
bool JSONLayout::getThreadInfo() const
99
0
{
100
0
  return m_priv->threadInfo;
101
0
}
102
103
LogString JSONLayout::getContentType() const
104
0
{
105
0
  return LOG4CXX_STR("application/json");
106
0
}
107
108
void JSONLayout::activateOptions( LOG4CXX_ACTIVATE_OPTIONS_FORMAL_PARAMETERS )
109
0
{
110
0
  m_priv->expectedPatternLength = priv::doubledLayoutSize(getFormattedEventCharacterCount());
111
0
}
112
113
void JSONLayout::setOption(const LogString& option, const LogString& value)
114
0
{
115
0
  if (StringHelper::equalsIgnoreCase(option,
116
0
      LOG4CXX_STR("LOCATIONINFO"), LOG4CXX_STR("locationinfo")))
117
0
  {
118
0
    setLocationInfo(OptionConverter::toBoolean(value, false));
119
0
  }
120
0
  else if (StringHelper::equalsIgnoreCase(option,
121
0
      LOG4CXX_STR("THREADINFO"), LOG4CXX_STR("threadinfo")))
122
0
  {
123
0
    setThreadInfo(OptionConverter::toBoolean(value, false));
124
0
  }
125
0
  else if (StringHelper::equalsIgnoreCase(option,
126
0
      LOG4CXX_STR("PRETTYPRINT"), LOG4CXX_STR("prettyprint")))
127
0
  {
128
0
    setPrettyPrint(OptionConverter::toBoolean(value, false));
129
0
  }
130
0
}
131
132
void JSONLayout::format( LOG4CXX_FORMAT_LAYOUT_FORMAL_PARAMETERS ) const
133
0
{
134
0
  auto& lsMsg = event->getRenderedMessage();
135
0
  priv::reserveFormattedEvent(output, m_priv->expectedPatternLength, lsMsg.size());
136
0
  output.append(LOG4CXX_STR("{"));
137
0
  output.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
138
139
0
  if (m_priv->prettyPrint)
140
0
  {
141
0
    output.append(m_priv->ppIndentL1);
142
0
  }
143
144
0
  output.append(LOG4CXX_STR("\"timestamp\": \""));
145
0
  m_priv->dateFormat.format(output, event->getTimeStamp());
146
0
  output.append(LOG4CXX_STR("\","));
147
0
  output.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
148
149
0
  if (m_priv->threadInfo)
150
0
  {
151
0
    if (m_priv->prettyPrint)
152
0
    {
153
0
      output.append(m_priv->ppIndentL1);
154
0
    }
155
0
    appendQuotedEscapedString(output, LOG4CXX_STR("thread"));
156
0
    output.append(LOG4CXX_STR(": "));
157
0
    appendQuotedEscapedString(output, event->getThreadName());
158
0
    output.append(LOG4CXX_STR(","));
159
0
    output.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
160
0
  }
161
162
0
  if (m_priv->prettyPrint)
163
0
  {
164
0
    output.append(m_priv->ppIndentL1);
165
0
  }
166
167
0
  output.append(LOG4CXX_STR("\"level\": "));
168
0
  LogString level;
169
0
  event->getLevel()->toString(level);
170
0
  appendQuotedEscapedString(output, level);
171
0
  output.append(LOG4CXX_STR(","));
172
0
  output.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
173
174
0
  if (m_priv->prettyPrint)
175
0
  {
176
0
    output.append(m_priv->ppIndentL1);
177
0
  }
178
179
0
  output.append(LOG4CXX_STR("\"logger\": "));
180
0
  appendQuotedEscapedString(output, event->getLoggerName());
181
0
  output.append(LOG4CXX_STR(","));
182
0
  output.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
183
184
0
  if (m_priv->prettyPrint)
185
0
  {
186
0
    output.append(m_priv->ppIndentL1);
187
0
  }
188
189
0
  output.append(LOG4CXX_STR("\"message\": "));
190
0
  appendQuotedEscapedString(output, lsMsg);
191
192
0
  appendSerializedMDC(output, event);
193
0
  appendSerializedNDC(output, event);
194
195
0
  if (m_priv->locationInfo)
196
0
  {
197
0
    output.append(LOG4CXX_STR(","));
198
0
    output.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
199
0
    appendSerializedLocationInfo(output, event);
200
0
  }
201
202
0
  output.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
203
0
  output.append(LOG4CXX_STR("}"));
204
0
  output.append(LOG4CXX_EOL);
205
0
}
206
207
void JSONLayout::appendQuotedEscapedString(LogString& buf,
208
  const LogString& input) const
209
0
{
210
0
  appendItem(input, buf);
211
0
}
212
213
void JSONLayout::appendItem(const LogString& input, LogString& buf)
214
0
{
215
0
  auto toHexDigit = [](int ch) -> int
216
0
  {
217
0
    return (10 <= ch ? (0x61 - 10) : 0x30) + ch;
218
0
  };
219
  /* add leading quote */
220
0
  buf.push_back(0x22);
221
222
0
  auto start = input.begin();
223
0
  for (auto nextCodePoint = start; input.end() != nextCodePoint; )
224
0
  {
225
0
    auto lastCodePoint = nextCodePoint;
226
0
    auto ch = Transcoder::decode(input, nextCodePoint);
227
0
    if (nextCodePoint == lastCodePoint) // failed to decode input?
228
0
    {
229
      // Skip the undecodable run and keep escaping the remaining input
230
      // instead of discarding it; the run collapses to one replacement.
231
0
      for (++nextCodePoint; nextCodePoint != input.end(); ++nextCodePoint)
232
0
      {
233
0
        auto probe = nextCodePoint;
234
0
        Transcoder::decode(input, probe);
235
0
        if (probe != nextCodePoint) // next unit starts a decodable sequence
236
0
          break;
237
0
      }
238
0
      ch = 0xFFFD; // The Unicode replacement character
239
0
    }
240
0
    else if ((0xD800 <= ch && ch <= 0xDFFF) || 0x10FFFF < ch)
241
0
    {
242
0
      ch = 0xFFFD; // The Unicode replacement character
243
0
    }
244
0
    else if (0x22 == ch || 0x5c == ch) // double quote or backslash?
245
0
      ;
246
0
    else if (0x20 <= ch) // not a control character?
247
0
      continue;
248
249
0
    if (start != lastCodePoint)
250
0
      buf.append(start, lastCodePoint);
251
0
    start = nextCodePoint;
252
0
    switch (ch)
253
0
    {
254
0
      case 0x08:
255
        /* \b backspace */
256
0
        buf.push_back(0x5c);
257
0
        buf.push_back('b');
258
0
        break;
259
260
0
      case 0x09:
261
        /* \t tab */
262
0
        buf.push_back(0x5c);
263
0
        buf.push_back('t');
264
0
        break;
265
266
0
      case 0x0a:
267
        /* \n newline */
268
0
        buf.push_back(0x5c);
269
0
        buf.push_back('n');
270
0
        break;
271
272
0
      case 0x0c:
273
        /* \f form feed */
274
0
        buf.push_back(0x5c);
275
0
        buf.push_back('f');
276
0
        break;
277
278
0
      case 0x0d:
279
        /* \r carriage return */
280
0
        buf.push_back(0x5c);
281
0
        buf.push_back('r');
282
0
        break;
283
284
0
      case 0x22:
285
        /* \" double quote */
286
0
        buf.push_back(0x5c);
287
0
        buf.push_back(0x22);
288
0
        break;
289
290
0
      case 0x5c:
291
        /* \\ backslash */
292
0
        buf.push_back(0x5c);
293
0
        buf.push_back(0x5c);
294
0
        break;
295
296
0
      default:
297
0
        buf.push_back(0x5c);
298
0
        buf.push_back(0x75); // 'u'
299
0
        buf.push_back(toHexDigit((ch & 0xF000) >> 12));
300
0
        buf.push_back(toHexDigit((ch & 0xF00) >> 8));
301
0
        buf.push_back(toHexDigit((ch & 0xF0) >> 4));
302
0
        buf.push_back(toHexDigit(ch & 0xF));
303
0
        break;
304
0
    }
305
0
  }
306
0
  buf.append(start, input.end());
307
308
  /* add trailing quote */
309
0
  buf.push_back(0x22);
310
0
}
311
312
void JSONLayout::appendSerializedMDC(LogString& buf,
313
  const LoggingEventPtr& event) const
314
0
{
315
0
  LoggingEvent::KeySet keys = event->getMDCKeySet();
316
317
0
  if (keys.empty())
318
0
  {
319
0
    return;
320
0
  }
321
322
0
  buf.append(LOG4CXX_STR(","));
323
0
  buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
324
325
0
  if (m_priv->prettyPrint)
326
0
  {
327
0
    buf.append(m_priv->ppIndentL1);
328
0
  }
329
330
0
  appendQuotedEscapedString(buf, LOG4CXX_STR("context_map"));
331
0
  buf.append(LOG4CXX_STR(": {"));
332
0
  buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
333
334
0
  for (LoggingEvent::KeySet::iterator it = keys.begin();
335
0
    it != keys.end(); ++it)
336
0
  {
337
0
    if (m_priv->prettyPrint)
338
0
    {
339
0
      buf.append(m_priv->ppIndentL2);
340
0
    }
341
342
0
    appendQuotedEscapedString(buf, *it);
343
0
    buf.append(LOG4CXX_STR(": "));
344
0
    LogString value;
345
0
    event->getMDC(*it, value);
346
0
    appendQuotedEscapedString(buf, value);
347
348
    /* if this isn't the last k:v pair, we need a comma */
349
0
    if (it + 1 != keys.end())
350
0
    {
351
0
      buf.append(LOG4CXX_STR(","));
352
0
      buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
353
0
    }
354
0
    else
355
0
    {
356
0
      buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
357
0
    }
358
0
  }
359
360
0
  if (m_priv->prettyPrint)
361
0
  {
362
0
    buf.append(m_priv->ppIndentL1);
363
0
  }
364
365
0
  buf.append(LOG4CXX_STR("}"));
366
0
}
367
368
void JSONLayout::appendSerializedNDC(LogString& buf,
369
  const LoggingEventPtr& event) const
370
0
{
371
0
  LogString ndcVal;
372
373
0
  if (!event->getNDC(ndcVal))
374
0
  {
375
0
    return;
376
0
  }
377
378
0
  buf.append(LOG4CXX_STR(","));
379
0
  buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
380
381
0
  if (m_priv->prettyPrint)
382
0
  {
383
0
    buf.append(m_priv->ppIndentL1);
384
0
  }
385
386
0
  appendQuotedEscapedString(buf, LOG4CXX_STR("context_stack"));
387
0
  buf.append(LOG4CXX_STR(": ["));
388
0
  buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
389
390
0
  if (m_priv->prettyPrint)
391
0
  {
392
0
    buf.append(m_priv->ppIndentL2);
393
0
  }
394
395
0
  appendQuotedEscapedString(buf, ndcVal);
396
0
  buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
397
398
0
  if (m_priv->prettyPrint)
399
0
  {
400
0
    buf.append(m_priv->ppIndentL1);
401
0
  }
402
403
0
  buf.append(LOG4CXX_STR("]"));
404
0
}
405
406
void JSONLayout::appendSerializedLocationInfo(LogString& buf, const LoggingEventPtr& event) const
407
0
{
408
0
  if (m_priv->prettyPrint)
409
0
  {
410
0
    buf.append(m_priv->ppIndentL1);
411
0
  }
412
413
0
  appendQuotedEscapedString(buf, LOG4CXX_STR("location_info"));
414
0
  buf.append(LOG4CXX_STR(": {"));
415
0
  buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
416
0
  const LocationInfo& locInfo = event->getLocationInformation();
417
418
0
  if (m_priv->prettyPrint)
419
0
  {
420
0
    buf.append(m_priv->ppIndentL2);
421
0
  }
422
423
0
  appendQuotedEscapedString(buf, LOG4CXX_STR("file"));
424
0
  buf.append(LOG4CXX_STR(": "));
425
0
  LOG4CXX_DECODE_CHAR(fileName, locInfo.getFileName());
426
0
  appendQuotedEscapedString(buf, fileName);
427
0
  buf.append(LOG4CXX_STR(","));
428
0
  buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
429
430
0
  if (m_priv->prettyPrint)
431
0
  {
432
0
    buf.append(m_priv->ppIndentL2);
433
0
  }
434
435
0
  appendQuotedEscapedString(buf, LOG4CXX_STR("line"));
436
0
  buf.append(LOG4CXX_STR(": "));
437
0
  LogString lineNumber;
438
0
  StringHelper::toString(locInfo.getLineNumber(), lineNumber);
439
0
  appendQuotedEscapedString(buf, lineNumber);
440
0
  buf.append(LOG4CXX_STR(","));
441
0
  buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
442
443
0
  if (m_priv->prettyPrint)
444
0
  {
445
0
    buf.append(m_priv->ppIndentL2);
446
0
  }
447
448
0
  appendQuotedEscapedString(buf, LOG4CXX_STR("class"));
449
0
  buf.append(LOG4CXX_STR(": "));
450
0
  LOG4CXX_DECODE_CHAR(className, locInfo.getClassName());
451
0
  appendQuotedEscapedString(buf, className);
452
0
  buf.append(LOG4CXX_STR(","));
453
0
  buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
454
455
0
  if (m_priv->prettyPrint)
456
0
  {
457
0
    buf.append(m_priv->ppIndentL2);
458
0
  }
459
460
0
  appendQuotedEscapedString(buf, LOG4CXX_STR("method"));
461
0
  buf.append(LOG4CXX_STR(": "));
462
0
  LOG4CXX_DECODE_CHAR(methodName, locInfo.getMethodName());
463
0
  appendQuotedEscapedString(buf, methodName);
464
0
  buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
465
466
0
  if (m_priv->prettyPrint)
467
0
  {
468
0
    buf.append(m_priv->ppIndentL1);
469
0
  }
470
471
0
  buf.append(LOG4CXX_STR("}"));
472
0
}
473
474
#if LOG4CXX_ABI_VERSION <= 15
475
void JSONLayout::appendSerializedLocationInfo(LogString& buf,
476
  const LoggingEventPtr& event, Pool& p) const
477
0
{
478
0
  appendSerializedLocationInfo(buf, event);
479
0
}
480
#endif