blob: 66ff09f3ce8ce2e9b00c75aace07d9fed22dade8 [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 <apr_time.h>
#include <apr_strings.h>
#include <string.h>
using namespace log4cxx;
using namespace log4cxx::helpers;
using namespace log4cxx::spi;
IMPLEMENT_LOG4CXX_OBJECT(JSONLayout)
JSONLayout::JSONLayout() :
locationInfo(false),
prettyPrint(false),
dateFormat(),
ppIndentL1(" "),
ppIndentL2(" ")
{
}
void JSONLayout::setOption(const LogString& option, const LogString& value)
{
if (StringHelper::equalsIgnoreCase(option,
LOG4CXX_STR("LOCATIONINFO"), LOG4CXX_STR("locationinfo")))
{
setLocationInfo(OptionConverter::toBoolean(value, false));
}
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.append("{");
output.append(prettyPrint ? "\n" : " ");
if (prettyPrint)
{
output.append(ppIndentL1);
}
appendQuotedEscapedString(output, "timestamp");
output.append(": ");
LogString timestamp;
dateFormat.format(timestamp, event->getTimeStamp(), p);
appendQuotedEscapedString(output, timestamp);
output.append(",");
output.append(prettyPrint ? "\n" : " ");
if (prettyPrint)
{
output.append(ppIndentL1);
}
appendQuotedEscapedString(output, "level");
output.append(": ");
LogString level;
event->getLevel()->toString(level);
appendQuotedEscapedString(output, level);
output.append(",");
output.append(prettyPrint ? "\n" : " ");
if (prettyPrint)
{
output.append(ppIndentL1);
}
appendQuotedEscapedString(output, "logger");
output.append(": ");
appendQuotedEscapedString(output, event->getLoggerName());
output.append(",");
output.append(prettyPrint ? "\n" : " ");
if (prettyPrint)
{
output.append(ppIndentL1);
}
appendQuotedEscapedString(output, "message");
output.append(": ");
appendQuotedEscapedString(output, event->getMessage());
appendSerializedMDC(output, event);
appendSerializedNDC(output, event);
if (locationInfo)
{
output.append(",");
output.append(prettyPrint ? "\n" : " ");
appendSerializedLocationInfo(output, event, p);
}
output.append(prettyPrint ? "\n" : " ");
output.append("}");
output.append(LOG4CXX_EOL);
}
void JSONLayout::appendQuotedEscapedString(LogString& buf,
const LogString& input) const
{
/* 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 */
};
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(",");
buf.append(prettyPrint ? "\n" : " ");
if (prettyPrint)
{
buf.append(ppIndentL1);
}
appendQuotedEscapedString(buf, "context_map");
buf.append(": {");
buf.append(prettyPrint ? "\n" : " ");
for (LoggingEvent::KeySet::iterator it = keys.begin();
it != keys.end(); ++it)
{
if (prettyPrint)
{
buf.append(ppIndentL2);
}
appendQuotedEscapedString(buf, *it);
buf.append(": ");
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(",");
buf.append(prettyPrint ? "\n" : " ");
}
else
{
buf.append(prettyPrint ? "\n" : " ");
}
}
if (prettyPrint)
{
buf.append(ppIndentL1);
}
buf.append("}");
}
void JSONLayout::appendSerializedNDC(LogString& buf,
const LoggingEventPtr& event) const
{
LogString ndcVal;
if (!event->getNDC(ndcVal))
{
return;
}
buf.append(",");
buf.append(prettyPrint ? "\n" : " ");
if (prettyPrint)
{
buf.append(ppIndentL1);
}
appendQuotedEscapedString(buf, "context_stack");
buf.append(": [");
buf.append(prettyPrint ? "\n" : " ");
if (prettyPrint)
{
buf.append(ppIndentL2);
}
appendQuotedEscapedString(buf, ndcVal);
buf.append(prettyPrint ? "\n" : " ");
if (prettyPrint)
{
buf.append(ppIndentL1);
}
buf.append("]");
}
void JSONLayout::appendSerializedLocationInfo(LogString& buf,
const LoggingEventPtr& event, Pool& p) const
{
if (prettyPrint)
{
buf.append(ppIndentL1);
}
appendQuotedEscapedString(buf, "location_info");
buf.append(": {");
buf.append(prettyPrint ? "\n" : " ");
const LocationInfo& locInfo = event->getLocationInformation();
if (prettyPrint)
{
buf.append(ppIndentL2);
}
appendQuotedEscapedString(buf, "file");
buf.append(": ");
LOG4CXX_DECODE_CHAR(fileName, locInfo.getFileName());
appendQuotedEscapedString(buf, fileName);
buf.append(",");
buf.append(prettyPrint ? "\n" : " ");
if (prettyPrint)
{
buf.append(ppIndentL2);
}
appendQuotedEscapedString(buf, "line");
buf.append(": ");
LogString lineNumber;
StringHelper::toString(locInfo.getLineNumber(), p, lineNumber);
appendQuotedEscapedString(buf, lineNumber);
buf.append(",");
buf.append(prettyPrint ? "\n" : " ");
if (prettyPrint)
{
buf.append(ppIndentL2);
}
appendQuotedEscapedString(buf, "class");
buf.append(": ");
appendQuotedEscapedString(buf, locInfo.getClassName());
buf.append(",");
buf.append(prettyPrint ? "\n" : " ");
if (prettyPrint)
{
buf.append(ppIndentL2);
}
appendQuotedEscapedString(buf, "method");
buf.append(": ");
appendQuotedEscapedString(buf, locInfo.getMethodName());
buf.append(prettyPrint ? "\n" : " ");
if (prettyPrint)
{
buf.append(ppIndentL1);
}
buf.append("}");
}