Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Logging: JSONFormatter #4717

Merged
merged 2 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading