Skip to content

Commit

Permalink
add StringConvertibleTypeCodec and OstreamConvertibleTypeCodec
Browse files Browse the repository at this point in the history
  • Loading branch information
odygrd committed Feb 13, 2025
1 parent ce24a34 commit 335b6f5
Show file tree
Hide file tree
Showing 13 changed files with 680 additions and 9 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ set(HEADER_FILES
include/quill/bundled/fmt/xchar.h

include/quill/codec/CopyConstructibleTypeCodec.h
include/quill/codec/OstreamConvertibleTypeCodec.h
include/quill/codec/StringConvertibleTypeCodec.h
include/quill/codec/TriviallyCopyableTypeCodec.h

include/quill/core/Attributes.h
Expand Down
139 changes: 139 additions & 0 deletions docs/user-defined-types.drawio
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.3 Safari/605.1.15" version="26.0.13">
<diagram name="Page-1" id="NHgCSlD5--Cc5E9ydP2n">
<mxGraphModel dx="2168" dy="1183" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0" />
<mxCell id="1" parent="0" />
<mxCell id="IKCe0DnfbzoJhUQUdqvM-2" value="Is the Operation Performance Critical?" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" parent="1" vertex="1">
<mxGeometry x="260" y="360" width="180" height="170" as="geometry" />
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-3" value="" style="endArrow=none;html=1;rounded=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;entryPerimeter=0;" parent="1" source="IKCe0DnfbzoJhUQUdqvM-2" target="IKCe0DnfbzoJhUQUdqvM-8" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="390" y="390" as="sourcePoint" />
<mxPoint x="390" y="290" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-4" value="" style="endArrow=none;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="IKCe0DnfbzoJhUQUdqvM-2" target="IKCe0DnfbzoJhUQUdqvM-7" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="390" y="650" as="sourcePoint" />
<mxPoint x="650" y="440" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-5" value="No (e.g., LOG_DEBUG&amp;nbsp;&lt;div&gt;or during initialization)&lt;/div&gt;" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=1;points=[];movable=1;rotatable=1;deletable=1;editable=1;locked=0;connectable=1;" parent="IKCe0DnfbzoJhUQUdqvM-4" vertex="1" connectable="0">
<mxGeometry relative="1" as="geometry">
<mxPoint x="-5" y="-25" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-7" value="Convert to a String on the Hot Path" style="rounded=1;whiteSpace=wrap;html=1;absoluteArcSize=1;arcSize=14;strokeWidth=2;" parent="1" vertex="1">
<mxGeometry x="570" y="395" width="200" height="100" as="geometry" />
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-8" value="&lt;span style=&quot;caret-color: rgb(0, 0, 0); color: rgb(0, 0, 0); font-family: Helvetica; font-size: 12px; font-style: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: auto; text-align: center; text-indent: 0px; text-transform: none; white-space: normal; widows: auto; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(251, 251, 251); text-decoration: none; display: inline !important; float: none;&quot;&gt;Logging STL or User-Defined Type&lt;/span&gt;" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.start_1;whiteSpace=wrap;" parent="1" vertex="1">
<mxGeometry x="285" y="140" width="130" height="110" as="geometry" />
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-10" value="Use StringConvertibleTypeCodec for conversion&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&amp;nbsp;template &amp;lt;&amp;gt; &amp;nbsp;struct fmtquill::formatter&amp;lt;UserType&amp;gt; &amp;nbsp;{ ... };&lt;/div&gt;&lt;div&gt;&amp;nbsp;&lt;/div&gt;&lt;div&gt;template &amp;lt;&amp;gt; struct quill::Codec&amp;lt;UserType&amp;gt; : StringConvertibleTypeCodec&amp;lt;UserType&amp;gt; { };&lt;/div&gt;&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="860" y="380" width="540" height="130" as="geometry" />
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-11" value="Use OstreamConvertibleTypeCodec for conversion&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;emplate &amp;lt;&amp;gt; struct quill::Codec&amp;lt;UserType&amp;gt; : OstreamConvertibleTypeCodec&amp;lt;UserType&amp;gt; { };&lt;br&gt;&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="861" y="220" width="540" height="130" as="geometry" />
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-12" value="Convert Explicitly e.g. using format()&lt;br&gt;&lt;div&gt;User u {};&lt;br&gt;&lt;div&gt;LOG_INFO(l, &quot;{}&quot;, std::format(&quot;{}&quot; u));&lt;/div&gt;&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="860" y="540" width="540" height="130" as="geometry" />
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-13" value="" style="endArrow=none;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="IKCe0DnfbzoJhUQUdqvM-7" target="IKCe0DnfbzoJhUQUdqvM-11" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="240" y="390" as="sourcePoint" />
<mxPoint x="290" y="340" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-14" value="" style="endArrow=none;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="IKCe0DnfbzoJhUQUdqvM-7" target="IKCe0DnfbzoJhUQUdqvM-10" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="720" y="450" as="sourcePoint" />
<mxPoint x="870" y="280" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-15" value="" style="endArrow=none;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="IKCe0DnfbzoJhUQUdqvM-7" target="IKCe0DnfbzoJhUQUdqvM-12" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="720" y="450" as="sourcePoint" />
<mxPoint x="870" y="460" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-16" value="" style="endArrow=none;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="IKCe0DnfbzoJhUQUdqvM-2" target="IKCe0DnfbzoJhUQUdqvM-18" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="450" y="450" as="sourcePoint" />
<mxPoint x="390" y="650" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-17" value="Yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=1;points=[];movable=1;rotatable=1;deletable=1;editable=1;locked=0;connectable=1;" parent="IKCe0DnfbzoJhUQUdqvM-16" vertex="1" connectable="0">
<mxGeometry relative="1" as="geometry">
<mxPoint x="-20" y="-100" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-18" value="Pass a binary copy, deferring string conversion to the backend logging thread" style="rounded=1;whiteSpace=wrap;html=1;absoluteArcSize=1;arcSize=14;strokeWidth=2;" parent="1" vertex="1">
<mxGeometry x="250" y="750" width="200" height="100" as="geometry" />
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-19" value="TriviallyCopyableTypeCodec&lt;div&gt;&lt;br&gt;&lt;div&gt;Simplest and most efficient method for trivially copiable user defined types&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp;template &amp;lt;&amp;gt; &amp;nbsp;struct fmtquill::formatter&amp;lt;UserType&amp;gt; &amp;nbsp;{ ... };&lt;/div&gt;&lt;div&gt;&amp;nbsp;&lt;/div&gt;&lt;div&gt;template &amp;lt;&amp;gt; struct quill::Codec&amp;lt;UserType&amp;gt; : TriviallyCopyableTypeCodec&amp;lt;UserType&amp;gt; { };&lt;br&gt;&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="860" y="780" width="540" height="130" as="geometry" />
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-20" value="STL Type" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" parent="1" vertex="1">
<mxGeometry x="300" y="950" width="100" height="100" as="geometry" />
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-21" value="" style="endArrow=none;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="IKCe0DnfbzoJhUQUdqvM-18" target="IKCe0DnfbzoJhUQUdqvM-20" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="399.5" y="790" as="sourcePoint" />
<mxPoint x="350" y="870" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-24" value="" style="endArrow=none;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="IKCe0DnfbzoJhUQUdqvM-20" target="IKCe0DnfbzoJhUQUdqvM-26" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="490" y="940" as="sourcePoint" />
<mxPoint x="540" y="910" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-25" value="Yes" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=1;points=[];movable=1;rotatable=1;deletable=1;editable=1;locked=0;connectable=1;" parent="IKCe0DnfbzoJhUQUdqvM-24" vertex="1" connectable="0">
<mxGeometry relative="1" as="geometry">
<mxPoint x="-20" y="-55" as="offset" />
</mxGeometry>
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-26" value="Include header from quill/std for STL there is probably already support on how to serialise it&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;e.g. #include &amp;lt;quill/std/Vector&amp;gt;&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="185" y="1180" width="330" height="100" as="geometry" />
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-27" value="" style="endArrow=none;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="IKCe0DnfbzoJhUQUdqvM-20" target="IKCe0DnfbzoJhUQUdqvM-29" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="409.5" y="800" as="sourcePoint" />
<mxPoint x="590" y="910" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-28" value="No" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=1;points=[];movable=1;rotatable=1;deletable=1;editable=1;locked=0;connectable=1;" parent="1" vertex="1" connectable="0">
<mxGeometry x="414" y="990" as="geometry" />
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-29" value="User Defined Type Codec Required" style="rounded=1;whiteSpace=wrap;html=1;absoluteArcSize=1;arcSize=14;strokeWidth=2;" parent="1" vertex="1">
<mxGeometry x="570" y="950" width="200" height="100" as="geometry" />
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-30" value="" style="endArrow=none;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="IKCe0DnfbzoJhUQUdqvM-29" target="IKCe0DnfbzoJhUQUdqvM-19" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="720" y="450" as="sourcePoint" />
<mxPoint x="870" y="280" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-31" value="CopyConstructibleTypeCodec: Copy Object via Copy Constructor&lt;div&gt;&lt;br&gt;&lt;div&gt;Copies the object using its copy constructor. Less efficient than copy members one by one in place using a custom codec, but still very good especially for very complex types&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&amp;nbsp;template &amp;lt;&amp;gt; &amp;nbsp;struct fmtquill::formatter&amp;lt;UserType&amp;gt; &amp;nbsp;{ ... };&lt;/div&gt;&lt;div&gt;&amp;nbsp;&lt;/div&gt;&lt;div&gt;template &amp;lt;&amp;gt; struct quill::Codec&amp;lt;UserType&amp;gt; : CopyConstructibleTypeCodec&amp;lt;UserType&amp;gt; { };&lt;/div&gt;&lt;/div&gt;" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="860" y="935" width="540" height="130" as="geometry" />
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-32" value="&lt;div&gt;Create Custom Codec&lt;/div&gt;&lt;div&gt;&lt;br&gt;&lt;/div&gt;Provide your own codec which copies all members of the class in place in the spec buffer. This is a faster alternative to CopyConstructibleCodec when for example you object has members like std::vector, avoiding any extra memory allocations of the copy constructor" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="860" y="1100" width="540" height="130" as="geometry" />
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-33" value="" style="endArrow=none;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="IKCe0DnfbzoJhUQUdqvM-29" target="IKCe0DnfbzoJhUQUdqvM-31" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="660" y="920" as="sourcePoint" />
<mxPoint x="870" y="850" as="targetPoint" />
</mxGeometry>
</mxCell>
<mxCell id="IKCe0DnfbzoJhUQUdqvM-34" value="" style="endArrow=none;html=1;rounded=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="IKCe0DnfbzoJhUQUdqvM-29" target="IKCe0DnfbzoJhUQUdqvM-32" edge="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="660" y="920" as="sourcePoint" />
<mxPoint x="870" y="1030" as="targetPoint" />
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
5 changes: 4 additions & 1 deletion examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@ set(EXAMPLE_TARGETS
quill_example_json_console_logging_custom_json
quill_example_csv_writing
quill_example_json_file_logging
quill_example_user_defined_types_logging_explicit_string_conversion
quill_example_user_defined_types_logging_copy_constructible_type
quill_example_user_defined_types_logging_explicit_string_conversion
quill_example_user_defined_types_logging_ostream_conversion
quill_example_user_defined_types_logging_string_convertible_type
quill_example_user_defined_types_logging_trivially_copyable_type
)

# Add additional examples that are using exceptions in the example code
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
#include "quill/sinks/ConsoleSink.h"

#include <cstdint>
#include <sstream>
#include <string>
#include <utility>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/Logger.h"
#include "quill/bundled/fmt/ostream.h"
#include "quill/sinks/ConsoleSink.h"

#include <cstdint>
Expand Down
53 changes: 53 additions & 0 deletions examples/user_defined_types_logging_ostream_conversion.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/Logger.h"
#include "quill/codec/OstreamConvertibleTypeCodec.h"
#include "quill/sinks/ConsoleSink.h"

#include <cstdint>
#include <sstream>
#include <string>
#include <utility>

/**
* This example illustrates logging user-defined types by converting them to string on the hot path explicitly
*/

class User
{
public:
User(std::string name, std::string surname, uint32_t age)
: name(std::move(name)), surname(std::move(surname)), age(age) {};

friend std::ostream& operator<<(std::ostream& os, User const& obj)
{
os << "name: " << obj.name << ", surname: " << obj.surname << ", age: " << obj.age;
return os;
}

private:
std::string name;
std::string surname;
uint32_t age;
};

template <>
struct quill::Codec<User> : quill::OstreamConvertibleTypeCodec<User>
{
};

int main()
{
// Start the backend thread
quill::BackendOptions backend_options;
quill::Backend::start(backend_options);

// Frontend
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink));

