Repository: VILLASframework/node (language: python)

Issue:
static logger initialization and subsequent static initializations using logging.get is undefined behavior
We initialize `villas::logging` as a static member in `log.cpp`. At the same time several other objects are created as static objects. Some of them (e.g., `MemoryManager`, `DirectedGraph`, `HostRamAllocator`) use the static object to create their own logging instances, i.e., `villas::logging.get()`.
The initialization order of these objects is undefined and thus what we are doing is undefined behavior.
If I activate link time optimization (`-lto`) I get segmentation faults because the initialization order is different.

I would suggest to move the initialization of these objects to an explicit initialization function, where we can make the initialization order explicit and call this function for example from a library constructor or via a single static initialization.

I only researched this briefly so I may have misunderstood some of this.


Discussion / hints:
Nitpick: This would not have happened if we were using static macro loggers :P

Fix patch:
diff --git a/common/include/villas/graph/directed.hpp b/common/include/villas/graph/directed.hpp
index ce19ef0db..9b4ea8328 100644
--- a/common/include/villas/graph/directed.hpp
+++ b/common/include/villas/graph/directed.hpp
@@ -31,7 +31,7 @@ class DirectedGraph {
   using Path = std::list<EdgeIdentifier>;
 
   DirectedGraph(const std::string &name = "DirectedGraph")
-      : lastVertexId(0), lastEdgeId(0), logger(logging.get(name)) {}
+      : lastVertexId(0), lastEdgeId(0), logger(Log::get(name)) {}
 
   std::shared_ptr<VertexType> getVertex(VertexIdentifier vertexId) const {
     // Cannot use [] operator, because creates non-existing elements
diff --git a/common/include/villas/kernel/pci.hpp b/common/include/villas/kernel/pci.hpp
index 2002ba7a2..8d792d7cc 100644
--- a/common/include/villas/kernel/pci.hpp
+++ b/common/include/villas/kernel/pci.hpp
@@ -60,11 +60,11 @@ struct Region {
 
 class Device {
 public:
-  Device(Id i, Slot s) : id(i), slot(s), log(logging.get("kernel:pci")) {}
+  Device(Id i, Slot s) : id(i), slot(s), log(Log::get("kernel:pci")) {}
 
-  Device(Id i) : id(i), log(logging.get("kernel:pci")) {}
+  Device(Id i) : id(i), log(Log::get("kernel:pci")) {}
 
-  Device(Slot s) : slot(s), log(logging.get("kernel:pci")) {}
+  Device(Slot s) : slot(s), log(Log::get("kernel:pci")) {}
 
   bool operator==(const Device &other);
 
diff --git a/common/include/villas/log.hpp b/common/include/villas/log.hpp
index 0cc62775e..459939499 100644
--- a/common/include/villas/log.hpp
+++ b/common/include/villas/log.hpp
@@ -20,12 +20,7 @@
 namespace villas {
 
 // Forward declarations
-class Log;
-
 using Logger = std::shared_ptr<spdlog::logger>;
-
-extern Log logging;
-
 class Log {
 
 public:
@@ -64,7 +59,19 @@ class Log {
 
   void parse(json_t *json);
 
-  Logger get(const std::string &name);
+  static Log &getInstance() {
+    // This will "leak" memory. Because we don't have a complex destructor it's okay
+    // that this will be cleaned up implicitly on program exit.
+    static auto log = new Log();
+    return *log;
+  };
+
+  Logger getNewLogger(const std::string &name);
+
+  static Logger get(const std::string &name) {
+    static auto &log = getInstance();
+    return log.getNewLogger(name);
+  }
 
   void setFormatter(const std::string &pattern, const std::string &pfx = "");
   void setLevel(Level lvl);
diff --git a/common/include/villas/memory.hpp b/common/include/villas/memory.hpp
index 526ac4ec5..a98556d4d 100644
--- a/common/include/villas/memory.hpp
+++ b/common/include/villas/memory.hpp
@@ -116,7 +116,7 @@ template <typename DerivedAllocator> class BaseAllocator {
     // CRTP
     derivedAlloc = static_cast<DerivedAllocator *>(this);
     std::string loggerName = fmt::format("memory:", derivedAlloc->getName());
-    logger = logging.get(loggerName);
+    logger = Log::get(loggerName);
 
     // Default deallocation callback
     free = [&](MemoryBlock *mem) {
@@ -249,10 +249,12 @@ class HostRam {
     allocateBlock(size_t size);
   };
 
-  static HostRamAllocator &getAllocator() { return allocator; }
-
-private:
-  static HostRamAllocator allocator;
+  static HostRamAllocator &getAllocator() {
+    // This will "leak" memory. Because we don't have a complex destructor it's okay
+    // that this will be cleaned up implicitly on program exit.
+    static auto allocator = new HostRamAllocator();
+    return *allocator;
+  }
 };
 
 class HostDmaRam {
diff --git a/common/include/villas/memory_manager.hpp b/common/include/villas/memory_manager.hpp
index cfed69b84..60f61c96c 100644
--- a/common/include/villas/memory_manager.hpp
+++ b/common/include/villas/memory_manager.hpp
@@ -65,7 +65,7 @@ class MemoryManager {
 private:
   // This is a singleton, so private constructor ...
   MemoryManager()
-      : memoryGraph("memory:graph"), logger(logging.get("memory:manager")) {
+      : memoryGraph("memory:graph"), logger(Log::get("memory:manager")) {
     pathCheckFunc = [&](const MemoryGraph::Path &path) {
       return this->pathCheck(path);
     };
diff --git a/common/include/villas/plugin.hpp b/common/include/villas/plugin.hpp
index f52bb594e..41a0f18d5 100644
--- a/common/include/villas/plugin.hpp
+++ b/common/include/villas/plugin.hpp
@@ -41,7 +41,7 @@ class Registry {
   List<SubRegistry> registries;
 
 public:
-  Logger getLogger() { return logging.get("plugin:registry"); }
+  Logger getLogger() { return Log::get("plugin:registry"); }
 
   void add(Plugin *p) { plugins.push_back(p); }
 
@@ -126,7 +126,7 @@ class Plugin {
   virtual Logger getLogger() {
     if (!logger) {
       auto name = fmt::format("{}:{}", getType(), getName());
-      logger = logging.get(name);
+      logger = Log::get(name);
     }
 
     return logger;
diff --git a/common/lib/kernel/kernel.cpp b/common/lib/kernel/kernel.cpp
index 70100da1e..902b67130 100644
--- a/common/lib/kernel/kernel.cpp
+++ b/common/lib/kernel/kernel.cpp
@@ -119,7 +119,7 @@ int villas::kernel::setModuleParam(const char *module, const char *param,
     throw RuntimeError("Failed set parameter {} for kernel module {} to {}",
                        module, param, value);
 
-  auto logger = logging.get("kernel");
+  auto logger = Log::get("kernel");
   logger->debug("Set parameter {} of kernel module {} to {}", module, param,
                 value);
 
@@ -134,7 +134,7 @@ int villas::kernel::loadModule(const char *module) {
 
   ret = isModuleLoaded(module);
   if (!ret) {
-    auto logger = logging.get("kernel");
+    auto logger = Log::get("kernel");
     logger->debug("Kernel module {} already loaded...", module);
     return 0;
   }
@@ -193,7 +193,7 @@ int villas::kernel::getCmdlineParam(const char *param, char *buf, size_t len) {
   do {
     ret = sscanf(tok, "%127[^=]=%127s", key, value);
     if (ret >= 1) {
-      auto logger = logging.get("kernel");
+      auto logger = Log::get("kernel");
       if (ret >= 2)
         logger->debug("Found kernel param: {}={}", key, value);
       else
@@ -220,7 +220,7 @@ int villas::kernel::getNrHugepages() {
 
   f = fopen(PROCFS_PATH "/sys/vm/nr_hugepages", "r");
   if (!f) {
-    auto logger = logging.get("kernel");
+    auto logger = Log::get("kernel");
     logger->error("Failed to open {}: {}", PROCFS_PATH "/sys/vm/nr_hugepages",
                   strerror(errno));
     return -1;
diff --git a/common/lib/kernel/rt.cpp b/common/lib/kernel/rt.cpp
index 14887fde1..6f1ec22d8 100644
--- a/common/lib/kernel/rt.cpp
+++ b/common/lib/kernel/rt.cpp
@@ -26,7 +26,7 @@ namespace kernel {
 namespace rt {
 
 void init(int priority, int affinity) {
-  Logger logger = logging.get("kernel:rt");
+  Logger logger = Log::get("kernel:rt");
 
   logger->info("Initialize sub-system");
 
@@ -82,7 +82,7 @@ void setProcessAffinity(int affinity) {
 
   assert(affinity != 0);
 
-  Logger logger = logging.get("kernel:rt");
+  Logger logger = Log::get("kernel:rt");
 
   // Pin threads to CPUs by setting the affinity
   CpuSet cset_pin(affinity);
@@ -101,7 +101,7 @@ void setThreadAffinity(pthread_t thread, int affinity) {
 
   assert(affinity != 0);
 
-  Logger logger = logging.get("kernel:rt");
+  Logger logger = Log::get("kernel:rt");
 
   CpuSet cset_pin(affinity);
 
@@ -119,7 +119,7 @@ void setPriority(int priority) {
   struct sched_param param;
   param.sched_priority = priority;
 
-  Logger logger = logging.get("kernel:rt");
+  Logger logger = Log::get("kernel:rt");
 
   ret = sched_setscheduler(0, SCHED_FIFO, &param);
   if (ret)
diff --git a/common/lib/kernel/vfio_container.cpp b/common/lib/kernel/vfio_container.cpp
index 79a101aaf..7b67035f1 100644
--- a/common/lib/kernel/vfio_container.cpp
+++ b/common/lib/kernel/vfio_container.cpp
@@ -68,7 +68,7 @@ static std::array<std::string, EXTENSION_SIZE> VFIO_EXTENSION_STR =
 
 Container::Container(std::vector<std::string> required_modules)
     : fd(-1), version(0), extensions(), iova_next(0), hasIommu(false), groups(),
-      log(logging.get("kernel:vfio:container")) {
+      log(Log::get("kernel:vfio:container")) {
   for (auto module : required_modules) {
     if (kernel::loadModule(module.c_str()) != 0) {
       throw RuntimeError("Kernel module '{}' required but could not be loaded. "
diff --git a/common/lib/kernel/vfio_device.cpp b/common/lib/kernel/vfio_device.cpp
index 02da4c898..160cff71d 100644
--- a/common/lib/kernel/vfio_device.cpp
+++ b/common/lib/kernel/vfio_device.cpp
@@ -56,7 +56,7 @@ Device::Device(const std::string &name, int groupFileDescriptor,
                const kernel::pci::Device *pci_device)
     : name(name), fd(-1), attachedToGroup(false), groupFd(groupFileDescriptor),
       info(), irqs(), regions(), mappings(), pci_device(pci_device),
-      log(logging.get("kernel:vfio:device")) {
+      log(Log::get("kernel:vfio:device")) {
   if (groupFileDescriptor < 0)
     throw RuntimeError("Invalid group file descriptor");
 
@@ -293,8 +293,8 @@ int Device::pciMsiInit(int efds[]) {
 }
 
 int Device::pciMsiDeinit(int efds[]) {
-  logging.get("Device")->debug("Deinitializing MSI interrupts for device {}",
-                               name);
+  Log::get("Device")->debug("Deinitializing MSI interrupts for device {}",
+                            name);
   // Check if this is really a vfio-pci device
   if (not isVfioPciDevice())
     return -1;
diff --git a/common/lib/kernel/vfio_group.cpp b/common/lib/kernel/vfio_group.cpp
index aae4781e9..56591bb9f 100644
--- a/common/lib/kernel/vfio_group.cpp
+++ b/common/lib/kernel/vfio_group.cpp
@@ -38,7 +38,7 @@ using namespace villas::kernel::vfio;
 
 Group::Group(int index, bool iommuEnabled)
     : fd(-1), index(index), attachedToContainer(false), status(), devices(),
-      log(logging.get("kernel:vfio:group")) {
+      log(Log::get("kernel:vfio:group")) {
   // Open group fd
   std::stringstream groupPath;
   groupPath << VFIO_PATH << (iommuEnabled ? "" : "noiommu-") << index;
diff --git a/common/lib/log.cpp b/common/lib/log.cpp
index 708b5b83b..3320eb579 100644
--- a/common/lib/log.cpp
+++ b/common/lib/log.cpp
@@ -20,9 +20,6 @@
 
 using namespace villas;
 
-// The global log instance
-Log villas::logging;
-
 static std::map<spdlog::level::level_enum, std::string> levelNames = {
     {spdlog::level::trace, "trc"}, {spdlog::level::debug, "dbg"},
     {spdlog::level::info, "info"}, {spdlog::level::warn, "warn"},
@@ -69,7 +66,7 @@ int Log::getWidth() {
   return width;
 }
 
-Logger Log::get(const std::string &name) {
+Logger Log::getNewLogger(const std::string &name) {
   Logger logger = spdlog::get(name);
 
   if (not logger) {
diff --git a/common/lib/memory.cpp b/common/lib/memory.cpp
index bb5b23656..205d8c70f 100644
--- a/common/lib/memory.cpp
+++ b/common/lib/memory.cpp
@@ -143,8 +143,6 @@ LinearAllocator::allocateBlock(size_t size) {
   return mem;
 }
 
-HostRam::HostRamAllocator HostRam::allocator;
-
 HostRam::HostRamAllocator::HostRamAllocator()
     : BaseAllocator(MemoryManager::get().getProcessAddressSpace()) {
   free = [&](MemoryBlock *mem) {
@@ -167,7 +165,7 @@ HostDmaRam::HostDmaRamAllocator::HostDmaRamAllocator(int num)
           getUdmaBufBufSize(num)),
       num(num) {
   auto &mm = MemoryManager::get();
-  logger = logging.get(getName());
+  logger = Log::get(getName());
 
   if (getSize() == 0) {
     logger->error(
diff --git a/common/lib/memory_manager.cpp b/common/lib/memory_manager.cpp
index 9e2976121..61f641b95 100644
--- a/common/lib/memory_manager.cpp
+++ b/common/lib/memory_manager.cpp
@@ -164,7 +164,7 @@ MemoryTranslation::getForeignAddr(uintptr_t addrInLocalAddrSpace) const {
 
 MemoryTranslation &
 MemoryTranslation::operator+=(const MemoryTranslation &other) {
-  Logger logger = logging.get("MemoryTranslation");
+  Logger logger = Log::get("MemoryTranslation");
 
   // Set level to debug to enable debug output
   logger->set_level(spdlog::level::info);
diff --git a/common/lib/table.cpp b/common/lib/table.cpp
index dd4d6dcac..8c1b50002 100644
--- a/common/lib/table.cpp
+++ b/common/lib/table.cpp
@@ -57,8 +57,8 @@ int Table::resize(int w) {
 }
 
 void Table::header() {
-  if (width != logging.getWidth())
-    resize(logging.getWidth());
+  if (width != Log::getInstance().getWidth())
+    resize(Log::getInstance().getWidth());
 
   char *line1 = nullptr;
   char *line2 = nullptr;
@@ -107,8 +107,8 @@ void Table::header() {
 }
 
 void Table::row(int count, ...) {
-  if (width != logging.getWidth()) {
-    resize(logging.getWidth());
+  if (width != Log::getInstance().getWidth()) {
+    resize(Log::getInstance().getWidth());
     header();
   }
 
diff --git a/common/lib/terminal.cpp b/common/lib/terminal.cpp
index 4ab4ce244..cb6540262 100644
--- a/common/lib/terminal.cpp
+++ b/common/lib/terminal.cpp
@@ -23,7 +23,7 @@ Terminal::Terminal() {
 
   isTty = isatty(STDERR_FILENO);
 
-  Logger logger = logging.get("terminal");
+  Logger logger = Log::get("terminal");
 
   if (isTty) {
     struct sigaction sa_resize;
@@ -57,7 +57,7 @@ void Terminal::resize(int, siginfo_t *, void *) {
   if (!current)
     current = new Terminal();
 
-  Logger logger = logging.get("terminal");
+  Logger logger = Log::get("terminal");
 
   int ret;
 
diff --git a/common/lib/tool.cpp b/common/lib/tool.cpp
index adb21e553..c84bc1a45 100644
--- a/common/lib/tool.cpp
+++ b/common/lib/tool.cpp
@@ -33,7 +33,7 @@ Tool::Tool(int ac, char *av[], const std::string &nme,
     : argc(ac), argv(av), name(nme), handlerSignals(sigs) {
   current_tool = this;
 
-  logger = logging.get(name);
+  logger = Log::get(name);
 }
 
 int Tool::run() {
diff --git a/common/lib/utils.cpp b/common/lib/utils.cpp
index 87e148f8c..f495d8121 100644
--- a/common/lib/utils.cpp
+++ b/common/lib/utils.cpp
@@ -87,7 +87,7 @@ int signalsInit(void (*cb)(int signal, siginfo_t *sinfo, void *ctx),
                 std::list<int> cbSignals, std::list<int> ignoreSignals) {
   int ret;
 
-  Logger logger = logging.get("signals");
+  Logger logger = Log::get("signals");
 
   logger->info("Initialize subsystem");
 
diff --git a/fpga/gpu/src/gpu.cpp b/fpga/gpu/src/gpu.cpp
index 4c6b426f9..f68d69265 100644
--- a/fpga/gpu/src/gpu.cpp
+++ b/fpga/gpu/src/gpu.cpp
@@ -47,7 +47,7 @@ std::string villas::gpu::GpuAllocator::getName() const {
 }
 
 GpuFactory::GpuFactory()
-    : logger(villas::logging.get("gpu:factory")),
+    : logger(villas::Log::get("gpu:factory")),
       Plugin("cuda", "CUDA capable GPUs") {}
 
 // Required to be defined here for PIMPL to compile
@@ -67,8 +67,8 @@ std::string Gpu::getName() const {
   cudaDeviceProp deviceProp;
   if (cudaGetDeviceProperties(&deviceProp, gpuId) != cudaSuccess) {
     // Logger not yet availabe
-    villas::logging.get("gpu")->error("Cannot retrieve properties for GPU {}",
-                                      gpuId);
+    villas::Log::get("gpu")->error("Cannot retrieve properties for GPU {}",
+                                   gpuId);
     throw std::exception();
   }
 
@@ -404,7 +404,7 @@ GpuAllocator::allocateBlock(size_t size) {
 }
 
 Gpu::Gpu(int gpuId) : pImpl{std::make_unique<impl>()}, gpuId(gpuId) {
-  logger = villas::logging.get(getName());
+  logger = villas::Log::get(getName());
 
   pImpl->gdr = gdr_open();
   if (pImpl->gdr == nullptr) {
diff --git a/fpga/include/villas/fpga/core.hpp b/fpga/include/villas/fpga/core.hpp
index b2ac49453..089aaf8e7 100644
--- a/fpga/include/villas/fpga/core.hpp
+++ b/fpga/include/villas/fpga/core.hpp
@@ -228,14 +228,12 @@ class CoreFactory : public plugin::Plugin {
     IRQ,
   };
 
-  Logger getLogger() { return villas::logging.get(getName()); }
+  Logger getLogger() { return villas::Log::get(getName()); }
 
   // Configure IP instance from JSON config
   virtual void parse(Core &, json_t *) {}
 
-  static Logger getStaticLogger() {
-    return villas::logging.get("core:factory");
-  }
+  static Logger getStaticLogger() { return villas::Log::get("core:factory"); }
 
 private:
   virtual void configurePollingMode(Core &, PollingMode) {}
diff --git a/fpga/include/villas/fpga/ips/i2c.hpp b/fpga/include/villas/fpga/ips/i2c.hpp
index 800ea7ce8..072a72157 100644
--- a/fpga/include/villas/fpga/ips/i2c.hpp
+++ b/fpga/include/villas/fpga/ips/i2c.hpp
@@ -45,8 +45,7 @@ class I2c : public Node {
 
   class Switch {
   public:
-    Switch(I2c *i2c, uint8_t address,
-           Logger logger = villas::logging.get("i2c"))
+    Switch(I2c *i2c, uint8_t address, Logger logger = villas::Log::get("i2c"))
         : i2c(i2c), address(address), channel(0), readOnce(false), switchLock(),
           logger(logger){};
     Switch(const Switch &other) = delete;
diff --git a/fpga/include/villas/fpga/pcie_card.hpp b/fpga/include/villas/fpga/pcie_card.hpp
index 76fa6f5ff..f5368d399 100644
--- a/fpga/include/villas/fpga/pcie_card.hpp
+++ b/fpga/include/villas/fpga/pcie_card.hpp
@@ -58,7 +58,7 @@ class PCIeCard : public Card {
   std::shared_ptr<kernel::pci::Device> pdev; // PCI device handle
 
 protected:
-  Logger getLogger() const { return villas::logging.get(name); }
+  Logger getLogger() const { return villas::Log::get(name); }
 };
 
 class PCIeCardFactory : public plugin::Plugin {
@@ -71,7 +71,7 @@ class PCIeCardFactory : public plugin::Plugin {
   static PCIeCard *make() { return new PCIeCard(); }
 
   static Logger getStaticLogger() {
-    return villas::logging.get("pcie:card:factory");
+    return villas::Log::get("pcie:card:factory");
   }
 
   virtual std::string getName() const { return "pcie"; }
diff --git a/fpga/lib/core.cpp b/fpga/lib/core.cpp
index 324256ba2..0a1f8de1f 100644
--- a/fpga/lib/core.cpp
+++ b/fpga/lib/core.cpp
@@ -126,7 +126,7 @@ CoreFactory::configureIps(std::list<IpIdentifier> orderedIps, json_t *json_ips,
     // Setup generic IP type properties
     ip->card = card;
     ip->id = id;
-    ip->logger = villas::logging.get(id.getName());
+    ip->logger = villas::Log::get(id.getName());
 
     json_t *json_ip = json_object_get(json_ips, id.getName().c_str());
 
diff --git a/fpga/lib/dma.cpp b/fpga/lib/dma.cpp
index 0afebfdc2..309ac8d60 100644
--- a/fpga/lib/dma.cpp
+++ b/fpga/lib/dma.cpp
@@ -22,7 +22,7 @@
 using namespace villas;
 
 static std::shared_ptr<kernel::pci::DeviceList> pciDevices;
-static auto logger = villas::logging.get("villasfpga_dma");
+static auto logger = villas::Log::get("villasfpga_dma");
 
 struct villasfpga_handle_t {
   std::shared_ptr<villas::fpga::Card> card;
@@ -40,7 +40,7 @@ villasfpga_handle villasfpga_init(const char *configFile) {
   bool dumpAuroraChannels = true;
   try {
     // Logging setup
-    logging.setLevel(spdlog::level::debug);
+    Log::getInstance().setLevel(spdlog::level::debug);
     fpga::setupColorHandling();
 
     if (configFile == nullptr || configFile[0] == '\0') {
diff --git a/fpga/lib/ips/dino.cpp b/fpga/lib/ips/dino.cpp
index 539c726d9..a03c520fa 100644
--- a/fpga/lib/ips/dino.cpp
+++ b/fpga/lib/ips/dino.cpp
@@ -171,7 +171,7 @@ void DinoAdc::setRegisterConfig(std::shared_ptr<Register> reg,
   float dacOffset = reg->getRegisterFloat(dinoRegisterDacOffset);
   uint32_t dacExternalTrig = reg->getRegister(dinoRegisterDacExternalTrig);
   uint32_t stsActive = reg->getRegister(dinoRegisterStsActive);
-  logging.get("Dino")->info(
+  Log::get("Dino")->info(
       "Check: Register configuration: TimerThresh: {}, Rate-Error: {} Hz, ADC "
       "Scale: {}, ADC Offset: {}, DAC Scale: {}, DAC Offset: {}, DAC External "
       "Trig: {:#x}, STS Active: {:#x}",
diff --git a/fpga/lib/utils.cpp b/fpga/lib/utils.cpp
index 723947183..be0f20b8a 100644
--- a/fpga/lib/utils.cpp
+++ b/fpga/lib/utils.cpp
@@ -31,7 +31,7 @@
 
 using namespace villas;
 
-static auto logger = villas::logging.get("streamer");
+static auto logger = villas::Log::get("streamer");
 
 std::shared_ptr<std::vector<std::shared_ptr<fpga::ip::Node>>>
 fpga::getAuroraChannels(std::shared_ptr<fpga::Card> card) {
@@ -52,7 +52,7 @@ fpga::getAuroraChannels(std::shared_ptr<fpga::Card> card) {
 }
 
 fpga::ConnectString::ConnectString(std::string &connectString, int maxPortNum)
-    : log(villas::logging.get("ConnectString")), maxPortNum(maxPortNum),
+    : log(villas::Log::get("ConnectString")), maxPortNum(maxPortNum),
       bidirectional(false), invert(false), srcType(ConnectType::LOOPBACK),
       dstType(ConnectType::LOOPBACK), srcAsInt(-1), dstAsInt(-1) {
   parseString(connectString);
diff --git a/fpga/src/villas-fpga-ctrl.cpp b/fpga/src/villas-fpga-ctrl.cpp
index b4c7076b9..8cdd424d3 100644
--- a/fpga/src/villas-fpga-ctrl.cpp
+++ b/fpga/src/villas-fpga-ctrl.cpp
@@ -35,7 +35,7 @@
 using namespace villas;
 
 static std::shared_ptr<kernel::pci::DeviceList> pciDevices;
-static auto logger = villas::logging.get("ctrl");
+static auto logger = villas::Log::get("ctrl");
 
 void writeToDmaFromStdIn(std::shared_ptr<villas::fpga::ip::Dma> dma) {
   auto &alloc = villas::HostRam::getAllocator();
@@ -203,7 +203,7 @@ int main(int argc, char *argv[]) {
 
     // Logging setup
 
-    logging.setLevel(spdlog::level::trace);
+    Log::getInstance().setLevel(spdlog::level::trace);
     logger->set_level(spdlog::level::trace);
     fpga::setupColorHandling();
 
diff --git a/fpga/src/villas-fpga-pipe.cpp b/fpga/src/villas-fpga-pipe.cpp
index edb8ce9e5..66841ea61 100644
--- a/fpga/src/villas-fpga-pipe.cpp
+++ b/fpga/src/villas-fpga-pipe.cpp
@@ -28,7 +28,7 @@
 using namespace villas;
 
 static std::shared_ptr<kernel::pci::DeviceList> pciDevices;
-static auto logger = villas::logging.get("streamer");
+static auto logger = villas::Log::get("streamer");
 
 int main(int argc, char *argv[]) {
   // Command Line Parser
diff --git a/lib/api.cpp b/lib/api.cpp
index c53bd47bb..ac7c92942 100644
--- a/lib/api.cpp
+++ b/lib/api.cpp
@@ -24,7 +24,7 @@ InvalidMethod::InvalidMethod(Request *req)
                  Session::methodToString(req->method)) {}
 
 Api::Api(SuperNode *sn)
-    : logger(logging.get("api")), state(State::INITIALIZED), super_node(sn) {}
+    : logger(Log::get("api")), state(State::INITIALIZED), super_node(sn) {}
 
 Api::~Api() {
   if (state == State::STARTED)
diff --git a/lib/api/requests/restart.cpp b/lib/api/requests/restart.cpp
index 8fc489ccf..cf29f76e9 100644
--- a/lib/api/requests/restart.cpp
+++ b/lib/api/requests/restart.cpp
@@ -28,7 +28,7 @@ class RestartRequest : public Request {
 
     const char *argv[] = {"villas-node", cfg, nullptr};
 
-    Logger logger = logging.get("api:restart");
+    Logger logger = Log::get("api:restart");
 
     if (cfg)
       logger->info("Restarting instance: config={}", cfg);
diff --git a/lib/api/response.cpp b/lib/api/response.cpp
index 62914191e..175d75419 100644
--- a/lib/api/response.cpp
+++ b/lib/api/response.cpp
@@ -12,7 +12,7 @@
 using namespace villas::node::api;
 
 Response::Response(Session *s, int c, const std::string &ct, const Buffer &b)
-    : session(s), logger(logging.get("api:response")), buffer(b), code(c),
+    : session(s), logger(Log::get("api:response")), buffer(b), code(c),
       contentType(ct),
       headers{{"Server:", HTTP_USER_AGENT},
               {"Access-Control-Allow-Origin:", "*"},
diff --git a/lib/api/session.cpp b/lib/api/session.cpp
index 331f6a1dc..0494aa10b 100644
--- a/lib/api/session.cpp
+++ b/lib/api/session.cpp
@@ -21,7 +21,7 @@ using namespace villas::node;
 using namespace villas::node::api;
 
 Session::Session(lws *w)
-    : version(Version::VERSION_2), wsi(w), logger(logging.get("api:session")) {
+    : version(Version::VERSION_2), wsi(w), logger(Log::get("api:session")) {
   lws_context *ctx = lws_get_context(wsi);
   void *user_ctx = lws_context_user(ctx);
 
diff --git a/lib/config.cpp b/lib/config.cpp
index 59f795534..433197426 100644
--- a/lib/config.cpp
+++ b/lib/config.cpp
@@ -27,7 +27,7 @@
 using namespace villas;
 using namespace villas::node;
 
-Config::Config() : logger(logging.get("config")), root(nullptr) {}
+Config::Config() : logger(Log::get("config")), root(nullptr) {}
 
 Config::Config(const std::string &u) : Config() { root = load(u); }
 
diff --git a/lib/dumper.cpp b/lib/dumper.cpp
index 7b0862605..5d00d9439 100644
--- a/lib/dumper.cpp
+++ b/lib/dumper.cpp
@@ -20,7 +20,7 @@ using namespace villas::node;
 
 Dumper::Dumper()
     : active(false), socketFd(0), socketPath(""), supressRepeatedWarning(true),
-      warningCounter(0), logger(logging.get("dumper")) {}
+      warningCounter(0), logger(Log::get("dumper")) {}
 
 Dumper::~Dumper() { closeSocket(); }
 
diff --git a/lib/hook.cpp b/lib/hook.cpp
index 61a6f59bc..feb61efa2 100644
--- a/lib/hook.cpp
+++ b/lib/hook.cpp
@@ -22,7 +22,7 @@ using namespace villas;
 using namespace villas::node;
 
 Hook::Hook(Path *p, Node *n, int fl, int prio, bool en)
-    : logger(logging.get("hook")), factory(nullptr),
+    : logger(Log::get("hook")), factory(nullptr),
       state(fl & (int)Hook::Flags::BUILTIN
                 ? State::CHECKED
                 : State::INITIALIZED), // We dont need to parse builtin hooks
diff --git a/lib/kernel/if.cpp b/lib/kernel/if.cpp
index 08aca8255..98477bfab 100644
--- a/lib/kernel/if.cpp
+++ b/lib/kernel/if.cpp
@@ -32,7 +32,7 @@ using namespace villas::kernel;
 
 Interface::Interface(struct rtnl_link *link, int aff)
     : nl_link(link), tc_qdisc(nullptr), affinity(aff) {
-  logger = logging.get(fmt::format("kernel:if:{}", getName()));
+  logger = Log::get(fmt::format("kernel:if:{}", getName()));
 
   int n = getIRQs();
   if (n)
@@ -118,7 +118,7 @@ std::string Interface::getName() const {
 Interface *Interface::getEgress(struct sockaddr *sa, SuperNode *sn) {
   struct rtnl_link *link;
 
-  Logger logger = logging.get("kernel:if");
+  Logger logger = Log::get("kernel:if");
 
   auto &interfaces = sn->getInterfaces();
   auto affinity = sn->getAffinity();
diff --git a/lib/kernel/tc.cpp b/lib/kernel/tc.cpp
index fd43d65e0..08cf3eb81 100644
--- a/lib/kernel/tc.cpp
+++ b/lib/kernel/tc.cpp
@@ -50,7 +50,7 @@ int villas::kernel::tc::prio(Interface *i, struct rtnl_qdisc **qd,
 
   *qd = q;
 
-  auto logger = logging.get("kernel");
+  auto logger = Log::get("kernel");
   logger->debug("Added prio qdisc with {} bands to interface '{}'", bands,
                 rtnl_link_get_name(i->nl_link));
 
@@ -80,7 +80,7 @@ int villas::kernel::tc::mark(Interface *i, struct rtnl_cls **cls,
 
   *cls = c;
 
-  auto logger = logging.get("kernel");
+  auto logger = Log::get("kernel");
   logger->debug("Added fwmark classifier with mark {} to interface '{}'", mark,
                 rtnl_link_get_name(i->nl_link));
 
diff --git a/lib/kernel/tc_netem.cpp b/lib/kernel/tc_netem.cpp
index 0b9ab2625..1f4f3cc17 100644
--- a/lib/kernel/tc_netem.cpp
+++ b/lib/kernel/tc_netem.cpp
@@ -277,7 +277,7 @@ int villas::kernel::tc::netem(Interface *i, struct rtnl_qdisc **qd,
 
   *qd = q;
 
-  auto logger = logging.get("kernel");
+  auto logger = Log::get("kernel");
   logger->debug("Added netem qdisc to interface '{}'",
                 rtnl_link_get_name(i->nl_link));
 
diff --git a/lib/memory.cpp b/lib/memory.cpp
index 9475dc5fa..fb1cae4fd 100644
--- a/lib/memory.cpp
+++ b/lib/memory.cpp
@@ -31,7 +31,7 @@ static Logger logger;
 int villas::node::memory::init(int hugepages) {
   int ret;
 
-  logger = logging.get("memory");
+  logger = Log::get("memory");
 
   logger->info("Initialize memory sub-system: #hugepages={}", hugepages);
 
diff --git a/lib/memory/ib.cpp b/lib/memory/ib.cpp
index 5a2bce3c0..9dc7a96e3 100644
--- a/lib/memory/ib.cpp
+++ b/lib/memory/ib.cpp
@@ -42,7 +42,7 @@ static struct Allocation *ib_alloc(size_t len, size_t alignment,
   ma->address = ma->parent->address;
 
   if (!mi->pd) {
-    auto logger = logging.get("memory:ib");
+    auto logger = Log::get("memory:ib");
     logger->error("Protection domain is not registered!");
   }
 
diff --git a/lib/memory/managed.cpp b/lib/memory/managed.cpp
index 8ffc1cdbf..3b3c5c412 100644
--- a/lib/memory/managed.cpp
+++ b/lib/memory/managed.cpp
@@ -145,7 +145,7 @@ struct Type *villas::node::memory::managed(void *ptr, size_t len) {
   char *cptr = (char *)ptr;
 
   if (len < sizeof(struct Type) + sizeof(struct Block)) {
-    auto logger = logging.get("memory:managed");
+    auto logger = Log::get("memory:managed");
     logger->info("Passed region is too small");
     return nullptr;
   }
diff --git a/lib/memory/mmap.cpp b/lib/memory/mmap.cpp
index f4060eaaf..65efb0b0d 100644
--- a/lib/memory/mmap.cpp
+++ b/lib/memory/mmap.cpp
@@ -36,7 +36,7 @@ static size_t hugepgsz = -1;
 static Logger logger;
 
 int villas::node::memory::mmap_init(int hugepages) {
-  logger = logging.get("memory:mmap");
+  logger = Log::get("memory:mmap");
 
   pgsz = kernel::getPageSize();
   if (pgsz < 0)
diff --git a/lib/node.cpp b/lib/node.cpp
index ebdf331b9..20b280e62 100644
--- a/lib/node.cpp
+++ b/lib/node.cpp
@@ -40,7 +40,7 @@ using namespace villas::node;
 using namespace villas::utils;
 
 Node::Node(const uuid_t &id, const std::string &name)
-    : logger(logging.get("node")), sequence_init(0), sequence(0),
+    : logger(Log::get("node")), sequence_init(0), sequence(0),
       in(NodeDirection::Direction::IN, this),
       out(NodeDirection::Direction::OUT, this), configPath(),
 #ifdef __linux__
diff --git a/lib/node_list.cpp b/lib/node_list.cpp
index 86b8397e6..0859cb788 100644
--- a/lib/node_list.cpp
+++ b/lib/node_list.cpp
@@ -37,7 +37,7 @@ int NodeList::parse(json_t *json, NodeList &all) {
   size_t index;
   json_t *elm;
 
-  auto logger = logging.get("node");
+  auto logger = Log::get("node");
 
   switch (json_typeof(json)) {
   case JSON_STRING:
diff --git a/lib/nodes/ethercat.cpp b/lib/nodes/ethercat.cpp
index 3b2944637..11dbba864 100644
--- a/lib/nodes/ethercat.cpp
+++ b/lib/nodes/ethercat.cpp
@@ -125,7 +125,7 @@ int villas::node::ethercat_type_start(villas::node::SuperNode *sn) {
 }
 
 int villas::node::ethercat_type_stop() {
-  auto logger = logging.get("node:ethercat");
+  auto logger = Log::get("node:ethercat");
 
   logger->info("Releasing EtherCAT master");
 
diff --git a/lib/nodes/kafka.cpp b/lib/nodes/kafka.cpp
index 7eceda0dc..c41551e8c 100644
--- a/lib/nodes/kafka.cpp
+++ b/lib/nodes/kafka.cpp
@@ -470,7 +470,7 @@ int villas::node::kafka_stop(NodeCompat *n) {
 int villas::node::kafka_type_start(villas::node::SuperNode *sn) {
   int ret;
 
-  logger = logging.get("node:kafka");
+  logger = Log::get("node:kafka");
 
   ret = list_init(&clients);
   if (ret)
diff --git a/lib/nodes/loopback_internal.cpp b/lib/nodes/loopback_internal.cpp
index b30030a9f..eceed8424 100644
--- a/lib/nodes/loopback_internal.cpp
+++ b/lib/nodes/loopback_internal.cpp
@@ -35,7 +35,7 @@ InternalLoopbackNode::InternalLoopbackNode(Node *src, unsigned id, unsigned ql)
 
   auto logger_name = fmt::format("node:{}", getNameShort());
 
-  logger = logging.get(logger_name);
+  logger = Log::get(logger_name);
 
   in.signals = source->getInputSignals(false);
 
diff --git a/lib/nodes/mqtt.cpp b/lib/nodes/mqtt.cpp
index 9860c9e2f..d76704ff0 100644
--- a/lib/nodes/mqtt.cpp
+++ b/lib/nodes/mqtt.cpp
@@ -412,7 +412,7 @@ int villas::node::mqtt_type_start(villas::node::SuperNode *sn) {
   return 0;
 
 mosquitto_error:
-  auto logger = logging.get("node:mqtt");
+  auto logger = Log::get("node:mqtt");
   logger->warn("{}", mosquitto_strerror(ret));
 
   return ret;
@@ -428,7 +428,7 @@ int villas::node::mqtt_type_stop() {
   return 0;
 
 mosquitto_error:
-  auto logger = logging.get("node:mqtt");
+  auto logger = Log::get("node:mqtt");
   logger->warn("{}", mosquitto_strerror(ret));
 
   return ret;
diff --git a/lib/nodes/ngsi.cpp b/lib/nodes/ngsi.cpp
index aeb00d7f0..8fed2e405 100644
--- a/lib/nodes/ngsi.cpp
+++ b/lib/nodes/ngsi.cpp
@@ -41,7 +41,7 @@ using namespace villas::utils;
 static pthread_mutex_t *mutex_buf = NULL;
 
 static void handle_error(const char *file, int lineno, const char *msg) {
-  auto logger = logging.get("curl");
+  auto logger = Log::get("curl");
 
   logger->error("** {}:{} {}", file, lineno, msg);
 
@@ -550,7 +550,7 @@ int villas::node::ngsi_type_start(villas::node::SuperNode *sn) {
   CRYPTO_set_id_callback(curl_ssl_thread_id_function);
   CRYPTO_set_locking_callback(curl_ssl_locking_function);
 
-  auto logger = logging.get("curl");
+  auto logger = Log::get("curl");
   logger->info("Setup libcurl/openssl locking primitives");
 #endif // CURL_SSL_REQUIRES_LOCKING
 
diff --git a/lib/nodes/opal.cpp b/lib/nodes/opal.cpp
index a60ea7725..f7e76fef2 100644
--- a/lib/nodes/opal.cpp
+++ b/lib/nodes/opal.cpp
@@ -124,7 +124,7 @@ int villas::node::opal_type_start(villas::node::SuperNode *sn) {
   if (err != EOK)
     throw RuntimeError("Failed to get list of recv ids ({})", err);
 
-  auto logger = logging.get("node:opal");
+  auto logger = Log::get("node:opal");
   logger->info("Started as OPAL Asynchronous process");
   logger->info("This is VILLASnode %s (built on %s, %s)", PROJECT_BUILD_ID,
                __DATE__, __TIME__);
@@ -141,7 +141,7 @@ int villas::node::opal_type_stop() {
   if (err != EOK)
     throw RuntimeError("Failed to close shared memory area ({})", err);
 
-  auto logger = logging.get("node:opal");
+  auto logger = Log::get("node:opal");
   logger->debug("Closing OPAL shared memory mapping");
 
   err = OpalSystemCtrl_UnRegister((char *)printShmemName.c_str());
@@ -155,7 +155,7 @@ int villas::node::opal_type_stop() {
 }
 
 static int opal_print_global() {
-  auto logger = logging.get("node:opal");
+  auto logger = Log::get("node:opal");
   logger->debug("Controller ID: {}", params.controllerID);
 
   std::stringstream sss, rss;
diff --git a/lib/nodes/redis.cpp b/lib/nodes/redis.cpp
index 8b38684d7..0c33f5d78 100644
--- a/lib/nodes/redis.cpp
+++ b/lib/nodes/redis.cpp
@@ -35,7 +35,7 @@ static std::unordered_map<sw::redis::ConnectionOptions, RedisConnection *>
 
 RedisConnection::RedisConnection(const sw::redis::ConnectionOptions &opts)
     : context(opts), subscriber(context.subscriber()),
-      logger(logging.get("nodes:redis")) {
+      logger(Log::get("nodes:redis")) {
   // Enable keyspace notifications
   context.command("config", "set", "notify-keyspace-events", "K$h");
 
diff --git a/lib/nodes/rtp.cpp b/lib/nodes/rtp.cpp
index be2ad20a0..503978691 100644
--- a/lib/nodes/rtp.cpp
+++ b/lib/nodes/rtp.cpp
@@ -76,7 +76,7 @@ static int rtp_aimd(NodeCompat *n, double loss_frac) {
 int villas::node::rtp_init(NodeCompat *n) {
   auto *r = n->getData<struct rtp>();
 
-  n->logger = villas::logging.get("node:rtp");
+  n->logger = villas::Log::get("node:rtp");
 
   // Default values
   r->aimd.a = 10;
diff --git a/lib/nodes/temper.cpp b/lib/nodes/temper.cpp
index da0138908..53289c8ab 100644
--- a/lib/nodes/temper.cpp
+++ b/lib/nodes/temper.cpp
@@ -168,7 +168,7 @@ bool TEMPer1Device::match(struct libusb_device *dev) {
   struct libusb_device_descriptor desc;
   int ret = libusb_get_device_descriptor(dev, &desc);
   if (ret < 0) {
-    logging.get("node:temper")
+    Log::get("node:temper")
         ->warn("Could not get USB device descriptor: {}",
                libusb_strerror((enum libusb_error)ret));
     return false;
@@ -185,7 +185,7 @@ bool TEMPer2Device::match(struct libusb_device *dev) {
   struct libusb_device_descriptor desc;
   ret = libusb_get_device_descriptor(dev, &desc);
   if (ret < 0) {
-    logging.get("node:temper")
+    Log::get("node:temper")
         ->warn("Could not get USB device descriptor: {}",
                libusb_strerror((enum libusb_error)ret));
     return false;
@@ -193,7 +193,7 @@ bool TEMPer2Device::match(struct libusb_device *dev) {
 
   ret = libusb_open(dev, &handle);
   if (ret < 0) {
-    logging.get("node:temper")
+    Log::get("node:temper")
         ->warn("Failed to open USB device: {}",
                libusb_strerror((enum libusb_error)ret));
     return false;
@@ -202,7 +202,7 @@ bool TEMPer2Device::match(struct libusb_device *dev) {
   ret = libusb_get_string_descriptor_ascii(handle, desc.iProduct, product,
                                            sizeof(product));
   if (ret < 0) {
-    logging.get("node:temper")
+    Log::get("node:temper")
         ->warn("Could not get USB string descriptor: {}",
                libusb_strerror((enum libusb_error)ret));
     return false;
@@ -217,7 +217,7 @@ bool TEMPerHUMDevice::match(struct libusb_device *dev) {
   struct libusb_device_descriptor desc;
   int ret = libusb_get_device_descriptor(dev, &desc);
   if (ret < 0) {
-    logging.get("node:temper")
+    Log::get("node:temper")
         ->warn("Could not get USB device descriptor: {}",
                libusb_strerror((enum libusb_error)ret));
     return false;
@@ -229,7 +229,7 @@ bool TEMPerHUMDevice::match(struct libusb_device *dev) {
 int villas::node::temper_type_start(villas::node::SuperNode *sn) {
   context = usb::get_context();
 
-  logger = logging.get("node:temper");
+  logger = Log::get("node:temper");
 
   // Enumerate temper devices
   devices.clear();
diff --git a/lib/nodes/uldaq.cpp b/lib/nodes/uldaq.cpp
index ad442eecd..9fba87861 100644
--- a/lib/nodes/uldaq.cpp
+++ b/lib/nodes/uldaq.cpp
@@ -194,7 +194,7 @@ int villas::node::uldaq_type_start(villas::node::SuperNode *sn) {
   if (err != ERR_NO_ERROR)
     throw RuntimeError("Failed to retrieve DAQ device list");
 
-  auto logger = logging.get("node:uldaq");
+  auto logger = Log::get("node:uldaq");
   logger->info("Found {} DAQ devices", num_devs);
   for (unsigned i = 0; i < num_devs; i++) {
     DaqDeviceDescriptor *desc = &descriptors[i];
diff --git a/lib/nodes/webrtc/peer_connection.cpp b/lib/nodes/webrtc/peer_connection.cpp
index f8b50be73..79e83dddd 100644
--- a/lib/nodes/webrtc/peer_connection.cpp
+++ b/lib/nodes/webrtc/peer_connection.cpp
@@ -32,7 +32,7 @@ PeerConnection::PeerConnection(const std::string &server,
                                rtc::DataChannelInit d)
     : web(w), extraServers({}), dataChannelInit(d), defaultConfig(cfg),
       conn(nullptr), chan(nullptr), out_signals(out_signals),
-      logger(logging.get("webrtc:pc")), stopStartup(false),
+      logger(Log::get("webrtc:pc")), stopStartup(false),
       warnNotConnected(false), standby(true), first(false), firstID(INT_MAX),
       secondID(INT_MAX), onMessageCallback(nullptr) {
   client = std::make_shared<SignalingClient>(server, session, peer, web);
diff --git a/lib/nodes/webrtc/signaling_client.cpp b/lib/nodes/webrtc/signaling_client.cpp
index f6d4cb53f..b00b9f5bc 100644
--- a/lib/nodes/webrtc/signaling_client.cpp
+++ b/lib/nodes/webrtc/signaling_client.cpp
@@ -24,7 +24,7 @@ SignalingClient::SignalingClient(const std::string &server,
                                  const std::string &session,
                                  const std::string &peer, Web *w)
     : retry_count(0), web(w), running(false),
-      logger(logging.get("webrtc:signal")) {
+      logger(Log::get("webrtc:signal")) {
   int ret;
   const char *prot, *a, *p;
 
diff --git a/lib/nodes/websocket.cpp b/lib/nodes/websocket.cpp
index f63178c9d..39da203fa 100644
--- a/lib/nodes/websocket.cpp
+++ b/lib/nodes/websocket.cpp
@@ -29,7 +29,7 @@ static std::list<struct websocket_connection *>
 static std::mutex connections_lock;
 
 static villas::node::Web *web;
-static villas::Logger logger = logging.get("websocket");
+static villas::Logger logger = Log::get("websocket");
 
 // Forward declarations
 static NodeCompatType p;
diff --git a/lib/path.cpp b/lib/path.cpp
index 904fedafb..e9ff9bcbd 100644
--- a/lib/path.cpp
+++ b/lib/path.cpp
@@ -113,7 +113,7 @@ Path::Path()
       rate(0), // Disabled
       affinity(0), enabled(true), poll(-1), reversed(false), builtin(true),
       original_sequence_no(-1), queuelen(DEFAULT_QUEUE_LENGTH),
-      logger(logging.get(fmt::format("path:{}", id++))) {
+      logger(Log::get(fmt::format("path:{}", id++))) {
   uuid_clear(uuid);
 
   pool.state = State::DESTROYED;
diff --git a/lib/pool.cpp b/lib/pool.cpp
index f3c4c0465..612108a79 100644
--- a/lib/pool.cpp
+++ b/lib/pool.cpp
@@ -17,7 +17,7 @@ using namespace villas;
 int villas::node::pool_init(struct Pool *p, size_t cnt, size_t blocksz,
                             struct memory::Type *m) {
   int ret;
-  auto logger = logging.get("pool");
+  auto logger = Log::get("pool");
 
   // Make sure that we use a block size that is aligned to the size of a cache line
   p->alignment = kernel::getCachelineSize();
diff --git a/lib/queue.cpp b/lib/queue.cpp
index eb1f96407..966d62eb3 100644
--- a/lib/queue.cpp
+++ b/lib/queue.cpp
@@ -46,7 +46,7 @@ int villas::node::queue_init(struct CQueue *q, size_t size,
     size_t old_size = size;
     size = LOG2_CEIL(size);
 
-    auto logger = logging.get("queue");
+    auto logger = Log::get("queue");
     logger->warn("A queue size was changed from {} to {}", old_size, size);
   }
 
diff --git a/lib/stats.cpp b/lib/stats.cpp
index f4f8dad06..7a0aa780c 100644
--- a/lib/stats.cpp
+++ b/lib/stats.cpp
@@ -93,7 +93,7 @@ enum Stats::Type Stats::lookupType(const std::string &str) {
   throw std::invalid_argument("Invalid stats type");
 }
 
-Stats::Stats(int buckets, int warmup) : logger(logging.get("stats")) {
+Stats::Stats(int buckets, int warmup) : logger(Log::get("stats")) {
   for (auto m : metrics) {
     histograms.emplace(std::piecewise_construct, std::forward_as_tuple(m.first),
                        std::forward_as_tuple(buckets, warmup));
@@ -133,7 +133,7 @@ void Stats::printHeader(enum Format fmt) {
 
 void Stats::setupTable() {
   if (!table) {
-    auto logger = logging.get("stats");
+    auto logger = Log::get("stats");
     table = std::make_shared<Table>(logger, columns);
   }
 }
diff --git a/lib/super_node.cpp b/lib/super_node.cpp
index 32a4999e8..a4284bddb 100644
--- a/lib/super_node.cpp
+++ b/lib/super_node.cpp
@@ -56,7 +56,7 @@ SuperNode::SuperNode()
   kernel::nl::init(); // Fill link cache
 #endif                // WITH_NETEM
 
-  logger = logging.get("super_node");
+  logger = Log::get("super_node");
 }
 
 void SuperNode::parse(const std::string &u) {
@@ -111,7 +111,7 @@ void SuperNode::parse(json_t *root) {
 #endif // WITH_WEB
 
   if (json_logging)
-    logging.parse(json_logging);
+    Log::getInstance().parse(json_logging);
 
   // Parse nodes
   if (json_nodes) {
diff --git a/lib/usb.cpp b/lib/usb.cpp
index 6c3f3dd79..1580e24ef 100644
--- a/lib/usb.cpp
+++ b/lib/usb.cpp
@@ -78,7 +78,7 @@ struct libusb_context *villas::usb::init() {
   int ret;
   struct libusb_context *ctx;
 
-  logger = logging.get("usb");
+  logger = Log::get("usb");
 
   ret = libusb_init(&ctx);
   if (ret)
diff --git a/lib/web.cpp b/lib/web.cpp
index b3e6d2251..b3b2be629 100644
--- a/lib/web.cpp
+++ b/lib/web.cpp
@@ -101,7 +101,7 @@ void Web::lwsLogger(int lws_lvl, const char *msg) {
   if (strstr(msg, "Unable to open") == msg)
     lws_lvl = LLL_WARN;
 
-  Logger logger = logging.get("lws");
+  Logger logger = Log::get("lws");
 
   switch (lws_lvl) {
   case LLL_ERR:
@@ -162,9 +162,9 @@ void Web::worker() {
 }
 
 Web::Web(Api *a)
-    : state(State::INITIALIZED), logger(logging.get("web")), context(nullptr),
+    : state(State::INITIALIZED), logger(Log::get("web")), context(nullptr),
       vhost(nullptr), port(getuid() > 0 ? 8080 : 80), api(a) {
-  lws_set_log_level(lwsLogLevel(logging.getLevel()), lwsLogger);
+  lws_set_log_level(lwsLogLevel(Log::getInstance().getLevel()), lwsLogger);
 }
 
 Web::~Web() {
diff --git a/src/villas-compare.cpp b/src/villas-compare.cpp
index 71c0e6b6b..ef88da2b3 100644
--- a/src/villas-compare.cpp
+++ b/src/villas-compare.cpp
@@ -165,7 +165,7 @@ class Compare : public Tool {
         exit(EXIT_SUCCESS);
 
       case 'd':
-        logging.setLevel(optarg);
+        Log::getInstance().setLevel(optarg);
         break;
 
       case 'h':
diff --git a/src/villas-convert.cpp b/src/villas-convert.cpp
index 89b28166e..944af0dba 100644
--- a/src/villas-convert.cpp
+++ b/src/villas-convert.cpp
@@ -88,7 +88,7 @@ class Convert : public Tool {
         break;
 
       case 'd':
-        logging.setLevel(optarg);
+        Log::getInstance().setLevel(optarg);
         break;
 
       case 'h':
diff --git a/src/villas-hook.cpp b/src/villas-hook.cpp
index 0a3985b91..d9b0c868b 100644
--- a/src/villas-hook.cpp
+++ b/src/villas-hook.cpp
@@ -144,7 +144,7 @@ class Hook : public Tool {
         goto check;
 
       case 'd':
-        logging.setLevel(optarg);
+        Log::getInstance().setLevel(optarg);
         break;
 
       case 'o':
diff --git a/src/villas-node.cpp b/src/villas-node.cpp
index 5bb432fc9..3a13d46e3 100644
--- a/src/villas-node.cpp
+++ b/src/villas-node.cpp
@@ -155,7 +155,7 @@ class Node : public Tool {
         exit(EXIT_SUCCESS);
 
       case 'd':
-        logging.setLevel(optarg);
+        Log::getInstance().setLevel(optarg);
         break;
 
       case 'C':
diff --git a/src/villas-pipe.cpp b/src/villas-pipe.cpp
index 68f375f1b..053d90ba2 100644
--- a/src/villas-pipe.cpp
+++ b/src/villas-pipe.cpp
@@ -57,7 +57,7 @@ class PipeDirection {
       : node(n), formatter(fmt), stop(false), enabled(en), limit(lim),
         count(0) {
     auto loggerName = fmt::format("pipe:{}", name);
-    logger = logging.get(loggerName);
+    logger = Log::get(loggerName);
 
     // Initialize memory
     unsigned pool_size =
@@ -372,7 +372,7 @@ class Pipe : public Tool {
         break;
 
       case 'd':
-        logging.setLevel(optarg);
+        Log::getInstance().setLevel(optarg);
         break;
 
       case 'h':
@@ -404,7 +404,7 @@ class Pipe : public Tool {
     json_t *json_format;
     json_error_t err;
 
-    logger->info("Logging level: {}", logging.getLevelName());
+    logger->info("Logging level: {}", Log::getInstance().getLevelName());
 
     if (!uri.empty())
       sn.parse(uri);
diff --git a/src/villas-relay.cpp b/src/villas-relay.cpp
index 8d2a71f14..d9e134ebe 100644
--- a/src/villas-relay.cpp
+++ b/src/villas-relay.cpp
@@ -36,7 +36,7 @@ namespace tools {
 RelaySession::RelaySession(Relay *r, Identifier sid)
     : identifier(sid), connects(0) {
   auto loggerName = fmt::format("relay:{}", sid);
-  logger = villas::logging.get(loggerName);
+  logger = villas::Log::get(loggerName);
 
   logger->info("Session created: {}", identifier);
 
@@ -76,7 +76,7 @@ RelaySession *RelaySession::get(Relay *r, lws *wsi) {
 
     return rs;
   } else {
-    auto logger = logging.get("villas-relay");
+    auto logger = Log::get("villas-relay");
     logger->info("Found existing session: {}", sid);
 
     return it->second;
@@ -198,7 +198,8 @@ Relay::Relay(int argc, char *argv[])
     throw RuntimeError("Failed to initialize memory");
 
   // Initialize logging
-  lws_set_log_level(Web::lwsLogLevel(logging.getLevel()), Web::lwsLogger);
+  lws_set_log_level(Web::lwsLogLevel(Log::getInstance().getLevel()),
+                    Web::lwsLogger);
 
   protocols = {{.name = "http",
                 .callback = lws_callback_http_dummy,
@@ -354,8 +355,9 @@ void Relay::parse() {
   while ((c = getopt(argc, argv, "hVp:P:ld:u:")) != -1) {
     switch (c) {
     case 'd':
-      logging.setLevel(optarg);
-      lws_set_log_level(Web::lwsLogLevel(logging.getLevel()), Web::lwsLogger);
+      Log::getInstance().setLevel(optarg);
+      lws_set_log_level(Web::lwsLogLevel(Log::getInstance().getLevel()),
+                        Web::lwsLogger);
       break;
 
     case 'p':
@@ -422,7 +424,7 @@ int Relay::main() {
   ctx_info.mounts = &mount;
   ctx_info.user = (void *)this;
 
-  auto lwsLogger = logging.get("lws");
+  auto lwsLogger = Log::get("lws");
 
   context = lws_create_context(&ctx_info);
   if (context == nullptr) {
diff --git a/src/villas-signal.cpp b/src/villas-signal.cpp
index 9dc7afac2..82e20548d 100644
--- a/src/villas-signal.cpp
+++ b/src/villas-signal.cpp
@@ -159,7 +159,7 @@ class Signal : public Tool {
         goto check;
 
       case 'd':
-        logging.setLevel(optarg);
+        Log::getInstance().setLevel(optarg);
         break;
 
       case 'V':


Test patch:
diff --git a/common/tests/unit/graph.cpp b/common/tests/unit/graph.cpp
index 4c27c772e..2c7cf7408 100644
--- a/common/tests/unit/graph.cpp
+++ b/common/tests/unit/graph.cpp
@@ -18,7 +18,7 @@ using namespace villas;
 TestSuite(graph, .description = "Graph library");
 
 Test(graph, basic, .description = "DirectedGraph") {
-  Logger logger = logging.get("test:graph:basic");
+  Logger logger = Log::get("test:graph:basic");
   villas::graph::DirectedGraph<> g("test:graph:basic");
 
   logger->info("Testing basic graph construction and modification");
@@ -48,7 +48,7 @@ Test(graph, basic, .description = "DirectedGraph") {
 }
 
 Test(graph, path, .description = "Find path") {
-  Logger logger = logging.get("test:graph:path");
+  Logger logger = Log::get("test:graph:path");
   logger->info("Testing path finding algorithm");
 
   using Graph = villas::graph::DirectedGraph<>;
@@ -109,7 +109,7 @@ Test(graph, path, .description = "Find path") {
 }
 
 Test(graph, memory_manager, .description = "Global Memory Manager") {
-  Logger logger = logging.get("test:graph:mm");
+  Logger logger = Log::get("test:graph:mm");
   auto &mm = villas::MemoryManager::get();
 
   logger->info("Create address spaces");
diff --git a/fpga/tests/unit/dma.cpp b/fpga/tests/unit/dma.cpp
index f17515bd4..6700266dd 100644
--- a/fpga/tests/unit/dma.cpp
+++ b/fpga/tests/unit/dma.cpp
@@ -20,7 +20,7 @@ using namespace villas;
 
 // cppcheck-suppress unknownMacro
 Test(fpga, dma, .description = "DMA") {
-  auto logger = logging.get("unit-test:dma");
+  auto logger = Log::get("unit-test:dma");
 
   std::list<std::shared_ptr<fpga::ip::Dma>> dmaIps;
 
diff --git a/fpga/tests/unit/fifo.cpp b/fpga/tests/unit/fifo.cpp
index 958e3ccc9..f875aa30d 100644
--- a/fpga/tests/unit/fifo.cpp
+++ b/fpga/tests/unit/fifo.cpp
@@ -23,7 +23,7 @@ Test(fpga, fifo, .description = "FIFO") {
   char src[255], dst[255];
   size_t count = 0;
 
-  auto logger = logging.get("unit-test:fifo");
+  auto logger = Log::get("unit-test:fifo");
 
   for (auto &ip : state.cards.front()->ips) {
     // Skip non-fifo IPs
diff --git a/fpga/tests/unit/gpu.cpp b/fpga/tests/unit/gpu.cpp
index ef249eedb..e9bbfe194 100644
--- a/fpga/tests/unit/gpu.cpp
+++ b/fpga/tests/unit/gpu.cpp
@@ -26,7 +26,7 @@ using namespace villas;
 
 // cppcheck-suppress unknownMacro
 Test(fpga, gpu_dma, .description = "GPU DMA tests") {
-  auto logger = logging.get("unit-test:dma");
+  auto logger = Log::get("unit-test:dma");
 
   auto &card = state.cards.front();
 
diff --git a/fpga/tests/unit/logging.cpp b/fpga/tests/unit/logging.cpp
index 2d7727c6d..46abafdbc 100644
--- a/fpga/tests/unit/logging.cpp
+++ b/fpga/tests/unit/logging.cpp
@@ -41,7 +41,7 @@ static int format_msg(char *buf, size_t buflen, const char *msg, va_list args) {
 }
 
 void criterion_log_noformat(enum criterion_severity severity, const char *msg) {
-  auto logger = villas::logging.get("criterion");
+  auto logger = villas::Log::get("criterion");
 
   switch (severity) {
   case CR_LOG_INFO:
@@ -67,7 +67,7 @@ void criterion_vlog(enum criterion_logging_level level, const char *msg,
 
   format_msg(formatted_msg, sizeof(formatted_msg), msg, args);
 
-  auto logger = villas::logging.get("criterion");
+  auto logger = villas::Log::get("criterion");
   logger->info(formatted_msg);
 }
 
@@ -85,7 +85,7 @@ void criterion_plog(enum criterion_logging_level level,
   format_msg(formatted_msg, sizeof(formatted_msg), msg, args);
   va_end(args);
 
-  auto logger = villas::logging.get("criterion");
+  auto logger = villas::Log::get("criterion");
 
   if (strstr(formatted_msg, "Warning"))
     logger->warn(formatted_msg);
diff --git a/fpga/tests/unit/main.cpp b/fpga/tests/unit/main.cpp
index db8385218..8aeb32d7d 100644
--- a/fpga/tests/unit/main.cpp
+++ b/fpga/tests/unit/main.cpp
@@ -35,7 +35,7 @@ static bool suite_enabled(struct criterion_test_set *tests, const char *name) {
 // Limit number of parallel jobs to 1 in case we use the FPGA
 ReportHook(PRE_ALL)(struct criterion_test_set *tests) {
   if (suite_enabled(tests, "fpga")) {
-    auto logger = villas::logging.get("unittest");
+    auto logger = villas::Log::get("unittest");
 
     logger->info("FPGA tests enabled. Only 1 job is executed in parallel!.");
     criterion_options.jobs = 1;
diff --git a/fpga/tests/unit/rtds.cpp b/fpga/tests/unit/rtds.cpp
index c3f64df4a..b5728667b 100644
--- a/fpga/tests/unit/rtds.cpp
+++ b/fpga/tests/unit/rtds.cpp
@@ -29,7 +29,7 @@ using namespace villas::fpga::ip;
 
 // cppcheck-suppress unknownMacro
 Test(fpga, rtds, .description = "RTDS") {
-  auto logger = villas::logging.get("unit-test:rtds");
+  auto logger = villas::Log::get("unit-test:rtds");
 
   std::list<villas::fpga::ip::RtdsGtfpga *> rtdsIps;
   std::list<villas::fpga::ip::Dma *> dmaIps;
diff --git a/fpga/tests/unit/rtds2gpu.cpp b/fpga/tests/unit/rtds2gpu.cpp
index a1f9ffcd2..20ba0890a 100644
--- a/fpga/tests/unit/rtds2gpu.cpp
+++ b/fpga/tests/unit/rtds2gpu.cpp
@@ -52,7 +52,7 @@ static void dumpMem(const uint32_t *addr, size_t len) {
 
 // cppcheck-suppress unknownMacro
 Test(fpga, rtds2gpu_loopback_dma, .description = "Rtds2Gpu") {
-  auto logger = logging.get("unit-test:rtds2gpu");
+  auto logger = Log::get("unit-test:rtds2gpu");
 
   for (auto &ip : state.cards.front()->ips) {
     if (*ip != fpga::Vlnv("acs.eonerc.rwth-aachen.de:hls:rtds2gpu:"))
@@ -158,7 +158,7 @@ Test(fpga, rtds2gpu_loopback_dma, .description = "Rtds2Gpu") {
 
 // cppcheck-suppress unknownMacro
 Test(fpga, rtds2gpu_rtt_cpu, .description = "Rtds2Gpu RTT via CPU") {
-  auto logger = logging.get("unit-test:rtds2gpu");
+  auto logger = Log::get("unit-test:rtds2gpu");
 
   // Collect neccessary IPs
   auto gpu2rtds = std::dynamic_pointer_cast<fpga::ip::Gpu2Rtds>(
@@ -233,7 +233,7 @@ void gpu_rtds_rtt_stop();
 
 // cppcheck-suppress unknownMacro
 Test(fpga, rtds2gpu_rtt_gpu, .description = "Rtds2Gpu RTT via GPU") {
-  auto logger = logging.get("unit-test:rtds2gpu");
+  auto logger = Log::get("unit-test:rtds2gpu");
 
   // Collect neccessary IPs
   auto gpu2rtds = std::dynamic_pointer_cast<fpga::ip::Gpu2Rtds>(
diff --git a/fpga/tests/unit/timer.cpp b/fpga/tests/unit/timer.cpp
index 23e518643..37e3e87cc 100644
--- a/fpga/tests/unit/timer.cpp
+++ b/fpga/tests/unit/timer.cpp
@@ -16,7 +16,7 @@
 
 // cppcheck-suppress unknownMacro
 Test(fpga, timer, .description = "Timer Counter") {
-  auto logger = villas::logging.get("unit-test:timer");
+  auto logger = villas::Log::get("unit-test:timer");
 
   size_t count = 0;
   for (auto &ip : state.cards.front()->ips) {
diff --git a/tests/unit/format.cpp b/tests/unit/format.cpp
index c05590ccf..d788dc9bc 100644
--- a/tests/unit/format.cpp
+++ b/tests/unit/format.cpp
@@ -240,7 +240,7 @@ ParameterizedTest(Param *p, format, lowlevel, .init = init_memory) {
   char buf[8192];
   size_t wbytes, rbytes;
 
-  Logger logger = logging.get("test:format:lowlevel");
+  Logger logger = Log::get("test:format:lowlevel");
 
   logger->info("Running test for format={}, cnt={}", p->fmt, p->cnt);
 
@@ -330,7 +330,7 @@ ParameterizedTest(Param *p, format, highlevel, .init = init_memory) {
   int ret, cnt;
   char *retp;
 
-  Logger logger = logging.get("test:format:highlevel");
+  Logger logger = Log::get("test:format:highlevel");
 
   logger->info("Running test for format={}, cnt={}", p->fmt, p->cnt);
 
diff --git a/tests/unit/pool.cpp b/tests/unit/pool.cpp
index de98f8a88..c78550f72 100644
--- a/tests/unit/pool.cpp
+++ b/tests/unit/pool.cpp
@@ -46,7 +46,7 @@ ParameterizedTest(struct param *p, pool, basic, .init = init_memory) {
   struct Pool pool;
   void *ptr, *ptrs[p->pool_size];
 
-  logging.setLevel("trace");
+  Log::getInstance().setLevel("trace");
 
   if (!utils::isPrivileged() && p->mt == &memory::mmap_hugetlb)
     cr_skip_test("Skipping memory_mmap_hugetlb tests allocatpr because we are "
diff --git a/tests/unit/queue.cpp b/tests/unit/queue.cpp
index 4c21fef4c..c3f213fba 100644
--- a/tests/unit/queue.cpp
+++ b/tests/unit/queue.cpp
@@ -260,7 +260,7 @@ ParameterizedTest(struct param *p, queue, multi_threaded, .timeout = 20,
   int ret, cycpop;
   struct Tsc tsc;
 
-  Logger logger = logging.get("test:queue:multi_threaded");
+  Logger logger = Log::get("test:queue:multi_threaded");
 
   if (!utils::isPrivileged() && p->mt == &memory::mmap_hugetlb)
     cr_skip_test("Skipping memory_mmap_hugetlb tests allocatpr because we are "