logd: remove SocketClient from LogBuffer and LogBufferElement

In the future, we'll want to be able to write to outputs that are not
necessarily a libsysutils SocketClient, for example host tests of
LogBuffer.  Therefore, we add a LogWriter class to be used instead of
SocketClient.

Test: logging unit tests
Change-Id: I4385be65e14e83a635691a7ba79e9bf060e49484
diff --git a/logd/ChattyLogBuffer.cpp b/logd/ChattyLogBuffer.cpp
index fa5bcee..9a45299 100644
--- a/logd/ChattyLogBuffer.cpp
+++ b/logd/ChattyLogBuffer.cpp
@@ -34,8 +34,6 @@
 #include <cutils/properties.h>
 #include <private/android_logger.h>
 
-#include "LogKlog.h"
-#include "LogReader.h"
 #include "LogUtils.h"
 
 #ifndef __predict_false
@@ -494,8 +492,8 @@
     if (stats_->Sizes(id) > (2 * log_buffer_size(id))) {  // +100%
         // A misbehaving or slow reader has its connection
         // dropped if we hit too much memory pressure.
-        android::prdebug("Kicking blocked reader, pid %d, from ChattyLogBuffer::kickMe()\n",
-                         me->client()->getPid());
+        android::prdebug("Kicking blocked reader, %s, from ChattyLogBuffer::kickMe()\n",
+                         me->name().c_str());
         me->release_Locked();
     } else if (me->deadline().time_since_epoch().count() != 0) {
         // Allow a blocked WRAP deadline reader to trigger and start reporting the log data.
@@ -503,8 +501,8 @@
     } else {
         // tell slow reader to skip entries to catch up
         android::prdebug(
-                "Skipping %lu entries from slow reader, pid %d, from ChattyLogBuffer::kickMe()\n",
-                pruneRows, me->client()->getPid());
+                "Skipping %lu entries from slow reader, %s, from ChattyLogBuffer::kickMe()\n",
+                pruneRows, me->name().c_str());
         me->triggerSkip_Locked(id, pruneRows);
     }
 }
@@ -872,8 +870,8 @@
                 for (const auto& reader_thread : reader_list_->reader_threads()) {
                     if (reader_thread->IsWatching(id)) {
                         android::prdebug(
-                                "Kicking blocked reader, pid %d, from ChattyLogBuffer::clear()\n",
-                                reader_thread->client()->getPid());
+                                "Kicking blocked reader, %s, from ChattyLogBuffer::clear()\n",
+                                reader_thread->name().c_str());
                         reader_thread->release_Locked();
                     }
                 }
