diff --git a/garnet/bin/logger/src/klogger.rs b/garnet/bin/logger/src/klogger.rs
index c617ea54d3ece3618ba42d7b05656363a1974b39..3b24b457bed2e810f76e27b33cd3f059b18d0c57 100644
--- a/garnet/bin/logger/src/klogger.rs
+++ b/garnet/bin/logger/src/klogger.rs
@@ -9,72 +9,52 @@ use byteorder::{ByteOrder, LittleEndian};
 use fidl_fuchsia_logger::{self, LogMessage};
 use fuchsia_async as fasync;
 use fuchsia_zircon as zx;
-use fuchsia_zircon::sys::zx_handle_t;
-use fuchsia_zircon::{AsHandleRef, Signals};
 use futures::{future, stream, TryFutureExt, TryStreamExt};
 
-const ZX_LOG_FLAG_READABLE: u32 = 0x40000000;
-const ZX_LOG_RECORD_MAX: usize = 256;
-
 pub fn add_listener<F>(callback: F) -> Result<(), zx::Status>
 where
     F: 'static + Send + Fn(LogMessage, usize) + Sync,
 {
-    let mut handle: zx_handle_t = 0;
-    let handle_ptr = &mut handle as *mut _ as *mut u32;
-
-    unsafe {
-        zx::ok(zx::sys::zx_debuglog_create(
-            zx::sys::ZX_HANDLE_INVALID,
-            ZX_LOG_FLAG_READABLE,
-            handle_ptr,
-        ))?;
-    }
-    let h = unsafe { zx::Handle::from_raw(handle) };
+    let debuglog = zx::DebugLog::create(zx::DebugLogOpts::READABLE)?;
 
     let f = stream::repeat(Ok(()))
-        .try_fold((callback, h), |(callback, handle), ()| {
+        .try_fold((callback, debuglog), |(callback, debuglog), ()| {
             // TODO: change OnSignals to wrap this so that is is not created again and again.
-            fasync::OnSignals::new(&handle, Signals::LOG_READABLE).and_then(|_| {
+            fasync::OnSignals::new(&debuglog, zx::Signals::LOG_READABLE).and_then(|_| {
+                let mut buf = Vec::with_capacity(zx::sys::ZX_LOG_RECORD_MAX);
                 loop {
-                    let mut buf = [0; ZX_LOG_RECORD_MAX as usize];
-                    let status = unsafe {
-                        zx::sys::zx_debuglog_read(
-                            handle.raw_handle(),
-                            0,
-                            buf.as_mut_ptr(),
-                            ZX_LOG_RECORD_MAX,
-                        )
-                    };
-
-                    if status == zx::sys::ZX_ERR_SHOULD_WAIT {
-                        return future::ready(Ok((callback, handle)));
-                    }
-                    if status < 0 {
-                        return future::ready(Err(zx::Status::from_raw(status)));
-                    }
-                    let mut l = LogMessage {
-                        time: LittleEndian::read_i64(&buf[8..16]),
-                        pid: LittleEndian::read_u64(&buf[16..24]),
-                        tid: LittleEndian::read_u64(&buf[24..32]),
-                        severity: fidl_fuchsia_logger::LogLevelFilter::Info as i32,
-                        dropped_logs: 0,
-                        tags: vec!["klog".to_string()],
-                        msg: String::new(),
-                    };
-                    let data_len = LittleEndian::read_u16(&buf[4..8]) as usize;
-                    l.msg = match String::from_utf8(buf[32..(32 + data_len)].to_vec()) {
-                        Err(e) => {
-                            eprintln!("logger: invalid log record: {:?}", e);
-                            continue;
+                    match debuglog.read(&mut buf) {
+                        Err(status) if status == zx::Status::SHOULD_WAIT => {
+                            return future::ready(Ok((callback, debuglog)));
+                        }
+                        Err(status) => {
+                            return future::ready(Err(status));
+                        }
+                        Ok(()) => {
+                            let mut l = LogMessage {
+                                time: LittleEndian::read_i64(&buf[8..16]),
+                                pid: LittleEndian::read_u64(&buf[16..24]),
+                                tid: LittleEndian::read_u64(&buf[24..32]),
+                                severity: fidl_fuchsia_logger::LogLevelFilter::Info as i32,
+                                dropped_logs: 0,
+                                tags: vec!["klog".to_string()],
+                                msg: String::new(),
+                            };
+                            let data_len = LittleEndian::read_u16(&buf[4..6]) as usize;
+                            l.msg = match String::from_utf8(buf[32..(32 + data_len)].to_vec()) {
+                                Err(e) => {
+                                    eprintln!("logger: invalid log record: {:?}", e);
+                                    continue;
+                                }
+                                Ok(s) => s,
+                            };
+                            if let Some(b'\n') = l.msg.bytes().last() {
+                                l.msg.pop();
+                            }
+                            let size = logger::METADATA_SIZE + 5/*tag*/ + l.msg.len() + 1;
+                            callback(l, size);
                         }
-                        Ok(s) => s,
-                    };
-                    if l.msg.len() > 0 && l.msg.as_bytes()[l.msg.len() - 1] == '\n' as u8 {
-                        l.msg.pop();
                     }
-                    let size = logger::METADATA_SIZE + 5/*tag*/ + l.msg.len() + 1;
-                    callback(l, size);
                 }
             })
         })
diff --git a/garnet/bin/logger/tests/rust/BUILD.gn b/garnet/bin/logger/tests/rust/BUILD.gn
index 7b6776dca4aab1d4df4fdf94fdd76bf863bac077..f29adf697e81b91738ba95001f9177497c3eb8f2 100644
--- a/garnet/bin/logger/tests/rust/BUILD.gn
+++ b/garnet/bin/logger/tests/rust/BUILD.gn
@@ -22,6 +22,7 @@ rustc_binary("bin") {
     "//garnet/public/rust/fuchsia-syslog",
     "//garnet/public/rust/fuchsia-syslog-listener",
     "//garnet/public/rust/fuchsia-zircon",
+    "//third_party/rust_crates:failure",
     "//third_party/rust_crates:futures-preview",
     "//third_party/rust_crates:log",
     "//third_party/rust_crates:parking_lot",
diff --git a/garnet/bin/logger/tests/rust/src/main.rs b/garnet/bin/logger/tests/rust/src/main.rs
index ab8fc590fbf73071f4b9b55717d7e7bb4959cba3..25b20eddef5a710b73d176bb96c7747801299142 100644
--- a/garnet/bin/logger/tests/rust/src/main.rs
+++ b/garnet/bin/logger/tests/rust/src/main.rs
@@ -11,11 +11,12 @@ fn main() {}
 
 #[cfg(test)]
 mod tests {
+    use failure::Error;
     use fidl_fuchsia_logger::{LogFilterOptions, LogLevelFilter, LogMessage};
     use fuchsia_async as fasync;
     use fuchsia_syslog::{self as syslog, fx_log_info};
     use fuchsia_syslog_listener::{self as syslog_listener, LogProcessor};
-    use fuchsia_zircon::DurationNum;
+    use fuchsia_zircon::{self as zx, DurationNum};
     use log::warn;
     use parking_lot::Mutex;
 
@@ -62,7 +63,7 @@ mod tests {
     }
 
     #[test]
-    fn test_full_stack() {
+    fn test_listen_for_syslog() {
         let mut executor = fasync::Executor::new().unwrap();
         let random = rand::random::<u16>();
         let tag = "logger_integration_rust".to_string() + &random.to_string();
@@ -89,4 +90,31 @@ mod tests {
         assert_eq!(logs[1].severity, LogLevelFilter::Warn as i32);
         assert_eq!(logs[1].msg, "log crate: 20");
     }
+
+    #[test]
+    fn test_listen_for_klog() -> Result<(), Error> {
+        let mut executor = fasync::Executor::new()?;
+        let logs = run_listener("klog".to_string());
+
+        let random = rand::random::<u64>();
+        let msg = format!("logger_integration_rust test_klog {}", random);
+
+        let debuglog = zx::DebugLog::create(zx::DebugLogOpts::empty())?;
+        debuglog.write(msg.as_bytes())?;
+
+        let tries = 50;
+        for _ in 0..tries {
+            let logs = logs.lock();
+            if logs.iter().find(|&m| m.msg == msg).is_some() {
+                return Ok(());
+            }
+            // Must release lock so the log listener started above with fasync::spawn can
+            // mutate it.
+            std::mem::drop(logs);
+
+            let timeout = fasync::Timer::new(100.millis().after_now());
+            executor.run_singlethreaded(timeout);
+        }
+        panic!("Failed to find klog");
+    }
 }
diff --git a/garnet/public/rust/fuchsia-zircon/fuchsia-zircon-sys/src/lib.rs b/garnet/public/rust/fuchsia-zircon/fuchsia-zircon-sys/src/lib.rs
index 23888a4046e4927d2bde09fcd753b940c6c94944..84c701ab162e4087a0b969c76289e8780e3f1416 100644
--- a/garnet/public/rust/fuchsia-zircon/fuchsia-zircon-sys/src/lib.rs
+++ b/garnet/public/rust/fuchsia-zircon/fuchsia-zircon-sys/src/lib.rs
@@ -582,4 +582,10 @@ multiconst!(zx_guest_trap_t, [
     ZX_GUEST_TRAP_IO   = 2;
 ]);
 
+pub const ZX_LOG_RECORD_MAX: usize = 256;
+
+multiconst!(u32, [
+    ZX_LOG_FLAG_READABLE = 0x40000000;
+]);
+
 include!("definitions.rs");
diff --git a/garnet/public/rust/fuchsia-zircon/src/debuglog.rs b/garnet/public/rust/fuchsia-zircon/src/debuglog.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e0a546e1637ea33cb245972b809d7466dfea92aa
--- /dev/null
+++ b/garnet/public/rust/fuchsia-zircon/src/debuglog.rs
@@ -0,0 +1,152 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+//! Type-safe bindings for Zircon resources.
+
+use crate::{AsHandleRef, HandleBased, HandleRef, Handle, Status};
+use crate::ok;
+use bitflags::bitflags;
+use fuchsia_zircon_sys as sys;
+
+/// An object representing a Zircon 'debuglog' object.
+///
+/// As essentially a subtype of `Handle`, it can be freely interconverted.
+#[derive(Debug, Eq, PartialEq)]
+#[repr(transparent)]
+pub struct DebugLog(Handle);
+impl_handle_based!(DebugLog);
+
+bitflags! {
+    #[repr(transparent)]
+    pub struct DebugLogOpts: u32 {
+        const READABLE = sys::ZX_LOG_FLAG_READABLE;
+    }
+}
+
+impl DebugLog {
+    /// Create a debug log object that allows access to read from and write to the kernel debug
+    /// logging facility.
+    ///
+    /// Wraps the
+    /// [zx_debuglog_create]((https://fuchsia.googlesource.com/fuchsia/+/master/zircon/docs/syscalls/debuglog_create.md)
+    /// syscall.
+    pub fn create(opts: DebugLogOpts) -> Result<DebugLog, Status> {
+        // TODO(ZX-2184): Once the resource required for zx_debuglog_create is defined, add it as a
+        // parameter to this call.
+        let resource = sys::ZX_HANDLE_INVALID;
+        let mut handle = 0;
+        let status = unsafe {
+            sys::zx_debuglog_create(resource, opts.bits(), &mut handle)
+        };
+        ok(status)?;
+        unsafe {
+            Ok(DebugLog::from(Handle::from_raw(handle)))
+        }
+    }
+
+    /// Write a message to the kernel debug log.
+    ///
+    /// Wraps the
+    /// [zx_debuglog_write]((https://fuchsia.googlesource.com/fuchsia/+/master/zircon/docs/syscalls/debuglog_write.md)
+    /// syscall.
+    pub fn write(&self, message: &[u8]) -> Result<(), Status> {
+        // TODO(ZX-3187): Discussion ongoing over whether debuglog levels are supported, so no
+        // options parameter for now.
+        let status = unsafe {
+            sys::zx_debuglog_write(
+                self.raw_handle(),
+                0,
+                message.as_ptr(),
+                message.len()
+            )
+        };
+        ok(status)
+    }
+
+    /// Read a single log record from the kernel debug log.
+    ///
+    /// The DebugLog object must have been created with DebugLogOpts::READABLE, or this will return
+    /// an error.
+    ///
+    /// Wraps the
+    /// [zx_debuglog_read]((https://fuchsia.googlesource.com/fuchsia/+/master/zircon/docs/syscalls/debuglog_read.md)
+    /// syscall.
+    // TODO(ZX-3187): Return a safe wrapper type for zx_log_record_t rather than raw bytes
+    // depending on resolution.
+    pub fn read(&self, record: &mut Vec<u8>) -> Result<(), Status> {
+        let mut buf = [0; sys::ZX_LOG_RECORD_MAX];
+
+        // zx_debuglog_read options appear to be unused.
+        // zx_debuglog_read returns either an error status or, on success, the actual size of bytes
+        // read into the buffer.
+        let status_or_actual = unsafe {
+            sys::zx_debuglog_read(self.raw_handle(), 0, buf.as_mut_ptr(), buf.len())
+        };
+        let actual = Status::ioctl_ok(status_or_actual)? as usize;
+
+        record.clear();
+        record.extend_from_slice(&buf[0..actual]);
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::{cprng_draw, Signals, Time};
+
+    // expect_message_in_debuglog will read the last 10000 messages in zircon's debuglog, looking
+    // for a message that equals `sent_msg`. If found, the function returns. If the first 10,000
+    // messages doesn't contain `sent_msg`, it will panic.
+    fn expect_message_in_debuglog(sent_msg: String) {
+        let debuglog = DebugLog::create(DebugLogOpts::READABLE).unwrap();
+        let mut record = Vec::with_capacity(sys::ZX_LOG_RECORD_MAX);
+        for _ in 0..10000 {
+            match debuglog.read(&mut record) {
+                Ok(()) => {
+                    // TODO(ZX-3187): Manually unpack log record until DebugLog::read returns
+                    // an wrapper type.
+                    let mut len_bytes = [0; 2];
+                    len_bytes.copy_from_slice(&record[4..6]);
+                    let data_len = u16::from_le_bytes(len_bytes) as usize;
+                    let log = &record[32..(32 + data_len)];
+                    if log == sent_msg.as_bytes() {
+                        // We found our log!
+                        return;
+                    }
+                }
+                Err(status) if status == Status::SHOULD_WAIT => {
+                    debuglog
+                        .wait_handle(Signals::LOG_READABLE, Time::INFINITE)
+                        .expect("Failed to wait for log readable");
+                    continue;
+                }
+                Err(status) => {
+                    panic!("Unexpected error from zx_debuglog_read: {}", status);
+                }
+            }
+
+        }
+        panic!("first 10000 log messages didn't include the one we sent!");
+    }
+
+    #[test]
+    fn read_from_nonreadable() {
+        let mut data = vec![];
+        let debuglog = DebugLog::create(DebugLogOpts::empty()).unwrap();
+        assert!(debuglog.read(&mut data).err() == Some(Status::ACCESS_DENIED));
+    }
+
+    #[test]
+    fn write_and_read_back() {
+        let mut bytes = [0; 8];
+        cprng_draw(&mut bytes).unwrap();
+        let rand = u64::from_ne_bytes(bytes);
+        let message = format!("log message {}", rand);
+
+        let debuglog = DebugLog::create(DebugLogOpts::empty()).unwrap();
+        debuglog.write(message.as_bytes()).unwrap();
+        expect_message_in_debuglog(message);
+    }
+}
diff --git a/garnet/public/rust/fuchsia-zircon/src/lib.rs b/garnet/public/rust/fuchsia-zircon/src/lib.rs
index a78af60b1fb14ffe4b98507acd818e3cba79c69e..e888d15fa74039f6400234ba6e91961298c5d45b 100644
--- a/garnet/public/rust/fuchsia-zircon/src/lib.rs
+++ b/garnet/public/rust/fuchsia-zircon/src/lib.rs
@@ -5,8 +5,6 @@
 //! Type-safe bindings for Zircon kernel
 //! [syscalls](https://fuchsia.googlesource.com/fuchsia/+/master/zircon/docs/syscalls.md).
 
-#![deny(warnings)]
-
 pub mod sys {
     pub use fuchsia_zircon_sys::*;
 }
@@ -140,6 +138,7 @@ macro_rules! assoc_values {
 
 mod channel;
 mod cprng;
+mod debuglog;
 mod event;
 mod eventpair;
 mod fifo;
@@ -164,6 +163,7 @@ mod vmo;
 
 pub use self::channel::*;
 pub use self::cprng::*;
+pub use self::debuglog::*;
 pub use self::event::*;
 pub use self::eventpair::*;
 pub use self::fifo::*;
diff --git a/src/sys/component_manager/src/klog.rs b/src/sys/component_manager/src/klog.rs
index cbf431b54c0ab29e701d14093d59345816f05c7c..59a0cb542db5627e635b7892e52a3bac5f92fdfd 100644
--- a/src/sys/component_manager/src/klog.rs
+++ b/src/sys/component_manager/src/klog.rs
@@ -15,7 +15,7 @@ const MAX_LOG_LEVEL: log::Level = log::Level::Info;
 /// KernelLogger is a logger implementation for the log crate. It attempts to use the kernel
 /// debuglog facility and automatically falls back to stderr if that fails.
 pub struct KernelLogger {
-    debuglog: zx::sys::zx_handle_t,
+    debuglog: zx::DebugLog,
 }
 
 lazy_static! {
@@ -24,18 +24,10 @@ lazy_static! {
 
 impl KernelLogger {
     fn new() -> KernelLogger {
-        // TODO: Create zx::DebugLog wrappers for zx_debuglog_* syscalls to avoid direct unsafe
-        // usage.
-        let mut log_handle = zx::sys::ZX_HANDLE_INVALID;
-        let status = unsafe {
-            zx::sys::zx_debuglog_create(
-                zx::sys::ZX_HANDLE_INVALID,
-                0,
-                &mut log_handle as *mut zx::sys::zx_handle_t,
-            )
-        };
-        zx::Status::ok(status).context("Failed to create debuglog object").unwrap();
-        KernelLogger{debuglog: log_handle}
+        let debuglog = zx::DebugLog::create(zx::DebugLogOpts::empty())
+            .context("Failed to create debuglog object")
+            .unwrap();
+        KernelLogger{debuglog}
     }
 
     /// Initialize the global logger to use KernelLogger.
@@ -73,20 +65,10 @@ impl KernelLogger {
     fn log_helper(&self, level: &str, args: &fmt::Arguments) {
         let msg = format!("[component_manager] {}: {}", level, args);
 
-        // TODO: Create zx::DebugLog wrappers for zx_debuglog_* syscalls to avoid direct unsafe
-        // usage.
-        // TODO: zx_debuglog_write also accepts options and the possible options include log
-        // levels, but they seem to be mostly unused and not displayed today, so we don't pass
+        // TODO(ZX-3187): zx_debuglog_write also accepts options and the possible options include
+        // log levels, but they seem to be mostly unused and not displayed today, so we don't pass
         // along log level yet.
-        let status = unsafe {
-            zx::sys::zx_debuglog_write(
-                self.debuglog,
-                0,
-                msg.as_ptr(),
-                msg.as_bytes().len(),
-            )
-        };
-        if let Err(s) = zx::Status::ok(status) {
+        if let Err(s) = self.debuglog.write(msg.as_bytes()) {
             eprintln!("failed to write log ({}): {}", s, msg);
         }
     }
@@ -110,14 +92,12 @@ impl log::Log for KernelLogger {
 mod tests {
     use {
         super::*,
-        byteorder::{ByteOrder, LittleEndian},
+        fuchsia_zircon::AsHandleRef,
         log::*,
         rand::Rng,
-        std::{panic, sync::Once, thread, time},
+        std::{panic, sync::Once},
     };
 
-    const ZX_LOG_FLAG_READABLE: u32 = 0x40000000;
-
     // KernelLogger::init/log::set_logger will fail if called more than once and there's no way to
     // reset.
     fn init_logger_once() {
@@ -127,42 +107,37 @@ mod tests {
         });
     }
 
-    // expect_message_in_debuglog will read the first 10000 messages in zircon's debuglog, looking
+    // expect_message_in_debuglog will read the last 10000 messages in zircon's debuglog, looking
     // for a message that equals `sent_msg`. If found, the function returns. If the first 10,000
     // messages doesn't contain `sent_msg`, it will panic.
     fn expect_message_in_debuglog(sent_msg: String) {
-        // TODO: Create zx::DebugLog wrappers for zx_debuglog_* syscalls to avoid direct unsafe
-        // usage.
-        let mut log_handle_for_reading = zx::sys::ZX_HANDLE_INVALID;
-        let status = unsafe {
-            zx::sys::zx_debuglog_create(
-                zx::sys::ZX_HANDLE_INVALID,
-                ZX_LOG_FLAG_READABLE,
-                &mut log_handle_for_reading as *mut zx::sys::zx_handle_t,
-            )
-        };
-        assert_eq!(zx::sys::ZX_OK, status);
-
+        let debuglog = zx::DebugLog::create(zx::DebugLogOpts::READABLE).unwrap();
+        let mut record = Vec::with_capacity(zx::sys::ZX_LOG_RECORD_MAX);
         for _ in 0..10000 {
-            let mut read_buffer = [0; 1024];
-            let status = unsafe {
-                zx::sys::zx_debuglog_read(log_handle_for_reading, 0, read_buffer.as_mut_ptr(), 1024)
-            };
-            if status <= 0 {
-                if status == zx::sys::ZX_ERR_SHOULD_WAIT {
-                    thread::sleep(time::Duration::from_millis(100));
+            match debuglog.read(&mut record) {
+                Ok(()) => {
+                    // TODO(ZX-3187): Manually unpack log record until zx::DebugLog::read returns
+                    // an wrapper type.
+                    let mut len_bytes = [0; 2];
+                    len_bytes.copy_from_slice(&record[4..6]);
+                    let data_len = u16::from_le_bytes(len_bytes) as usize;
+                    let log = &record[32..(32 + data_len)];
+                    if log == sent_msg.as_bytes() {
+                        // We found our log!
+                        return;
+                    }
+                }
+                Err(status) if status == zx::Status::SHOULD_WAIT => {
+                    debuglog
+                        .wait_handle(zx::Signals::LOG_READABLE, zx::Time::INFINITE)
+                        .expect("Failed to wait for log readable");
                     continue;
                 }
-                assert_eq!(zx::sys::ZX_OK, status);
+                Err(status) => {
+                    panic!("Unexpected error from zx_debuglog_read: {}", status);
+                }
             }
 
-            let data_len = LittleEndian::read_u16(&read_buffer[4..8]) as usize;
-            let log = String::from_utf8(read_buffer[32..(32 + data_len)].to_vec())
-                .expect("failed to read log buffer");
-            if log == sent_msg {
-                // We found our log!
-                return;
-            }
         }
         panic!("first 10000 log messages didn't include the one we sent!");
     }