Skip to content

Commit

Permalink
Merge pull request #4717 from pocoproject/feat/json-logging
Browse files Browse the repository at this point in the history
Logging: JSONFormatter
  • Loading branch information
obiltschnig authored Sep 27, 2024
2 parents d911539 + 32bf4e6 commit a525065
Show file tree
Hide file tree
Showing 16 changed files with 462 additions and 3 deletions.
2 changes: 2 additions & 0 deletions Foundation/Foundation_vs160.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,7 @@
<ClCompile Include="src\inflate.c" />
<ClCompile Include="src\InflatingStream.cpp" />
<ClCompile Include="src\inftrees.c" />
<ClCompile Include="src\JSONFormatter.cpp" />
<ClCompile Include="src\JSONString.cpp" />
<ClCompile Include="src\Latin1Encoding.cpp" />
<ClCompile Include="src\Latin2Encoding.cpp" />
Expand Down Expand Up @@ -1637,6 +1638,7 @@
<ClInclude Include="include\Poco\HMACEngine.h" />
<ClInclude Include="include\Poco\InflatingStream.h" />
<ClInclude Include="include\Poco\Instantiator.h" />
<ClInclude Include="include\Poco\JSONFormatter.h" />
<ClInclude Include="include\Poco\JSONString.h" />
<ClInclude Include="include\Poco\KeyValueArgs.h" />
<ClInclude Include="include\Poco\Latin1Encoding.h" />
Expand Down
6 changes: 6 additions & 0 deletions Foundation/Foundation_vs160.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,9 @@
<ClCompile Include="src\utf8proc_data.c">
<Filter>Text\Utf8Proc Source Files</Filter>
</ClCompile>
<ClCompile Include="src\JSONFormatter.cpp">
<Filter>Logging\Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="include\Poco\Any.h">
Expand Down Expand Up @@ -1896,6 +1899,9 @@
<ClInclude Include="src\utf8proc.h">
<Filter>Text\Utf8Proc Header Files</Filter>
</ClInclude>
<ClInclude Include="include\Poco\JSONFormatter.h">
<Filter>Logging\Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="src\pocomsg.rc">
Expand Down
2 changes: 2 additions & 0 deletions Foundation/Foundation_vs170.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1275,6 +1275,7 @@
<ClCompile Include="src\inflate.c" />
<ClCompile Include="src\InflatingStream.cpp" />
<ClCompile Include="src\inftrees.c" />
<ClCompile Include="src\JSONFormatter.cpp" />
<ClCompile Include="src\JSONString.cpp" />
<ClCompile Include="src\Latin1Encoding.cpp" />
<ClCompile Include="src\Latin2Encoding.cpp" />
Expand Down Expand Up @@ -2255,6 +2256,7 @@
<ClInclude Include="include\Poco\HMACEngine.h" />
<ClInclude Include="include\Poco\InflatingStream.h" />
<ClInclude Include="include\Poco\Instantiator.h" />
<ClInclude Include="include\Poco\JSONFormatter.h" />
<ClInclude Include="include\Poco\JSONString.h" />
<ClInclude Include="include\Poco\KeyValueArgs.h" />
<ClInclude Include="include\Poco\Latin1Encoding.h" />
Expand Down
6 changes: 6 additions & 0 deletions Foundation/Foundation_vs170.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,9 @@
<ClCompile Include="src\utf8proc_data.c">
<Filter>Text\Utf8Proc Source Files</Filter>
</ClCompile>
<ClCompile Include="src\JSONFormatter.cpp">
<Filter>Logging\Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="include\Poco\Any.h">
Expand Down Expand Up @@ -1896,6 +1899,9 @@
<ClInclude Include="src\utf8proc.h">
<Filter>Text\Utf8Proc Header Files</Filter>
</ClInclude>
<ClInclude Include="include\Poco\JSONFormatter.h">
<Filter>Logging\Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="src\pocomsg.rc">
Expand Down
2 changes: 1 addition & 1 deletion Foundation/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ objects = ArchiveStrategy Ascii ASCIIEncoding AsyncChannel AsyncNotificationCent
NestedDiagnosticContext Notification NotificationCenter \
NotificationQueue PriorityNotificationQueue TimedNotificationQueue \
NullStream NumberFormatter NumberParser NumericString AbstractObserver \
Path PatternFormatter PIDFile Process ProcessRunner PurgeStrategy RWLock Random RandomStream \
Path PatternFormatter JSONFormatter PIDFile Process ProcessRunner PurgeStrategy RWLock Random RandomStream \
DirectoryIteratorStrategy RegularExpression RefCountedObject Runnable RotateStrategy \
SHA1Engine SHA2Engine Semaphore SharedLibrary SimpleFileChannel \
SignalHandler SplitterChannel SortedDirectoryIterator Stopwatch StreamChannel \
Expand Down
109 changes: 109 additions & 0 deletions Foundation/include/Poco/JSONFormatter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//
// JSONFormatter.h
//
// Library: Foundation
// Package: Logging
// Module: JSONFormatter
//
// Definition of the JSONFormatter class.
//
// Copyright (c) 2024, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//


