diff --git a/qt/logger.cpp b/qt/logger.cpp
new file mode 100644
index 0000000..d8a3e57
--- /dev/null
+++ b/qt/logger.cpp
@@ -0,0 +1,45 @@
+#include "logger.h"
+
+#include <QDebug>
+
+Logger::Logger(QObject *parent)
+    : QObject(parent),
+      socket(Q_NULLPTR)
+{
+
+}
+
+bool Logger::init(const QString &tag)
+{
+    this->tag = tag;
+    if (socket == Q_NULLPTR)
+        socket = new UnixDomainDatagramSocket(this);
+
+    return socket->open();
+}
+
+void Logger::print(const QString &string)
+{
+    QByteArray bytes(string.toLocal8Bit().data());
+    bytes.prepend(' ').prepend(tag.toLocal8Bit().data());
+
+    qDebug() << bytes;
+
+    socket->writeDatagram(bytes, "/tmp/logger.uds");
+}
+
+void Logger::hexdump(const QString &message, const QByteArray &bytes)
+{
+    QByteArray line;
+    line += tag + " " + message + " [";
+    for (int i = 0; i < bytes.length(); i++)
+    {
+        line += QByteArray::number(uchar(bytes.at(i)), 16).toUpper();
+        line += " ";
+    }
+    line = line.trimmed();
+    line += "]\n";
+
+    qDebug() << line;
+    socket->writeDatagram(line, "/tmp/logger.uds");
+}
diff --git a/qt/logger.h b/qt/logger.h
new file mode 100644
index 0000000..d8920db
--- /dev/null
+++ b/qt/logger.h
@@ -0,0 +1,29 @@
+// Logger는 pool sw의 app-logger를 사용할 수 있게 해준다.
+
+#ifndef LOGGER_H
+#define LOGGER_H
+
+#include <QObject>
+
+#include "unixdomaindatagramsocket.h"
+
+class Logger : public QObject
+{
+    Q_OBJECT
+public:
+    explicit Logger(QObject *parent = nullptr);
+
+    bool init(const QString &tag);
+
+signals:
+
+public slots:
+    void print(const QString &string);
+    void hexdump(const QString &message, const QByteArray &bytes);
+
+private:
+    UnixDomainDatagramSocket *socket;
+    QString tag;
+};
+
+#endif // LOGGER_H
diff --git a/qt/unixdomaindatagramsocket.cpp b/qt/unixdomaindatagramsocket.cpp
new file mode 100644
index 0000000..159f621
--- /dev/null
+++ b/qt/unixdomaindatagramsocket.cpp
@@ -0,0 +1,152 @@
+#include "unixdomaindatagramsocket.h"
+
+#include <QVarLengthArray>
+
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/ioctl.h>
+#include <linux/sockios.h>
+
+UnixDomainDatagramSocket::UnixDomainDatagramSocket(QObject *parent)
+    : QObject(parent),
+      sockfd(-1), notifier(Q_NULLPTR)
+{
+
+}
+
+UnixDomainDatagramSocket::~UnixDomainDatagramSocket()
+{
+    if (sockfd >= 0)
+        close(sockfd);
+
+    if (!boundPath.isEmpty())
+        unlink(boundPath.toLocal8Bit().data());
+}
+
+bool UnixDomainDatagramSocket::open()
+{
+    if (sockfd >= 0)
+        return false;
+
+    sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
+    if (sockfd < 0)
+        return false;
+
+    notifier = new QSocketNotifier(sockfd, QSocketNotifier::Read, this);
+    connect(notifier, SIGNAL(activated(int)), SLOT(emitReadyRead()));
+
+    return true;
+}
+
+bool UnixDomainDatagramSocket::bind(const QString &path)
+{
+    return bind(path.toLocal8Bit().data());
+}
+
+bool UnixDomainDatagramSocket::bind(const char *path)
+{
+    if (sockfd < 0 && !open())
+        return false;
+
+    unlink(path);
+
+    struct sockaddr_un address;
+    address.sun_family = AF_UNIX;
+    strncpy(address.sun_path, path, sizeof(address.sun_path));
+
+    int ret = ::bind(sockfd, reinterpret_cast<struct sockaddr *>(&address), sizeof(address));
+    if (ret == -1)
+        return false;
+
+    return true;
+}
+
+bool UnixDomainDatagramSocket::hasPendingDatagrams()
+{
+    ssize_t readBytes;
+    do
+    {
+        char c;
+        readBytes = ::recv(sockfd, &c, 1, MSG_DONTWAIT | MSG_PEEK);
+    }
+    while (readBytes == -1 && errno == EINTR);
+
+    return (readBytes != -1) || errno == EMSGSIZE;
+}
+
+qint64 UnixDomainDatagramSocket::pendingDatagramSize()
+{
+    QVarLengthArray<char, 8192> udpMessagePeekBuffer(8192);
+    ssize_t recvResult = -1;
+
+    for (;;)
+    {
+        recvResult = ::recv(sockfd, udpMessagePeekBuffer.data(),
+            size_t(udpMessagePeekBuffer.size()), MSG_DONTWAIT | MSG_PEEK);
+        if (recvResult == -1 && errno == EINTR)
+            continue;
+
+        if (recvResult != ssize_t(udpMessagePeekBuffer.size()))
+            break;
+
+        udpMessagePeekBuffer.resize(udpMessagePeekBuffer.size() * 2);
+    }
+
+    return qint64(recvResult);
+}
+
+qint64 UnixDomainDatagramSocket::readDatagram(QByteArray *datagram, QString *path)
+{
+    return readDatagram(datagram->data(), datagram->size(), path);
+}
+
+qint64 UnixDomainDatagramSocket::readDatagram(char *data, qint64 maxSize, QString *path)
+{
+    if (!notifier->isEnabled())
+        notifier->setEnabled(true);
+
+    struct sockaddr_un address;
+    socklen_t length;
+
+    ssize_t recvResult;
+    do
+    {
+        recvResult = ::recvfrom(sockfd, data, size_t(maxSize), 0, reinterpret_cast<struct sockaddr *>(&address), &length);
+    }
+    while (recvResult == -1 && errno == EINTR);
+
+    if (recvResult == -1)
+        return -1;
+
+    if (path)
+        *path = QString::fromLocal8Bit(address.sun_path);
+
+    return recvResult;
+}
+
+qint64 UnixDomainDatagramSocket::writeDatagram(const QByteArray &datagram, const QString &path)
+{
+    return writeDatagram(datagram.data(), datagram.size(), path);
+}
+
+qint64 UnixDomainDatagramSocket::writeDatagram(const char *data, qint64 size, const QString &path)
+{
+    return writeDatagram(data, size, path.toLocal8Bit().data());
+}
+
+qint64 UnixDomainDatagramSocket::writeDatagram(const char *data, qint64 size, const char *path)
+{
+    struct sockaddr_un address;
+    address.sun_family = AF_UNIX;
+    strncpy(address.sun_path, path, sizeof(address.sun_path));
+
+    ssize_t sendResult = ::sendto(sockfd, data, size_t(size), 0, reinterpret_cast<struct sockaddr *>(&address), sizeof(address));
+    return qint64(sendResult);
+}
+
+void UnixDomainDatagramSocket::emitReadyRead()
+{
+    notifier->setEnabled(false);
+    emit readyRead();
+}
diff --git a/qt/unixdomaindatagramsocket.h b/qt/unixdomaindatagramsocket.h
new file mode 100644
index 0000000..419f35d
--- /dev/null
+++ b/qt/unixdomaindatagramsocket.h
@@ -0,0 +1,44 @@
+// UnixDomainDatagramSocket은 UDS를 데이터그램 형식으로 사용하는 소켓 구현체다. QUdpSocket처럼 사용하도록 구현했다.
+//
+// 주의: 자동 변수 대신 new로 할당하라. 소멸자에서 열린 소켓을 닫고 만든 파일을 지우기 때문에 문제가 생길 수 있다.
+
+#ifndef UNIXDOMAINDATAGRAMSOCKET_H
+#define UNIXDOMAINDATAGRAMSOCKET_H
+
+#include <QObject>
+#include <QSocketNotifier>
+
+class UnixDomainDatagramSocket : public QObject
+{
+    Q_OBJECT
+public:
+    explicit UnixDomainDatagramSocket(QObject *parent = Q_NULLPTR);
+    ~UnixDomainDatagramSocket();
+
+    bool open();
+    bool bind(const QString &path);
+    bool bind(const char *path);
+
+    bool hasPendingDatagrams();
+    qint64 pendingDatagramSize();
+    qint64 readDatagram(QByteArray *datagram, QString *path = Q_NULLPTR);
+    qint64 readDatagram(char *data, qint64 maxSize, QString *path = Q_NULLPTR);
+    qint64 writeDatagram(const QByteArray &datagram, const QString &path);
+    qint64 writeDatagram(const char *data, qint64 size, const QString &path);
+    qint64 writeDatagram(const char *data, qint64 size, const char *path);
+
+signals:
+    void readyRead();
+
+public slots:
+
+private:
+    int sockfd;
+    QString boundPath;
+    QSocketNotifier *notifier;
+
+private slots:
+    void emitReadyRead();
+};
+
+#endif // UNIXDOMAINDATAGRAMSOCKET_H