diff --git a/system/core/devmgr/devhost/devhost.cpp b/system/core/devmgr/devhost/devhost.cpp
index 078b004facb805b3cb188837cb8584cdddeb4ea3..40069821072e3b9af4581af9346ebf5b4b93904c 100644
--- a/system/core/devmgr/devhost/devhost.cpp
+++ b/system/core/devmgr/devhost/devhost.cpp
@@ -38,6 +38,7 @@
 #include <lib/zx/debuglog.h>
 #include <lib/zx/resource.h>
 #include <lib/zx/vmo.h>
+#include <lib/zxio/null.h>
 
 #include "../shared/async-loop-owned-rpc-handler.h"
 #include "main.h"
@@ -832,18 +833,29 @@ __EXPORT void driver_printf(uint32_t flags, const char* fmt, ...) {
 
 namespace devmgr {
 
-static ssize_t devhost_log_write(void* cookie, const void* data, size_t len) {
-    return devhost_log_write_internal(0, data, len);
+static zx_status_t devhost_log_write(zxio_t* io, const void* buffer,
+                                     size_t capacity, size_t* out_actual) {
+    devhost_log_write_internal(0, buffer, capacity);
+    *out_actual = capacity;
+    return ZX_OK;
 }
 
+static constexpr zxio_ops_t devhost_log_ops = []() {
+    zxio_ops_t ops = zxio_default_ops;
+    ops.write = devhost_log_write;
+    return ops;
+}();
+
 static void devhost_io_init() {
     if (zx::debuglog::create(zx::resource(), 0, &devhost_log_handle) < 0) {
         return;
     }
-    fdio_t* io;
-    if ((io = fdio_output_create(devhost_log_write, nullptr)) == nullptr) {
+    fdio_t* io = nullptr;
+    zxio_storage_t* storage = nullptr;
+    if ((io = fdio_zxio_create(&storage)) == nullptr) {
         return;
     }
+    zxio_init(&storage->io, &devhost_log_ops);
     close(1);
     fdio_bind_to_fd(io, 1, 0);
     dup2(1, 2);
diff --git a/system/ulib/driver/rules.mk b/system/ulib/driver/rules.mk
index f7c582f10711382e8a808293a967dfb7abc6050a..4e3355e53ccf1c436f123f2cc6a763246c8e9a20 100644
--- a/system/ulib/driver/rules.mk
+++ b/system/ulib/driver/rules.mk
@@ -49,6 +49,7 @@ MODULE_STATIC_LIBS := \
     system/ulib/port \
     system/ulib/zx \
     system/ulib/zxcpp \
+    system/ulib/zxio \
 
 # There are pieces of the trace engine that are always present.
 # They don't provide tracing support, but the tracing API provides
diff --git a/system/ulib/fdio/include/lib/fdio/util.h b/system/ulib/fdio/include/lib/fdio/util.h
index 0e327c8747a164778653efeb8ec81d2ec05bbfa9..0e77e2229f5b4b3b494cd087a4fd782111f183e8 100644
--- a/system/ulib/fdio/include/lib/fdio/util.h
+++ b/system/ulib/fdio/include/lib/fdio/util.h
@@ -75,11 +75,6 @@ fdio_t* fdio_service_create(zx_handle_t);
 // entire log-lines and flush them on newline or buffer full.
 fdio_t* fdio_logger_create(zx_handle_t);
 
-// create a fdio that wraps a function
-// used for plumbing stdout/err to logging subsystems, etc
-fdio_t* fdio_output_create(ssize_t (*func)(void* cookie, const void* data, size_t len),
-                           void* cookie);
-
 typedef struct zxio_storage zxio_storage_t;
 
 // Creates an |fdio_t| that is backed by a |zxio_t|.
diff --git a/system/ulib/fdio/output.c b/system/ulib/fdio/output.c
deleted file mode 100644
index 4bc9b7a758a4da0cb4ba4ea257413900a8ca65f7..0000000000000000000000000000000000000000
--- a/system/ulib/fdio/output.c
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2016 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.
-
-#include <lib/fdio/io.h>
-
-#include <stdatomic.h>
-#include <stdlib.h>
-#include <threads.h>
-
-#include <zircon/syscalls.h>
-
-#include "private.h"
-
-typedef struct fdio_out fdio_out_t;
-
-struct fdio_out {
-    fdio_t io;
-    ssize_t (*func)(void* cookie, const void* data, size_t len);
-    void* cookie;
-};
-
-static ssize_t log_write(fdio_t* io, const void* data, size_t len) {
-    fdio_out_t* out = (void*)io;
-    return out->func(out->cookie, data, len);
-}
-
-static zx_status_t log_close(fdio_t* io) {
-    return ZX_OK;
-}
-
-static fdio_ops_t out_io_ops = {
-    .read = fdio_default_read,
-    .read_at = fdio_default_read_at,
-    .write = log_write,
-    .write_at = fdio_default_write_at,
-    .seek = fdio_default_seek,
-    .misc = fdio_default_misc,
-    .close = fdio_default_close,
-    .open = fdio_default_open,
-    .clone = fdio_default_clone,
-    .ioctl = fdio_default_ioctl,
-    .wait_begin = fdio_default_wait_begin,
-    .wait_end = fdio_default_wait_end,
-    .unwrap = fdio_default_unwrap,
-    .posix_ioctl = fdio_default_posix_ioctl,
-    .get_vmo = fdio_default_get_vmo,
-    .get_token = fdio_default_get_token,
-    .get_attr = fdio_default_get_attr,
-    .set_attr = fdio_default_set_attr,
-    .sync = fdio_default_sync,
-    .readdir = fdio_default_readdir,
-    .rewind = fdio_default_rewind,
-    .unlink = fdio_default_unlink,
-    .truncate = fdio_default_truncate,
-    .rename = fdio_default_rename,
-    .link = fdio_default_link,
-    .get_flags = fdio_default_get_flags,
-    .set_flags = fdio_default_set_flags,
-    .recvfrom = fdio_default_recvfrom,
-    .sendto = fdio_default_sendto,
-    .recvmsg = fdio_default_recvmsg,
-    .sendmsg = fdio_default_sendmsg,
-    .shutdown = fdio_default_shutdown,
-};
-
-__EXPORT
-fdio_t* fdio_output_create(ssize_t (*func)(void* cookie, const void* data, size_t len),
-                           void* cookie) {
-    fdio_out_t* out = fdio_alloc(sizeof(fdio_out_t));
-    if (out == NULL) {
-        return NULL;
-    }
-    out->io.ops = &out_io_ops;
-    out->io.magic = FDIO_MAGIC;
-    atomic_init(&out->io.refcount, 1);
-    out->func = func;
-    out->cookie = cookie;
-    return &out->io;
-}
diff --git a/system/ulib/fdio/rules.mk b/system/ulib/fdio/rules.mk
index 23861b126e33aedc0830cc2d7d4cf82943a2cb9f..001260f029260416c69604771e37c992ea9dfb3e 100644
--- a/system/ulib/fdio/rules.mk
+++ b/system/ulib/fdio/rules.mk
@@ -19,7 +19,6 @@ MODULE_SRCS += \
     $(LOCAL_DIR)/logger.c \
     $(LOCAL_DIR)/namespace.c \
     $(LOCAL_DIR)/null.c \
-    $(LOCAL_DIR)/output.c \
     $(LOCAL_DIR)/remoteio.c \
     $(LOCAL_DIR)/service.c \
     $(LOCAL_DIR)/socket.c \