#ifndef Foundation_JSONFormatter_INCLUDED
#define Foundation_JSONFormatter_INCLUDED


#include "Poco/Foundation.h"
#include "Poco/Formatter.h"
#include "Poco/Message.h"
#include <vector>


namespace Poco {


class Foundation_API JSONFormatter: public Formatter
/// This formatter formats log messages as compact
/// (no unnecessary whitespace) single-line JSON strings.
///
/// The following JSON schema is used:
/// {
/// "timestamp": "2024-09-26T13:41:23.324461Z",
/// "source": "sample",
/// "level": "information",
/// "message": "This is a test message.",
/// "thread": 12,
/// "file": "source.cpp",
/// "line": 456,
/// "params": {
/// "prop1": "value1"
/// }
/// }
///
/// The "file" and "line" properties will only be included if the log
/// message contains a file name and line number.
///
/// The "params" object will only be included if custom parameters
/// have been added to the Message.
{
public:
using Ptr = AutoPtr<JSONFormatter>;

JSONFormatter() = default;
/// Creates a JSONFormatter.

~JSONFormatter() = default;
/// Destroys the JSONFormatter.

void format(const Message& msg, std::string& text);
/// Formats the message as a JSON string.

void setProperty(const std::string& name, const std::string& value);
/// Sets the property with the given name to the given value.
///
/// The following properties are supported:
///
/// * times: Specifies whether times are adjusted for local time
/// or taken as they are in UTC. Supported values are "local" and "UTC".
/// * thread: Specifies the value given for the thread. Can be
/// "none" (excluded), "name" (thread name), "id" (POCO thread ID) or "osid"
/// (operating system thread ID).
///
/// If any other property name is given, a PropertyNotSupported
/// exception is thrown.

std::string getProperty(const std::string& name) const;
/// Returns the value of the property with the given name or
/// throws a PropertyNotSupported exception if the given
/// name is not recognized.

static const std::string PROP_TIMES;
static const std::string PROP_THREAD;

protected:
std::string getThread(const Message& message) const;
static const std::string& getPriorityName(int prio);

enum ThreadFormat
{
JSONF_THREAD_NONE = 0,
JSONF_THREAD_NAME = 1,
JSONF_THREAD_ID = 2,
JSONF_THREAD_OS_ID = 3
};

private:
bool _localTime = false;
ThreadFormat _threadFormat = JSONF_THREAD_ID;
};


} // namespace Poco


#endif // Foundation_JSONFormatter_INCLUDED
182 changes: 182 additions & 0 deletions Foundation/src/JSONFormatter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
//
// JSONFormatter.cpp
//
// Library: Foundation
// Package: Logging
// Module: JSONFormatter
//
// Copyright (c) 2024, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//


#include "Poco/JSONFormatter.h"
#include "Poco/JSONString.h"
#include "Poco/Message.h"
#include "Poco/String.h"
#include "Poco/JSONString.h"
#include "Poco/NumberFormatter.h"
#include "Poco/DateTimeFormatter.h"
#include "Poco/DateTimeFormat.h"
#include "Poco/Timezone.h"


