| /* | |
| * 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 <log4cxx/private/layout_priv.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( ) | |
| { | |
| m_priv->expectedPatternLength = priv::doubledLayoutSize(getFormattedEventCharacterCount()); | |
| } | |
| 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)); | |
| } | |
| else | |
| Layout::setOption(option, value); | |
| } | |
| void JSONLayout::format( LogString& output, const spi::LoggingEventPtr& event ) const | |
| { | |
| auto& lsMsg = event->getRenderedMessage(); | |
| priv::reserveFormattedEvent(output, 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()); | |
| 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); | |
| } | |
| 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); | |
| auto start = input.begin(); | |
| for (auto nextCodePoint = start; input.end() != nextCodePoint; ) | |
| { | |
| auto lastCodePoint = nextCodePoint; | |
| auto ch = static_cast<unsigned int>(*nextCodePoint); | |
| if (ch <= 0x7f) | |
| ++nextCodePoint; | |
| else | |
| ch = Transcoder::getCodePoint(input, nextCodePoint); | |
| if (0x22 == ch || 0x5c == ch) // double quote or backslash? | |
| ; | |
| else if (0x20 <= ch && 0xFFFD != ch) // not a control character or the replacement character? | |
| continue; | |
| if (start != lastCodePoint) | |
| buf.append(start, lastCodePoint); | |
| start = nextCodePoint; | |
| 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; | |
| } | |
| } | |
| buf.append(start, input.end()); | |
| /* 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) 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(), 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("}")); | |
| } | |