diff --git a/src/common/logging/clap.cpp b/src/common/logging/clap.cpp
new file mode 100644
index 00000000..971da3ca
--- /dev/null
+++ b/src/common/logging/clap.cpp
@@ -0,0 +1,35 @@
+// yabridge: a Wine plugin bridge
+// Copyright (C) 2020-2022 Robbert van der Helm
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include "clap.h"
+
+#include
+
+#include "../serialization/clap.h"
+
+ClapLogger::ClapLogger(Logger& generic_logger) : logger_(generic_logger) {}
+
+// bool ClapLogger::log_request(bool is_host_plugin,
+// const ClapPluginFactoryProxy::Construct&) {
+// return log_request_base(is_host_plugin, [&](auto& message) {
+// message << "GetPluginFactory()";
+// });
+// }
+
+// void ClapLogger::log_response(bool is_host_plugin, const Ack&) {
+// log_response_base(is_host_plugin, [&](auto& message) { message << "ACK";
+// });
+// }
diff --git a/src/common/logging/clap.h b/src/common/logging/clap.h
new file mode 100644
index 00000000..4521592d
--- /dev/null
+++ b/src/common/logging/clap.h
@@ -0,0 +1,144 @@
+// yabridge: a Wine plugin bridge
+// Copyright (C) 2020-2022 Robbert van der Helm
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#pragma once
+
+#include
+
+// #include "../serialization/clap.h"
+#include "common.h"
+
+/**
+ * Wraps around `Logger` to provide CLAP-specific logging functionality for
+ * debugging plugins. This way we can have all the complex initialisation be
+ * performed in one place.
+ */
+class ClapLogger {
+ public:
+ ClapLogger(Logger& generic_logger);
+
+ /**
+ * @see Logger::log
+ */
+ inline void log(const std::string& message) { logger_.log(message); }
+
+ // TODO: Logging for extension queries, factory type queries
+
+ // For every object we send using `ClapMessageHandler` we have overloads
+ // that print information about the request and the response. The boolean
+ // flag here indicates whether the request was initiated on the host side
+ // (what we'll call a control message).
+ // `log_response()` should only be called if the corresponding
+ // `log_request()` call returned `true`. This way we can filter out the
+ // log message for the response together with the request.
+
+ // TODO: M ain thread rquests
+ // bool log_request(bool is_host_plugin,
+ // const ClapPluginFactoryProxy::Construct&);
+
+ // TODO: Audio thread requests
+ // bool log_request(bool is_host_plugin,
+ // const YaAudioProcessor::SetBusArrangements&);
+
+ // TOOD: Main thread responses
+ // void log_response(bool is_host_plugin, const Ack&);
+ // void log_response(bool is_host_plugin,
+ // const UniversalTResult&,
+ // bool from_cache = false);
+
+ // TODO: Audio thread responses
+ // void log_response(bool is_host_plugin,
+ // const YaAudioProcessor::GetBusArrangementResponse&);
+
+ // TODO: Universal response
+ // template
+ // void log_response(bool is_host_plugin,
+ // const PrimitiveWrapper& value,
+ // bool from_cache = false) {
+ // // For logging all primitive return values other than `tresult`
+ // log_response_base(is_host_plugin, [&](auto& message) {
+ // message << value;
+ // if (from_cache) {
+ // message << " (from cache)";
+ // }
+ // });
+ // }
+
+ /**
+ * @see Logger::log_trace
+ */
+ template F>
+ inline void log_trace(F&& fn) {
+ logger_.log_trace(std::forward(fn));
+ }
+
+ Logger& logger_;
+
+ private:
+ /**
+ * Log a request with a standard prefix based on the boolean flag we pass to
+ * every logging function so we don't have to repeat it everywhere.
+ *
+ * Returns `true` if the log message was displayed, and the response should
+ * thus also be logged.
+ */
+ template F>
+ bool log_request_base(bool is_host_plugin,
+ Logger::Verbosity min_verbosity,
+ F callback) {
+ if (logger_.verbosity_ >= min_verbosity) [[unlikely]] {
+ std::ostringstream message;
+ if (is_host_plugin) {
+ message << "[host -> vst] >> ";
+ } else {
+ message << "[vst -> host] >> ";
+ }
+
+ callback(message);
+ log(message.str());
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ template F>
+ bool log_request_base(bool is_host_plugin, F callback) {
+ return log_request_base(is_host_plugin, Logger::Verbosity::most_events,
+ callback);
+ }
+
+ /**
+ * Log a response with a standard prefix based on the boolean flag we pass
+ * to every logging function so we don't have to repeat it everywhere.
+ *
+ * This should only be called when the corresonding `log_request()` returned
+ * `true`.
+ */
+ template F>
+ void log_response_base(bool is_host_plugin, F callback) {
+ std::ostringstream message;
+ if (is_host_plugin) {
+ message << "[vst <- host] ";
+ } else {
+ message << "[host <- vst] ";
+ }
+
+ callback(message);
+ log(message.str());
+ }
+};