@@ -911,10 +909,10 @@
 }
 
 uint64_t ChattyLogBuffer::FlushTo(
-        SocketClient* reader, uint64_t start, pid_t* lastTid, bool privileged, bool security,
+        LogWriter* writer, uint64_t start, pid_t* lastTid,
         const std::function<FlushToResult(const LogBufferElement* element)>& filter) {
     LogBufferElementCollection::iterator it;
-    uid_t uid = reader->getUid();
+    uid_t uid = writer->uid();
 
     rdlock();
 
@@ -940,11 +938,11 @@
     for (; it != mLogElements.end(); ++it) {
         LogBufferElement* element = *it;
 
-        if (!privileged && (element->getUid() != uid)) {
+        if (!writer->privileged() && element->getUid() != uid) {
             continue;
         }
 
-        if (!security && (element->getLogId() == LOG_ID_SECURITY)) {
+        if (!writer->can_read_security_logs() && element->getLogId() == LOG_ID_SECURITY) {
             continue;
         }
 
@@ -973,11 +971,10 @@
 
         unlock();
 
+        curr = element->getSequence();
         // range locking in LastLogTimes looks after us
-        curr = element->flushTo(reader, stats_, sameTid);
-
-        if (curr == element->FLUSH_ERROR) {
-            return curr;
+        if (!element->FlushTo(writer, stats_, sameTid)) {
+            return FLUSH_ERROR;
         }
 
         rdlock();
diff --git a/logd/ChattyLogBuffer.h b/logd/ChattyLogBuffer.h
index d9cd24f..29a421d 100644
--- a/logd/ChattyLogBuffer.h
+++ b/logd/ChattyLogBuffer.h
@@ -28,15 +28,15 @@
 
 #include "LogBuffer.h"
 #include "LogBufferElement.h"
+#include "LogReaderList.h"
+#include "LogReaderThread.h"
 #include "LogStatistics.h"
 #include "LogTags.h"
 #include "LogWhiteBlackList.h"
+#include "LogWriter.h"
 
 typedef std::list<LogBufferElement*> LogBufferElementCollection;
 
-class LogReaderList;
-class LogReaderThread;
-
 class ChattyLogBuffer : public LogBuffer {
     LogBufferElementCollection mLogElements;
     pthread_rwlock_t mLogElementsLock;
@@ -63,7 +63,7 @@
     int Log(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid, const char* msg,
             uint16_t len) override;
     uint64_t FlushTo(
-            SocketClient* writer, uint64_t start, pid_t* lastTid, bool privileged, bool security,
+            LogWriter* writer, uint64_t start, pid_t* lastTid,
             const std::function<FlushToResult(const LogBufferElement* element)>& filter) override;
 
     bool Clear(log_id_t id, uid_t uid = AID_ROOT) override;
diff --git a/logd/CommandListener.h b/logd/CommandListener.h
index fd934f7..d81a1b7 100644
--- a/logd/CommandListener.h
+++ b/logd/CommandListener.h
@@ -21,7 +21,6 @@
 #include "LogBuffer.h"
 #include "LogCommand.h"
 #include "LogListener.h"
-#include "LogReader.h"
 #include "LogStatistics.h"
 #include "LogTags.h"
 #include "LogWhiteBlackList.h"
diff --git a/logd/LogBuffer.h b/logd/LogBuffer.h
index 887e5f0..6274051 100644
--- a/logd/LogBuffer.h
+++ b/logd/LogBuffer.h
@@ -25,6 +25,8 @@
 
 #include "LogBufferElement.h"
 
+class LogWriter;
+
 enum class FlushToResult {
     kSkip,
     kStop,
@@ -42,10 +44,10 @@
     // lastTid is an optional context to help detect if the last previous
     // valid message was from the same source so we can differentiate chatty
     // filter types (identical or expired)
+    static const uint64_t FLUSH_ERROR = 0;
     virtual uint64_t FlushTo(
-            SocketClient* writer, uint64_t start,
+            LogWriter* writer, uint64_t start,
             pid_t* last_tid,  // nullable
-            bool privileged, bool security,
             const std::function<FlushToResult(const LogBufferElement* element)>& filter) = 0;
 
     virtual bool Clear(log_id_t id, uid_t uid) = 0;
diff --git a/logd/LogBufferElement.cpp b/logd/LogBufferElement.cpp
index 32f641b..0f17cc6 100644
--- a/logd/LogBufferElement.cpp
+++ b/logd/LogBufferElement.cpp
@@ -28,11 +28,9 @@
 #include <private/android_logger.h>
 
 #include "LogCommand.h"
-#include "LogReader.h"
 #include "LogStatistics.h"
 #include "LogUtils.h"
 
-const uint64_t LogBufferElement::FLUSH_ERROR(0);
 atomic_int_fast64_t LogBufferElement::sequence(1);
 
 LogBufferElement::LogBufferElement(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid,
@@ -243,7 +241,7 @@
     return retval;
 }
 
-uint64_t LogBufferElement::flushTo(SocketClient* reader, LogStatistics* stats, bool lastSame) {
+bool LogBufferElement::FlushTo(LogWriter* writer, LogStatistics* stats, bool lastSame) {
     struct logger_entry entry = {};
 
     entry.hdr_size = sizeof(struct logger_entry);
@@ -254,23 +252,18 @@
     entry.sec = mRealTime.tv_sec;
     entry.nsec = mRealTime.tv_nsec;
 
-    struct iovec iovec[2];
-    iovec[0].iov_base = &entry;
-    iovec[0].iov_len = entry.hdr_size;
-
     char* buffer = nullptr;
-
+    const char* msg;
     if (mDropped) {
         entry.len = populateDroppedMessage(buffer, stats, lastSame);
-        if (!entry.len) return mSequence;
-        iovec[1].iov_base = buffer;
+        if (!entry.len) return true;
+        msg = buffer;
     } else {
+        msg = mMsg;
         entry.len = mMsgLen;
-        iovec[1].iov_base = mMsg;
     }
-    iovec[1].iov_len = entry.len;
 
-    uint64_t retval = reader->sendDatav(iovec, 1 + (entry.len != 0)) ? FLUSH_ERROR : mSequence;
+    bool retval = writer->Write(entry, msg);
 
     if (buffer) free(buffer);
 
diff --git a/logd/LogBufferElement.h b/logd/LogBufferElement.h
index 3d0b65e..2f2d70d 100644
--- a/logd/LogBufferElement.h
+++ b/logd/LogBufferElement.h
@@ -22,7 +22,8 @@
 #include <sys/types.h>
 
 #include <log/log.h>
-#include <sysutils/SocketClient.h>
+
+#include "LogWriter.h"
 
 class LogStatistics;
 
@@ -94,6 +95,5 @@
         return mRealTime;
     }
 
-    static const uint64_t FLUSH_ERROR;
-    uint64_t flushTo(SocketClient* writer, LogStatistics* parent, bool lastSame);
+    bool FlushTo(LogWriter* writer, LogStatistics* parent, bool lastSame);
 };
diff --git a/logd/LogKlog.h b/logd/LogKlog.h
index 77b24bc..56e0452 100644
--- a/logd/LogKlog.h
+++ b/logd/LogKlog.h
@@ -19,10 +19,9 @@
 #include <private/android_logger.h>
 #include <sysutils/SocketListener.h>
 
+#include "LogBuffer.h"
 #include "LogStatistics.h"
 
-class LogBuffer;
-
 class LogKlog : public SocketListener {
     LogBuffer* logbuf;
     const log_time signature;
diff --git a/logd/LogListener.h b/logd/LogListener.h
index d468df8..c114e38 100644
--- a/logd/LogListener.h
+++ b/logd/LogListener.h
@@ -17,7 +17,6 @@
 #pragma once
 
 #include "LogBuffer.h"
-#include "LogReader.h"
 
 class LogListener {
   public:
diff --git a/logd/LogReader.cpp b/logd/LogReader.cpp
index 6f91372..c69e4bd 100644
--- a/logd/LogReader.cpp
+++ b/logd/LogReader.cpp
@@ -23,6 +23,7 @@
 
 #include <chrono>
 
+#include <android-base/stringprintf.h>
 #include <cutils/sockets.h>
 #include <private/android_filesystem_config.h>
 #include <private/android_logger.h>
@@ -31,11 +32,48 @@
 #include "LogBufferElement.h"
 #include "LogReader.h"
 #include "LogUtils.h"
+#include "LogWriter.h"
 
 static bool CanReadSecurityLogs(SocketClient* client) {
     return client->getUid() == AID_SYSTEM || client->getGid() == AID_SYSTEM;
 }
 
+static std::string SocketClientToName(SocketClient* client) {
+    return android::base::StringPrintf("pid %d, fd %d", client->getPid(), client->getSocket());
+}
+
+class SocketLogWriter : public LogWriter {
+  public:
+    SocketLogWriter(LogReader* reader, SocketClient* client, bool privileged,
+                    bool can_read_security_logs)
+        : LogWriter(client->getUid(), privileged, can_read_security_logs),
+          reader_(reader),
+          client_(client) {}
+
+    bool Write(const logger_entry& entry, const char* msg) override {
+        struct iovec iovec[2];
+        iovec[0].iov_base = const_cast<logger_entry*>(&entry);
+        iovec[0].iov_len = entry.hdr_size;
+        iovec[1].iov_base = const_cast<char*>(msg);
+        iovec[1].iov_len = entry.len;
+
+        return client_->sendDatav(iovec, 1 + (entry.len != 0)) == 0;
+    }
+
+    void Release() override {
+        reader_->release(client_);
+        client_->decRef();
+    }
+
+    void Shutdown() override { shutdown(client_->getSocket(), SHUT_RDWR); }
+
+    std::string name() const override { return SocketClientToName(client_); }
+
+  private:
+    LogReader* reader_;
+    SocketClient* client_;
+};
+
 LogReader::LogReader(LogBuffer* logbuf, LogReaderList* reader_list)
     : SocketListener(getLogSocket(), true), log_buffer_(logbuf), reader_list_(reader_list) {}
 
@@ -51,21 +89,14 @@
 
     int len = read(cli->getSocket(), buffer, sizeof(buffer) - 1);
     if (len <= 0) {
-        doSocketDelete(cli);
+        DoSocketDelete(cli);
         return false;
     }
     buffer[len] = '\0';
 
-    // Clients are only allowed to send one command, disconnect them if they
-    // send another.
-    {
-        auto lock = std::lock_guard{reader_list_->reader_threads_lock()};
-        for (const auto& entry : reader_list_->reader_threads()) {
-            if (entry->client() == cli) {
-                entry->release_Locked();
-                return false;
-            }
-        }
+    // Clients are only allowed to send one command, disconnect them if they send another.
+    if (DoSocketDelete(cli)) {
+        return false;
     }
 
     unsigned long tail = 0;
@@ -131,6 +162,9 @@
     bool privileged = clientHasLogCredentials(cli);
     bool can_read_security = CanReadSecurityLogs(cli);
 
+    std::unique_ptr<LogWriter> socket_log_writer(
+            new SocketLogWriter(this, cli, privileged, can_read_security));
+
     uint64_t sequence = 1;
     // Convert realtime to sequence number
     if (start != log_time::EPOCH) {
@@ -159,11 +193,10 @@
             return FlushToResult::kSkip;
         };
 
-        log_buffer_->FlushTo(cli, sequence, nullptr, privileged, can_read_security, log_find_start);
+        log_buffer_->FlushTo(socket_log_writer.get(), sequence, nullptr, log_find_start);
 
         if (!start_time_set) {
             if (nonBlock) {
-                doSocketDelete(cli);
                 return false;
             }
             sequence = LogBufferElement::getCurrentSequence();
@@ -181,13 +214,9 @@
     }
 
     auto lock = std::lock_guard{reader_list_->reader_threads_lock()};
-    auto entry = std::make_unique<LogReaderThread>(*this, *reader_list_, cli, nonBlock, tail,
-                                                   logMask, pid, start, sequence, deadline,
-                                                   privileged, can_read_security);
-    if (!entry->startReader_Locked()) {
-        return false;
-    }
-
+    auto entry = std::make_unique<LogReaderThread>(log_buffer_, reader_list_,
+                                                   std::move(socket_log_writer), nonBlock, tail,
+                                                   logMask, pid, start, sequence, deadline);
     // release client and entry reference counts once done
     cli->incRef();
     reader_list_->reader_threads().emplace_front(std::move(entry));
@@ -200,17 +229,16 @@
     return true;
 }
 
-void LogReader::doSocketDelete(SocketClient* cli) {
+bool LogReader::DoSocketDelete(SocketClient* cli) {
+    auto cli_name = SocketClientToName(cli);
     auto lock = std::lock_guard{reader_list_->reader_threads_lock()};
-    auto it = reader_list_->reader_threads().begin();
-    while (it != reader_list_->reader_threads().end()) {
-        LogReaderThread* entry = it->get();
-        if (entry->client() == cli) {
-            entry->release_Locked();
-            break;
+    for (const auto& reader : reader_list_->reader_threads()) {
+        if (reader->name() == cli_name) {
+            reader->release_Locked();
+            return true;
         }
-        it++;
     }
+    return false;
 }
 
 int LogReader::getLogSocket() {
diff --git a/logd/LogReader.h b/logd/LogReader.h
index 7df3f6b..b85a584 100644
--- a/logd/LogReader.h
+++ b/logd/LogReader.h
@@ -18,26 +18,23 @@
 
 #include <sysutils/SocketListener.h>
 
+#include "LogBuffer.h"
 #include "LogReaderList.h"
 #include "LogReaderThread.h"
 
 #define LOGD_SNDTIMEO 32
 
-class LogBuffer;
-
 class LogReader : public SocketListener {
   public:
     explicit LogReader(LogBuffer* logbuf, LogReaderList* reader_list);
 
-    LogBuffer* log_buffer() const { return log_buffer_; }
-
   protected:
     virtual bool onDataAvailable(SocketClient* cli);
 
   private:
     static int getLogSocket();
 
-    void doSocketDelete(SocketClient* cli);
+    bool DoSocketDelete(SocketClient* cli);
 
     LogBuffer* log_buffer_;
     LogReaderList* reader_list_;
diff --git a/logd/LogReaderThread.cpp b/logd/LogReaderThread.cpp
index e58e3eb..b2001b5 100644
--- a/logd/LogReaderThread.cpp
+++ b/logd/LogReaderThread.cpp
@@ -23,50 +23,40 @@
 #include <thread>
 
 #include "LogBuffer.h"
-#include "LogReader.h"
+#include "LogReaderList.h"
 
 using namespace std::placeholders;
 
-LogReaderThread::LogReaderThread(LogReader& reader, LogReaderList& reader_list,
-                                 SocketClient* client, bool non_block, unsigned long tail,
-                                 unsigned int log_mask, pid_t pid, log_time start_time,
-                                 uint64_t start, std::chrono::steady_clock::time_point deadline,
-                                 bool privileged, bool can_read_security_logs)
-    : leading_dropped_(false),
-      reader_(reader),
+LogReaderThread::LogReaderThread(LogBuffer* log_buffer, LogReaderList* reader_list,
+                                 std::unique_ptr<LogWriter> writer, bool non_block,
+                                 unsigned long tail, unsigned int log_mask, pid_t pid,
+                                 log_time start_time, uint64_t start,
+                                 std::chrono::steady_clock::time_point deadline)
+    : log_buffer_(log_buffer),
       reader_list_(reader_list),
+      writer_(std::move(writer)),
+      leading_dropped_(false),
       log_mask_(log_mask),
       pid_(pid),
       tail_(tail),
       count_(0),
       index_(0),
-      client_(client),
       start_time_(start_time),
       start_(start),
       deadline_(deadline),
-      non_block_(non_block),
-      privileged_(privileged),
-      can_read_security_logs_(can_read_security_logs) {
+      non_block_(non_block) {
     memset(last_tid_, 0, sizeof(last_tid_));
     cleanSkip_Locked();
-}
-
-bool LogReaderThread::startReader_Locked() {
     auto thread = std::thread{&LogReaderThread::ThreadFunction, this};
     thread.detach();
-    return true;
 }
 
 void LogReaderThread::ThreadFunction() {
     prctl(PR_SET_NAME, "logd.reader.per");
 
-    SocketClient* client = client_;
-
-    LogBuffer& logbuf = *reader_.log_buffer();
-
     leading_dropped_ = true;
 
-    auto lock = std::unique_lock{reader_list_.reader_threads_lock()};
+    auto lock = std::unique_lock{reader_list_->reader_threads_lock()};
 
     uint64_t start = start_;
 
@@ -84,14 +74,14 @@
         lock.unlock();
 
         if (tail_) {
-            logbuf.FlushTo(client, start, nullptr, privileged_, can_read_security_logs_,
-                           std::bind(&LogReaderThread::FilterFirstPass, this, _1));
+            log_buffer_->FlushTo(writer_.get(), start, nullptr,
+                                 std::bind(&LogReaderThread::FilterFirstPass, this, _1));
             leading_dropped_ =
                     true;  // TODO: Likely a bug, if leading_dropped_ was not true before calling
                            // flushTo(), then it should not be reset to true after.
         }
-        start = logbuf.FlushTo(client, start, last_tid_, privileged_, can_read_security_logs_,
-                               std::bind(&LogReaderThread::FilterSecondPass, this, _1));
+        start = log_buffer_->FlushTo(writer_.get(), start, last_tid_,
+                                     std::bind(&LogReaderThread::FilterSecondPass, this, _1));
 
         // We only ignore entries before the original start time for the first flushTo(), if we
         // get entries after this first flush before the original start time, then the client
@@ -104,7 +94,7 @@
 
         lock.lock();
 
-        if (start == LogBufferElement::FLUSH_ERROR) {
+        if (start == LogBuffer::FLUSH_ERROR) {
             break;
         }
 
@@ -121,10 +111,9 @@
         }
     }
 
-    reader_.release(client);
-    client->decRef();
+    writer_->Release();
 
-    auto& log_reader_threads = reader_list_.reader_threads();
+    auto& log_reader_threads = reader_list_->reader_threads();
     auto it = std::find_if(log_reader_threads.begin(), log_reader_threads.end(),
                            [this](const auto& other) { return other.get() == this; });
 
@@ -135,7 +124,7 @@
 
 // A first pass to count the number of elements
 FlushToResult LogReaderThread::FilterFirstPass(const LogBufferElement* element) {
-    auto lock = std::lock_guard{reader_list_.reader_threads_lock()};
+    auto lock = std::lock_guard{reader_list_->reader_threads_lock()};
 
     if (leading_dropped_) {
         if (element->getDropped()) {
@@ -158,7 +147,7 @@
 
 // A second pass to send the selected elements
 FlushToResult LogReaderThread::FilterSecondPass(const LogBufferElement* element) {
-    auto lock = std::lock_guard{reader_list_.reader_threads_lock()};
+    auto lock = std::lock_guard{reader_list_->reader_threads_lock()};
 
     start_ = element->getSequence();
 
diff --git a/logd/LogReaderThread.h b/logd/LogReaderThread.h
index f828b6e..e48a3ca 100644
--- a/logd/LogReaderThread.h
+++ b/logd/LogReaderThread.h
@@ -30,21 +30,17 @@
 #include <sysutils/SocketClient.h>
 
 #include "LogBuffer.h"
+#include "LogBufferElement.h"
+#include "LogWriter.h"
 
-class LogReader;
-class LogBufferElement;
 class LogReaderList;
 
 class LogReaderThread {
   public:
-    LogReaderThread(LogReader& reader, LogReaderList& reader_list, SocketClient* client,
-                    bool non_block, unsigned long tail, unsigned int log_mask, pid_t pid,
-                    log_time start_time, uint64_t sequence,
-                    std::chrono::steady_clock::time_point deadline, bool privileged,
-                    bool can_read_security_logs);
-
-    bool startReader_Locked();
-
+    LogReaderThread(LogBuffer* log_buffer, LogReaderList* reader_list,
+                    std::unique_ptr<LogWriter> writer, bool non_block, unsigned long tail,
+                    unsigned int log_mask, pid_t pid, log_time start_time, uint64_t sequence,
+                    std::chrono::steady_clock::time_point deadline);
     void triggerReader_Locked() { thread_triggered_condition_.notify_all(); }
 
     void triggerSkip_Locked(log_id_t id, unsigned int skip) { skip_ahead_[id] = skip; }
@@ -52,7 +48,7 @@
 
     void release_Locked() {
         // gracefully shut down the socket.
-        shutdown(client_->getSocket(), SHUT_RDWR);
+        writer_->Shutdown();
         release_ = true;
         thread_triggered_condition_.notify_all();
     }
@@ -60,7 +56,7 @@
     bool IsWatching(log_id_t id) const { return log_mask_ & (1 << id); }
     bool IsWatchingMultiple(unsigned int log_mask) const { return log_mask_ & log_mask; }
 
-    const SocketClient* client() const { return client_; }
+    std::string name() const { return writer_->name(); }
     uint64_t start() const { return start_; }
     std::chrono::steady_clock::time_point deadline() const { return deadline_; }
 
@@ -70,19 +66,17 @@
     FlushToResult FilterFirstPass(const LogBufferElement* element);
     FlushToResult FilterSecondPass(const LogBufferElement* element);
 
+    std::condition_variable thread_triggered_condition_;
+    LogBuffer* log_buffer_;
+    LogReaderList* reader_list_;
+    std::unique_ptr<LogWriter> writer_;
+
     // Set to true to cause the thread to end and the LogReaderThread to delete itself.
     bool release_ = false;
     // Indicates whether or not 'leading' (first logs seen starting from start_) 'dropped' (chatty)
     // messages should be ignored.
     bool leading_dropped_;
 
-    // Condition variable for waking the reader thread if there are messages pending for its client.
-    std::condition_variable thread_triggered_condition_;
-
-    // Reference to the parent thread that manages log reader sockets.
-    LogReader& reader_;
-    // Reference to the parent list that shares its lock with each instance
-    LogReaderList& reader_list_;
     // A mask of the logs buffers that are read by this reader.
     const unsigned int log_mask_;
     // If set to non-zero, only pids equal to this are read by the reader.
@@ -105,8 +99,6 @@
     // and to disconnect the reader (if it is dumpAndClose, `adb logcat -t`), when index_ >= count_.
     unsigned long index_;
 
-    // A pointer to the socket for this reader.
-    SocketClient* client_;
     // When a reader requests logs starting from a given timestamp, its stored here for the first
     // pass, such that logs before this time stamp that are accumulated in the buffer are ignored.
     log_time start_time_;
@@ -117,10 +109,4 @@
     std::chrono::steady_clock::time_point deadline_;
     // If this reader is 'dumpAndClose' and will disconnect once it has read its intended logs.
     const bool non_block_;
-
-    // Whether or not this reader can read logs from all UIDs or only its own UID.  See
-    // clientHasLogCredentials().
-    bool privileged_;
-    // Whether or not this reader can read security logs.  See CanReadSecurityLogs().
-    bool can_read_security_logs_;
 };
diff --git a/logd/LogWriter.h b/logd/LogWriter.h
new file mode 100644
index 0000000..b6c5b67
--- /dev/null
+++ b/logd/LogWriter.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <log/log_read.h>
+
+// An interface for writing logs to a reader.
+class LogWriter {
+  public:
+    LogWriter(uid_t uid, bool privileged, bool can_read_security_logs)
+        : uid_(uid), privileged_(privileged), can_read_security_logs_(can_read_security_logs) {}
+    virtual ~LogWriter() {}
+
+    virtual bool Write(const logger_entry& entry, const char* msg) = 0;
+    virtual void Shutdown() {}
+    virtual void Release() {}
+
+    virtual std::string name() const = 0;
+    uid_t uid() const { return uid_; }
+
+    bool privileged() const { return privileged_; }
+    bool can_read_security_logs() const { return can_read_security_logs_; }
+
+  private:
+    uid_t uid_;
+
+    // If this writer sees logs from all UIDs or only its own UID.  See clientHasLogCredentials().
+    bool privileged_;
+    bool can_read_security_logs_;  // If this writer sees security logs.  See CanReadSecurityLogs().
+};
\ No newline at end of file
diff --git a/logd/main.cpp b/logd/main.cpp
index 6e1144b..dfbad31 100644
--- a/logd/main.cpp
+++ b/logd/main.cpp
@@ -53,6 +53,7 @@
 #include "LogBuffer.h"
 #include "LogKlog.h"
 #include "LogListener.h"
+#include "LogReader.h"
 #include "LogStatistics.h"
 #include "LogTags.h"
 #include "LogUtils.h"