blob: d8d062d796875e42973ee2d082e445f41ca319f7 [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/iso8601dateformat.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),
dateFormat(),
ppIndentL1(LOG4CXX_STR(" ")),
ppIndentL2(LOG4CXX_STR(" ")),
expectedPatternLength(100),
threadInfo(false) {}
// Print no location info by default
bool locationInfo; //= false
bool prettyPrint; //= false
helpers::ISO8601DateFormat dateFormat;
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
{
output.reserve(m_priv->expectedPatternLength + event->getMessage().size());
output.append(LOG4CXX_STR("{"));
output.append(m_priv->prettyPrint ? LOG4CXX_EOL : LOG4CXX_STR(" "));
if (m_priv->prettyPrint)
{
output.append(m_priv->ppIndentL1);
}
appendQuotedEscapedString(output, LOG4CXX_STR("timestamp"));
output.append(LOG4CXX_STR(": "));
LogString timestamp;
m_priv->dateFormat.format(timestamp, event->getTimeStamp(), p);
appendQuotedEscapedString(output, timestamp);
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);
}
appendQuotedEscapedString(output, LOG4CXX_STR("level"));
output.append(LOG4CXX_STR(": "));
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);
}
appendQuotedEscapedString(output, LOG4CXX_STR("logger"));
output.append(LOG4CXX_STR(": "));
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);
}
appendQuotedEscapedString(output, LOG4CXX_STR("message"));
output.append(LOG4CXX_STR(": "));
appendQuotedEscapedString(output, event->getMessage());
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)
{
/* add leading quote */
buf.push_back(0x22);
logchar specialChars[] =
{
0x08, /* \b backspace */
0x09, /* \t tab */
0x0a, /* \n newline */
0x0c, /* \f form feed */
0x0d, /* \r carriage return */
0x22, /* \" double quote */
0x5c, /* \\ backslash */
0x00 /* terminating NULL for C-strings */
};
size_t start = 0;
size_t found = input.find_first_of(specialChars, start);
while (found != LogString::npos)
{
if (found > start)
{
buf.append(input, start, found - start);
}
switch (input[found])
{
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(input[found]);
break;
}
start = found + 1;
if (found < input.size())
{
found = input.find_first_of(specialChars, start);
}
else
{
found = LogString::npos;
}
}
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("}"));
}