microfmt
Zero-allocation C++ formatting for constrained environments
Loading...
Searching...
No Matches
systemd_sink.hpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Jarosław Pelczar <jarek@jpelczar.com>
2//
3// SPDX-License-Identifier: BSD-2-Clause
4
5#pragma once
6
9#include "../log/sink.hpp"
10#include <cstddef>
11#include <string_view>
12#include <sys/uio.h>
13#include <syslog.h>
14#include <systemd/sd-daemon.h>
15#include <systemd/sd-journal.h>
16
17namespace microfmt::log {
18
19template <std::size_t MessageCapacity, std::size_t IdentifierCapacity> class systemd_sink;
20
22template <std::size_t MessageCapacity, std::size_t IdentifierCapacity> struct systemd_sink_tag {};
23
24template <std::size_t MessageCapacity = 512, std::size_t IdentifierCapacity = 64>
27 static_assert(MessageCapacity > 0, "Message capacity must be at least 1 byte");
28 static_assert(IdentifierCapacity > 0, "Identifier capacity must be at least 1 byte");
29
30public:
31 using write_fn_t = void (*)(int priority, microfmt::string_view identifier, microfmt::string_view message) noexcept;
32
33 explicit constexpr systemd_sink(write_fn_t write_fn = write_to_journal) noexcept : write_fn_(write_fn) {}
34
35 [[nodiscard]] log_sink as_sink() noexcept RELOCO_LIFETIMEBOUND {
37 }
38
39 void log_impl(const log_msg &msg) noexcept {
40 if (msg.lvl != level::off) {
41 write_fn_(priority_for(msg.lvl), msg.logger_name, msg.payload);
42 }
43 }
44
45private:
46 static void write_to_journal(int priority, microfmt::string_view identifier, microfmt::string_view message) noexcept {
47 if (::sd_booted() <= 0) {
48 write_to_stdio(identifier, message);
49 return;
50 }
51
52 char priority_field[] = "PRIORITY=0";
53 priority_field[9] = static_cast<char>('0' + priority);
54
56 const auto identifier_out = identifier_field.as_sink();
57 identifier_out.write("SYSLOG_IDENTIFIER=");
58 identifier_out.write(identifier.empty() ? "microfmt" : identifier);
59
61 const auto message_out = message_field.as_sink();
62 message_out.write("MESSAGE=");
63 message_out.write(message);
64
65 const auto identifier_view = identifier_field.view();
66 const auto message_view = message_field.view();
67 const iovec fields[] = {
68 {priority_field, sizeof(priority_field) - 1},
69 {const_cast<char *>(identifier_view.data()), identifier_view.size()},
70 {const_cast<char *>(message_view.data()), message_view.size()},
71 };
72 if (::sd_journal_sendv(fields, 3) < 0) {
73 write_to_stdio(identifier, message);
74 }
75 }
76
77 static void write_to_stdio(microfmt::string_view identifier, microfmt::string_view message) noexcept {
78 buffer_sink<MessageCapacity + IdentifierCapacity + 4> output;
79 const auto out = output.as_sink();
80 if (!identifier.empty()) {
81 microfmt::format_to(out, "[{}] ", identifier);
82 }
83 microfmt::format_to(out, "{}\n", message);
84 stdout_sink().write(output.view());
85 }
86
87 static int priority_for(level lvl) noexcept {
88 switch (lvl) {
89 case level::trace:
90 case level::debug:
91 return LOG_DEBUG;
92 case level::info:
93 return LOG_INFO;
94 case level::warn:
95 return LOG_WARNING;
96 case level::err:
97 return LOG_ERR;
98 case level::critical:
99 return LOG_CRIT;
100 case level::off:
101 return LOG_DEBUG;
102 }
103 return LOG_DEBUG;
104 }
105
106 write_fn_t write_fn_;
107};
108
109// Explicit guides: GCC 11 cannot synthesize the implicit CTAD guide because the
110// constructor's default argument (write_to_journal) names a member of the
111// still-incomplete class template.
113systemd_sink(void (*)(int, microfmt::string_view, microfmt::string_view) noexcept) -> systemd_sink<>;
114
115template <std::size_t MessageCapacity, std::size_t IdentifierCapacity>
116struct log_sink_traits<systemd_sink_tag<MessageCapacity, IdentifierCapacity>> {
118
119 static void log(value_ref<context_type> ctx, const log_msg &msg) noexcept { ctx->log_impl(msg); }
120};
121
122} // namespace microfmt::log
Output sink that stores formatted characters in an internal fixed-size stack/inline buffer.
Definition microfmt.hpp:185
constexpr microfmt::string_view view() const &noexcept RELOCO_LIFETIMEBOUND
Returns a string view over the characters written so far.
Definition microfmt.hpp:245
RELOCO_END_UNSAFE_BUFFER_USAGE RELOCO_CONSTEXPR20 sink as_sink() &noexcept RELOCO_LIFETIMEBOUND
Creates a type-erased sink adapter pointing to this instance.
Definition microfmt.hpp:236
Type-erased, two-word handle to a log sink backend, plus the severity threshold below which records a...
Definition sink.hpp:73
Adapter that writes structured records to the systemd journal.
Definition systemd_sink.hpp:26
void log_impl(const log_msg &msg) noexcept
Definition systemd_sink.hpp:39
void(*)(int priority, microfmt::string_view identifier, microfmt::string_view message) noexcept write_fn_t
Definition systemd_sink.hpp:31
log_sink as_sink() noexcept RELOCO_LIFETIMEBOUND
Definition systemd_sink.hpp:35
constexpr systemd_sink(write_fn_t write_fn=write_to_journal) noexcept
Definition systemd_sink.hpp:33
Definition default_log_sink.template.hpp:51
level
Shared log severity levels in ascending order.
Definition log_msg.hpp:22
systemd_sink() -> systemd_sink<>
RELOCO_END_UNSAFE_CODE sink stdout_sink() noexcept
Definition stdio.hpp:36
RELOCO_CONSTEXPR20 void format_to(const sink &out, microfmt::string_view fmt, const Args &...args) noexcept
Definition microfmt.hpp:1766
Structured log record forwarded by a logger to each sink.
Definition log_msg.hpp:69
static void log(value_ref< context_type > ctx, const log_msg &msg) noexcept
Definition systemd_sink.hpp:119
Static customization point describing a log sink backend.
Definition sink.hpp:46
Tag selecting systemd_sink<MessageCapacity, IdentifierCapacity> as a log_sink backend.
Definition systemd_sink.hpp:22