diff --git a/src/main/java/org/tubes/db/Database.java b/src/main/java/org/tubes/db/Database.java
index db1527bd7016da8c9a5cd4a366bd7e892118eba1..a062b08954aa99f191aa57f5268c5f00e41902e5 100644
--- a/src/main/java/org/tubes/db/Database.java
+++ b/src/main/java/org/tubes/db/Database.java
@@ -3,6 +3,7 @@ package org.tubes.db;
 import com.zaxxer.hikari.HikariDataSource;
 import lombok.NonNull;
 import lombok.var;
+import org.tubes.models.Log;
 import org.tubes.utils.Config;
 import org.tubes.models.Subscription;
 
@@ -29,6 +30,7 @@ public class Database {
         try (var con = getConnection(); var stmt = con.createStatement()) {
             System.out.println(Subscription.getDDL());
             stmt.execute(Subscription.getDDL());
+            stmt.execute(Log.getDDL());
         } catch (SQLException e) {
             throw new RuntimeException(e);
         }
diff --git a/src/main/java/org/tubes/middlewares/LoggingMiddleware.java b/src/main/java/org/tubes/middlewares/LoggingMiddleware.java
new file mode 100644
index 0000000000000000000000000000000000000000..4b844b995846513f861c83aea4ab11e83ee6c66c
--- /dev/null
+++ b/src/main/java/org/tubes/middlewares/LoggingMiddleware.java
@@ -0,0 +1,101 @@
+package org.tubes.middlewares;
+
+import com.sun.net.httpserver.HttpExchange;
+import org.tubes.models.Log;
+import org.tubes.repositories.LogRepo;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import javax.annotation.Resource;
+import javax.xml.namespace.QName;
+import javax.xml.soap.SOAPBody;
+import javax.xml.soap.SOAPEnvelope;
+import javax.xml.soap.SOAPException;
+import javax.xml.soap.SOAPPart;
+import javax.xml.ws.WebServiceContext;
+import javax.xml.ws.handler.MessageContext;
+import javax.xml.ws.handler.soap.SOAPHandler;
+import javax.xml.ws.handler.soap.SOAPMessageContext;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class LoggingMiddleware implements SOAPHandler<SOAPMessageContext> {
+    private final String httpExchangeKey = "com.sun.xml.internal.ws.http.exchange";
+
+    protected void recordClient(String endpoint, String description, String ipAddr) throws SQLException {
+        System.out.println("Client " + ipAddr + " called " + endpoint + " with description: " + description);
+        Timestamp ts = new Timestamp(System.currentTimeMillis());
+        String s = ts.toString().split("\\.")[0];
+
+        Log model = new Log(description, ipAddr, endpoint, s);
+        LogRepo.getInstance().save(model);
+    }
+
+    private String getRemoteAddr(SOAPMessageContext context) {
+        HttpExchange httpExchange = (HttpExchange) context.get(this.httpExchangeKey);
+        System.out.println("remote addr: " + httpExchange.getRemoteAddress());
+        return httpExchange.getRemoteAddress().toString().replace("/", "");
+    }
+
+    protected String getClient(SOAPMessageContext context) {
+        return (String) context.get("client");
+    }
+
+    @Override
+    public Set<QName> getHeaders() {
+        return null;
+    }
+
+    @Override
+    public boolean handleMessage(SOAPMessageContext context) {
+        String client = (String) context.get("authenticated-client");
+        Boolean isOutbound = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
+        HttpExchange httpExchange = (HttpExchange) context.get(this.httpExchangeKey);
+
+        if (isOutbound) {
+            return true;
+        }
+        String remoteAddr = getRemoteAddr(context);
+        String endpoint = httpExchange.getRequestURI().toString();
+
+
+        try {
+            SOAPPart soapPart = context.getMessage().getSOAPPart();
+            SOAPEnvelope soapEnvelope = soapPart.getEnvelope();
+            SOAPBody soapBody = soapEnvelope.getBody();
+
+            Node operation = soapBody.getChildNodes().item(1);
+            String description = String.format("called %s", operation.getLocalName());
+
+            NodeList parameters = operation.getChildNodes();
+
+            for (int i = 1; i < parameters.getLength(); i += 2){
+                description = String.format("%s %s(%s)", description, parameters.item(i).getLocalName(), parameters.item(i).getTextContent());
+            }
+            Timestamp ts = new Timestamp(System.currentTimeMillis());
+            String s = ts.toString().split("\\.")[0];
+            Log model = new Log(description, remoteAddr, endpoint, s);
+            LogRepo.getInstance().save(model);
+
+        } catch (SOAPException e) {
+            return false;
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+        return true;
+    }
+
+    @Override
+    public boolean handleFault(SOAPMessageContext context) {
+        return false;
+    }
+
+    @Override
+    public void close(MessageContext context) {
+
+    }
+}
diff --git a/src/main/java/org/tubes/models/Log.java b/src/main/java/org/tubes/models/Log.java
new file mode 100644
index 0000000000000000000000000000000000000000..90063b4537b5dde09421856a24ed4ab78e6f1b32
--- /dev/null
+++ b/src/main/java/org/tubes/models/Log.java
@@ -0,0 +1,38 @@
+package org.tubes.models;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.tubes.models.annotation.Column;
+import org.tubes.models.annotation.Table;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@Table(name = "logs")
+public class Log extends BaseModel {
+    @Column("description")
+    private String description;
+    @Column("ip")
+    private String IP;
+    @Column("endpoint")
+    private String endpoint;
+    @Column("requested_at")
+    private String requested_at;
+
+
+    public static String getDDL() {
+        return String.join(
+                System.getProperty("line.separator"),
+                "CREATE TABLE IF NOT EXISTS logs (",
+                "  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,",
+                "  description TEXT NOT NULL,",
+                "  ip VARCHAR(50) NOT NULL,",
+                "  endpoint VARCHAR(50) NOT NULL,",
+                "  requested_at TIMESTAMP NOT NULL",
+                ");"
+        );
+    }
+}
diff --git a/src/main/java/org/tubes/repositories/LogRepo.java b/src/main/java/org/tubes/repositories/LogRepo.java
new file mode 100644
index 0000000000000000000000000000000000000000..83dca906034cc44b50ca59c632db23d9606a852f
--- /dev/null
+++ b/src/main/java/org/tubes/repositories/LogRepo.java
@@ -0,0 +1,27 @@
+package org.tubes.repositories;
+
+import lombok.NonNull;
+import lombok.var;
+import org.tubes.models.Log;
+
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.Collections;
+import java.util.List;
+
+import org.tubes.db.Database;
+
+public class LogRepo extends Repository<Log> {
+    private static LogRepo instance = null;
+    @Override
+    public @NonNull Class<Log> getModelClass() {
+        return Log.class;
+    }
+
+    public static @NonNull LogRepo getInstance() {
+        if (instance == null) {
+            instance = new LogRepo();
+        }
+        return instance;
+    }
+}
diff --git a/src/main/java/org/tubes/services/SubscriptionServiceImpl.java b/src/main/java/org/tubes/services/SubscriptionServiceImpl.java
index b0d085b345ac875827faf016579de9f48dc10445..cbb6535b9425c41bae17851b10e856a569b9257c 100644
--- a/src/main/java/org/tubes/services/SubscriptionServiceImpl.java
+++ b/src/main/java/org/tubes/services/SubscriptionServiceImpl.java
@@ -39,6 +39,7 @@ public class SubscriptionServiceImpl implements SubscriptionService {
 
     @Override
     public List<Subscription> getSubscriptions(String email) {
+        System.out.println("request");
         if (email == null) return Collections.emptyList();
         return SubscriptionRepo.getInstance().getByEmail(email);
     }
diff --git a/src/main/resources/handler-chain.xml b/src/main/resources/handler-chain.xml
index 2e0782fdcc6e613480163b1a343d42ab9f3c5d4b..35090ccc3ea930092e04f1fd150b877769f4b8b5 100644
--- a/src/main/resources/handler-chain.xml
+++ b/src/main/resources/handler-chain.xml
@@ -4,5 +4,8 @@
         <handler>
             <handler-class>org.tubes.middlewares.ApiKeyMiddleware</handler-class>
         </handler>
+        <handler>
+            <handler-class>org.tubes.middlewares.LoggingMiddleware</handler-class>
+        </handler>
     </handler-chain>
 </handler-chains>
\ No newline at end of file