namespace Poco {


const std::string JSONFormatter::PROP_TIMES("times");
const std::string JSONFormatter::PROP_THREAD("thread");


void JSONFormatter::format(const Message& msg, std::string& text)
{
Timestamp timestamp = msg.getTime();
int tzd = DateTimeFormatter::UTC;
if (_localTime)
{
tzd = Timezone::utcOffset();
tzd += Timezone::dst();
timestamp += tzd*Timestamp::resolution();
}

text += '{';
text += "\"timestamp\":\"";
text += Poco::DateTimeFormatter::format(timestamp, Poco::DateTimeFormat::ISO8601_FRAC_FORMAT, tzd);
text += "\",\"source\":";
text += toJSON(msg.getSource());
text += ",\"level\":\"";
text += getPriorityName(msg.getPriority());
text += "\",\"message\":";
text += toJSON(msg.getText());
if (_threadFormat != JSONF_THREAD_NONE)
{
text += ",\"thread\":";
text += getThread(msg);
}
if (msg.getSourceFile())
{
text += ",\"file\":";
text += toJSON(msg.getSourceFile());
}
if (msg.getSourceLine())
{
text += ",\"line\":\"";
text += Poco::NumberFormatter::format(msg.getSourceLine());
text += "\"";
}
if (!msg.getAll().empty())
{
text += ",\"params\":{";
const auto& props = msg.getAll();
bool first = true;
for (const auto& p: props)
{
if (!first)
text += ',';
else
first = false;
text += toJSON(p.first);
text += ':';
text += toJSON(p.second);
}
text += '}';
}
text += '}';
}


void JSONFormatter::setProperty(const std::string& name, const std::string& value)
{
if (name == PROP_TIMES)
{
if (Poco::icompare(value, "local"s) == 0)
_localTime = true;
else if (Poco::icompare(value, "utc"s) == 0)
_localTime = false;
else
throw Poco::InvalidArgumentException("Invalid times value (must be local or UTC)"s, value);
}
else if (name == PROP_THREAD)
{
if (Poco::icompare(value, "none"s) == 0)
_threadFormat = JSONF_THREAD_NONE;
else if (Poco::icompare(value, "name"s) == 0)
_threadFormat = JSONF_THREAD_NAME;
else if (Poco::icompare(value, "id"s) == 0)
_threadFormat = JSONF_THREAD_ID;
else if (Poco::icompare(value, "osid"s) == 0)
_threadFormat = JSONF_THREAD_OS_ID;
else
throw Poco::InvalidArgumentException("Invalid thread value (must be name, id or osID)"s, value);
}
else throw Poco::PropertyNotSupportedException(name);
}


std::string JSONFormatter::getProperty(const std::string& name) const
{
if (name == PROP_TIMES)
{
return _localTime ? "local"s : "UTC"s;
}
else if (name == PROP_THREAD)
{
switch (_threadFormat)
{
case JSONF_THREAD_NONE:
return "none"s;
case JSONF_THREAD_NAME:
return "name"s;
case JSONF_THREAD_ID:
return "id"s;
case JSONF_THREAD_OS_ID:
return "osID"s;
default:
return "invalid"s;
}
}
else throw Poco::PropertyNotSupportedException(name);
}


std::string JSONFormatter::getThread(const Message& message) const
{
switch (_threadFormat)
{
case JSONF_THREAD_NONE:
return ""s;
case JSONF_THREAD_NAME:
return toJSON(message.getThread());
case JSONF_THREAD_ID:
return Poco::NumberFormatter::format(message.getTid());
case JSONF_THREAD_OS_ID:
return Poco::NumberFormatter::format(message.getOsTid());
default:
return ""s;
}
}


const std::string& JSONFormatter::getPriorityName(int prio)
{
static const std::string PRIORITY_NAMES[] = {
"none"s,
"fatal"s,
"critical"s,
"error"s,
"warning"s,
"notice"s,
"information"s,
"debug"s,
"trace"
};

poco_assert (prio >= Message::PRIO_FATAL && prio <= Message::PRIO_TRACE);

return PRIORITY_NAMES[prio];
}


} // namespace Poco
2 changes: 2 additions & 0 deletions Foundation/src/LoggingFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "Poco/WindowsConsoleChannel.h"
#endif
#include "Poco/PatternFormatter.h"
#include "Poco/JSONFormatter.h"


using namespace std::string_literals;
Expand Down Expand Up @@ -112,6 +113,7 @@ void LoggingFactory::registerBuiltins()
#endif

_formatterFactory.registerClass("PatternFormatter"s, new Instantiator<PatternFormatter, Formatter>);
_formatterFactory.registerClass("JSONFormatter"s, new Instantiator<JSONFormatter, Formatter>);
}


Expand Down
Loading

0 comments on commit a525065

Please sign in to comment.