blob: 547123590e4a6e2ee0b44ffdbcc14548a7f2be6a [file] [log] [blame]
/*
* 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/logstring.h>
#include <log4cxx/jsonlayout.h>
#include <log4cxx/spi/loggingevent.h>
#include <log4cxx/level.h>
#include <log4cxx/helpers/optionconverter.h>
#include <log4cxx/helpers/cacheddateformat.h>
#include <log4cxx/helpers/simpledateformat.h>
#include <log4cxx/helpers/stringhelper.h>
#include <log4cxx/helpers/transcoder.h>
#include <string.h>
using namespace LOG4CXX_NS;
using namespace LOG4CXX_NS::helpers;
using namespace LOG4CXX_NS::spi;
IMPLEMENT_LOG4CXX_OBJECT(JSONLayout)
struct JSONLayout::JSONLayoutPrivate
{
JSONLayoutPrivate() :
locationInfo(false),
prettyPrint(false),
ppIndentL1(LOG4CXX_STR(" ")),
ppIndentL2(LOG4CXX_STR(" ")),
expectedPatternLength(100),
threadInfo(false) {}
// Print no location info by default
bool locationInfo; //= false
bool prettyPrint; //= false
pattern::CachedDateFormat dateFormat
{ std::make_shared<helpers::SimpleDateFormat>(LOG4CXX_STR("yyyy-MM-dd HH:mm:ss,SSS"))
, pattern::CachedDateFormat::getMaximumCacheValidity(LOG4CXX_STR("yyyy-MM-dd HH:mm:ss,SSS"))
};
LogString ppIndentL1;
LogString ppIndentL2;
// Expected length of a formatted event excluding the message text
size_t expectedPatternLength;
// Thread info is not included by default
bool threadInfo; //= false
};
JSONLayout::JSONLayout() :
m_priv(std::make_unique<JSONLayoutPrivate>())
{
}
JSONLayout::~JSONLayout(){}
void JSONLayout::setLocationInfo(bool locationInfoFlag)
{
m_priv->locationInfo = locationInfoFlag;
}
bool JSONLayout::getLocationInfo() const
{
return m_priv->locationInfo;
}
void JSONLayout::setPrettyPrint(bool prettyPrintFlag)
{
m_priv->prettyPrint = prettyPrintFlag;
}
bool JSONLayout::getPrettyPrint() const
{
return m_priv->prettyPrint;
}
void JSONLayout::setThreadInfo(bool newValue)
{
m_priv->threadInfo = newValue;
}
bool JSONLayout::getThreadInfo() const
{
return m_priv->threadInfo;
}
LogString JSONLayout::getContentType() const
{
return LOG4CXX_STR("application/json");
}
void JSONLayout::activateOptions(helpers::Pool& /* p */)
{
m_priv->expectedPatternLength = getFormattedEventCharacterCount() * 2;
}
void JSONLayout::setOption(const LogString& option, const LogString& value)
{
if (StringHelper::equalsIgnoreCase(option,
LOG4CXX_STR("LOCATIONINFO"), LOG4CXX_STR("locationinfo")))
{
setLocationInfo(OptionConverter::toBoolean(value, false));
}
else if (StringHelper::equalsIgnoreCase(option,
LOG4CXX_STR("THREADINFO"), LOG4CXX_STR("threadinfo")))
{
setThreadInfo(OptionConverter::toBoolean(value, false));
}
else if (StringHelper::equalsIgnoreCase(option,
LOG4CXX_STR("PRETTYPRINT"), LOG4CXX_STR("prettyprint")))
{
setPrettyPrint(OptionConverter::toBoolean(value, false));
}
}
void JSONLayout::format(LogString& output,
const spi::LoggingEventPtr& event,
Pool& p) const
{
auto& lsMsg = event->getRenderedMessage();
output.reserve(m_priv->expectedPatternLength + lsMsg.size());
output.append(LOG4CXX_STR("{"));
output.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
if (m_priv->prettyPrint)
{
output.append(m_priv->ppIndentL1);
}
output.append(LOG4CXX_STR("\"timestamp\": \""));
m_priv->dateFormat.format(output, event->getTimeStamp(), p);
output.append(LOG4CXX_STR("\","));
output.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
if (m_priv->threadInfo)
{
if (m_priv->prettyPrint)
{
output.append(m_priv->ppIndentL1);
}
appendQuotedEscapedString(output, LOG4CXX_STR("thread"));
output.append(LOG4CXX_STR(": "));
appendQuotedEscapedString(output, event->getThreadName());
output.append(LOG4CXX_STR(","));
output.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
}
if (m_priv->prettyPrint)
{
output.append(m_priv->ppIndentL1);
}
output.append(LOG4CXX_STR("\"level\": "));
LogString level;
event->getLevel()->toString(level);
appendQuotedEscapedString(output, level);
output.append(LOG4CXX_STR(","));
output.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
if (m_priv->prettyPrint)
{
output.append(m_priv->ppIndentL1);
}
output.append(LOG4CXX_STR("\"logger\": "));
appendQuotedEscapedString(output, event->getLoggerName());
output.append(LOG4CXX_STR(","));
output.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
if (m_priv->prettyPrint)
{
output.append(m_priv->ppIndentL1);
}
output.append(LOG4CXX_STR("\"message\": "));
appendQuotedEscapedString(output, lsMsg);
appendSerializedMDC(output, event);
appendSerializedNDC(output, event);
if (m_priv->locationInfo)
{
output.append(LOG4CXX_STR(","));
output.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
appendSerializedLocationInfo(output, event, p);
}
output.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
output.append(LOG4CXX_STR("}"));
output.append(LOG4CXX_EOL);
}
void JSONLayout::appendQuotedEscapedString(LogString& buf,
const LogString& input) const
{
appendItem(input, buf);
}
void JSONLayout::appendItem(const LogString& input, LogString& buf)
{
auto toHexDigit = [](int ch) -> int
{
return (10 <= ch ? (0x61 - 10) : 0x30) + ch;
};
/* add leading quote */
buf.push_back(0x22);
size_t start = 0;
size_t index = 0;
for (int ch : input)
{
if (0x22 == ch || 0x5c == ch)
;
else if (0x20 <= ch)
{
++index;
continue;
}
if (start < index)
{
buf.append(input, start, index - start);
}
switch (ch)
{
case 0x08:
/* \b backspace */
buf.push_back(0x5c);
buf.push_back('b');
break;
case 0x09:
/* \t tab */
buf.push_back(0x5c);
buf.push_back('t');
break;
case 0x0a:
/* \n newline */
buf.push_back(0x5c);
buf.push_back('n');
break;
case 0x0c:
/* \f form feed */
buf.push_back(0x5c);
buf.push_back('f');
break;
case 0x0d:
/* \r carriage return */
buf.push_back(0x5c);
buf.push_back('r');
break;
case 0x22:
/* \" double quote */
buf.push_back(0x5c);
buf.push_back(0x22);
break;
case 0x5c:
/* \\ backslash */
buf.push_back(0x5c);
buf.push_back(0x5c);
break;
default:
buf.push_back(0x5c);
buf.push_back(0x75); // 'u'
buf.push_back(toHexDigit((ch & 0xF000) >> 12));
buf.push_back(toHexDigit((ch & 0xF00) >> 8));
buf.push_back(toHexDigit((ch & 0xF0) >> 4));
buf.push_back(toHexDigit(ch & 0xF));
break;
}
start = ++index;
}
if (start < input.size())
{
buf.append(input, start, input.size() - start);
}
/* add trailing quote */
buf.push_back(0x22);
}
void JSONLayout::appendSerializedMDC(LogString& buf,
const LoggingEventPtr& event) const
{
LoggingEvent::KeySet keys = event->getMDCKeySet();
if (keys.empty())
{
return;
}
buf.append(LOG4CXX_STR(","));
buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
if (m_priv->prettyPrint)
{
buf.append(m_priv->ppIndentL1);
}
appendQuotedEscapedString(buf, LOG4CXX_STR("context_map"));
buf.append(LOG4CXX_STR(": {"));
buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
for (LoggingEvent::KeySet::iterator it = keys.begin();
it != keys.end(); ++it)
{
if (m_priv->prettyPrint)
{
buf.append(m_priv->ppIndentL2);
}
appendQuotedEscapedString(buf, *it);
buf.append(LOG4CXX_STR(": "));
LogString value;
event->getMDC(*it, value);
appendQuotedEscapedString(buf, value);
/* if this isn't the last k:v pair, we need a comma */
if (it + 1 != keys.end())
{
buf.append(LOG4CXX_STR(","));
buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
}
else
{
buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
}
}
if (m_priv->prettyPrint)
{
buf.append(m_priv->ppIndentL1);
}
buf.append(LOG4CXX_STR("}"));
}
void JSONLayout::appendSerializedNDC(LogString& buf,
const LoggingEventPtr& event) const
{
LogString ndcVal;
if (!event->getNDC(ndcVal))
{
return;
}
buf.append(LOG4CXX_STR(","));
buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
if (m_priv->prettyPrint)
{
buf.append(m_priv->ppIndentL1);
}
appendQuotedEscapedString(buf, LOG4CXX_STR("context_stack"));
buf.append(LOG4CXX_STR(": ["));
buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
if (m_priv->prettyPrint)
{
buf.append(m_priv->ppIndentL2);
}
appendQuotedEscapedString(buf, ndcVal);
buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
if (m_priv->prettyPrint)
{
buf.append(m_priv->ppIndentL1);
}
buf.append(LOG4CXX_STR("]"));
}
void JSONLayout::appendSerializedLocationInfo(LogString& buf,
const LoggingEventPtr& event, Pool& p) const
{
if (m_priv->prettyPrint)
{
buf.append(m_priv->ppIndentL1);
}
appendQuotedEscapedString(buf, LOG4CXX_STR("location_info"));
buf.append(LOG4CXX_STR(": {"));
buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
const LocationInfo& locInfo = event->getLocationInformation();
if (m_priv->prettyPrint)
{
buf.append(m_priv->ppIndentL2);
}
appendQuotedEscapedString(buf, LOG4CXX_STR("file"));
buf.append(LOG4CXX_STR(": "));
LOG4CXX_DECODE_CHAR(fileName, locInfo.getFileName());
appendQuotedEscapedString(buf, fileName);
buf.append(LOG4CXX_STR(","));
buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
if (m_priv->prettyPrint)
{
buf.append(m_priv->ppIndentL2);
}
appendQuotedEscapedString(buf, LOG4CXX_STR("line"));
buf.append(LOG4CXX_STR(": "));
LogString lineNumber;
StringHelper::toString(locInfo.getLineNumber(), p, lineNumber);
appendQuotedEscapedString(buf, lineNumber);
buf.append(LOG4CXX_STR(","));
buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
if (m_priv->prettyPrint)
{
buf.append(m_priv->ppIndentL2);
}
appendQuotedEscapedString(buf, LOG4CXX_STR("class"));
buf.append(LOG4CXX_STR(": "));
LOG4CXX_DECODE_CHAR(className, locInfo.getClassName());
appendQuotedEscapedString(buf, className);
buf.append(LOG4CXX_STR(","));
buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
if (m_priv->prettyPrint)
{
buf.append(m_priv->ppIndentL2);
}
appendQuotedEscapedString(buf, LOG4CXX_STR("method"));
buf.append(LOG4CXX_STR(": "));
LOG4CXX_DECODE_CHAR(methodName, locInfo.getMethodName());
appendQuotedEscapedString(buf, methodName);
buf.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
if (m_priv->prettyPrint)
{
buf.append(m_priv->ppIndentL1);
}
buf.append(LOG4CXX_STR("}"));
}