User user_1{"Super", "User", 1};

LOG_INFO(logger, "User is [{}]", user_1);
}
71 changes: 71 additions & 0 deletions examples/user_defined_types_logging_string_convertible_type.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#include "quill/Backend.h"
#include "quill/Frontend.h"
#include "quill/LogMacros.h"
#include "quill/Logger.h"
#include "quill/bundled/fmt/ranges.h"
#include "quill/codec/StringConvertibleTypeCodec.h"
#include "quill/sinks/ConsoleSink.h"

#include <cstdint>
#include <string>
#include <utility>

/**
* This example illustrates logging user-defined types by implicitly converting it to a string in the hot path
*/

class User
{
public:
User(std::string name, std::string surname, uint32_t age)
: name(std::move(name)), surname(std::move(surname)), age(age)
{
favorite_colors.push_back("red");
favorite_colors.push_back("blue");
favorite_colors.push_back("green");
};

std::string name;
std::string surname;
uint32_t age;
std::vector<std::string> favorite_colors;
};

/***/
template <>
struct fmtquill::formatter<User>
{
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }

auto format(::User const& user, format_context& ctx) const
{
return fmtquill::format_to(ctx.out(), "Name: {}, Surname: {}, Age: {}, Favorite Colors: {}",
user.name, user.surname, user.age, user.favorite_colors);
}
};

/***/
template <>
struct quill::Codec<User> : quill::StringConvertibleTypeCodec<User>
{
};

/***/
int main()
{
// Start the backend thread
quill::BackendOptions backend_options;
quill::Backend::start(backend_options);

// Frontend
auto console_sink = quill::Frontend::create_or_get_sink<quill::ConsoleSink>("sink_id_1");
quill::Logger* logger = quill::Frontend::create_or_get_logger("root", std::move(console_sink));

User user_1{"Super", "User", 1};

LOG_INFO(logger, "User is [{}]", user_1);

User user_2{"Another", "User", 12};

LOG_INFO(logger, "User is [{}]", user_2);
}
Loading

0 comments on commit 335b6f5

Please sign in to comment.