]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/common/Journald.cc
import quincy beta 17.1.0
[ceph.git] / ceph / src / common / Journald.cc
diff --git a/ceph/src/common/Journald.cc b/ceph/src/common/Journald.cc
new file mode 100644 (file)
index 0000000..e3ae2d3
--- /dev/null
@@ -0,0 +1,318 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "Journald.h"
+
+#include <endian.h>
+#include <fcntl.h>
+#include <iterator>
+#include <memory>
+#include <string>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+#include <syslog.h>
+#include <unistd.h>
+#include <fmt/format.h>
+#include <fmt/ostream.h>
+
+#include "include/ceph_assert.h"
+#include "common/LogEntry.h"
+#include "log/Entry.h"
+#include "log/SubsystemMap.h"
+
+
+namespace ceph::logging {
+
+namespace {
+const struct sockaddr_un sockaddr = {
+  AF_UNIX,
+  "/run/systemd/journal/socket",
+};
+
+ssize_t sendmsg_fd(int transport_fd, int fd)
+{
+  constexpr size_t control_len = CMSG_LEN(sizeof(int));
+  char control[control_len];
+  struct msghdr mh = {
+    (struct sockaddr*)&sockaddr, // msg_name
+    sizeof(sockaddr),            // msg_namelen
+    nullptr,                     // msg_iov
+    0,                           // msg_iovlen
+    &control,                    // msg_control
+    control_len,                 // msg_controllen
+  };
+  ceph_assert(transport_fd >= 0);
+
+  struct cmsghdr *cmsg = CMSG_FIRSTHDR(&mh);
+  cmsg->cmsg_level = SOL_SOCKET;
+  cmsg->cmsg_type = SCM_RIGHTS;
+  cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+  *reinterpret_cast<int *>(CMSG_DATA(cmsg)) = fd;
+
+  return sendmsg(transport_fd, &mh, MSG_NOSIGNAL);
+}
+
+char map_prio(short ceph_prio)
+{
+  if (ceph_prio < 0)
+    return LOG_ERR;
+  if (ceph_prio == 0)
+    return LOG_WARNING;
+  if (ceph_prio < 5)
+    return LOG_NOTICE;
+  if (ceph_prio < 10)
+    return LOG_INFO;
+  return LOG_DEBUG;
+}
+}
+
+namespace detail {
+class EntryEncoderBase {
+ public:
+  EntryEncoderBase():
+    m_msg_vec {
+     {}, {}, {}, { (char *)"\n", 1 },
+    }
+  {
+    std::string id = program_invocation_short_name;
+    for (auto& c : id) {
+      if (c == '\n')
+        c = '_';
+    }
+    static_segment = "SYSLOG_IDENTIFIER=" + id + "\n";
+    m_msg_vec[0].iov_base = static_segment.data();
+    m_msg_vec[0].iov_len = static_segment.size();
+  }
+
+  constexpr struct iovec *iovec() { return this->m_msg_vec; }
+  constexpr std::size_t iovec_len()
+  {
+    return sizeof(m_msg_vec) / sizeof(m_msg_vec[0]);
+  }
+
+ private:
+  struct iovec m_msg_vec[4];
+  std::string static_segment;
+
+ protected:
+  fmt::memory_buffer meta_buf;
+
+  struct iovec &meta_vec() { return m_msg_vec[1]; }
+  struct iovec &msg_vec() { return m_msg_vec[2]; }
+};
+
+class EntryEncoder : public EntryEncoderBase {
+ public:
+  void encode(const Entry& e, const SubsystemMap *s)
+  {
+    meta_buf.clear();
+    fmt::format_to(std::back_inserter(meta_buf),
+      R"(PRIORITY={:d}
+CEPH_SUBSYS={}
+TIMESTAMP={}
+CEPH_PRIO={}
+THREAD={:016x}
+MESSAGE
+)",
+      map_prio(e.m_prio),
+      s->get_name(e.m_subsys),
+      e.m_stamp.time_since_epoch().count().count,
+      e.m_prio,
+      e.m_thread);
+
+    uint64_t msg_len = htole64(e.size());
+    meta_buf.resize(meta_buf.size() + sizeof(msg_len));
+    *(reinterpret_cast<uint64_t*>(meta_buf.end()) - 1) = htole64(e.size());
+
+    meta_vec().iov_base = meta_buf.data();
+    meta_vec().iov_len = meta_buf.size();
+
+    msg_vec().iov_base = (void *)e.strv().data();
+    msg_vec().iov_len = e.size();
+  }
+};
+
+class LogEntryEncoder : public EntryEncoderBase {
+ public:
+  void encode(const LogEntry& le)
+  {
+    meta_buf.clear();
+    fmt::format_to(std::back_inserter(meta_buf),
+      R"(PRIORITY={:d}
+TIMESTAMP={}
+CEPH_NAME={}
+CEPH_RANK={}
+CEPH_SEQ={}
+CEPH_CHANNEL={}
+MESSAGE
+)",
+      clog_type_to_syslog_level(le.prio),
+      le.stamp.to_nsec(),
+      le.name.to_str(),
+      le.rank,
+      le.seq,
+      le.channel);
+
+    uint64_t msg_len = htole64(le.msg.size());
+    meta_buf.resize(meta_buf.size() + sizeof(msg_len));
+    *(reinterpret_cast<uint64_t*>(meta_buf.end()) - 1) = htole64(le.msg.size());
+
+    meta_vec().iov_base = meta_buf.data();
+    meta_vec().iov_len = meta_buf.size();
+
+    msg_vec().iov_base = (void *)le.msg.data();
+    msg_vec().iov_len = le.msg.size();
+  }
+};
+
+enum class JournaldClient::MemFileMode {
+  MEMFD_CREATE,
+  OPEN_TMPFILE,
+  OPEN_UNLINK,  
+};
+
+constexpr const char *mem_file_dir = "/dev/shm";
+
+void JournaldClient::detect_mem_file_mode()
+{
+  int memfd = memfd_create("ceph-journald", MFD_ALLOW_SEALING | MFD_CLOEXEC);
+  if (memfd >= 0) {
+    mem_file_mode = MemFileMode::MEMFD_CREATE;
+    close(memfd);
+    return;
+  }
+  memfd = open(mem_file_dir, O_TMPFILE | O_EXCL | O_CLOEXEC, S_IRUSR | S_IWUSR);
+  if (memfd >= 0) {
+    mem_file_mode = MemFileMode::OPEN_TMPFILE;
+    close(memfd);
+    return;
+  }
+  mem_file_mode = MemFileMode::OPEN_UNLINK;
+}
+
+int JournaldClient::open_mem_file()
+{
+  switch (mem_file_mode) {
+  case MemFileMode::MEMFD_CREATE:
+    return memfd_create("ceph-journald", MFD_ALLOW_SEALING | MFD_CLOEXEC);
+  case MemFileMode::OPEN_TMPFILE:
+    return open(mem_file_dir, O_TMPFILE | O_EXCL | O_CLOEXEC, S_IRUSR | S_IWUSR);
+  case MemFileMode::OPEN_UNLINK:
+    char mem_file_template[] = "/dev/shm/ceph-journald-XXXXXX";
+    int fd = mkostemp(mem_file_template, O_CLOEXEC);
+    unlink(mem_file_template);
+    return fd;
+  }
+  ceph_abort("Unexpected mem_file_mode");
+}
+
+JournaldClient::JournaldClient() :
+  m_msghdr({
+    (struct sockaddr*)&sockaddr, // msg_name
+    sizeof(sockaddr),            // msg_namelen
+  })
+{
+  fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+  ceph_assertf(fd > 0, "socket creation failed: %s", strerror(errno));
+
+  int sendbuf = 2 * 1024 * 1024;
+  setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &sendbuf, sizeof(sendbuf));
+
+  detect_mem_file_mode();
+}
+
+JournaldClient::~JournaldClient()
+{
+  close(fd);
+}
+
+int JournaldClient::send()
+{
+  int ret = sendmsg(fd, &m_msghdr, MSG_NOSIGNAL);
+  if (ret >= 0)
+    return 0;
+
+  /* Fail silently if the journal is not available */
+  if (errno == ENOENT)
+    return -1;
+
+  if (errno != EMSGSIZE && errno != ENOBUFS) {
+    std::cerr << "Failed to send log to journald: " << strerror(errno) << std::endl;
+    return -1;
+  }
+  /* Message doesn't fit... Let's dump the data in a memfd and
+   * just pass a file descriptor of it to the other side.
+   */
+  int buffer_fd = open_mem_file();
+  if (buffer_fd < 0) {
+    std::cerr << "Failed to open buffer_fd while sending log to journald: " << strerror(errno) << std::endl;
+    return -1;
+  }
+
+  ret = writev(buffer_fd, m_msghdr.msg_iov, m_msghdr.msg_iovlen);
+  if (ret < 0) {
+    std::cerr << "Failed to write to buffer_fd while sending log to journald: " << strerror(errno) << std::endl;
+    goto err_close_buffer_fd;
+  }
+
+  if (mem_file_mode == MemFileMode::MEMFD_CREATE) {
+    ret = fcntl(buffer_fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL);
+    if (ret) {
+      std::cerr << "Failed to seal buffer_fd while sending log to journald: " << strerror(errno) << std::endl;
+      goto err_close_buffer_fd;
+    }
+  }
+  
+  ret = sendmsg_fd(fd, buffer_fd);
+  if (ret < 0) {
+    /* Fail silently if the journal is not available */
+    if (errno == ENOENT)
+      goto err_close_buffer_fd;
+
+    std::cerr << "Failed to send fd while sending log to journald: " << strerror(errno) << std::endl;
+    goto err_close_buffer_fd;
+  }
+  close(buffer_fd);
+  return 0;
+
+err_close_buffer_fd:
+  close(buffer_fd);
+  return -1;
+}
+
+} // namespace ceph::logging::detail
+
+JournaldLogger::JournaldLogger(const SubsystemMap *s) :
+  m_entry_encoder(std::make_unique<detail::EntryEncoder>()),
+  m_subs(s)
+{
+  client.m_msghdr.msg_iov = m_entry_encoder->iovec();
+  client.m_msghdr.msg_iovlen = m_entry_encoder->iovec_len();
+}
+
+JournaldLogger::~JournaldLogger() = default;
+
+int JournaldLogger::log_entry(const Entry& e)
+{
+  m_entry_encoder->encode(e, m_subs);
+  return client.send();
+}
+
+JournaldClusterLogger::JournaldClusterLogger() :
+  m_log_entry_encoder(std::make_unique<detail::LogEntryEncoder>())
+{
+  client.m_msghdr.msg_iov = m_log_entry_encoder->iovec();
+  client.m_msghdr.msg_iovlen = m_log_entry_encoder->iovec_len();
+}
+
+JournaldClusterLogger::~JournaldClusterLogger() = default;
+
+int JournaldClusterLogger::log_log_entry(const LogEntry &le)
+{
+  m_log_entry_encoder->encode(le);
+  return client.send();
+}
+
+}