Repository: nasa/fprime (language: python)

Issue:
Refactor Os::Log into CMake Selection
| | |
|:---|:---|
|**_F´ Version_**| |
|**_Affected Component_**|  |
---
## Feature Description

- [x] Consider if `Os::Log` should be renamed `Os::Console`. 
- [x] Refactor Os::Log into the Interface, Os::Log, and Implementation classes (following pattern of Os::File)
- [x] Implement Posix implementation
- [x] Implement Stub implementation
- [x] Implement Test stub implementation
- [x] Build common rules-based tests
- [x] Build specific posix tests
- [x] Build stub interface tests
- [x] Setup default posix implementation for Posix platforms
- [x] Document



Discussion / hints:

Fix patch:
diff --git a/Drv/Ip/IpSocket.cpp b/Drv/Ip/IpSocket.cpp
index d15c8fc295..267c080e80 100644
--- a/Drv/Ip/IpSocket.cpp
+++ b/Drv/Ip/IpSocket.cpp
@@ -53,11 +53,12 @@ IpSocket::IpSocket() : m_fd(-1), m_timeoutSeconds(0), m_timeoutMicroseconds(0),
 SocketIpStatus IpSocket::configure(const char* const hostname, const U16 port, const U32 timeout_seconds, const U32 timeout_microseconds) {
     FW_ASSERT(timeout_microseconds < 1000000, static_cast<FwAssertArgType>(timeout_microseconds));
     FW_ASSERT(this->isValidPort(port));
+    FW_ASSERT(hostname != nullptr);
     this->m_lock.lock();
     this->m_timeoutSeconds = timeout_seconds;
     this->m_timeoutMicroseconds = timeout_microseconds;
     this->m_port = port;
-    (void) Fw::StringUtils::string_copy(this->m_hostname, hostname, SOCKET_MAX_HOSTNAME_SIZE);
+    (void) Fw::StringUtils::string_copy(this->m_hostname, hostname, static_cast<FwSizeType>(SOCKET_MAX_HOSTNAME_SIZE));
     this->m_lock.unlock();
     return SOCK_SUCCESS;
 }
diff --git a/Drv/Ip/SocketReadTask.cpp b/Drv/Ip/SocketReadTask.cpp
index da9f34c01c..10d6e3c953 100644
--- a/Drv/Ip/SocketReadTask.cpp
+++ b/Drv/Ip/SocketReadTask.cpp
@@ -75,7 +75,7 @@ void SocketReadTask::readTask(void* pointer) {
         // Open a network connection if it has not already been open
         if ((not self->getSocketHandler().isStarted()) and (not self->m_stop) and
             ((status = self->startup()) != SOCK_SUCCESS)) {
-            Fw::Logger::logMsg(
+            Fw::Logger::log(
                 "[WARNING] Failed to open port with status %d and errno %d\n",
                 static_cast<POINTER_CAST>(status),
                 static_cast<POINTER_CAST>(errno));
@@ -86,7 +86,7 @@ void SocketReadTask::readTask(void* pointer) {
         // Open a network connection if it has not already been open
         if ((not self->getSocketHandler().isOpened()) and (not self->m_stop) and
             ((status = self->open()) != SOCK_SUCCESS)) {
-            Fw::Logger::logMsg(
+            Fw::Logger::log(
                 "[WARNING] Failed to open port with status %d and errno %d\n",
                 static_cast<POINTER_CAST>(status),
                 static_cast<POINTER_CAST>(errno));
@@ -102,7 +102,7 @@ void SocketReadTask::readTask(void* pointer) {
             U32 size = buffer.getSize();
             status = self->getSocketHandler().recv(data, size);
             if ((status != SOCK_SUCCESS) && (status != SOCK_INTERRUPTED_TRY_AGAIN)) {
-                Fw::Logger::logMsg("[WARNING] Failed to recv from port with status %d and errno %d\n",
+                Fw::Logger::log("[WARNING] Failed to recv from port with status %d and errno %d\n",
                 static_cast<POINTER_CAST>(status),
                 static_cast<POINTER_CAST>(errno));
                 self->getSocketHandler().close();
diff --git a/Drv/Ip/TcpClientSocket.cpp b/Drv/Ip/TcpClientSocket.cpp
index 31808ce606..9c93184e44 100644
--- a/Drv/Ip/TcpClientSocket.cpp
+++ b/Drv/Ip/TcpClientSocket.cpp
@@ -84,7 +84,7 @@ SocketIpStatus TcpClientSocket::openProtocol(NATIVE_INT_TYPE& fd) {
         return SOCK_FAILED_TO_CONNECT;
     }
     fd = socketFd;
-    Fw::Logger::logMsg("Connected to %s:%hu as a tcp client\n", reinterpret_cast<POINTER_CAST>(m_hostname), m_port);
+    Fw::Logger::log("Connected to %s:%hu as a tcp client\n", m_hostname, m_port);
     return SOCK_SUCCESS;
 }
 
diff --git a/Drv/Ip/TcpServerSocket.cpp b/Drv/Ip/TcpServerSocket.cpp
index a9281aa6e0..a26727dd2f 100644
--- a/Drv/Ip/TcpServerSocket.cpp
+++ b/Drv/Ip/TcpServerSocket.cpp
@@ -83,7 +83,7 @@ SocketIpStatus TcpServerSocket::startup() {
         return SOCK_FAILED_TO_READ_BACK_PORT;
     }
     U16 port = ntohs(address.sin_port);
-    Fw::Logger::logMsg("Listening for single client at %s:%hu\n", reinterpret_cast<POINTER_CAST>(m_hostname), port);
+    Fw::Logger::log("Listening for single client at %s:%hu\n", m_hostname, port);
     // TCP requires listening on the socket. Since we only expect a single client, set the TCP backlog (second argument) to 1 to prevent queuing of multiple clients. 
     if (::listen(serverFd, 1) < 0) {
         ::close(serverFd);
@@ -133,7 +133,7 @@ SocketIpStatus TcpServerSocket::openProtocol(NATIVE_INT_TYPE& fd) {
         return SOCK_FAILED_TO_SET_SOCKET_OPTIONS;
     }
 
-    Fw::Logger::logMsg("Accepted client at %s:%hu\n", reinterpret_cast<POINTER_CAST>(m_hostname), m_port);
+    Fw::Logger::log("Accepted client at %s:%hu\n", m_hostname, m_port);
     fd = clientFd;
     return SOCK_SUCCESS;
 }
diff --git a/Drv/Ip/UdpSocket.cpp b/Drv/Ip/UdpSocket.cpp
index 1cc685117d..52902454cd 100644
--- a/Drv/Ip/UdpSocket.cpp
+++ b/Drv/Ip/UdpSocket.cpp
@@ -62,14 +62,16 @@ UdpSocket::~UdpSocket() {
 SocketIpStatus UdpSocket::configureSend(const char* const hostname, const U16 port, const U32 timeout_seconds, const U32 timeout_microseconds) {
     //Timeout is for the send, so configure send will work with the base class
     FW_ASSERT(port != 0, port); // Send cannot be on port 0
+    FW_ASSERT(hostname != nullptr);
     return this->IpSocket::configure(hostname, port, timeout_seconds, timeout_microseconds);
 }
 
 SocketIpStatus UdpSocket::configureRecv(const char* hostname, const U16 port) {
     FW_ASSERT(this->isValidPort(port));
+    FW_ASSERT(hostname != nullptr);
     this->m_lock.lock();
     this->m_recv_port = port;
-    (void) Fw::StringUtils::string_copy(this->m_recv_hostname, hostname, SOCKET_MAX_HOSTNAME_SIZE);
+    (void) Fw::StringUtils::string_copy(this->m_recv_hostname, hostname, static_cast<FwSizeType>(SOCKET_MAX_HOSTNAME_SIZE));
     this->m_lock.unlock();
     return SOCK_SUCCESS;
 }
@@ -175,13 +177,13 @@ SocketIpStatus UdpSocket::openProtocol(NATIVE_INT_TYPE& fd) {
     this->m_lock.unlock();
     // Log message for UDP
     if (port == 0) {
-        Fw::Logger::logMsg("Setup to receive udp at %s:%hu\n", reinterpret_cast<POINTER_CAST>(m_recv_hostname),
+        Fw::Logger::log("Setup to receive udp at %s:%hu\n", m_recv_hostname,
                            recv_port);
     } else {
-        Fw::Logger::logMsg("Setup to receive udp at %s:%hu and send to %s:%hu\n",
-                           reinterpret_cast<POINTER_CAST>(m_recv_hostname),
+        Fw::Logger::log("Setup to receive udp at %s:%hu and send to %s:%hu\n",
+                           m_recv_hostname,
                            recv_port,
-                           reinterpret_cast<POINTER_CAST>(m_hostname),
+                           m_hostname,
                            port);
     }
     FW_ASSERT(status == SOCK_SUCCESS, status);
diff --git a/Drv/LinuxI2cDriver/LinuxI2cDriver.cpp b/Drv/LinuxI2cDriver/LinuxI2cDriver.cpp
index be160c4eb0..2f01887d8c 100644
--- a/Drv/LinuxI2cDriver/LinuxI2cDriver.cpp
+++ b/Drv/LinuxI2cDriver/LinuxI2cDriver.cpp
@@ -81,18 +81,18 @@ namespace Drv {
       }
 
 #if DEBUG_PRINT
-      Fw::Logger::logMsg("I2c addr: 0x%02X\n",addr);
+      Fw::Logger::log("I2c addr: 0x%02X\n",addr);
       for (U32 byte = 0; byte < serBuffer.getSize(); byte++) {
-    	  Fw::Logger::logMsg("0x%02X ",serBuffer.getData()[byte]);
+    	  Fw::Logger::log("0x%02X ",serBuffer.getData()[byte]);
 
       }
-      Fw::Logger::logMsg("\n");
+      Fw::Logger::log("\n");
 #endif
       // select slave address
       int stat = ioctl(this->m_fd, I2C_SLAVE, addr);
       if (stat == -1) {
 #if DEBUG_PRINT
-          Fw::Logger::logMsg("Status: %d Errno: %d\n", stat, errno);
+          Fw::Logger::log("Status: %d Errno: %d\n", stat, errno);
 #endif
 	  return I2cStatus::I2C_ADDRESS_ERR;
       }
@@ -102,7 +102,7 @@ namespace Drv {
       stat = static_cast<int>(write(this->m_fd, serBuffer.getData(), serBuffer.getSize()));
       if (stat == -1) {
 #if DEBUG_PRINT
-          Fw::Logger::logMsg("Status: %d Errno: %d\n", stat, errno);
+          Fw::Logger::log("Status: %d Errno: %d\n", stat, errno);
 #endif
 	  return I2cStatus::I2C_WRITE_ERR;
       }
@@ -122,13 +122,13 @@ namespace Drv {
       }
 
 #if DEBUG_PRINT
-      Fw::Logger::logMsg("I2c addr: 0x%02X\n",addr);
+      Fw::Logger::log("I2c addr: 0x%02X\n",addr);
 #endif
       // select slave address
       int stat = ioctl(this->m_fd, I2C_SLAVE, addr);
       if (stat == -1) {
 #if DEBUG_PRINT
-          Fw::Logger::logMsg("Status: %d Errno: %d\n", stat, errno);
+          Fw::Logger::log("Status: %d Errno: %d\n", stat, errno);
 #endif
 	  return I2cStatus::I2C_ADDRESS_ERR;
       }
@@ -138,16 +138,16 @@ namespace Drv {
       stat = static_cast<int>(read(this->m_fd, serBuffer.getData(), serBuffer.getSize()));
       if (stat == -1) {
 #if DEBUG_PRINT
-          Fw::Logger::logMsg("Status: %d Errno: %d\n", stat, errno);
+          Fw::Logger::log("Status: %d Errno: %d\n", stat, errno);
 #endif
 	  return I2cStatus::I2C_READ_ERR;
       }
 #if DEBUG_PRINT
       for (U32 byte = 0; byte < serBuffer.getSize(); byte++) {
-    	  Fw::Logger::logMsg("0x%02X ",serBuffer.getData()[byte]);
+    	  Fw::Logger::log("0x%02X ",serBuffer.getData()[byte]);
 
       }
-      Fw::Logger::logMsg("\n");
+      Fw::Logger::log("\n");
 #endif
       return I2cStatus::I2C_OK;
   }
@@ -171,7 +171,7 @@ namespace Drv {
     FW_ASSERT(readBuffer.getData());
 
     #if DEBUG_PRINT
-      Fw::Logger::logMsg("I2c addr: 0x%02X\n",addr);
+      Fw::Logger::log("I2c addr: 0x%02X\n",addr);
     #endif
 
     struct i2c_msg rdwr_msgs[2];
@@ -197,25 +197,25 @@ namespace Drv {
 
     if(stat == -1){
       #if DEBUG_PRINT
-        Fw::Logger::logMsg("Status: %d Errno: %d\n", stat, errno);
+        Fw::Logger::log("Status: %d Errno: %d\n", stat, errno);
       #endif
       //Because we're using ioctl to perform the transaction we dont know exactly the type of error that occurred
       return I2cStatus::I2C_OTHER_ERR;
     }
 
 #if DEBUG_PRINT
-    Fw::Logger::logMsg("Wrote:\n");
+    Fw::Logger::log("Wrote:\n");
     for (U32 byte = 0; byte < writeBuffer.getSize(); byte++) {
-    	  Fw::Logger::logMsg("0x%02X ",writeBuffer.getData()[byte]);
+    	  Fw::Logger::log("0x%02X ",writeBuffer.getData()[byte]);
 
     }
-    Fw::Logger::logMsg("\n");
-    Fw::Logger::logMsg("Read:\n");
+    Fw::Logger::log("\n");
+    Fw::Logger::log("Read:\n");
     for (U32 byte = 0; byte < readBuffer.getSize(); byte++) {
-    	  Fw::Logger::logMsg("0x%02X ",readBuffer.getData()[byte]);
+    	  Fw::Logger::log("0x%02X ",readBuffer.getData()[byte]);
 
     }
-    Fw::Logger::logMsg("\n");
+    Fw::Logger::log("\n");
 #endif
 
     return I2cStatus::I2C_OK;
diff --git a/Fw/FilePacket/PathName.cpp b/Fw/FilePacket/PathName.cpp
index 7f66f62ca8..91741c828a 100644
--- a/Fw/FilePacket/PathName.cpp
+++ b/Fw/FilePacket/PathName.cpp
@@ -21,7 +21,7 @@ namespace Fw {
   void FilePacket::PathName ::
     initialize(const char *const value)
   {
-    const U8 length = static_cast<U8>(StringUtils::string_length(value, MAX_LENGTH));
+    const U8 length = static_cast<U8>(StringUtils::string_length(value, static_cast<FwSizeType>(MAX_LENGTH)));
     this->m_length = length;
     this->m_value = value;
   }
diff --git a/Fw/Logger/CMakeLists.txt b/Fw/Logger/CMakeLists.txt
index 0c10a94b0b..e89d0262c7 100644
--- a/Fw/Logger/CMakeLists.txt
+++ b/Fw/Logger/CMakeLists.txt
@@ -11,7 +11,6 @@ set(MOD_DEPS
 )
 set(SOURCE_FILES
   "${CMAKE_CURRENT_LIST_DIR}/Logger.cpp"
-  "${CMAKE_CURRENT_LIST_DIR}/LogAssert.cpp"
 )
 register_fprime_module()
 
@@ -26,4 +25,4 @@ set(UT_SOURCE_FILES
     "${CMAKE_CURRENT_LIST_DIR}/test/ut/LoggerMain.cpp"
 )
 # STest Includes for this UT
-register_fprime_ut("Logger_Rules_Testing")
+register_fprime_ut()
diff --git a/Fw/Logger/LogAssert.cpp b/Fw/Logger/LogAssert.cpp
deleted file mode 100644
index 13ddb6fd47..0000000000
--- a/Fw/Logger/LogAssert.cpp
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * LogAssert.cpp
- *
- *  Created on: Sep 9, 2016
- *      Author: tcanham
- * Note: this file was originally a  log assert file, under Fw::Types. It now made generic
- * to log asserts to Fw::Logger
- */
-
-#include <Fw/Logger/LogAssert.hpp>
-#include <Fw/Logger/Logger.hpp>
-
-#if FW_ASSERT_LEVEL == FW_NO_ASSERT
-
-#else
-
-#if FW_ASSERT_LEVEL == FW_FILEID_ASSERT
-#define fileIdFs "Assert: %d:%d"
-#define ASSERT_CAST static_cast<POINTER_CAST>
-#else
-#define fileIdFs "Assert: \"%s:%d\""
-#define ASSERT_CAST reinterpret_cast<POINTER_CAST>
-#endif
-
-
-namespace Fw {
-
-    LogAssertHook::LogAssertHook() {
-
-    }
-
-    LogAssertHook::~LogAssertHook() {
-    }
-
-    void LogAssertHook::reportAssert(
-        FILE_NAME_ARG file,
-        NATIVE_UINT_TYPE lineNo,
-        NATIVE_UINT_TYPE numArgs,
-        FwAssertArgType arg1,
-        FwAssertArgType arg2,
-        FwAssertArgType arg3,
-        FwAssertArgType arg4,
-        FwAssertArgType arg5,
-        FwAssertArgType arg6
-    ) {
-        // Assumption is that file (when string) goes back to static macro in the code and will persist
-        switch (numArgs) {
-            case 0:
-                Fw::Logger::logMsg(
-                    fileIdFs,
-                    ASSERT_CAST(file),
-                    lineNo,
-                    0,
-                    0,
-                    0,
-                    0);
-                break;
-            case 1:
-                Fw::Logger::logMsg(
-                    fileIdFs " %d\n",
-                    ASSERT_CAST(file),lineNo,
-                    static_cast<POINTER_CAST>(arg1),
-                    0,
-                    0,
-                    0);
-                break;
-            case 2:
-                Fw::Logger::logMsg(
-                    fileIdFs " %d %d\n",
-                    ASSERT_CAST(file),lineNo,
-                    static_cast<POINTER_CAST>(arg1),
-                    static_cast<POINTER_CAST>(arg2),
-                    0,
-                    0);
-                break;
-            case 3:
-                Fw::Logger::logMsg(
-                    fileIdFs " %d %d %d\n",
-                    ASSERT_CAST(file),lineNo,
-                    static_cast<POINTER_CAST>(arg1),
-                    static_cast<POINTER_CAST>(arg2),
-                    static_cast<POINTER_CAST>(arg3),
-                    0);
-                break;
-            case 4:
-                Fw::Logger::logMsg(
-                    fileIdFs " %d %d %d %d\n",
-                    ASSERT_CAST(file),lineNo,
-                    static_cast<POINTER_CAST>(arg1),
-                    static_cast<POINTER_CAST>(arg2),
-                    static_cast<POINTER_CAST>(arg3),
-                    static_cast<POINTER_CAST>(arg4));
-                break;
-            default: // can't fit remainder of arguments in log message
-                Fw::Logger::logMsg(
-                    fileIdFs " %d %d %d %d +\n",
-                    ASSERT_CAST(file),lineNo,
-                    static_cast<POINTER_CAST>(arg1),
-                    static_cast<POINTER_CAST>(arg2),
-                    static_cast<POINTER_CAST>(arg3),
-                    static_cast<POINTER_CAST>(arg4));
-                break;
-        }
-
-    }
-
-    void LogAssertHook::printAssert(const CHAR* msg) {
-        // do nothing since reportAssert() sends message
-    }
-
-    void LogAssertHook::doAssert() {
-    }
-
-} // namespace Fw
-
-#endif
diff --git a/Fw/Logger/LogAssert.hpp b/Fw/Logger/LogAssert.hpp
deleted file mode 100644
index 6927b06d90..0000000000
--- a/Fw/Logger/LogAssert.hpp
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * LogAssert.hpp
- *
- *  Created on: Sep 9, 2016
- *      Author: tcanham
- * Note: this file was originally a  log assert file, under Fw::Types. It now made generic
- * to log asserts to Fw::Logger
- */
-
-#ifndef LOGGER_LOGASSERT_HPP_
-#define LOGGER_LOGASSERT_HPP_
-
-#include <Fw/Types/Assert.hpp>
-
-namespace Fw {
-
-    class LogAssertHook: public Fw::AssertHook {
-        public:
-            LogAssertHook();
-            virtual ~LogAssertHook();
-            void reportAssert(
-                FILE_NAME_ARG file,
-                NATIVE_UINT_TYPE lineNo,
-                NATIVE_UINT_TYPE numArgs,
-                FwAssertArgType arg1,
-                FwAssertArgType arg2,
-                FwAssertArgType arg3,
-                FwAssertArgType arg4,
-                FwAssertArgType arg5,
-                FwAssertArgType arg6
-            );
-            void printAssert(const CHAR* msg);
-            void doAssert();
-    };
-
-}
-
-#endif /* VXWORKSLOGASSERT_HPP_ */
diff --git a/Fw/Logger/Logger.cpp b/Fw/Logger/Logger.cpp
index 07d08becad..9493cf4fea 100644
--- a/Fw/Logger/Logger.cpp
+++ b/Fw/Logger/Logger.cpp
@@ -4,31 +4,38 @@
  * Author: mstarch
  *
  * This file adds in support to the core 'Fw' package, to separate it from Os and other loggers, and
- * allow the architect of the system to select which core framework logging should be used. 
+ * allow the architect of the system to select which core framework logging should be used.
  */
-
 #include <Fw/Logger/Logger.hpp>
+#include <Fw/Types/Assert.hpp>
+#include <Fw/Types/String.hpp>
+#include <Fw/Types/StringUtils.hpp>
+#include <cstdarg>
+#include <limits>
 
 namespace Fw {
 
-//Initial logger is NULL
-    Logger* Logger::s_current_logger = nullptr;
+// Initial logger is NULL
+Logger* Logger::s_current_logger = nullptr;
 
-// Basic log implementation
-    void Logger::logMsg(const char* fmt, POINTER_CAST a0, POINTER_CAST a1,
-            POINTER_CAST a2, POINTER_CAST a3, POINTER_CAST a4, POINTER_CAST a5,
-            POINTER_CAST a6, POINTER_CAST a7, POINTER_CAST a8, POINTER_CAST a9) {
-        // Log if capable, otherwise drop
-        if (Logger::s_current_logger != nullptr) {
-            Logger::s_current_logger->log(fmt, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9);
-        }
-    }
-// Register the logger
-    void Logger::registerLogger(Logger* logger) {
-        Logger::s_current_logger = logger;
-    }
+void Logger::log(const char* format, ...) {
+    Fw::String formatted_string;
+    // Forward the variable arguments to the vformat format implementation
+    va_list args;
+    va_start(args, format);
+    formatted_string.vformat(format, args);
+    va_end(args);
+    Logger::log(formatted_string);
+}
 
-    Logger::~Logger() {
+void Logger::log(const StringBase& string) {
+    if (Logger::s_current_logger != nullptr) {
+        Logger::s_current_logger->writeMessage(string);
     }
+}
+
+void Logger::registerLogger(Logger* logger) {
+    Logger::s_current_logger = logger;
+}
 
-} //End namespace Fw
+}  // End namespace Fw
diff --git a/Fw/Logger/Logger.hpp b/Fw/Logger/Logger.hpp
index 981612b09c..53f3f017e7 100644
--- a/Fw/Logger/Logger.hpp
+++ b/Fw/Logger/Logger.hpp
@@ -4,87 +4,59 @@
  * Author: mstarch
  *
  * This file adds in support to the core 'Fw' package, to separate it from Os and other loggers, and
- * allow the architect of the system to select which core framework logging should be used. 
+ * allow the architect of the system to select which core framework logging should be used.
  */
-#ifndef _Fw_Logger_hpp_
-#define _Fw_Logger_hpp_
-
+#ifndef Fw_Logger_hpp_
+#define Fw_Logger_hpp_
 #include <FpConfig.hpp>
+#include <Fw/Deprecate.hpp>
+#include <Fw/Types/StringBase.hpp>
+
+// Unit testing predeclaration hook
+namespace LoggerRules {
+struct Register;
+}
 
 namespace Fw {
-    class Logger {
-        public:
-            /**
-             * Function called on the logger to log a message. This is abstract virtual method and
-             * must be supplied by the subclass. This logger object should be registered with the
-             * Fw::Log::registerLogger function.
-             * \param fmt: format string in which to place arguments
-             * \param a0: zeroth argument. (Default: 0)
-             * \param a1: first argument. (Default: 0)
-             * \param a2: second argument. (Default: 0)
-             * \param a3: third argument. (Default: 0)
-             * \param a4: fourth argument. (Default: 0)
-             * \param a5: fifth argument. (Default: 0)
-             * \param a6: sixth argument. (Default: 0)
-             * \param a7: seventh argument. (Default: 0)
-             * \param a8: eighth argument. (Default: 0)
-             * \param a9: ninth argument. (Default: 0)
-             */
-            virtual void log(
-                const char* fmt,
-                POINTER_CAST a0 = 0,
-                POINTER_CAST a1 = 0,
-                POINTER_CAST a2 = 0,
-                POINTER_CAST a3 = 0,
-                POINTER_CAST a4 = 0,
-                POINTER_CAST a5 = 0,
-                POINTER_CAST a6 = 0,
-                POINTER_CAST a7 = 0,
-                POINTER_CAST a8 = 0,
-                POINTER_CAST a9 = 0
-            ) = 0;
+class Logger {
+    friend struct LoggerRules::Register;
 
-            /**
-             * Logs a message using the currently specified static logger. If a logger is not
-             * registered, then the log message is dropped.
-             * \param fmt: format string in which to place arguments
-             * \param a0: zeroth argument. (Default: 0)
-             * \param a1: first argument. (Default: 0)
-             * \param a2: second argument. (Default: 0)
-             * \param a3: third argument. (Default: 0)
-             * \param a4: fourth argument. (Default: 0)
-             * \param a5: fifth argument. (Default: 0)
-             * \param a6: sixth argument. (Default: 0)
-             * \param a7: seventh argument. (Default: 0)
-             * \param a8: eighth argument. (Default: 0)
-             * \param a9: ninth argument. (Default: 0)
-             */
-            static void logMsg(
-                const char* fmt,
-                POINTER_CAST a0 = 0,
-                POINTER_CAST a1 = 0,
-                POINTER_CAST a2 = 0,
-                POINTER_CAST a3 = 0,
-                POINTER_CAST a4 = 0,
-                POINTER_CAST a5 = 0,
-                POINTER_CAST a6 = 0,
-                POINTER_CAST a7 = 0,
-                POINTER_CAST a8 = 0,
-                POINTER_CAST a9 = 0
-            );
+  public:
+    //! \brief log a formated string with supplied arguments
+    //!
+    //! Logs a format string with the arguments filled-in. This delegates to StringBase.format, which in-turn
+    //! delegates to snprintf. This implies that the caller is fully responsible for handling the type safety of
+    //! the supplied format string. The format string uses C-style (printf function family) formatting.
+    //! \param format: format string
+    //! \param ...: var-args list of arguments to inject into format string.
+    static void log(const char* format, ...);
 
-            /**
-             * Registers the static logger for use with the Fw::Log::logMsg function. This must be
-             * a subclass of Fw::Log.
-             * \param logger: logger to log to when Fw::Log::logMsg is called.
-             */
-            static void registerLogger(Logger* logger);
+    //! \brief log a string message directly
+    //!
+    //! Logs the string directly to the backing store without any formatting changes.
+    //! \param message: message to log
+    static void log(const Fw::StringBase& message);
 
-            //!< Static logger to use when calling the above 'logMsg' function
-            static Logger* s_current_logger;
+    //! \brief register a logger implementation
+    //!
+    //! This registers the supplied logger as the system logger used for calls to Fw::Logger::log.
+    //! \param logger: logger to register as the system logger
+    static void registerLogger(Logger* logger);
 
-            virtual ~Logger();
-    };
-}
+    //! Virtual destructor
+    virtual ~Logger() = default;
+
+  protected:
+    //! \brief write the output of the log message
+    //!
+    //! Log implementations must provide this method used to write the output of a string to the log backing.
+    //!
+    //! \param message: message to log
+    virtual void writeMessage(const StringBase& message) = 0;
+
+  private:
+    static Logger* s_current_logger;  //!< Static logger to use when calling Fw::Logger::log function
+};
+}  // namespace Fw
 
 #endif
diff --git a/Fw/Obj/SimpleObjRegistry.cpp b/Fw/Obj/SimpleObjRegistry.cpp
index e743d6ea50..2521349fa7 100644
--- a/Fw/Obj/SimpleObjRegistry.cpp
+++ b/Fw/Obj/SimpleObjRegistry.cpp
@@ -28,15 +28,15 @@ namespace Fw {
 #if FW_OBJECT_TO_STRING == 1
             char objDump[FW_OBJ_SIMPLE_REG_BUFF_SIZE];
             this->m_objPtrArray[obj]->toString(objDump,sizeof(objDump));
-            Fw::Logger::logMsg("Entry: %d Ptr: %p Str: %s\n", static_cast<POINTER_CAST>(obj),
-                    reinterpret_cast<POINTER_CAST>(this->m_objPtrArray[obj]), reinterpret_cast<POINTER_CAST>(objDump));
+            Fw::Logger::log("Entry: %d Ptr: %p Str: %s\n", obj,
+                            this->m_objPtrArray[obj], objDump);
 #else
-            Fw::Logger::logMsg("Entry: %d Ptr: %p Name: %s\n",static_cast<POINTER_CAST>(obj),
-                    reinterpret_cast<POINTER_CAST>(this->m_objPtrArray[obj]),
-                    reinterpret_cast<POINTER_CAST>(this->m_objPtrArray[obj]->getObjName()));
+            Fw::Logger::log("Entry: %d Ptr: %p Name: %s\n", obj,
+                            this->m_objPtrArray[obj],
+                            this->m_objPtrArray[obj]->getObjName());
 #endif // FW_OBJECT_TO_STRING
 #else
-            Fw::Logger::logMsg("Entry: %d Ptr: %p Str:\n", obj, reinterpret_cast<POINTER_CAST>(this->m_objPtrArray[obj]));
+            Fw::Logger::log("Entry: %d Ptr: %p\n", obj, this->m_objPtrArray[obj]);
 #endif
         }
     }
@@ -48,12 +48,12 @@ namespace Fw {
             if (strncmp(objName,this->m_objPtrArray[obj]->getObjName(),sizeof(objDump)) == 0) {
 #if FW_OBJECT_TO_STRING == 1
                 this->m_objPtrArray[obj]->toString(objDump,sizeof(objDump));
-                Fw::Logger::logMsg("Entry: %d Ptr: %p Str: %s\n", static_cast<POINTER_CAST>(obj),
-                        reinterpret_cast<POINTER_CAST>(this->m_objPtrArray[obj]), reinterpret_cast<POINTER_CAST>(objDump));
+                Fw::Logger::log("Entry: %d Ptr: %p Str: %s\n", obj,
+                                this->m_objPtrArray[obj], objDump);
 #else
-                Fw::Logger::logMsg("Entry: %d Ptr: %p Name: %s\n",static_cast<POINTER_CAST>(obj),
-                        reinterpret_cast<POINTER_CAST>(this->m_objPtrArray[obj]),
-                        reinterpret_cast<POINTER_CAST>(this->m_objPtrArray[obj]->getObjName()));
+                Fw::Logger::log("Entry: %d Ptr: %p Name: %s\n",obj,
+                                this->m_objPtrArray[obj],
+                                this->m_objPtrArray[obj]->getObjName());
 #endif
             }
         }
diff --git a/Fw/Port/OutputPortBase.cpp b/Fw/Port/OutputPortBase.cpp
index 84a05522a7..009a51c6b4 100644
--- a/Fw/Port/OutputPortBase.cpp
+++ b/Fw/Port/OutputPortBase.cpp
@@ -1,6 +1,6 @@
 #include <FpConfig.hpp>
 #include <Fw/Port/OutputPortBase.hpp>
-#include <Os/Log.hpp>
+#include <Os/Console.hpp>
 #include <cstdio>
 #include <Fw/Types/Assert.hpp>
 
diff --git a/Fw/Port/PortBase.cpp b/Fw/Port/PortBase.cpp
index e53242c0f5..5a63bb1b0a 100644
--- a/Fw/Port/PortBase.cpp
+++ b/Fw/Port/PortBase.cpp
@@ -57,9 +57,9 @@ namespace Fw {
 
         if (do_trace) {
 #if FW_OBJECT_NAMES == 1
-            Fw::Logger::logMsg("Trace: %s\n", reinterpret_cast<POINTER_CAST>(this->m_objName.toChar()), 0, 0, 0, 0, 0);
+            Fw::Logger::log("Trace: %s\n", this->m_objName.toChar());
 #else
-            Fw::Logger::logMsg("Trace: %p\n", reinterpret_cast<POINTER_CAST>(this), 0, 0, 0, 0, 0);
+            Fw::Logger::log("Trace: %p\n", this);
 #endif
         }
     }
diff --git a/Fw/Types/StringBase.cpp b/Fw/Types/StringBase.cpp
index 5fc9820488..c3ff506b17 100644
--- a/Fw/Types/StringBase.cpp
+++ b/Fw/Types/StringBase.cpp
@@ -54,14 +54,23 @@ bool StringBase::operator==(const CHAR* other) const {
 }
 
 void StringBase::format(const CHAR* formatString, ...) {
-    CHAR* us = const_cast<CHAR*>(this->toChar());
-    SizeType cap = this->getCapacity();
-    FW_ASSERT(us);
     va_list args;
     va_start(args, formatString);
-    (void)vsnprintf(us, cap, formatString, args);
+    this->vformat(formatString, args);
     va_end(args);
-    // null terminate
+}
+
+void StringBase::vformat(const CHAR* formatString, va_list args) {
+    CHAR* us = const_cast<CHAR*>(this->toChar());
+    SizeType cap = this->getCapacity();
+    FW_ASSERT(us != nullptr);
+    FW_ASSERT(formatString != nullptr);
+#if FW_USE_PRINTF_FAMILY_FUNCTIONS_IN_STRING_FORMATTING
+    (void) vsnprintf(us, cap, formatString, args);
+#else
+    *this = formatString;
+#endif
+    // Force null terminate
     us[cap - 1] = 0;
 }
 
diff --git a/Fw/Types/StringBase.hpp b/Fw/Types/StringBase.hpp
index 11fcc7dfca..a0fddc0cbd 100644
--- a/Fw/Types/StringBase.hpp
+++ b/Fw/Types/StringBase.hpp
@@ -15,6 +15,7 @@
 
 #include <FpConfig.hpp>
 #include <Fw/Types/Serializable.hpp>
+#include <cstdarg>
 #ifdef BUILD_UT
 #include <iostream>
 #endif
@@ -62,6 +63,7 @@ class StringBase : public Serializable {
     StringBase& operator=(const StringBase& src);         //!< Assign another StringBase
 
     void format(const CHAR* formatString, ...);  //!< write formatted string to buffer
+    void vformat(const CHAR* formatString, va_list args);  //!< write formatted string to buffer using va_list
 
     virtual SerializeStatus serialize(SerializeBufferBase& buffer) const;                   //!< serialization function
     virtual SerializeStatus serialize(SerializeBufferBase& buffer, SizeType maxLen) const;  //!< serialization function
diff --git a/Fw/Types/StringUtils.cpp b/Fw/Types/StringUtils.cpp
index 4f1417b587..f865836d36 100644
--- a/Fw/Types/StringUtils.cpp
+++ b/Fw/Types/StringUtils.cpp
@@ -4,6 +4,22 @@
 #include <limits>
 
 char* Fw::StringUtils::string_copy(char* destination, const char* source, U32 num) {
+    // Check for size support
+    FW_ASSERT(std::numeric_limits<U32>::max() <= std::numeric_limits<FwSizeType>::max());
+    char* returned = Fw::StringUtils::string_copy(destination, source, static_cast<FwSizeType>(num));
+    return returned;
+}
+
+U32 Fw::StringUtils::string_length(const CHAR* source, U32 max_len) {
+    // Check for size support
+    FW_ASSERT(std::numeric_limits<U32>::max() <= std::numeric_limits<FwSizeType>::max());
+    FwSizeType returned = Fw::StringUtils::string_length(source, static_cast<FwSizeType>(max_len));
+    // Range checking for type remapping
+    FW_ASSERT(returned <= static_cast<FwSizeType>(std::numeric_limits<U32>::max()));
+    return static_cast<U32>(returned);
+}
+
+char* Fw::StringUtils::string_copy(char* destination, const char* source, FwSizeType num) {
     // Handle self-copy and 0 bytes copy
     if (destination == source || num == 0) {
         return destination;
@@ -12,7 +28,7 @@ char* Fw::StringUtils::string_copy(char* destination, const char* source, U32 nu
     FW_ASSERT(destination != nullptr);
 
     // Copying an overlapping range is undefined
-    U32 source_len = string_length(source, num) + 1;
+    FwSizeType source_len = string_length(source, num) + 1;
     FW_ASSERT(source + source_len <= destination || destination + num <= source);
 
     char* returned = strncpy(destination, source, num);
@@ -20,7 +36,7 @@ char* Fw::StringUtils::string_copy(char* destination, const char* source, U32 nu
     return returned;
 }
 
-U32 Fw::StringUtils::string_length(const CHAR* source, U32 max_len) {
+FwSizeType Fw::StringUtils::string_length(const CHAR* source, FwSizeType max_len) {
     U32 length = 0;
     FW_ASSERT(source != nullptr);
     for (length = 0; length < max_len; length++) {
diff --git a/Fw/Types/StringUtils.hpp b/Fw/Types/StringUtils.hpp
index 800efc7bcd..be1a46ea37 100644
--- a/Fw/Types/StringUtils.hpp
+++ b/Fw/Types/StringUtils.hpp
@@ -32,6 +32,33 @@ char* string_copy(char* destination, const char* source, U32 num);
  */
 U32 string_length(const CHAR* source, U32 max_len);
 
+/**
+ * \brief copy string with null-termination guaranteed
+ *
+ * Standard implementations of strncpy fail to guarantee the termination of a
+ * string with the null terminator. This implementation guarantees the string is
+ * properly null-terminated at the possible expense of the last character of the
+ * copied string being lost. The user is responsible for providing a destination
+ * large enough for the content and a null-character. Other behavior retains the
+ * behavior of strncpy.
+ *
+ * \param destination: destination buffer to hold copied contents
+ * \param source: source buffer to read content to copy
+ * \param num: length of destination buffer
+ * \return destination buffer
+ */
+char* string_copy(char* destination, const char* source, FwSizeType num);
+
+/**
+ * \brief get the length of the source string or max_len if the string is
+ * longer than max_len.
+ *
+ * \param source: string to calculate the length
+ * \param max_len: the maximum length of the source string
+ * \return length of the source string or max_len
+ */
+FwSizeType string_length(const CHAR* source, FwSizeType max_len);
+
 /**
  * \brief find the first occurrence of a substring
  *
diff --git a/Os/CMakeLists.txt b/Os/CMakeLists.txt
index 6119e93668..f208c3184f 100644
--- a/Os/CMakeLists.txt
+++ b/Os/CMakeLists.txt
@@ -48,7 +48,7 @@ endif()
 # Posix systems typically share these
 if (FPRIME_USE_POSIX)
     list(APPEND SOURCE_FILES
-        "${CMAKE_CURRENT_LIST_DIR}/LogPrintf.cpp"
+        "${CMAKE_CURRENT_LIST_DIR}/Console.cpp"
         "${CMAKE_CURRENT_LIST_DIR}/Pthreads/Queue.cpp"
         "${CMAKE_CURRENT_LIST_DIR}/Pthreads/BufferQueueCommon.cpp"
         "${CMAKE_CURRENT_LIST_DIR}/Pthreads/PriorityBufferQueue.cpp"
diff --git a/Os/Console.cpp b/Os/Console.cpp
new file mode 100644
index 0000000000..5cdd52bde3
--- /dev/null
+++ b/Os/Console.cpp
@@ -0,0 +1,69 @@
+// ======================================================================
+// \title Os/Console.cpp
+// \brief common function implementation for Os::Console
+// ======================================================================
+#include <Os/Console.hpp>
+#include <Fw/Types/Assert.hpp>
+
+namespace Os {
+    Console* Console::s_singleton;
+
+    Console::Console() : ConsoleInterface(), Fw::Logger(), m_handle_storage(), m_delegate(*ConsoleInterface::getDelegate(m_handle_storage)) {}
+
+    Console::~Console() {
+        FW_ASSERT(&this->m_delegate == reinterpret_cast<ConsoleInterface*>(&this->m_handle_storage[0]));
+        m_delegate.~ConsoleInterface();
+    }
+
+    Console::Console(const Console& other) :
+          m_handle_storage(),
+          m_delegate(*Console::getDelegate(m_handle_storage, &other.m_delegate)) {
+        FW_ASSERT(&this->m_delegate == reinterpret_cast<Console*>(&this->m_handle_storage[0]));
+    }
+
+    Console& Console::operator=(const Console& other) {
+        FW_ASSERT(&this->m_delegate == reinterpret_cast<Console*>(&this->m_handle_storage[0]));
+        if (this != &other) {
+            this->m_delegate = *ConsoleInterface::getDelegate(m_handle_storage, &other.m_delegate);
+        }
+        return *this;
+    }
+
+    void Console::writeMessage(const CHAR *message, const FwSizeType size) {
+        FW_ASSERT(&this->m_delegate == reinterpret_cast<ConsoleInterface*>(&this->m_handle_storage));
+        FW_ASSERT(message != nullptr || size == 0);
+        this->m_delegate.writeMessage(message, size);
+    }
+
+    void Console::writeMessage(const Fw::StringBase& message) {
+        this->writeMessage(message.toChar(), message.length());
+    }
+
+    ConsoleHandle* Console::getHandle() {
+        FW_ASSERT(&this->m_delegate == reinterpret_cast<ConsoleInterface*>(&this->m_handle_storage));
+        return this->m_delegate.getHandle();
+    }
+
+    void Console::write(const CHAR *message, const FwSizeType size) {
+        Console::getSingleton().writeMessage(message, size);
+    }
+
+    void Console::write(const Fw::StringBase& message) {
+        Console::getSingleton().writeMessage(message.toChar(), message.length());
+    }
+
+    void Console::init() {
+        // Force trigger on the fly singleton setup
+        (void) Console::getSingleton();
+    }
+
+    Console& Console::getSingleton() {
+        // On the fly construction of singleton
+        if (s_singleton == nullptr) {
+            s_singleton = new Console();
+            Fw::Logger::registerLogger(s_singleton);
+        }
+        return *s_singleton;
+    }
+}
+
diff --git a/Os/Console.hpp b/Os/Console.hpp
new file mode 100644
index 0000000000..9ff47ab3f8
--- /dev/null
+++ b/Os/Console.hpp
@@ -0,0 +1,140 @@
+// ======================================================================
+// \title Os/Console.hpp
+// \brief common function definitions for Os::Console
+// ======================================================================
+#ifndef Os_Console_hpp_
+#define Os_Console_hpp_
+
+#include <FpConfig.hpp>
+#include <Fw/Logger/Logger.hpp>
+#include <Os/Console.hpp>
+#include <Os/Os.hpp>
+
+namespace Os {
+    //! \brief Base class for storing implementation specific handle information
+    struct ConsoleHandle {
+    };
+
+    // \brief Interface defining the properties of the console
+    class ConsoleInterface {
+      public:
+        //! \brief Default constructor
+        ConsoleInterface() = default;
+
+        //! \brief Default destructor
+        virtual ~ConsoleInterface() = default;
+
+        //! \brief write message to console
+        //!
+        //! Write a message to the console with a bounded size.
+        //!
+        //! \param message: raw message to write
+        //! \param size: size of the message to write to the console
+        virtual void writeMessage(const CHAR *message, const FwSizeType size) = 0;
+
+        //! \brief returns the raw console handle
+        //!
+        //! Gets the raw console handle from the implementation. Note: users must include the implementation specific
+        //! header to make any real use of this handle. Otherwise it will be as an opaque type.
+        //!
+        //! \return raw console handle
+        //!
+        virtual ConsoleHandle *getHandle() = 0;
+
+        //! \brief provide a pointer to a console delegate object
+        //!
+        //! This function must return a pointer to a `ConsoleInterface` object that contains the real implementation of
+        //! the console functions as defined by the implementor.  This function must do several things to be considered
+        //! correctly implemented:
+        //!
+        //! 1. Assert that their implementation fits within FW_HANDLE_MAX_SIZE.
+        //!    e.g. `static_assert(sizeof(PosixFileImplementation) <= sizeof Os::File::m_handle_storage,
+        //!        "FW_HANDLE_MAX_SIZE too small");`
+        //! 2. Assert that their implementation aligns within FW_HANDLE_ALIGNMENT.
+        //!    e.g. `static_assert((FW_HANDLE_ALIGNMENT % alignof(PosixFileImplementation)) == 0, "Bad handle alignment");`
+        //! 3. If to_copy is null, placement new their implementation into `aligned_placement_new_memory`
+        //!    e.g. `FileInterface* interface = new (aligned_placement_new_memory) PosixFileImplementation;`
+        //! 4. If to_copy is non-null, placement new using copy constructor their implementation into
+        //!    `aligned_placement_new_memory`
+        //!    e.g. `FileInterface* interface = new (aligned_placement_new_memory) PosixFileImplementation(*to_copy);`
+        //! 5. Return the result of the placement new
+        //!    e.g. `return interface;`
+        //!
+        //! \return result of placement new, must be equivalent to `aligned_placement_new_memory`
+        //!
+        static ConsoleInterface* getDelegate(HandleStorage& aligned_placement_new_memory, const ConsoleInterface* to_copy=nullptr);
+    };
+
+    class Console : public ConsoleInterface, public Fw::Logger {
+      public:
+        //! \brief Default constructor
+        Console();
+
+        //! \brief Default destructor
+        ~Console();
+
+        //! \brief copy constructor that copies the internal representation
+        Console(const Console& other);
+
+        //! \brief assignment operator that copies the internal representation
+        Console& operator=(const Console& other);
+
+        //! \brief write message to console
+        //!
+        //! Write a message to the console with a bounded size. This will delegate to the implementation defined write
+        //! method.
+        //!
+        //! \param message: raw message to write
+        //! \param size: size of the message to write to the console
+        void writeMessage(const CHAR *message, const FwSizeType size) override;
+
+        //! \brief write message to console
+        //!
+        //! Write a message to the console as stored as a StringBase
+        //!
+        //! \param message: raw message to write (StringBase)
+        void writeMessage(const Fw::StringBase& message) override;
+
+        //! \brief returns the raw console handle
+        //!
+        //! Gets the raw console handle from the implementation. Note: users must include the implementation specific
+        //! header to make any real use of this handle. Otherwise it will be as an opaque type.
+        //!
+        //! \return raw console handle
+        //!
+        ConsoleHandle *getHandle() override;
+
+        //! \brief write message to console
+        //!
+        //! Write a message to the console as stored as a StringBase
+        //!
+        //! \param message: raw message to write (StringBase)
+        static void write(const Fw::StringBase& message);
+
+        //! \brief write message to the global console
+        //!
+        //! Write a message to the console with a bounded size. This will delegate to the global singleton
+        //! implementation.
+        //!
+        //! \param message: raw message to write
+        //! \param size: size of the message to write to the console
+        static void write(const CHAR *message, const FwSizeType size);
+
+        //! \brief initialize singleton
+        static void init();
+
+        //! \brief get a reference to singleton
+        //! \return reference to singleton
+        static Console& getSingleton();
+
+      private:
+        static Console* s_singleton;
+        // This section is used to store the implementation-defined console handle. To Os::Console and fprime, this type
+        // is opaque and thus normal allocation cannot be done. Instead, we allow the implementor to store then handle
+        // in the byte-array here and set `handle` to that address for storage.
+        alignas(FW_HANDLE_ALIGNMENT) HandleStorage m_handle_storage; // Storage for the delegate
+        ConsoleInterface &m_delegate; //!< Delegate for the real implementation
+    };
+}
+
+#endif
diff --git a/Os/File.hpp b/Os/File.hpp
index 0e7c926851..64103b47ee 100644
--- a/Os/File.hpp
+++ b/Os/File.hpp
@@ -179,7 +179,7 @@ namespace Os {
             //! \brief returns the raw file handle
             //!
             //! Gets the raw file handle from the implementation. Note: users must include the implementation specific
-            //! header to make any real use of this handle. Otherwise it//!must* be passed as an opaque type.
+            //! header to make any real use of this handle. Otherwise it will be as an opaque type.
             //!
             //! \return raw file handle
             //!
diff --git a/Os/Log.hpp b/Os/Log.hpp
deleted file mode 100644
index 0690b19cd4..0000000000
--- a/Os/Log.hpp
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * File: Os/Log.hpp
- * Description: this file provides an implementation of the Fw::Logger class that is backed by the
- * Os abstraction layer.
- */
-#ifndef _Log_hpp_
-#define _Log_hpp_
-
-#include <FpConfig.hpp>
-#include <Fw/Logger/Logger.hpp>
-
-namespace Os {
-    class Log : public Fw::Logger {
-        public:
-            /**
-             * Constructor for the Os::Log object.
-             */
-            Log();
-
-            /**
-             * Function called on the logger to log a message. This is abstract virtual method and
-             * must be supplied by the subclass. This logger object should be registered with the
-             * Fw::Log::registerLogger function.
-             * \param fmt: format string in which to place arguments
-             * \param a0: zeroth argument. (Default: 0)
-             * \param a1: first argument. (Default: 0)
-             * \param a2: second argument. (Default: 0)
-             * \param a3: third argument. (Default: 0)
-             * \param a4: fourth argument. (Default: 0)
-             * \param a5: fifth argument. (Default: 0)
-             * \param a6: sixth argument. (Default: 0)
-             * \param a7: seventh argument. (Default: 0)
-             * \param a8: eighth argument. (Default: 0)
-             * \param a9: ninth argument. (Default: 0)
-             */
-            void log(
-                const char* fmt,
-                POINTER_CAST a0 = 0,
-                POINTER_CAST a1 = 0,
-                POINTER_CAST a2 = 0,
-                POINTER_CAST a3 = 0,
-                POINTER_CAST a4 = 0,
-                POINTER_CAST a5 = 0,
-                POINTER_CAST a6 = 0,
-                POINTER_CAST a7 = 0,
-                POINTER_CAST a8 = 0,
-                POINTER_CAST a9 = 0
-            );
-    };
-}
-
-#endif
diff --git a/Os/LogDefault.cpp b/Os/LogDefault.cpp
index e3dff44f0e..732c41a100 100644
--- a/Os/LogDefault.cpp
+++ b/Os/LogDefault.cpp
@@ -1,10 +1,10 @@
 /**
- * LogDefault.cpp:
+ * ConsoleDefault.cpp:
  *
- * This file ensures that the Os::Log has a default instance. This means it will be created in static space here, and
- * registered as the default implementation. If the user does not intend to use the default implementation of Os::Log,
- * then this file can safely be ignored.
+ * This file ensures that the Os::Console has a default instance. This means it will be created in static space, and
+ * registered as the default implementation. If the user does not intend to use the default implementation of
+ * Os::Console, then this file Should be excluded from the build.
  */
-#include <Os/Log.hpp>
-Os::Log __default_logger__; // Create a default instance which will register itself in the constructor
+#include <Os/Console.hpp>
+Os::Console __default_console__; // Create a default instance which will register itself in the constructor
 
diff --git a/Os/LogPrintf.cpp b/Os/LogPrintf.cpp
deleted file mode 100644
index c972599dc4..0000000000
--- a/Os/LogPrintf.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * File: Os/LogPrintf.cpp
- * Description: an implementation on the Os::Log abstraction that routes log messages into standard
- * printf calls.
- */
-#include <Os/Log.hpp>
-
-#include <cstdio>
-
-namespace Os {
-    Log::Log() {
-
-        // Register myself as a logger at construction time. If used in unison with LogDefault.cpp, this will
-        // automatically create this as a default logger.
-        this->registerLogger(this);
-    }
-
-    // Instance implementation
-    void Log::log(
-        const char* fmt,
-        POINTER_CAST a0,
-        POINTER_CAST a1,
-        POINTER_CAST a2,
-        POINTER_CAST a3,
-        POINTER_CAST a4,
-        POINTER_CAST a5,
-        POINTER_CAST a6,
-        POINTER_CAST a7,
-        POINTER_CAST a8,
-        POINTER_CAST a9
-    ) {
-        (void) printf(fmt, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9);
-        (void) fflush(stdout);
-    }
-}
-
diff --git a/Os/Posix/CMakeLists.txt b/Os/Posix/CMakeLists.txt
index c9f3b32317..9502490067 100644
--- a/Os/Posix/CMakeLists.txt
+++ b/Os/Posix/CMakeLists.txt
@@ -28,6 +28,27 @@ set(MOD_DEPS Os_Posix_Shared)
 register_fprime_module(Os_File_Posix)
 register_fprime_implementation(Os/File Os_File_Posix "${CMAKE_CURRENT_LIST_DIR}/DefaultFile.cpp")
 
+#### Os/Console/Posix Section ####
+set(SOURCE_FILES
+    "${CMAKE_CURRENT_LIST_DIR}/Console.cpp"
+)
+set(HEADER_FILES
+    "${CMAKE_CURRENT_LIST_DIR}/Console.hpp"
+)
+set(MOD_DEPS)
+register_fprime_module(Os_Console_Posix)
+register_fprime_implementation(Os/Console Os_Console_Posix "${CMAKE_CURRENT_LIST_DIR}/DefaultConsole.cpp")
+
+#### Os/Task/Posix Unit Tests ####
+set(UT_SOURCE_FILES
+        "${CMAKE_CURRENT_LIST_DIR}/test/ut/PosixConsoleTests.cpp"
+)
+set(UT_MOD_DEPS
+        Os
+)
+choose_fprime_implementation(Os/Console Os_Console_Posix)
+register_fprime_ut(PosixConsoleTest)
+
 #### Os/File/Posix Unit Tests ####
 set(UT_SOURCE_FILES
         "${CMAKE_CURRENT_LIST_DIR}/../test/ut/file/CommonTests.cpp"
diff --git a/Os/Posix/Console.cpp b/Os/Posix/Console.cpp
new file mode 100644
index 0000000000..477495ab45
--- /dev/null
+++ b/Os/Posix/Console.cpp
@@ -0,0 +1,46 @@
+// ======================================================================
+// \title Os/Posix/Console.cpp
+// \brief posix implementation for Os::Console
+// ======================================================================
+#include <Os/Posix/Console.hpp>
+#include <Fw/Types/Assert.hpp>
+#include <limits>
+#include <cstdio>
+
+namespace Os {
+namespace Posix {
+namespace Console {
+
+
+void PosixConsole::writeMessage(const CHAR *message, const FwSizeType size) {
+    // size_t is defined as different sizes on different platforms. Since FwSizeType is likely larger than size_t
+    // on these platforms, and the user is unlikely to console-log more than size_t-max data, we cap the total
+    // size at the limit of the interface.
+    FwSizeType capped_size = (size <= std::numeric_limits<size_t>::max()) ? size : std::numeric_limits<size_t>::max();
+    if (message != nullptr) {
+        (void)::fwrite(message, sizeof(CHAR), static_cast<size_t>(capped_size), this->m_handle.m_file_descriptor);
+    }
+}
+
+ConsoleHandle* PosixConsole::getHandle() {
+    return &this->m_handle;
+}
+
+void PosixConsole ::setOutputStream(Stream stream) {
+    switch (stream) {
+        case STANDARD_OUT:
+            this->m_handle.m_file_descriptor = stdout;
+            break;
+        case STANDARD_ERROR:
+            this->m_handle.m_file_descriptor = stderr;
+            break;
+        default:
+            FW_ASSERT(0);
+            break;
+    }
+}
+
+
+} // namespace Console
+} // namespace Posix
+} // namespace Os
diff --git a/Os/Posix/Console.hpp b/Os/Posix/Console.hpp
new file mode 100644
index 0000000000..7004d1e02c
--- /dev/null
+++ b/Os/Posix/Console.hpp
@@ -0,0 +1,85 @@
+// ======================================================================
+// \title Os/Posix/Console.hpp
+// \brief posix implementation for Os::Console, header and test definitions
+// ======================================================================
+#include <cstdio>
+#include <Os/Console.hpp>
+#ifndef OS_POSIX_Console_HPP
+#define OS_POSIX_Console_HPP
+
+namespace Os {
+namespace Posix {
+namespace Console {
+
+//! ConsoleHandle class definition for posix implementations.
+//!
+struct PosixConsoleHandle : public ConsoleHandle {
+    //! Posix console file descriptor
+    FILE* m_file_descriptor = stdout;
+};
+
+//! \brief posix implementation of Os::ConsoleInterface
+//!
+//! Posix implementation of `ConsoleInterface` for use as a delegate class handling posix console operations. Posix
+//! consoles write to either standard out or standard error. The default file descriptor used is standard out. This may
+//! be changed by calling `setOutputStream`.
+//!
+class PosixConsole : public ConsoleInterface {
+  public:
+    //! Stream selection enumeration
+    enum Stream {
+        STANDARD_OUT = 0, //!< Use standard output stream
+        STANDARD_ERROR = 1 //!< Use standard error stream
+    };
+    //! \brief constructor
+    //!
+    PosixConsole() = default;
+
+    //! \brief copy constructor
+    PosixConsole(const PosixConsole& other) = default;
+
+    //! \brief assignment operator that copies the internal representation
+    PosixConsole& operator=(const PosixConsole& other) = default;
+
+    //! \brief destructor
+    //!
+    ~PosixConsole() override = default;
+
+    // ------------------------------------
+    // Functions overrides
+    // ------------------------------------
+
+    //! \brief write message to console
+    //!
+    //! Write a message to the console with a bounded size. This will use the active file descriptor as the output
+    //! destination.
+    //!
+    //! \param message: raw message to write
+    //! \param size: size of the message to write to the console
+    void writeMessage(const CHAR *message, const FwSizeType size) override;
+
+
+    //! \brief returns the raw console handle
+    //!
+    //! Gets the raw console handle from the implementation. Note: users must include the implementation specific
+    //! header to make any real use of this handle. Otherwise it will be as an opaque type.
+    //!
+    //! \return raw console handle
+    //!
+    ConsoleHandle *getHandle() override;
+
+    //! \brief select the output stream
+    //!
+    //! There are two streams defined: standard out, and standard error. This allows users of the posix log
+    //! implementation to chose which stream to use.
+    void setOutputStream(Stream stream);
+
+  private:
+    //! File handle for PosixFile
+    PosixConsoleHandle m_handle;
+};
+} // namespace Console
+} // namespace Posix
+} // namespace Os
+
+#endif // OS_POSIX_Console_HPP
diff --git a/Os/Posix/DefaultConsole.cpp b/Os/Posix/DefaultConsole.cpp
new file mode 100644
index 0000000000..8a3b8f75cc
--- /dev/null
+++ b/Os/Posix/DefaultConsole.cpp
@@ -0,0 +1,13 @@
+// ======================================================================
+// \title Os/Posix/DefaultConsole.cpp
+// \brief sets default Os::Console to posix implementation via linker
+// ======================================================================
+#include "Os/Console.hpp"
+#include "Os/Posix/Console.hpp"
+#include "Os/Delegate.hpp"
+
+namespace Os {
+ConsoleInterface* ConsoleInterface::getDelegate(HandleStorage& aligned_new_memory, const ConsoleInterface* to_copy) {
+    return Os::Delegate::makeDelegate<ConsoleInterface, Os::Posix::Console::PosixConsole>(aligned_new_memory, to_copy);
+}
+}
diff --git a/Os/Posix/Task.cpp b/Os/Posix/Task.cpp
index 2e29f7b29b..b7e007f33b 100644
--- a/Os/Posix/Task.cpp
+++ b/Os/Posix/Task.cpp
@@ -35,32 +35,32 @@ namespace Task {
         // Check for stack size multiple of page size
         long page_size = sysconf(_SC_PAGESIZE);
         if (page_size <= 0) {
-            Fw::Logger::logMsg(
+            Fw::Logger::log(
                     "[WARNING] %s could not determine page size %s. Skipping stack-size check.\n",
-                    reinterpret_cast<PlatformPointerCastType>(const_cast<CHAR*>(arguments.m_name.toChar())),
-                    reinterpret_cast<PlatformPointerCastType>(strerror(errno))
+                    const_cast<CHAR*>(arguments.m_name.toChar()),
+                    strerror(errno)
             );
         }
         else if ((stack % static_cast<FwSizeType>(page_size)) != 0) {
             // Round-down to nearest page size multiple
             FwSizeType rounded = (stack / static_cast<FwSizeType>(page_size)) * static_cast<FwSizeType>(page_size);
-            Fw::Logger::logMsg(
+            Fw::Logger::log(
                     "[WARNING] %s stack size of %" PRI_FwSizeType " is not multiple of page size %ld, rounding to %" PRI_FwSizeType "\n",
-                    reinterpret_cast<PlatformPointerCastType>(const_cast<CHAR*>(arguments.m_name.toChar())),
-                    static_cast<PlatformPointerCastType>(stack),
-                    static_cast<PlatformPointerCastType>(page_size),
-                    static_cast<PlatformPointerCastType>(rounded)
+                    const_cast<CHAR*>(arguments.m_name.toChar()),
+                    stack,
+                    page_size,
+                    rounded
             );
             stack = rounded;
         }
 
         // Clamp invalid stack sizes
         if (stack <= static_cast<FwSizeType>(PTHREAD_STACK_MIN)) {
-            Fw::Logger::logMsg(
+            Fw::Logger::log(
                     "[WARNING] %s stack size of %" PRI_FwSizeType "  is too small, clamping to %" PRI_FwSizeType "\n",
-                    reinterpret_cast<PlatformPointerCastType>(const_cast<CHAR*>(arguments.m_name.toChar())),
-                    static_cast<PlatformPointerCastType>(stack),
-                    static_cast<PlatformPointerCastType>(static_cast<FwSizeType>(PTHREAD_STACK_MIN))
+                    const_cast<CHAR*>(arguments.m_name.toChar()),
+                    stack,
+                    static_cast<FwSizeType>(PTHREAD_STACK_MIN)
             );
             stack = static_cast<FwSizeType>(PTHREAD_STACK_MIN);
         }
@@ -75,18 +75,18 @@ namespace Task {
         FwSizeType priority = arguments.m_priority;
         // Clamp to minimum priority
         if (priority < min_priority) {
-            Fw::Logger::logMsg("[WARNING] %s low task priority of %" PRI_FwSizeType " clamped to %" PRI_FwSizeType "\n",
-                               reinterpret_cast<PlatformPointerCastType>(const_cast<CHAR*>(arguments.m_name.toChar())),
-                               static_cast<PlatformPointerCastType>(priority),
-                               static_cast<PlatformPointerCastType>(min_priority));
+            Fw::Logger::log("[WARNING] %s low task priority of %" PRI_FwSizeType " clamped to %" PRI_FwSizeType "\n",
+                               const_cast<CHAR*>(arguments.m_name.toChar()),
+                               priority,
+                               min_priority);
             priority = min_priority;
         }
         // Clamp to maximum priority
         else if (priority > max_priority) {
-            Fw::Logger::logMsg("[WARNING] %s high task priority of %" PRI_FwSizeType " clamped to %" PRI_FwSizeType "\n",
-                               reinterpret_cast<PlatformPointerCastType>(const_cast<CHAR*>(arguments.m_name.toChar())),
-                               static_cast<PlatformPointerCastType>(priority),
-                               static_cast<PlatformPointerCastType>(max_priority));
+            Fw::Logger::log("[WARNING] %s high task priority of %" PRI_FwSizeType " clamped to %" PRI_FwSizeType "\n",
+                               const_cast<CHAR*>(arguments.m_name.toChar()),
+                               priority,
+                               max_priority);
             priority = max_priority;
         }
 
@@ -117,8 +117,8 @@ namespace Task {
         status = pthread_attr_setaffinity_np(&attributes, sizeof(cpu_set_t), &cpu_set);
         status = (status == PosixTaskHandle::SUCCESS) ? status : errno;
 #else
-            Fw::Logger::logMsg("[WARNING] %s setting CPU affinity is only available with GNU pthreads\n",
-                               reinterpret_cast<PlatformPointerCastType>(const_cast<CHAR*>(arguments.m_name.toChar())));
+            Fw::Logger::log("[WARNING] %s setting CPU affinity is only available with GNU pthreads\n",
+                            const_cast<CHAR*>(arguments.m_name.toChar()));
 #endif
         return status;
     }
@@ -163,24 +163,24 @@ namespace Task {
         // Failure due to permission automatically retried
         if (status == Os::Task::Status::ERROR_PERMISSION) {
             if (not PosixTask::s_permissions_reported) {
-                Fw::Logger::logMsg("\n");
-                Fw::Logger::logMsg("[NOTE] Task Permissions:\n");
-                Fw::Logger::logMsg("[NOTE]\n");
-                Fw::Logger::logMsg("[NOTE] You have insufficient permissions to create a task with priority and/or cpu affinity.\n");
-                Fw::Logger::logMsg("[NOTE] A task without priority and affinity will be created.\n");
-                Fw::Logger::logMsg("[NOTE]\n");
-                Fw::Logger::logMsg("[NOTE] There are three possible resolutions:\n");
-                Fw::Logger::logMsg("[NOTE] 1. Use tasks without priority and affinity using parameterless start()\n");
-                Fw::Logger::logMsg("[NOTE] 2. Run this executable as a user with task priority permission\n");
-                Fw::Logger::logMsg("[NOTE] 3. Grant capability with \"setcap 'cap_sys_nice=eip'\" or equivalent\n");
-                Fw::Logger::logMsg("\n");
+                Fw::Logger::log("\n");
+                Fw::Logger::log("[NOTE] Task Permissions:\n");
+                Fw::Logger::log("[NOTE]\n");
+                Fw::Logger::log("[NOTE] You have insufficient permissions to create a task with priority and/or cpu affinity.\n");
+                Fw::Logger::log("[NOTE] A task without priority and affinity will be created.\n");
+                Fw::Logger::log("[NOTE]\n");
+                Fw::Logger::log("[NOTE] There are three possible resolutions:\n");
+                Fw::Logger::log("[NOTE] 1. Use tasks without priority and affinity using parameterless start()\n");
+                Fw::Logger::log("[NOTE] 2. Run this executable as a user with task priority permission\n");
+                Fw::Logger::log("[NOTE] 3. Grant capability with \"setcap 'cap_sys_nice=eip'\" or equivalent\n");
+                Fw::Logger::log("\n");
                 PosixTask::s_permissions_reported = true;
             }
             // Fallback with no permission
             status = this->create(arguments, PermissionExpectation::EXPECT_NO_PERMISSION);
         } else if (status != Os::Task::Status::OP_OK) {
-            Fw::Logger::logMsg("[ERROR] Failed to create task with status: %d",
-                               static_cast<PlatformPointerCastType>(static_cast<PlatformIntType>(status)));
+            Fw::Logger::log("[ERROR] Failed to create task with status: %d",
+                            static_cast<PlatformIntType>(status));
         }
         return status;
     }
diff --git a/Os/Pthreads/MaxHeap/MaxHeap.cpp b/Os/Pthreads/MaxHeap/MaxHeap.cpp
index 347566f95a..42e1c0c502 100644
--- a/Os/Pthreads/MaxHeap/MaxHeap.cpp
+++ b/Os/Pthreads/MaxHeap/MaxHeap.cpp
@@ -125,7 +125,7 @@ namespace Os {
       // This will put the smallest value in the
       // heap on the top, violating the heap property.
       NATIVE_UINT_TYPE index = this->m_size-1;
-      // Fw::Logger::logMsg("Putting on top: i: %u v: %d\n", index, this->m_heap[index].value);
+      // Fw::Logger::log("Putting on top: i: %u v: %d\n", index, this->m_heap[index].value);
       this->m_heap[0]= this->m_heap[index];
       --this->m_size;
 
@@ -202,7 +202,7 @@ namespace Os {
         }
 
         // Swap the largest node with the current node:
-        // Fw::Logger::logMsg("Swapping: i: %u v: %d with i: %u v: %d\n",
+        // Fw::Logger::log("Swapping: i: %u v: %d with i: %u v: %d\n",
         //   index, this->m_heap[index].value,
         //   largest, this->m_heap[largest].value);
         this->swap(index, largest);
@@ -263,25 +263,25 @@ namespace Os {
       NATIVE_UINT_TYPE index = 0;
       NATIVE_UINT_TYPE left;
       NATIVE_UINT_TYPE right;
-      Fw::Logger::logMsg("Printing Heap of Size: %d\n", this->m_size);
+      Fw::Logger::log("Printing Heap of Size: %d\n", this->m_size);
       while(index < this->m_size) {
         left = LCHILD(index);
         right = RCHILD(index);
 
         if( left >= m_size && index == 0) {
-          Fw::Logger::logMsg("i: %u v: %d d: %u -> (NULL, NULL)\n",
-            index, static_cast<POINTER_CAST>(this->m_heap[index].value), this->m_heap[index].id);
+          Fw::Logger::log("i: %u v: %d d: %u -> (NULL, NULL)\n",
+            index, this->m_heap[index].value, this->m_heap[index].id);
         }
         else if( right >= m_size && left < m_size ) {
-          Fw::Logger::logMsg("i: %u v: %d d: %u -> (i: %u v: %d d: %u, NULL)\n",
-            index, static_cast<POINTER_CAST>(this->m_heap[index].value), this->m_heap[index].id,
-            left, static_cast<POINTER_CAST>(this->m_heap[left].value), this->m_heap[left].id);
+          Fw::Logger::log("i: %u v: %d d: %u -> (i: %u v: %d d: %u, NULL)\n",
+            index, this->m_heap[index].value, this->m_heap[index].id,
+            left, this->m_heap[left].value, this->m_heap[left].id);
         }
         else if( right < m_size && left < m_size ) {
-          Fw::Logger::logMsg("i: %u v: %d d: %u -> (i: %u v: %d d: %u, i: %u v: %d d: %u)\n",
-            index, static_cast<POINTER_CAST>(this->m_heap[index].value), this->m_heap[index].id,
-            left, static_cast<POINTER_CAST>(this->m_heap[left].value),this->m_heap[left].id,
-            right, static_cast<POINTER_CAST>(this->m_heap[right].value), this->m_heap[right].id);
+          Fw::Logger::log("i: %u v: %d d: %u -> (i: %u v: %d d: %u, i: %u v: %d d: %u)\n",
+            index, this->m_heap[index].value, this->m_heap[index].id,
+            left, this->m_heap[left].value,this->m_heap[left].id,
+            right, this->m_heap[right].value, this->m_heap[right].id);
         }
 
         ++index;
diff --git a/Os/Stub/CMakeLists.txt b/Os/Stub/CMakeLists.txt
index 0d793eeb29..75b9271f2b 100644
--- a/Os/Stub/CMakeLists.txt
+++ b/Os/Stub/CMakeLists.txt
@@ -50,6 +50,32 @@ set(UT_MOD_DEPS
 choose_fprime_implementation(Os/File Os_File_Test_Stub)
 register_fprime_ut(StubFileTest)
 
+#### Console Stub Testing ####
+set(SOURCE_FILES
+        "${CMAKE_CURRENT_LIST_DIR}/Console.cpp"
+)
+set(HEADER_FILES
+        "${CMAKE_CURRENT_LIST_DIR}/Console.hpp"
+)
+set(MOD_DEPS)
+register_fprime_module(Os_Console_Stub)
+register_fprime_implementation(Os/Console Os_Console_Stub "${CMAKE_CURRENT_LIST_DIR}/DefaultConsole.cpp")
+
+set(SOURCE_FILES
+        "${CMAKE_CURRENT_LIST_DIR}/test/Console.cpp"
+)
+set(MOD_DEPS)
+register_fprime_module(Os_Console_Test_Stub)
+register_fprime_implementation(Os/Console Os_Console_Test_Stub "${CMAKE_CURRENT_LIST_DIR}/test/DefaultConsole.cpp")
+
+set(UT_SOURCE_FILES
+        "${CMAKE_CURRENT_LIST_DIR}/test/ut/StubConsoleTests.cpp"
+)
+set(UT_MOD_DEPS)
+choose_fprime_implementation(Os/Console Os_Console_Test_Stub)
+register_fprime_ut(StubConsoleTest)
+
+
 #### Task Stub Testing ####
 set(SOURCE_FILES
         "${CMAKE_CURRENT_LIST_DIR}/test/Task.cpp"
diff --git a/Os/Stub/Console.cpp b/Os/Stub/Console.cpp
new file mode 100644
index 0000000000..d8cc0d35fb
--- /dev/null
+++ b/Os/Stub/Console.cpp
@@ -0,0 +1,23 @@
+// ======================================================================
+// \title Os/Stub/Console.cpp
+// \brief stub implementation for Os::Console
+// ======================================================================
+#include <Os/Stub/Console.hpp>
+
+namespace Os {
+namespace Stub {
+namespace Console {
+
+
+void StubConsole::writeMessage(const CHAR *message, const FwSizeType size) {
+}
+
+ConsoleHandle* StubConsole::getHandle() {
+    return &this->m_handle;
+}
+
+
+
+} // namespace Console
+} // namespace Stub
+} // namespace Os
diff --git a/Os/Stub/Console.hpp b/Os/Stub/Console.hpp
new file mode 100644
index 0000000000..2d70b77b4e
--- /dev/null
+++ b/Os/Stub/Console.hpp
@@ -0,0 +1,68 @@
+// ======================================================================
+// \title Os/Stub/Console.hpp
+// \brief stub implementation for Os::Console, header and test definitions
+// ======================================================================
+#include <cstdio>
+#include <Os/Console.hpp>
+#ifndef OS_Stub_Console_HPP
+#define OS_Stub_Console_HPP
+
+namespace Os {
+namespace Stub {
+namespace Console {
+
+//! ConsoleHandle class definition for stub implementations.
+//!
+struct StubConsoleHandle : public ConsoleHandle {
+};
+
+//! \brief stub implementation of Os::ConsoleInterface
+//!
+//! Stub implementation of `ConsoleInterface` for use as a delegate class handling stub console operations.
+//!
+class StubConsole : public ConsoleInterface {
+  public:
+    //! \brief constructor
+    //!
+    StubConsole() = default;
+
+    //! \brief copy constructor
+    StubConsole(const StubConsole& other) = default;
+
+    //! \brief default copy assignment
+    StubConsole& operator=(const StubConsole& other) = default;
+
+    //! \brief destructor
+    //!
+    ~StubConsole() override = default;
+
+    // ------------------------------------
+    // Functions overrides
+    // ------------------------------------
+
+    //! \brief write message to console
+    //!
+    //! Write a message to the console with a bounded size. This will use the active file descriptor as the output
+    //! destination.
+    //!
+    //! \param message: raw message to write
+    //! \param size: size of the message to write to the console
+    void writeMessage(const CHAR *message, const FwSizeType size) override;
+
+    //! \brief returns the raw console handle
+    //!
+    //! Gets the raw console handle from the implementation. Note: users must include the implementation specific
+    //! header to make any real use of this handle. Otherwise it will be as an opaque type.
+    //!
+    //! \return raw console handle
+    //!
+    ConsoleHandle *getHandle() override;
+  private:
+    //! File handle for PosixFile
+    StubConsoleHandle m_handle;
+};
+} // namespace Console
+} // namespace Stub
+} // namespace Os
+
+#endif // OS_Stub_Console_HPP
diff --git a/Os/Stub/DefaultConsole.cpp b/Os/Stub/DefaultConsole.cpp
new file mode 100644
index 0000000000..91b11a1f66
--- /dev/null
+++ b/Os/Stub/DefaultConsole.cpp
@@ -0,0 +1,13 @@
+// ======================================================================
+// \title Os/Stub/DefaultConsole.cpp
+// \brief sets default Os::Console to stub implementation via linker
+// ======================================================================
+#include "Os/Console.hpp"
+#include "Os/Stub/Console.hpp"
+#include "Os/Delegate.hpp"
+
+namespace Os {
+ConsoleInterface* ConsoleInterface::getDelegate(HandleStorage& aligned_new_memory, const ConsoleInterface* to_copy) {
+    return Os::Delegate::makeDelegate<ConsoleInterface, Os::Stub::Console::StubConsole>(aligned_new_memory, to_copy);
+}
+}
diff --git a/RPI/Main.cpp b/RPI/Main.cpp
index ab032f7d08..f4b6955e81 100644
--- a/RPI/Main.cpp
+++ b/RPI/Main.cpp
@@ -7,8 +7,6 @@
 #include <RPI/Top/RPITopologyAc.hpp>
 
 RPI::TopologyState state;
-// Enable the console logging provided by Os::Log
-Os::Log logger;
 
 void print_usage(const char* app) {
     (void) printf("Usage: ./%s [options]\n-p\tport_number\n-a\thostname/IP address\n",app);
@@ -22,6 +20,7 @@ static void sighandler(int signum) {
 }
 
 int main(int argc, char* argv[]) {
+    Os::Console::init();
     I32 option = 0;
 
     while ((option = getopt(argc, argv, "hp:a:")) != -1){
diff --git a/RPI/Top/RPITopologyDefs.hpp b/RPI/Top/RPITopologyDefs.hpp
index cdbf9e5f5d..f434f2e9b0 100644
--- a/RPI/Top/RPITopologyDefs.hpp
+++ b/RPI/Top/RPITopologyDefs.hpp
@@ -2,7 +2,7 @@
 #define RPITopologyDefs_HPP
 
 #include "Fw/Types/MallocAllocator.hpp"
-#include "Os/Log.hpp"
+#include "Os/Console.hpp"
 #include "RPI/Top/FppConstantsAc.hpp"
 #include "Svc/FramingProtocol/FprimeProtocol.hpp"
 #include "Svc/LinuxTimer/LinuxTimer.hpp"
diff --git a/RPI/Top/instances.fpp b/RPI/Top/instances.fpp
index b162f33687..992aced0cc 100644
--- a/RPI/Top/instances.fpp
+++ b/RPI/Top/instances.fpp
@@ -315,7 +315,7 @@ module RPI {
           1024
       );
       if (!status) {
-        Fw::Logger::logMsg("[ERROR] Could not open UART driver\\n");
+        Fw::Logger::log("[ERROR] Could not open UART driver\\n");
         Init::status = false;
       }
     }
@@ -326,7 +326,7 @@ module RPI {
       uartDrv.start();
     }
     else {
-      Fw::Logger::logMsg("[ERROR] Initialization failed; not starting UART driver\\n");
+      Fw::Logger::log("[ERROR] Initialization failed; not starting UART driver\\n");
     }
     """
 
@@ -343,7 +343,7 @@ module RPI {
     {
       const bool status = ledDrv.open(21, Drv::LinuxGpioDriverComponentImpl::GPIO_OUT);
       if (!status) {
-        Fw::Logger::logMsg("[ERROR] Could not open LED driver\\n");
+        Fw::Logger::log("[ERROR] Could not open LED driver\\n");
         Init::status = false;
       }
     }
@@ -358,7 +358,7 @@ module RPI {
     {
       const bool status = gpio23Drv.open(23, Drv::LinuxGpioDriverComponentImpl::GPIO_OUT);
       if (!status) {
-        Fw::Logger::logMsg("[ERROR] Could not open GPIO 23 driver\\n");
+        Fw::Logger::log("[ERROR] Could not open GPIO 23 driver\\n");
         Init::status = false;
       }
     }
@@ -373,7 +373,7 @@ module RPI {
     {
       const bool status = gpio24Drv.open(24, Drv::LinuxGpioDriverComponentImpl::GPIO_OUT);
       if (!status) {
-        Fw::Logger::logMsg("[ERROR] Could not open GPIO 24 driver\\n");
+        Fw::Logger::log("[ERROR] Could not open GPIO 24 driver\\n");
         Init::status = false;
       }
     }
@@ -388,7 +388,7 @@ module RPI {
     {
       const bool status = gpio25Drv.open(25, Drv::LinuxGpioDriverComponentImpl::GPIO_IN);
       if (!status) {
-        Fw::Logger::logMsg("[ERROR] Could not open GPIO 25 driver\\n");
+        Fw::Logger::log("[ERROR] Could not open GPIO 25 driver\\n");
         Init::status = false;
       }
     }
@@ -403,7 +403,7 @@ module RPI {
     {
       const bool status = gpio17Drv.open(17, Drv::LinuxGpioDriverComponentImpl::GPIO_IN);
       if (!status) {
-        Fw::Logger::logMsg("[ERROR] Could not open GPIO 17 driver\\n");
+        Fw::Logger::log("[ERROR] Could not open GPIO 17 driver\\n");
         Init::status = false;
       }
     }
@@ -418,7 +418,7 @@ module RPI {
     {
       const bool status = spiDrv.open(0, 0, Drv::SPI_FREQUENCY_1MHZ);
       if (!status) {
-        Fw::Logger::logMsg("[ERROR] Could not open SPI driver\\n");
+        Fw::Logger::log("[ERROR] Could not open SPI driver\\n");
         Init::status = false;
       }
     }
diff --git a/Ref/Main.cpp b/Ref/Main.cpp
index cff59cf6c9..0cd5895787 100644
--- a/Ref/Main.cpp
+++ b/Ref/Main.cpp
@@ -16,6 +16,9 @@
 #include <getopt.h>
 // Used for printf functions
 #include <cstdlib>
+// Used to get the Os::Console
+#include <Os/Console.hpp>
+
 
 /**
  * \brief print commandline help message
@@ -51,6 +54,7 @@ static void signalHandler(int signum) {
  * @return: 0 on success, something else on failure
  */
 int main(int argc, char* argv[]) {
+    Os::Console::init();
     U32 port_number = 0;
     I32 option = 0;
     char* hostname = nullptr;
diff --git a/Ref/RecvBuffApp/RecvBuffComponentImpl.cpp b/Ref/RecvBuffApp/RecvBuffComponentImpl.cpp
index 3ac4ca37cd..e67da07baa 100644
--- a/Ref/RecvBuffApp/RecvBuffComponentImpl.cpp
+++ b/Ref/RecvBuffApp/RecvBuffComponentImpl.cpp
@@ -1,6 +1,6 @@
 #include <Ref/RecvBuffApp/RecvBuffComponentImpl.hpp>
 #include <FpConfig.hpp>
-#include <Os/Log.hpp>
+#include <Os/Console.hpp>
 #include <Fw/Types/Assert.hpp>
 
 #include <cstdio>
diff --git a/Ref/SendBuffApp/SendBuffComponentImpl.cpp b/Ref/SendBuffApp/SendBuffComponentImpl.cpp
index a9335252bf..5b17c95c2d 100644
--- a/Ref/SendBuffApp/SendBuffComponentImpl.cpp
+++ b/Ref/SendBuffApp/SendBuffComponentImpl.cpp
@@ -1,7 +1,7 @@
 #include <Ref/SendBuffApp/SendBuffComponentImpl.hpp>
 #include <FpConfig.hpp>
 #include <Fw/Types/Assert.hpp>
-#include <Os/Log.hpp>
+#include <Os/Console.hpp>
 #include <cstring>
 
 #include <cstdio>
diff --git a/Ref/Top/RefTopology.cpp b/Ref/Top/RefTopology.cpp
index cc08a20570..9cfd30cffd 100644
--- a/Ref/Top/RefTopology.cpp
+++ b/Ref/Top/RefTopology.cpp
@@ -14,7 +14,7 @@
 
 // Necessary project-specified types
 #include <Fw/Types/MallocAllocator.hpp>
-#include <Os/Log.hpp>
+#include <Os/Console.hpp>
 #include <Svc/FramingProtocol/FprimeProtocol.hpp>
 
 // Used for 1Hz synthetic cycling
@@ -23,8 +23,8 @@
 // Allows easy reference to objects in FPP/autocoder required namespaces
 using namespace Ref;
 
-// Instantiate a system logger that will handle Fw::Logger::logMsg calls
-Os::Log logger;
+// Instantiate a system logger that will handle Fw::Logger::log calls
+Os::Console logger;
 
 // The reference topology uses a malloc-based allocator for components that need to allocate memory during the
 // initialization phase.
diff --git a/Svc/ActiveRateGroup/ActiveRateGroup.cpp b/Svc/ActiveRateGroup/ActiveRateGroup.cpp
index 2d94b7e030..bed4355d01 100644
--- a/Svc/ActiveRateGroup/ActiveRateGroup.cpp
+++ b/Svc/ActiveRateGroup/ActiveRateGroup.cpp
@@ -16,7 +16,7 @@
 #include <ActiveRateGroupCfg.hpp>
 #include <FpConfig.hpp>
 #include <Fw/Types/Assert.hpp>
-#include <Os/Log.hpp>
+#include <Os/Console.hpp>
 
 namespace Svc {
 
diff --git a/Svc/ActiveTextLogger/ActiveTextLogger.cpp b/Svc/ActiveTextLogger/ActiveTextLogger.cpp
index d1dab9dc00..b6af87dda1 100644
--- a/Svc/ActiveTextLogger/ActiveTextLogger.cpp
+++ b/Svc/ActiveTextLogger/ActiveTextLogger.cpp
@@ -121,7 +121,7 @@ namespace Svc {
     {
 
         // Print to console:
-        Fw::Logger::logMsg(text.toChar(),0,0,0,0,0,0,0,0,0);
+        Fw::Logger::log(text);
 
         // Print to file if there is one:
         (void) this->m_log_file.write_to_log(text.toChar(), text.length());  // Ignoring return status
diff --git a/Svc/ActiveTextLogger/LogFile.cpp b/Svc/ActiveTextLogger/LogFile.cpp
index d53bb30664..927762f46f 100644
--- a/Svc/ActiveTextLogger/LogFile.cpp
+++ b/Svc/ActiveTextLogger/LogFile.cpp
@@ -89,7 +89,7 @@ namespace Svc {
         }
 
         // If file name is too large, return failure:
-        U32 fileNameSize = Fw::StringUtils::string_length(fileName, Fw::String::STRING_SIZE);
+        FwSizeType fileNameSize = Fw::StringUtils::string_length(fileName, static_cast<FwSizeType>(Fw::String::STRING_SIZE));
         if (fileNameSize == Fw::String::STRING_SIZE) {
             return false;
         }
diff --git a/Svc/AssertFatalAdapter/AssertFatalAdapterComponentImpl.cpp b/Svc/AssertFatalAdapter/AssertFatalAdapterComponentImpl.cpp
index 3bf3faab67..818c0dceed 100644
--- a/Svc/AssertFatalAdapter/AssertFatalAdapterComponentImpl.cpp
+++ b/Svc/AssertFatalAdapter/AssertFatalAdapterComponentImpl.cpp
@@ -85,7 +85,7 @@ namespace Svc {
                   arg1,arg2,arg3,arg4,arg5,arg6);
       } else {
           // Can't assert, what else can we do? Maybe somebody will see it.
-          Fw::Logger::logMsg("Svc::AssertFatalAdapter not registered!\n");
+          Fw::Logger::log("Svc::AssertFatalAdapter not registered!\n");
           assert(0);
       }
   }
diff --git a/Svc/ComLogger/ComLogger.cpp b/Svc/ComLogger/ComLogger.cpp
index c6b8c27c81..7db091a964 100644
--- a/Svc/ComLogger/ComLogger.cpp
+++ b/Svc/ComLogger/ComLogger.cpp
@@ -67,11 +67,11 @@ namespace Svc {
       FW_ASSERT(maxFileSize > sizeof(U16), static_cast<FwAssertArgType>(maxFileSize));
     }
 
-    FW_ASSERT(Fw::StringUtils::string_length(incomingFilePrefix, sizeof(this->m_filePrefix)) < sizeof(this->m_filePrefix),
-      static_cast<FwAssertArgType>(Fw::StringUtils::string_length(incomingFilePrefix, sizeof(this->m_filePrefix))),
+    FW_ASSERT(Fw::StringUtils::string_length(incomingFilePrefix, static_cast<FwSizeType>(sizeof(this->m_filePrefix))) < sizeof(this->m_filePrefix),
+      static_cast<FwAssertArgType>(Fw::StringUtils::string_length(incomingFilePrefix, static_cast<FwSizeType>(sizeof(this->m_filePrefix)))),
       static_cast<FwAssertArgType>(sizeof(this->m_filePrefix))); // ensure that file prefix is not too big
 
-    (void)Fw::StringUtils::string_copy(this->m_filePrefix, incomingFilePrefix, sizeof(this->m_filePrefix));
+    (void)Fw::StringUtils::string_copy(this->m_filePrefix, incomingFilePrefix, static_cast<FwSizeType>(sizeof(this->m_filePrefix)));
 
     this->m_initialized = true;
   }
diff --git a/Svc/Deframer/Deframer.cpp b/Svc/Deframer/Deframer.cpp
index 3005aa6f99..8ed542dec5 100644
--- a/Svc/Deframer/Deframer.cpp
+++ b/Svc/Deframer/Deframer.cpp
@@ -140,7 +140,7 @@ void Deframer ::route(Fw::Buffer& packetBuffer) {
                     comOut_out(0, com, 0);
                 }
                 else {
-                    Fw::Logger::logMsg(
+                    Fw::Logger::log(
                         "[ERROR] Serializing com buffer failed with status %d\n",
                         status
                     );
@@ -170,7 +170,7 @@ void Deframer ::route(Fw::Buffer& packetBuffer) {
         }
     }
     else {
-        Fw::Logger::logMsg(
+        Fw::Logger::log(
             "[ERROR] Deserializing packet type failed with status %d\n",
             status
         );
@@ -301,7 +301,7 @@ void Deframer ::processRing() {
             // Log checksum errors
             // This is likely a real error, not an artifact of other data corruption
             if (status == DeframingProtocol::DEFRAMING_INVALID_CHECKSUM) {
-                Fw::Logger::logMsg("[ERROR] Deframing checksum validation failed\n");
+                Fw::Logger::log("[ERROR] Deframing checksum validation failed\n");
             }
         }
     }
diff --git a/Svc/FatalHandler/FatalHandlerComponentBaremetalImpl.cpp b/Svc/FatalHandler/FatalHandlerComponentBaremetalImpl.cpp
index e62f4535ec..b92ca49965 100644
--- a/Svc/FatalHandler/FatalHandlerComponentBaremetalImpl.cpp
+++ b/Svc/FatalHandler/FatalHandlerComponentBaremetalImpl.cpp
@@ -5,7 +5,7 @@
 // ======================================================================
 
 #include <cstdlib>
-#include <Os/Log.hpp>
+#include <Os/Console.hpp>
 #include <Svc/FatalHandler/FatalHandlerComponentImpl.hpp>
 #include <FpConfig.hpp>
 
@@ -19,7 +19,7 @@ namespace Svc {
             const NATIVE_INT_TYPE portNum,
             FwEventIdType Id) {
         // for **nix, delay then exit with error code
-        Os::Log::logMsg("FATAL %d handled.\n",Id,0,0,0,0,0);
+        Os::Log::log("FATAL %d handled.\n",Id);
         while (true) {} // Returning might be bad
     }
 
diff --git a/Svc/FatalHandler/FatalHandlerComponentLinuxImpl.cpp b/Svc/FatalHandler/FatalHandlerComponentLinuxImpl.cpp
index f95329de61..cb51088160 100644
--- a/Svc/FatalHandler/FatalHandlerComponentLinuxImpl.cpp
+++ b/Svc/FatalHandler/FatalHandlerComponentLinuxImpl.cpp
@@ -27,9 +27,9 @@ namespace Svc {
             const NATIVE_INT_TYPE portNum,
             FwEventIdType Id) {
         // for **nix, delay then exit with error code
-        Fw::Logger::logMsg("FATAL %d handled.\n",Id,0,0,0,0,0);
+        Fw::Logger::log("FATAL %d handled.\n",Id);
         (void)Os::Task::delay(Fw::Time(1, 0));
-        Fw::Logger::logMsg("Exiting with abort signal and core dump file.\n",0,0,0,0,0,0);
+        Fw::Logger::log("Exiting with abort signal and core dump file.\n");
         (void)raise( SIGABRT );
         exit(1);
     }
diff --git a/Svc/FatalHandler/FatalHandlerComponentVxWorksImpl.cpp b/Svc/FatalHandler/FatalHandlerComponentVxWorksImpl.cpp
index 25a8e689b1..0341f07ce2 100644
--- a/Svc/FatalHandler/FatalHandlerComponentVxWorksImpl.cpp
+++ b/Svc/FatalHandler/FatalHandlerComponentVxWorksImpl.cpp
@@ -20,7 +20,7 @@ namespace Svc {
     void FatalHandlerComponentImpl::FatalReceive_handler(
             const NATIVE_INT_TYPE portNum,
             FwEventIdType Id) {
-        Fw::Logger::logMsg("FATAL %d handled.\n",Id,0,0,0,0,0);
+        Fw::Logger::log("FATAL %d handled.\n",Id,0,0,0,0,0);
         taskSuspend(0);
     }
 
diff --git a/Svc/FileDownlink/FileDownlink.cpp b/Svc/FileDownlink/FileDownlink.cpp
index d52ef0737e..baac26209a 100644
--- a/Svc/FileDownlink/FileDownlink.cpp
+++ b/Svc/FileDownlink/FileDownlink.cpp
@@ -167,8 +167,8 @@ namespace Svc {
 
     FW_ASSERT(sourceFilename.length() < sizeof(entry.srcFilename));
     FW_ASSERT(destFilename.length() < sizeof(entry.destFilename));
-    (void) Fw::StringUtils::string_copy(entry.srcFilename, sourceFilename.toChar(), sizeof(entry.srcFilename));
-    (void) Fw::StringUtils::string_copy(entry.destFilename, destFilename.toChar(), sizeof(entry.destFilename));
+    (void) Fw::StringUtils::string_copy(entry.srcFilename, sourceFilename.toChar(), static_cast<FwSizeType>(sizeof(entry.srcFilename)));
+    (void) Fw::StringUtils::string_copy(entry.destFilename, destFilename.toChar(), static_cast<FwSizeType>(sizeof(entry.destFilename)));
 
     Os::Queue::QueueStatus status = m_fileQueue.send(reinterpret_cast<U8*>(&entry), sizeof(entry), 0, Os::Queue::QUEUE_NONBLOCKING);
 
@@ -240,8 +240,8 @@ namespace Svc {
 
     FW_ASSERT(sourceFilename.length() < sizeof(entry.srcFilename));
     FW_ASSERT(destFilename.length() < sizeof(entry.destFilename));
-    (void) Fw::StringUtils::string_copy(entry.srcFilename, sourceFilename.toChar(), sizeof(entry.srcFilename));
-    (void) Fw::StringUtils::string_copy(entry.destFilename, destFilename.toChar(), sizeof(entry.destFilename));
+    (void) Fw::StringUtils::string_copy(entry.srcFilename, sourceFilename.toChar(), static_cast<FwSizeType>(sizeof(entry.srcFilename)));
+    (void) Fw::StringUtils::string_copy(entry.destFilename, destFilename.toChar(), static_cast<FwSizeType>(sizeof(entry.destFilename)));
 
     Os::Queue::QueueStatus status = m_fileQueue.send(reinterpret_cast<U8*>(&entry), sizeof(entry), 0, Os::Queue::QUEUE_NONBLOCKING);
 
@@ -273,8 +273,8 @@ namespace Svc {
 
     FW_ASSERT(sourceFilename.length() < sizeof(entry.srcFilename));
     FW_ASSERT(destFilename.length() < sizeof(entry.destFilename));
-    (void) Fw::StringUtils::string_copy(entry.srcFilename, sourceFilename.toChar(), sizeof(entry.srcFilename));
-    (void) Fw::StringUtils::string_copy(entry.destFilename, destFilename.toChar(), sizeof(entry.destFilename));
+    (void) Fw::StringUtils::string_copy(entry.srcFilename, sourceFilename.toChar(), static_cast<FwSizeType>(sizeof(entry.srcFilename)));
+    (void) Fw::StringUtils::string_copy(entry.destFilename, destFilename.toChar(), static_cast<FwSizeType>(sizeof(entry.destFilename)));
 
     Os::Queue::QueueStatus status = m_fileQueue.send(reinterpret_cast<U8*>(&entry), sizeof(entry), 0, Os::Queue::QUEUE_NONBLOCKING);
 
diff --git a/Svc/Framer/Framer.cpp b/Svc/Framer/Framer.cpp
index 3ec1cf8f1e..0061be08b5 100644
--- a/Svc/Framer/Framer.cpp
+++ b/Svc/Framer/Framer.cpp
@@ -78,7 +78,7 @@ void Framer ::send(Fw::Buffer& outgoing) {
         // Note: if there is a data sending problem, an EVR likely wouldn't
         // make it down. Log the issue in hopes that
         // someone will see it.
-        Fw::Logger::logMsg("[ERROR] Failed to send framed data: %d\n", sendStatus.e);
+        Fw::Logger::log("[ERROR] Failed to send framed data: %d\n", sendStatus.e);
     }
     this->m_frame_sent = true;  // A frame was sent
 }
diff --git a/Svc/Framer/docs/sdd.md b/Svc/Framer/docs/sdd.md
index 229c601e7e..5b6ad1021e 100644
--- a/Svc/Framer/docs/sdd.md
+++ b/Svc/Framer/docs/sdd.md
@@ -155,7 +155,7 @@ _B_ representing framed data and does the following:
 
 1. Check the return status of the invocation. 
 If the return status is not `Drv::SendStatus::SEND_OK`, then
-use `Fw::Logger::logMsg` to log an error message.
+use `Fw::Logger::log` to log an error message.
 Don't send an event report in this case, because downlink is
 apparently not working.
 
diff --git a/Svc/LinuxTimer/LinuxTimerComponentImplTimerFd.cpp b/Svc/LinuxTimer/LinuxTimerComponentImplTimerFd.cpp
index fe53fa5b70..aeaac6deaa 100644
--- a/Svc/LinuxTimer/LinuxTimerComponentImplTimerFd.cpp
+++ b/Svc/LinuxTimer/LinuxTimerComponentImplTimerFd.cpp
@@ -38,7 +38,7 @@ namespace Svc {
           unsigned long long missed;
           int ret = static_cast<int>(read (fd, &missed, sizeof (missed)));
           if (-1 == ret) {
-              Fw::Logger::logMsg("timer read error: %s\n", reinterpret_cast<POINTER_CAST>(strerror(errno)));
+              Fw::Logger::log("timer read error: %s\n", strerror(errno));
           }
           this->m_mutex.lock();
           bool quit = this->m_quit;
diff --git a/Svc/PassiveConsoleTextLogger/ConsoleTextLoggerImplCommon.cpp b/Svc/PassiveConsoleTextLogger/ConsoleTextLoggerImplCommon.cpp
index bb27536235..c7c0ed0991 100644
--- a/Svc/PassiveConsoleTextLogger/ConsoleTextLoggerImplCommon.cpp
+++ b/Svc/PassiveConsoleTextLogger/ConsoleTextLoggerImplCommon.cpp
@@ -43,7 +43,7 @@ namespace Svc {
                 severityString = "SEVERITY ERROR";
                 break;
         }
-        Fw::Logger::logMsg("EVENT: (%" PRI_FwEventIdType ") (%" PRI_FwTimeBaseStoreType ":%" PRIu32 ",%" PRIu32 ") %s: %s\n",
+        Fw::Logger::log("EVENT: (%" PRI_FwEventIdType ") (%" PRI_FwTimeBaseStoreType ":%" PRIu32 ",%" PRIu32 ") %s: %s\n",
                 id, static_cast<FwTimeBaseStoreType>(timeTag.getTimeBase()), timeTag.getSeconds(), timeTag.getUSeconds(),
                 reinterpret_cast<PlatformPointerCastType>(severityString), reinterpret_cast<PlatformPointerCastType>(text.toChar()));
     }
diff --git a/Svc/PassiveRateGroup/PassiveRateGroup.cpp b/Svc/PassiveRateGroup/PassiveRateGroup.cpp
index d9e62107d8..5436340557 100644
--- a/Svc/PassiveRateGroup/PassiveRateGroup.cpp
+++ b/Svc/PassiveRateGroup/PassiveRateGroup.cpp
@@ -13,7 +13,7 @@
 
 #include <FpConfig.hpp>
 #include <Fw/Types/Assert.hpp>
-#include <Os/Log.hpp>
+#include <Os/Console.hpp>
 #include <Svc/PassiveRateGroup/PassiveRateGroup.hpp>
 
 namespace Svc {
diff --git a/Utils/Types/CircularBuffer.cpp b/Utils/Types/CircularBuffer.cpp
index b4fe9daec4..0eac02c513 100644
--- a/Utils/Types/CircularBuffer.cpp
+++ b/Utils/Types/CircularBuffer.cpp
@@ -169,12 +169,12 @@ void CircularBuffer ::clear_high_water_mark() {
 #ifdef CIRCULAR_DEBUG
 void CircularBuffer :: print() {
     NATIVE_UINT_TYPE idx = m_head_idx;
-    Os::Log::logMsg("Ring: ", 0, 0, 0, 0, 0, 0);
+    Os::Log::log("Ring: ");
     for (NATIVE_UINT_TYPE i = 0; i < m_allocated_size; ++i) {
-        Os::Log::logMsg("%c", m_store[idx], 0, 0, 0, 0, 0);
+        Os::Log::log("%c", m_store[idx]);
         idx = advance_idx(idx);
     }
-    Os::Log::logMsg("\n", 0, 0, 0, 0, 0, 0);
+    Os::Log::log("\n");
 }
 #endif
 } //End Namespace Types
diff --git a/cmake/platform/Darwin.cmake b/cmake/platform/Darwin.cmake
index a51674248b..efb41dd128 100644
--- a/cmake/platform/Darwin.cmake
+++ b/cmake/platform/Darwin.cmake
@@ -19,6 +19,7 @@ if (NOT DEFINED FPRIME_USE_BAREMETAL_SCHEDULER)
    FIND_PACKAGE ( Threads REQUIRED )
 endif()
 choose_fprime_implementation(Os/File Os/File/Posix)
+choose_fprime_implementation(Os/Console Os/Console/Posix)
 choose_fprime_implementation(Os/Task Os/Task/Posix)
 choose_fprime_implementation(Os/Mutex Os/Mutex/Posix)
 
diff --git a/cmake/platform/Linux.cmake b/cmake/platform/Linux.cmake
index 3b19460ee4..14bd5146db 100644
--- a/cmake/platform/Linux.cmake
+++ b/cmake/platform/Linux.cmake
@@ -10,6 +10,7 @@ if (NOT DEFINED FPRIME_USE_BAREMETAL_SCHEDULER)
    FIND_PACKAGE ( Threads REQUIRED )
 endif()
 choose_fprime_implementation(Os/File Os/File/Posix)
+choose_fprime_implementation(Os/Console Os/Console/Posix)
 choose_fprime_implementation(Os/Task Os/Task/Posix)
 choose_fprime_implementation(Os/Mutex Os/Mutex/Posix)
 
diff --git a/config/FpConfig.h b/config/FpConfig.h
index 3d59e7ad4c..5e4bde9d65 100644
--- a/config/FpConfig.h
+++ b/config/FpConfig.h
@@ -171,6 +171,14 @@ typedef FwIndexType FwQueueSizeType;
     0  //!< Indicates whether or not a baremetal scheduler should be used. Alternatively the Os scheduler is used.
 #endif
 
+// On some systems, use of *printf family functions (snprintf, printf, etc) require a prohibitive amount of program
+// space. Setting this to `0` indicates that the Fw/String methods should stop using these functions to conserve
+// program size. However, this comes at the expense of discarding format parameters. i.e. the format string is returned
+// unchanged.
+#ifndef FW_USE_PRINTF_FAMILY_FUNCTIONS_IN_STRING_FORMATTING
+#define FW_USE_PRINTF_FAMILY_FUNCTIONS_IN_STRING_FORMATTING 1
+#endif
+
 // Port Facilities
 
 // This allows tracing calls through ports for debugging
diff --git a/docs/Tutorials/GpsTutorial/Tutorial.md b/docs/Tutorials/GpsTutorial/Tutorial.md
index ba2da79544..2a8a2b3889 100644
--- a/docs/Tutorials/GpsTutorial/Tutorial.md
+++ b/docs/Tutorials/GpsTutorial/Tutorial.md
@@ -538,7 +538,7 @@ namespace GpsApp {
       char* pointer = reinterpret_cast<char*>(serBuffer.getData());
       // Check for invalid read status, log an error, return buffer and abort if there is a problem
       if (serial_status != Drv::SER_OK) {
-          Fw::Logger::logMsg("[WARNING] Received buffer with bad packet: %d\n", serial_status);
+          Fw::Logger::log("[WARNING] Received buffer with bad packet: %d\n", serial_status);
           // We MUST return the buffer or the serial driver won't be able to reuse it. The same buffer send call is used
           // as we did in "preamble".  Since the buffer's size was overwritten to hold the actual data size, we need to
           // reset it to the full data block size before returning it.
@@ -581,7 +581,7 @@ namespace GpsApp {
       }
       // If we found some of the message but not all of the message, then log an error, return the buffer and exit.
       else if (status != 9) {
-          Fw::Logger::logMsg("[ERROR] GPS parsing failed: %d\n", status);
+          Fw::Logger::log("[ERROR] GPS parsing failed: %d\n", status);
           // We MUST return the buffer or the serial driver won't be able to reuse it. The same buffer send call is used
           // as we did in "preamble".  Since the buffer's size was overwritten to hold the actual data size, we need to
           // reset it to the full data block size before returning it.
diff --git a/docs/UsersGuide/dev/os-docs.md b/docs/UsersGuide/dev/os-docs.md
index f0f662f2a7..a61dcc80ff 100644
--- a/docs/UsersGuide/dev/os-docs.md
+++ b/docs/UsersGuide/dev/os-docs.md
@@ -1,5 +1,9 @@
 # Operating System Abstraction Layer (OSAL)
 
+
+> [!WARNING]  
+> This document is out of date and will be updated shortly.
+
 The framework incorporates an OSAL providing service abstraction classes of a fictitious operating system that can perform all the operations of a real operating system.
 This layer shows the user an abstraction of the common functionality provided by all real-time operating systems (RTEMS, VxWorks, Azure ThreadX, QNX, FreeRTOS, Zephyr,...) or not (Linux, macOS, WinCE,...). We find the following services:
 


Test patch:
diff --git a/Drv/Ip/test/ut/TestTcp.cpp b/Drv/Ip/test/ut/TestTcp.cpp
index 051cd1a270..d06f0dd5d4 100644
--- a/Drv/Ip/test/ut/TestTcp.cpp
+++ b/Drv/Ip/test/ut/TestTcp.cpp
@@ -5,11 +5,11 @@
 #include <Drv/Ip/TcpClientSocket.hpp>
 #include <Drv/Ip/TcpServerSocket.hpp>
 #include <Drv/Ip/IpSocket.hpp>
-#include <Os/Log.hpp>
+#include <Os/Console.hpp>
 #include <Fw/Logger/Logger.hpp>
 #include <Drv/Ip/test/ut/SocketTestHelper.hpp>
 
-Os::Log logger;
+Os::Console logger;
 
 
 void test_with_loop(U32 iterations) {
diff --git a/Drv/Ip/test/ut/TestUdp.cpp b/Drv/Ip/test/ut/TestUdp.cpp
index 44c65f9bab..76fe4591d1 100644
--- a/Drv/Ip/test/ut/TestUdp.cpp
+++ b/Drv/Ip/test/ut/TestUdp.cpp
@@ -4,12 +4,12 @@
 #include <gtest/gtest.h>
 #include <Drv/Ip/UdpSocket.hpp>
 #include <Drv/Ip/IpSocket.hpp>
-#include <Os/Log.hpp>
+#include <Os/Console.hpp>
 #include <Fw/Logger/Logger.hpp>
 #include <Drv/Ip/test/ut/PortSelector.hpp>
 #include <Drv/Ip/test/ut/SocketTestHelper.hpp>
 
-Os::Log logger;
+Os::Console logger;
 
 void test_with_loop(U32 iterations, bool duplex) {
     Drv::SocketIpStatus status1 = Drv::SOCK_SUCCESS;
diff --git a/Drv/TcpClient/test/ut/TcpClientTester.cpp b/Drv/TcpClient/test/ut/TcpClientTester.cpp
index 2319874e5d..dcc3520f27 100644
--- a/Drv/TcpClient/test/ut/TcpClientTester.cpp
+++ b/Drv/TcpClient/test/ut/TcpClientTester.cpp
@@ -11,10 +11,11 @@
 // ======================================================================
 #include "TcpClientTester.hpp"
 #include "STest/Pick/Pick.hpp"
-#include <Os/Log.hpp>
+#include <Os/Console.hpp>
+#include <Drv/Ip/test/ut/PortSelector.hpp>
 #include <Drv/Ip/test/ut/SocketTestHelper.hpp>
 
-Os::Log logger;
+Os::Console logger;
 
 namespace Drv {
 
diff --git a/Drv/TcpServer/test/ut/TcpServerTester.cpp b/Drv/TcpServer/test/ut/TcpServerTester.cpp
index 1000bd5e0b..40f0d13a2e 100644
--- a/Drv/TcpServer/test/ut/TcpServerTester.cpp
+++ b/Drv/TcpServer/test/ut/TcpServerTester.cpp
@@ -11,10 +11,11 @@
 // ======================================================================
 #include "TcpServerTester.hpp"
 #include "STest/Pick/Pick.hpp"
-#include "Os/Log.hpp"
+#include "Os/Console.hpp"
+#include <Drv/Ip/test/ut/PortSelector.hpp>
 #include <Drv/Ip/test/ut/SocketTestHelper.hpp>
 
-Os::Log logger;
+Os::Console logger;
 
 namespace Drv {
 
diff --git a/Drv/Udp/test/ut/UdpTester.cpp b/Drv/Udp/test/ut/UdpTester.cpp
index 819ba127be..be06a59137 100644
--- a/Drv/Udp/test/ut/UdpTester.cpp
+++ b/Drv/Udp/test/ut/UdpTester.cpp
@@ -13,10 +13,10 @@
 #include "STest/Pick/Pick.hpp"
 #include <Drv/Ip/test/ut/PortSelector.hpp>
 #include <Drv/Ip/test/ut/SocketTestHelper.hpp>
-#include "Os/Log.hpp"
+#include "Os/Console.hpp"
 #include <sys/socket.h>
 
-Os::Log logger;
+Os::Console logger;
 
 namespace Drv {
 
diff --git a/Fw/Logger/test/ut/FakeLogger.cpp b/Fw/Logger/test/ut/FakeLogger.cpp
index f4652d4dc1..b8a48ceff0 100644
--- a/Fw/Logger/test/ut/FakeLogger.cpp
+++ b/Fw/Logger/test/ut/FakeLogger.cpp
@@ -10,76 +10,19 @@
 #include <Fw/Logger/test/ut/FakeLogger.hpp>
 
 namespace MockLogging {
-    Fw::Logger* FakeLogger::s_current = nullptr;
+Fw::Logger* FakeLogger::s_current = nullptr;
 
-    FakeLogger::FakeLogger() {
-        memset(&m_last, 0, sizeof(m_last));
-    }
+FakeLogger::FakeLogger(): m_last("") {}
 
-    void FakeLogger::log(
-            const char *fmt,
-            POINTER_CAST a0,
-            POINTER_CAST a1,
-            POINTER_CAST a2,
-            POINTER_CAST a3,
-            POINTER_CAST a4,
-            POINTER_CAST a5,
-            POINTER_CAST a6,
-            POINTER_CAST a7,
-            POINTER_CAST a8,
-            POINTER_CAST a9
-    ) {
-        m_last.fmt = fmt;
-        m_last.a0 = a0;
-        m_last.a1 = a1;
-        m_last.a2 = a2;
-        m_last.a3 = a3;
-        m_last.a4 = a4;
-        m_last.a5 = a5;
-        m_last.a6 = a6;
-        m_last.a7 = a7;
-        m_last.a8 = a8;
-        m_last.a9 = a9;
-    }
-
-    void FakeLogger::check(
-            const char *fmt,
-            POINTER_CAST a0,
-            POINTER_CAST a1,
-            POINTER_CAST a2,
-            POINTER_CAST a3,
-            POINTER_CAST a4,
-            POINTER_CAST a5,
-            POINTER_CAST a6,
-            POINTER_CAST a7,
-            POINTER_CAST a8,
-            POINTER_CAST a9
+void FakeLogger::writeMessage(const Fw::StringBase& message) {
+    m_last = message.toChar();
+}
 
-    ) {
-        ASSERT_EQ(m_last.fmt, fmt);
-        ASSERT_EQ(m_last.a0, a0);
-        ASSERT_EQ(m_last.a1, a1);
-        ASSERT_EQ(m_last.a2, a2);
-        ASSERT_EQ(m_last.a3, a3);
-        ASSERT_EQ(m_last.a4, a4);
-        ASSERT_EQ(m_last.a5, a5);
-        ASSERT_EQ(m_last.a6, a6);
-        ASSERT_EQ(m_last.a7, a7);
-        ASSERT_EQ(m_last.a8, a8);
-        ASSERT_EQ(m_last.a9, a9);
-    }
+void FakeLogger::check(const char* message) {
+    ASSERT_EQ(m_last, std::string(message));
+}
 
-    void FakeLogger::reset() {
-        m_last.fmt = nullptr;
-        m_last.a0 = 0;
-        m_last.a1 = 0;
-        m_last.a2 = 0;
-        m_last.a3 = 0;
-        m_last.a4 = 0;
-        m_last.a5 = 0;
-        m_last.a6 = 0;
-        m_last.a7 = 0;
-        m_last.a8 = 0;
-        m_last.a9 = 0;
-    }
+void FakeLogger::reset() {
+    m_last = "";
 }
+}  // namespace MockLogging
diff --git a/Fw/Logger/test/ut/FakeLogger.hpp b/Fw/Logger/test/ut/FakeLogger.hpp
index 125023d4b9..353d408e58 100644
--- a/Fw/Logger/test/ut/FakeLogger.hpp
+++ b/Fw/Logger/test/ut/FakeLogger.hpp
@@ -8,100 +8,43 @@
  */
 
 #include <FpConfig.hpp>
+#include <Fw/Types/String.hpp>
 #include <Fw/Logger/Logger.hpp>
+#include <string>
 
 #ifndef FPRIME_FAKELOGGER_HPP
 #define FPRIME_FAKELOGGER_HPP
 namespace MockLogging {
+/**
+ * Fake logger used for two purposes:
+ *   1. it acts as logging truth for the test
+ *   2. it intercepts logging calls bound for the system
+ */
+class FakeLogger : public Fw::Logger {
+  public:
+    //!< Constructor
+    FakeLogger();
+
     /**
-     * LogMessage data type to map inputs too.
+     * Fake implementation of the logger.
+     * @param message: formatted message to log
      */
-    struct LogMessage {
-        const char *fmt;
-        POINTER_CAST a0;
-        POINTER_CAST a1;
-        POINTER_CAST a2;
-        POINTER_CAST a3;
-        POINTER_CAST a4;
-        POINTER_CAST a5;
-        POINTER_CAST a6;
-        POINTER_CAST a7;
-        POINTER_CAST a8;
-        POINTER_CAST a9;
-    };
+    void writeMessage(const Fw::StringBase& message);
+
     /**
-     * Fake logger used for two purposes:
-     *   1. it acts as logging truth for the test
-     *   2. it intercepts logging calls bound for the system
+     * Check last message.
+     * @param message: formatted message to check
+     * @param size: size to log
      */
-    class FakeLogger : public Fw::Logger {
-        public:
-            //!< Constructor
-            FakeLogger();
-
-            /**
-             * Fake implementation of the logger.
-             * @param fmt: format
-             * @param a0: arg0
-             * @param a1: arg1
-             * @param a2: arg2
-             * @param a3: arg3
-             * @param a4: arg4
-             * @param a5: arg5
-             * @param a6: arg6
-             * @param a7: arg7
-             * @param a8: arg8
-             * @param a9: arg9
-             */
-            void log(
-                    const char *fmt,
-                    POINTER_CAST a0 = 0,
-                    POINTER_CAST a1 = 0,
-                    POINTER_CAST a2 = 0,
-                    POINTER_CAST a3 = 0,
-                    POINTER_CAST a4 = 0,
-                    POINTER_CAST a5 = 0,
-                    POINTER_CAST a6 = 0,
-                    POINTER_CAST a7 = 0,
-                    POINTER_CAST a8 = 0,
-                    POINTER_CAST a9 = 0
-            );
-
-            /**
-             * Check last message.
-             * @param fmt: format
-             * @param a0: arg1
-             * @param a1: arg1
-             * @param a2: arg2
-             * @param a3: arg3
-             * @param a4: arg4
-             * @param a5: arg5
-             * @param a6: arg6
-             * @param a7: arg6
-             * @param a8: arg6
-             * @param a9: arg6
-             */
-            virtual void check(
-                    const char *fmt,
-                    POINTER_CAST a0 = 0,
-                    POINTER_CAST a1 = 0,
-                    POINTER_CAST a2 = 0,
-                    POINTER_CAST a3 = 0,
-                    POINTER_CAST a4 = 0,
-                    POINTER_CAST a5 = 0,
-                    POINTER_CAST a6 = 0,
-                    POINTER_CAST a7 = 0,
-                    POINTER_CAST a8 = 0,
-                    POINTER_CAST a9 = 0
-            );
+    virtual void check(const char* message);
 
-            //!< Reset this logger
-            void reset();
+    //!< Reset this logger
+    void reset();
 
-            //!< Last message that came in
-            LogMessage m_last;
-            //!< Logger to use within the system
-            static Fw::Logger* s_current;
-    };
+    //!< Last message that came in
+    std::string m_last;
+    //!< Logger to use within the system
+    static Fw::Logger* s_current;
 };
-#endif //FPRIME_FAKELOGGER_HPP
+};      // namespace MockLogging
+#endif  // FPRIME_FAKELOGGER_HPP
diff --git a/Fw/Logger/test/ut/LoggerMain.cpp b/Fw/Logger/test/ut/LoggerMain.cpp
index 8ef9dc8d02..7dfe126e4f 100644
--- a/Fw/Logger/test/ut/LoggerMain.cpp
+++ b/Fw/Logger/test/ut/LoggerMain.cpp
@@ -6,13 +6,13 @@
  *  Created on: May 23, 2019
  *      Author: mstarch
  */
-#include <STest/Scenario/Scenario.hpp>
-#include <STest/Scenario/RandomScenario.hpp>
 #include <STest/Scenario/BoundedScenario.hpp>
+#include <STest/Scenario/RandomScenario.hpp>
+#include <STest/Scenario/Scenario.hpp>
 
-#include <Fw/Test/UnitTest.hpp>
-#include <Fw/Logger/test/ut/LoggerRules.hpp>
 #include <gtest/gtest.h>
+#include <Fw/Logger/test/ut/LoggerRules.hpp>
+#include <Fw/Test/UnitTest.hpp>
 
 #include <cstdio>
 
@@ -28,20 +28,15 @@ TEST(LoggerTests, RandomLoggerTests) {
     LoggerRules::Register reg(Fw::String("Register"));
     LoggerRules::LogGood log(Fw::String("Log Successfully"));
     LoggerRules::LogBad nolog(Fw::String("Log unsuccessfully"));
+    LoggerRules::LogBad string_log(Fw::String("Log Successfully (String)"));
 
     // Setup a list of rules to choose from
-    STest::Rule<MockLogging::FakeLogger>* rules[] = {
-            &reg,
-            &log,
-            &nolog
-    };
+    STest::Rule<MockLogging::FakeLogger>* rules[] = {&reg, &log, &nolog, &string_log};
     // Construct the random scenario and run it with the defined bounds
-    STest::RandomScenario<MockLogging::FakeLogger> random("Random Rules", rules,
-                                                      FW_NUM_ARRAY_ELEMENTS(rules));
+    STest::RandomScenario<MockLogging::FakeLogger> random("Random Rules", rules, FW_NUM_ARRAY_ELEMENTS(rules));
 
     // Setup a bounded scenario to run rules a set number of times
-    STest::BoundedScenario<MockLogging::FakeLogger> bounded("Bounded Random Rules Scenario",
-                                                        random, STEP_COUNT);
+    STest::BoundedScenario<MockLogging::FakeLogger> bounded("Bounded Random Rules Scenario", random, STEP_COUNT);
     // Run!
     const U32 numSteps = bounded.run(logger);
     printf("Ran %u steps.\n", numSteps);
@@ -58,6 +53,19 @@ TEST(LoggerTests, BasicGoodLogger) {
     LoggerRules::LogGood log(Fw::String("Log Successfully"));
     log.apply(logger);
 }
+/**
+ * Test that the most basic logging function works.
+ */
+TEST(LoggerTests, BasicGoodStringLogger) {
+    // Setup and register logger
+    MockLogging::FakeLogger logger;
+    Fw::Logger::registerLogger(&logger);
+    logger.s_current = &logger;
+    // Basic logging
+    LoggerRules::LogGoodStringObject log(Fw::String("Log Successfully"));
+    log.apply(logger);
+}
+
 /**
  * Test that null-logging function works.
  */
@@ -86,7 +94,6 @@ TEST(LoggerTests, BasicRegLogger) {
     reg.apply(logger);
 }
 
-
 int main(int argc, char* argv[]) {
     ::testing::InitGoogleTest(&argc, argv);
     STest::Random::seed();
diff --git a/Fw/Logger/test/ut/LoggerRules.cpp b/Fw/Logger/test/ut/LoggerRules.cpp
index 547f44f181..45ee2519f4 100644
--- a/Fw/Logger/test/ut/LoggerRules.cpp
+++ b/Fw/Logger/test/ut/LoggerRules.cpp
@@ -16,161 +16,183 @@
 
 namespace LoggerRules {
 
-    // Constructor
-    Register::Register(const Fw::String& name) : STest::Rule<MockLogging::FakeLogger>(name.toChar()) {}
+// Constructor
+Register::Register(const Fw::String& name) : STest::Rule<MockLogging::FakeLogger>(name.toChar()) {}
 
-    // Check for registration, always allowed
-    bool Register::precondition(const MockLogging::FakeLogger& truth) {
-        return true;
-    }
+// Check for registration, always allowed
+bool Register::precondition(const MockLogging::FakeLogger& truth) {
+    return true;
+}
 
-    // Register NULL or truth as the system logger
-    void Register::action(MockLogging::FakeLogger& truth) {
-        // Select a registration value: 1 -> logger, 0 -> NULL
-        NATIVE_INT_TYPE random = STest::Pick::lowerUpper(0, 1);
-        if (random == 1) {
-            Fw::Logger::registerLogger(&truth);
-            truth.s_current = &truth;
-        }
-        else {
-            Fw::Logger::registerLogger(nullptr);
-            truth.s_current = nullptr;
-        }
-        ASSERT_EQ(truth.s_current, Fw::Logger::s_current_logger);
+// Register NULL or truth as the system logger
+void Register::action(MockLogging::FakeLogger& truth) {
+    // Select a registration value: 1 -> logger, 0 -> NULL
+    NATIVE_INT_TYPE random = STest::Pick::lowerUpper(0, 1);
+    if (random == 1) {
+        Fw::Logger::registerLogger(&truth);
+        truth.s_current = &truth;
+    } else {
+        Fw::Logger::registerLogger(nullptr);
+        truth.s_current = nullptr;
     }
+    ASSERT_EQ(truth.s_current, Fw::Logger::s_current_logger);
+}
 
-    // Constructor
-    LogGood::LogGood(const Fw::String& name) : STest::Rule<MockLogging::FakeLogger>(name.toChar()) {}
+// Constructor
+LogGood::LogGood(const Fw::String& name) : STest::Rule<MockLogging::FakeLogger>(name.toChar()) {}
 
-    // Check for logging, only when not NULL
-    bool LogGood::precondition(const MockLogging::FakeLogger& truth) {
-        return truth.s_current != nullptr;
-    }
+// Check for logging, only when not NULL
+bool LogGood::precondition(const MockLogging::FakeLogger& truth) {
+    return truth.s_current != nullptr;
+}
 
-    // Log valid messages
-    void LogGood::action(MockLogging::FakeLogger& truth) {
-        NATIVE_INT_TYPE random = STest::Pick::lowerUpper(0, 10);
-        NATIVE_INT_TYPE ra[10];
-        for (int i = 0; i < 10; ++i) {
-            ra[i] = STest::Pick::lowerUpper(0, 0xffffffff);
-        }
-        
-        switch (random) {
-            case 0:
-                Fw::Logger::logMsg("No args");
-                truth.check("No args");
-                break;
-            case 1:
-                Fw::Logger::logMsg("One arg: %lu", ra[0]);
-                truth.check("One arg: %lu", ra[0]);
-                break;
-            case 2:
-                Fw::Logger::logMsg("Two arg: %lu", ra[0], ra[1]);
-                truth.check("Two arg: %lu", ra[0], ra[1]);
-                break;
-            case 3:
-                Fw::Logger::logMsg("Three arg: %lu", ra[0], ra[1], ra[2]);
-                truth.check("Three arg: %lu", ra[0], ra[1], ra[2]);
-                break;
-            case 4:
-                Fw::Logger::logMsg("Four arg: %lu", ra[0], ra[1], ra[2], ra[3]);
-                truth.check("Four arg: %lu", ra[0], ra[1], ra[2], ra[3]);
-                break;
-            case 5:
-                Fw::Logger::logMsg("Five arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4]);
-                truth.check("Five arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4]);
-                break;
-            case 6:
-                Fw::Logger::logMsg("Six arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5]);
-                truth.check("Six arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5]);
-                break;
-            case 7:
-                Fw::Logger::logMsg("Seven arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5], ra[6]);
-                truth.check("Seven arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5], ra[6]);
-                break;
-            case 8:
-                Fw::Logger::logMsg("Eight arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5], ra[6], ra[7]);
-                truth.check("Eight arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5], ra[6], ra[7]);
-                break;
-            case 9:
-                Fw::Logger::logMsg("Nine arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5], ra[6], ra[7], ra[8]);
-                truth.check("Nine arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5], ra[6], ra[7], ra[8]);
-                break;
-            case 10:
-                Fw::Logger::logMsg("Ten arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5], ra[6], ra[7], ra[8], ra[9]);
-                truth.check("Ten arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5], ra[6], ra[7], ra[8], ra[9]);
-                break;
+// Log valid messages
+void LogGood::action(MockLogging::FakeLogger& truth) {
+    NATIVE_INT_TYPE random = STest::Pick::lowerUpper(0, 10);
+    NATIVE_INT_TYPE ra[10];
+    for (int i = 0; i < 10; ++i) {
+        ra[i] = STest::Pick::lowerUpper(0, 0xffffffff);
+    }
+    Fw::String correct;
+    switch (random) {
+        case 0:
+            Fw::Logger::log("No args");
+            correct = "No args";
+            break;
+        case 1:
+            Fw::Logger::log("One arg: %lu", ra[0]);
+            correct.format("One arg: %lu", ra[0]);
+            break;
+        case 2:
+            Fw::Logger::log("Two arg: %lu  %lu", ra[0], ra[1]);
+            correct.format("Two arg: %lu  %lu", ra[0], ra[1]);
+            break;
+        case 3:
+            Fw::Logger::log("Three arg: %lu  %lu  %lu", ra[0], ra[1], ra[2]);
+            correct.format("Three arg: %lu  %lu  %lu", ra[0], ra[1], ra[2]);
+            break;
+        case 4:
+            Fw::Logger::log("Four arg: %lu  %lu  %lu  %lu", ra[0], ra[1], ra[2], ra[3]);
+            correct.format("Four arg: %lu  %lu  %lu  %lu", ra[0], ra[1], ra[2], ra[3]);
+            break;
+        case 5:
+            Fw::Logger::log("Five arg: %lu  %lu  %lu  %lu  %lu", ra[0], ra[1], ra[2], ra[3], ra[4]);
+            correct.format("Five arg: %lu  %lu  %lu  %lu  %lu", ra[0], ra[1], ra[2], ra[3], ra[4]);
+            break;
+        case 6:
+            Fw::Logger::log("Six arg: %lu  %lu  %lu  %lu  %lu  %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5]);
+            correct.format("Six arg: %lu  %lu  %lu  %lu  %lu  %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5]);
+            break;
+        case 7:
+            Fw::Logger::log("Seven arg: %lu  %lu  %lu  %lu  %lu  %lu  %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5],
+                            ra[6]);
+            correct.format("Seven arg: %lu  %lu  %lu  %lu  %lu  %lu  %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5],
+                           ra[6]);
+            break;
+        case 8:
+            Fw::Logger::log("Eight arg: %lu  %lu  %lu  %lu  %lu  %lu  %lu  %lu", ra[0], ra[1], ra[2], ra[3], ra[4],
+                            ra[5], ra[6], ra[7]);
+            correct.format("Eight arg: %lu  %lu  %lu  %lu  %lu  %lu  %lu  %lu", ra[0], ra[1], ra[2], ra[3], ra[4],
+                           ra[5], ra[6], ra[7]);
+            break;
+        case 9:
+            Fw::Logger::log("Nine arg: %lu  %lu  %lu  %lu  %lu  %lu  %lu  %lu  %lu", ra[0], ra[1], ra[2], ra[3], ra[4],
+                            ra[5], ra[6], ra[7], ra[8]);
+            correct.format("Nine arg: %lu  %lu  %lu  %lu  %lu  %lu  %lu  %lu  %lu", ra[0], ra[1], ra[2], ra[3], ra[4],
+                           ra[5], ra[6], ra[7], ra[8]);
+            break;
+        case 10:
+            Fw::Logger::log("Ten arg: %lu  %lu  %lu  %lu  %lu  %lu  %lu  %lu  %lu  %lu", ra[0], ra[1], ra[2], ra[3],
+                            ra[4], ra[5], ra[6], ra[7], ra[8], ra[9]);
+            correct.format("Ten arg: %lu  %lu  %lu  %lu  %lu  %lu  %lu  %lu  %lu  %lu", ra[0], ra[1], ra[2], ra[3],
+                           ra[4], ra[5], ra[6], ra[7], ra[8], ra[9]);
+            break;
 
-            default:
-                ASSERT_EQ(0, 1);
-        }
-        truth.reset();
+        default:
+            ASSERT_EQ(0, 1);
     }
+    truth.check(correct.toChar());
+    truth.reset();
+}
+
+// Constructor
+LogGoodStringObject::LogGoodStringObject(const Fw::String& name)
+    : STest::Rule<MockLogging::FakeLogger>(name.toChar()) {}
 
-    // Constructor
-    LogBad::LogBad(const Fw::String& name) : STest::Rule<MockLogging::FakeLogger>(name.toChar()) {}
+// Check for logging, only when not NULL
+bool LogGoodStringObject::precondition(const MockLogging::FakeLogger& truth) {
+    return truth.s_current != nullptr;
+}
 
-    // Check for logging, only when not NULL
-    bool LogBad::precondition(const MockLogging::FakeLogger& truth) {
-        return truth.s_current == nullptr;
+// Log valid messages
+void LogGoodStringObject::action(MockLogging::FakeLogger& truth) {
+    Fw::String my_string;
+    NATIVE_INT_TYPE random = STest::Pick::lowerUpper(0, my_string.getCapacity() - 1);
+    for (int i = 0; i < random; ++i) {
+        const_cast<char*>(my_string.toChar())[i] =
+            static_cast<char>(STest::Pick::lowerUpper(0, std::numeric_limits<unsigned char>::max()));
     }
+    const_cast<char*>(my_string.toChar())[random] = 0;
+    Fw::String copy1 = my_string.toChar();
+    Fw::Logger::log(copy1);
+    truth.check(my_string.toChar());
+    truth.reset();
+}
 
-    // Log valid messages
-    void LogBad::action(MockLogging::FakeLogger& truth) {
-        NATIVE_INT_TYPE random = STest::Pick::lowerUpper(0, 10);
-        NATIVE_INT_TYPE ra[10];
-        for (int i = 0; i < 10; ++i) {
-            ra[i] = STest::Pick::lowerUpper(0, 0xffffffff);
-        }
+// Constructor
+LogBad::LogBad(const Fw::String& name) : STest::Rule<MockLogging::FakeLogger>(name.toChar()) {}
+
+// Check for logging, only when not NULL
+bool LogBad::precondition(const MockLogging::FakeLogger& truth) {
+    return truth.s_current == nullptr;
+}
+
+// Log valid messages
+void LogBad::action(MockLogging::FakeLogger& truth) {
+    NATIVE_INT_TYPE random = STest::Pick::lowerUpper(0, 10);
+    NATIVE_INT_TYPE ra[10];
+    for (int i = 0; i < 10; ++i) {
+        ra[i] = STest::Pick::lowerUpper(0, 0xffffffff);
+    }
 
-        switch (random) {
-            case 0:
-                Fw::Logger::logMsg("No args");
-                truth.check(nullptr);
-                break;
-            case 1:
-                Fw::Logger::logMsg("One arg: %lu", ra[0]);
-                truth.check(nullptr);
-                break;
-            case 2:
-                Fw::Logger::logMsg("Two arg: %lu", ra[0], ra[1]);
-                truth.check(nullptr);
-                break;
-            case 3:
-                Fw::Logger::logMsg("Three arg: %lu", ra[0], ra[1], ra[2]);
-                truth.check(nullptr);
-                break;
-            case 4:
-                Fw::Logger::logMsg("Four arg: %lu", ra[0], ra[1], ra[2], ra[3]);
-                truth.check(nullptr);
-                break;
-            case 5:
-                Fw::Logger::logMsg("Five arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4]);
-                truth.check(nullptr);
-                break;
-            case 6:
-                Fw::Logger::logMsg("Six arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5]);
-                truth.check(nullptr);
-                break;
-            case 7:
-                Fw::Logger::logMsg("Seven arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5], ra[6]);
-                truth.check(nullptr);
-                break;
-            case 8:
-                Fw::Logger::logMsg("Eight arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5], ra[6], ra[7]);
-                truth.check(nullptr);
-                break;
-            case 9:
-                Fw::Logger::logMsg("Nine arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5], ra[6], ra[7], ra[8]);
-                truth.check(nullptr);
-                break;
-            case 10:
-                Fw::Logger::logMsg("Ten arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5], ra[6], ra[7], ra[8], ra[9]);
-                truth.check(nullptr);
-                break;
-            default:
-                ASSERT_EQ(0, 1);
-        }
-        truth.reset();
+    switch (random) {
+        case 0:
+            Fw::Logger::log("No args");
+            break;
+        case 1:
+            Fw::Logger::log("One arg: %lu", ra[0]);
+            break;
+        case 2:
+            Fw::Logger::log("Two arg: %lu", ra[0], ra[1]);
+            break;
+        case 3:
+            Fw::Logger::log("Three arg: %lu", ra[0], ra[1], ra[2]);
+            break;
+        case 4:
+            Fw::Logger::log("Four arg: %lu", ra[0], ra[1], ra[2], ra[3]);
+            break;
+        case 5:
+            Fw::Logger::log("Five arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4]);
+            break;
+        case 6:
+            Fw::Logger::log("Six arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5]);
+            break;
+        case 7:
+            Fw::Logger::log("Seven arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5], ra[6]);
+            break;
+        case 8:
+            Fw::Logger::log("Eight arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5], ra[6], ra[7]);
+            break;
+        case 9:
+            Fw::Logger::log("Nine arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5], ra[6], ra[7], ra[8]);
+            break;
+        case 10:
+            Fw::Logger::log("Ten arg: %lu", ra[0], ra[1], ra[2], ra[3], ra[4], ra[5], ra[6], ra[7], ra[8], ra[9]);
+            break;
+        default:
+            ASSERT_EQ(0, 1);
     }
-};
+    truth.check("");
+    truth.reset();
+}
+};  // namespace LoggerRules
diff --git a/Fw/Logger/test/ut/LoggerRules.hpp b/Fw/Logger/test/ut/LoggerRules.hpp
index a0f34a4948..015c017733 100644
--- a/Fw/Logger/test/ut/LoggerRules.hpp
+++ b/Fw/Logger/test/ut/LoggerRules.hpp
@@ -16,60 +16,76 @@
 #define FPRIME_LOGGERRULES_HPP
 
 #include <FpConfig.hpp>
-#include <Fw/Types/String.hpp>
 #include <Fw/Logger/test/ut/FakeLogger.hpp>
-#include <STest/STest/Rule/Rule.hpp>
+#include <Fw/Types/String.hpp>
 #include <STest/STest/Pick/Pick.hpp>
-
+#include <STest/STest/Rule/Rule.hpp>
 
 namespace LoggerRules {
 
-    /**
-     * Register:
-     *
-     * Rule to handle the registration of a logger to the global logger. It may also register a "NULL" logger and thus
-     * stop output logging.
-     */
-    struct Register : public STest::Rule<MockLogging::FakeLogger> {
-        // Constructor
-        Register(const Fw::String& name);
+/**
+ * Register:
+ *
+ * Rule to handle the registration of a logger to the global logger. It may also register a "NULL" logger and thus
+ * stop output logging.
+ */
+struct Register : public STest::Rule<MockLogging::FakeLogger> {
+    // Constructor
+    explicit Register(const Fw::String& name);
+
+    // Check for registration, always allowed
+    bool precondition(const MockLogging::FakeLogger& truth);
+
+    // Register NULL or truth as the system logger
+    void action(MockLogging::FakeLogger& truth);
+};
+
+/**
+ * LogGood:
+ *
+ * As long as a non-NULL logger is set as the system logger, then valid log messages should be processed.
+ */
+struct LogGood : public STest::Rule<MockLogging::FakeLogger> {
+    // Constructor
+    explicit LogGood(const Fw::String& name);
 
-        // Check for registration, always allowed
-        bool precondition(const MockLogging::FakeLogger& truth);
+    // Check for logging, only when not NULL
+    bool precondition(const MockLogging::FakeLogger& truth);
 
-        // Register NULL or truth as the system logger
-        void action(MockLogging::FakeLogger& truth);
-    };
-    /**
-     * LogGood:
-     *
-     * As long as a non-NULL logger is set as the system logger, then valid log messages should be processed.
-     */
-    struct LogGood : public STest::Rule<MockLogging::FakeLogger> {
-        // Constructor
-        LogGood(const Fw::String& name);
+    // Log valid messages
+    void action(MockLogging::FakeLogger& truth);
+};
+
+/**
+ * LogBad:
+ *
+ * As long as a non-NULL logger is set as the system logger, then valid log messages should be processed.
+ */
+struct LogBad : public STest::Rule<MockLogging::FakeLogger> {
+    // Constructor
+    explicit LogBad(const Fw::String& name);
 
-        // Check for logging, only when not NULL
-        bool precondition(const MockLogging::FakeLogger& truth);
+    // Check for logging, only when not NULL
+    bool precondition(const MockLogging::FakeLogger& truth);
 
-        // Log valid messages
-        void action(MockLogging::FakeLogger& truth);
-    };
+    // Log valid messages
+    void action(MockLogging::FakeLogger& truth);
+};
 
-    /**
-     * LogBad:
-     *
-     * As long as a non-NULL logger is set as the system logger, then valid log messages should be processed.
-     */
-    struct LogBad : public STest::Rule<MockLogging::FakeLogger> {
-        // Constructor
-        LogBad(const Fw::String& name);
+/**
+ * LogGoodStringObject:
+ *
+ * As long as a non-NULL logger is set as the system logger, then valid log messages should be processed.
+ */
+struct LogGoodStringObject : public STest::Rule<MockLogging::FakeLogger> {
+    // Constructor
+    explicit LogGoodStringObject(const Fw::String& name);
 
-        // Check for logging, only when not NULL
-        bool precondition(const MockLogging::FakeLogger& truth);
+    // Check for logging, only when not NULL
+    bool precondition(const MockLogging::FakeLogger& truth);
 
-        // Log valid messages
-        void action(MockLogging::FakeLogger& truth);
-    };
+    // Log valid messages
+    void action(MockLogging::FakeLogger& truth);
 };
-#endif //FPRIME_LOGGERRULES_HPP
+};      // namespace LoggerRules
+#endif  // FPRIME_LOGGERRULES_HPP
diff --git a/Fw/Types/test/ut/TypesTest.cpp b/Fw/Types/test/ut/TypesTest.cpp
index 197aade022..20b801306a 100644
--- a/Fw/Types/test/ut/TypesTest.cpp
+++ b/Fw/Types/test/ut/TypesTest.cpp
@@ -1249,8 +1249,8 @@ TEST(Nominal, string_copy) {
     char buffer_out_test[10];
     char buffer_out_truth[10];
 
-    char* out_truth = ::strncpy(buffer_out_truth, copy_string, sizeof(buffer_out_truth));
-    char* out_test = Fw::StringUtils::string_copy(buffer_out_test, copy_string, sizeof(buffer_out_test));
+    char* out_truth = ::strncpy(buffer_out_truth, copy_string, static_cast<FwSizeType>(sizeof(buffer_out_truth)));
+    char* out_test = Fw::StringUtils::string_copy(buffer_out_test, copy_string, static_cast<FwSizeType>(sizeof(buffer_out_test)));
 
     ASSERT_EQ(sizeof(buffer_out_truth), sizeof(buffer_out_test)) << "Buffer size mismatch";
 
@@ -1275,8 +1275,8 @@ TEST(OffNominal, string_copy) {
     char buffer_out_test[sizeof(copy_string) - 1];
     char buffer_out_truth[sizeof(copy_string) - 1];
 
-    char* out_truth = ::strncpy(buffer_out_truth, copy_string, sizeof(buffer_out_truth));
-    char* out_test = Fw::StringUtils::string_copy(buffer_out_test, copy_string, sizeof(buffer_out_test));
+    char* out_truth = ::strncpy(buffer_out_truth, copy_string, static_cast<FwSizeType>(sizeof(buffer_out_truth)));
+    char* out_test = Fw::StringUtils::string_copy(buffer_out_test, copy_string, static_cast<FwSizeType>(sizeof(buffer_out_test)));
 
     ASSERT_EQ(sizeof(buffer_out_truth), sizeof(buffer_out_test)) << "Buffer size mismatch";
 
@@ -1296,13 +1296,13 @@ TEST(OffNominal, string_copy) {
 
 TEST(Nominal, string_len) {
     const char* test_string = "abc123";
-    ASSERT_EQ(Fw::StringUtils::string_length(test_string, 50), 6);
-    ASSERT_EQ(Fw::StringUtils::string_length(test_string, 3), 3);
+    ASSERT_EQ(Fw::StringUtils::string_length(test_string, static_cast<FwSizeType>(50)), 6);
+    ASSERT_EQ(Fw::StringUtils::string_length(test_string, static_cast<FwSizeType>(3)), 3);
 }
 
 TEST(OffNominal, string_len_zero) {
     const char* test_string = "abc123";
-    ASSERT_EQ(Fw::StringUtils::string_length(test_string, 0), 0);
+    ASSERT_EQ(Fw::StringUtils::string_length(test_string, static_cast<FwSizeType>(0)), 0);
 }
 
 TEST(OffNominal, sub_string_no_match) {
diff --git a/Os/Posix/test/ut/PosixConsoleTests.cpp b/Os/Posix/test/ut/PosixConsoleTests.cpp
new file mode 100644
index 0000000000..b790e2305a
--- /dev/null
+++ b/Os/Posix/test/ut/PosixConsoleTests.cpp
@@ -0,0 +1,29 @@
+// ======================================================================
+// \title Os/Posix/test/ut/PosixFileTests.cpp
+// \brief tests for posix implementation for Os::File
+// ======================================================================
+#include <gtest/gtest.h>
+#include "Os/Console.hpp"
+#include "Os/Posix/Console.hpp"
+
+
+TEST(Nominal, SwitchStream) {
+    Os::Posix::Console::PosixConsole posix_console;
+    ASSERT_EQ(reinterpret_cast<Os::Posix::Console::PosixConsoleHandle*>(posix_console.getHandle())->m_file_descriptor,
+              stdout);
+    posix_console.setOutputStream(Os::Posix::Console::PosixConsole::Stream::STANDARD_ERROR);
+    ASSERT_EQ(reinterpret_cast<Os::Posix::Console::PosixConsoleHandle*>(posix_console.getHandle())->m_file_descriptor,
+              stderr);
+    posix_console.setOutputStream(Os::Posix::Console::PosixConsole::Stream::STANDARD_OUT);
+    ASSERT_EQ(reinterpret_cast<Os::Posix::Console::PosixConsoleHandle*>(posix_console.getHandle())->m_file_descriptor,
+              stdout);
+}
+TEST(OffNominal, SwitchStream) {
+    Os::Posix::Console::PosixConsole posix_console;
+    ASSERT_DEATH(posix_console.setOutputStream(static_cast<Os::Posix::Console::PosixConsole::Stream>(3)), "Posix/|\\Console.cpp:33");
+}
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/Os/Stub/test/Console.cpp b/Os/Stub/test/Console.cpp
new file mode 100644
index 0000000000..7d83e3833e
--- /dev/null
+++ b/Os/Stub/test/Console.cpp
@@ -0,0 +1,41 @@
+// ======================================================================
+// \title Os/Stub/Console.cpp
+// \brief stub implementation for Os::Console
+// ======================================================================
+#include <Os/Stub/test/Console.hpp>
+
+namespace Os {
+namespace Stub {
+namespace Console {
+namespace Test {
+
+StaticData StaticData::data;
+
+TestConsole::TestConsole() {
+    StaticData::data.lastCalled = StaticData::LastFn::CONSTRUCT_FN;
+}
+
+TestConsole::~TestConsole() {
+    StaticData::data.lastCalled = StaticData::LastFn::DESTRUCT_FN;
+}
+
+TestConsole::TestConsole(const Os::Stub::Console::Test::TestConsole& other) {
+    StaticData::data.lastCalled = StaticData::LastFn::CONSTRUCT_COPY_FN;
+    StaticData::data.copyObject = &other;
+    this->m_handle = other.m_handle;
+}
+
+void TestConsole::writeMessage(const CHAR* message, const FwSizeType size) {
+    StaticData::data.message = message;
+    StaticData::data.size = size;
+    StaticData::data.lastCalled = StaticData::LastFn::WRITE_FN;
+}
+
+ConsoleHandle* TestConsole::getHandle() {
+    return &this->m_handle;
+}
+
+} // namespace Test
+} // namespace Console
+} // namespace Stub
+} // namespace Os
diff --git a/Os/Stub/test/Console.hpp b/Os/Stub/test/Console.hpp
new file mode 100644
index 0000000000..9ef135afa3
--- /dev/null
+++ b/Os/Stub/test/Console.hpp
@@ -0,0 +1,98 @@
+// ======================================================================
+// \title Os/Stub/test/Console.hpp
+// \brief test stub implementation for Os::Console, header and test definitions
+// ======================================================================
+#include <cstdio>
+#include <Os/Console.hpp>
+#ifndef OS_Stub_Test_Console_HPP
+#define OS_Stub_Test_Console_HPP
+
+namespace Os {
+namespace Stub {
+namespace Console {
+namespace Test {
+
+class TestConsole;
+
+//! Data that supports the stubbed File implementation.
+//!/
+struct StaticData {
+    enum LastFn {
+        NONE_FN,
+        CONSTRUCT_FN,
+        CONSTRUCT_COPY_FN,
+        COPY_FN,
+        DESTRUCT_FN,
+        WRITE_FN,
+    };
+    //! Last function called
+    LastFn lastCalled = NONE_FN;
+    //! Copy object
+    const TestConsole* copyObject;
+
+    //! Last message passed
+    const CHAR* message = nullptr;
+    //! Last size passed
+    FwSizeType size = 0;
+
+    // Singleton data
+    static StaticData data;
+};
+
+//! ConsoleHandle class definition for stub implementations.
+//!
+struct TestConsoleHandle : public ConsoleHandle {
+};
+
+//! \brief stub implementation of Os::ConsoleInterface
+//!
+//! Stub implementation of `ConsoleInterface` for use as a delegate class handling stub console operations.
+//!
+class TestConsole : public ConsoleInterface {
+  public:
+    //! \brief constructor
+    //!
+    TestConsole();
+
+    //! \brief copy constructor
+    TestConsole(const TestConsole& other);
+
+    //! \brief assignment operator that copies the internal representation
+    TestConsole& operator=(const TestConsole& other) = delete;
+
+    //! \brief destructor
+    //!
+    ~TestConsole() override;
+
+    // ------------------------------------
+    // Functions overrides
+    // ------------------------------------
+
+    //! \brief write message to console
+    //!
+    //! Write a message to the console with a bounded size. This will use the active file descriptor as the output
+    //! destination.
+    //!
+    //! \param message: raw message to write
+    //! \param size: size of the message to write to the console
+    void writeMessage(const CHAR* message, const FwSizeType size) override;
+
+    //! \brief returns the raw console handle
+    //!
+    //! Gets the raw console handle from the implementation. Note: users must include the implementation specific
+    //! header to make any real use of this handle. Otherwise it will be as an opaque type.
+    //!
+    //! \return raw console handle
+    //!
+    ConsoleHandle* getHandle() override;
+
+  private:
+    //! File handle for PosixFile
+    TestConsoleHandle m_handle;
+};
+} // namespace Test
+} // namespace Console
+} // namespace Stub
+} // namespace Os
+
+#endif // OS_Stub_Test_Console_HPP
diff --git a/Os/Stub/test/DefaultConsole.cpp b/Os/Stub/test/DefaultConsole.cpp
new file mode 100644
index 0000000000..21a9f57f01
--- /dev/null
+++ b/Os/Stub/test/DefaultConsole.cpp
@@ -0,0 +1,13 @@
+// ======================================================================
+// \title Os/Stub/test/DefaultConsole.cpp
+// \brief sets default Os::Console to stub test implementation via linker
+// ======================================================================
+#include "Os/Console.hpp"
+#include "Os/Stub/test/Console.hpp"
+#include "Os/Delegate.hpp"
+
+namespace Os {
+ConsoleInterface* ConsoleInterface::getDelegate(HandleStorage& aligned_new_memory, const ConsoleInterface* to_copy) {
+    return Os::Delegate::makeDelegate<ConsoleInterface, Os::Stub::Console::Test::TestConsole>(aligned_new_memory, to_copy);
+}
+}
diff --git a/Os/Stub/test/ut/StubConsoleTests.cpp b/Os/Stub/test/ut/StubConsoleTests.cpp
new file mode 100644
index 0000000000..61bae28618
--- /dev/null
+++ b/Os/Stub/test/ut/StubConsoleTests.cpp
@@ -0,0 +1,53 @@
+// ======================================================================
+// \title Os/Stub/test/ut/StubFileTests.cpp
+// \brief tests using stub implementation for Os::File interface testing
+// ======================================================================
+#include <gtest/gtest.h>
+#include "Os/Console.hpp"
+#include "Os/Stub/test/Console.hpp"
+
+
+TEST(Interface, Construction) {
+    Os::Console console;
+    ASSERT_EQ(Os::Stub::Console::Test::StaticData::data.lastCalled, Os::Stub::Console::Test::StaticData::CONSTRUCT_FN);
+}
+TEST(Interface, ConstructionCopy) {
+    Os::Console console;
+    ASSERT_EQ(Os::Stub::Console::Test::StaticData::data.lastCalled, Os::Stub::Console::Test::StaticData::CONSTRUCT_FN);
+    Os::Console console2(console);
+    ASSERT_EQ(const_cast<Os::Stub::Console::Test::TestConsole*>(Os::Stub::Console::Test::StaticData::data.copyObject)->getHandle(),
+              console.getHandle());
+    ASSERT_EQ(Os::Stub::Console::Test::StaticData::data.lastCalled, Os::Stub::Console::Test::StaticData::CONSTRUCT_COPY_FN);
+
+
+}
+TEST(Interface, Copy) {
+    Os::Console console;
+    Os::Console console2;
+    ASSERT_EQ(Os::Stub::Console::Test::StaticData::data.lastCalled, Os::Stub::Console::Test::StaticData::CONSTRUCT_FN);
+    console2 = console;
+    ASSERT_EQ(const_cast<Os::Stub::Console::Test::TestConsole*>(Os::Stub::Console::Test::StaticData::data.copyObject)->getHandle(),
+              console.getHandle());
+    ASSERT_EQ(Os::Stub::Console::Test::StaticData::data.lastCalled, Os::Stub::Console::Test::StaticData::CONSTRUCT_COPY_FN);
+}
+
+TEST(Interface, Destruction) {
+    delete (new Os::Console);
+    ASSERT_EQ(Os::Stub::Console::Test::StaticData::data.lastCalled, Os::Stub::Console::Test::StaticData::DESTRUCT_FN);
+}
+
+TEST(Interface, Write) {
+    Os::Console console;
+    const char* message = "hello";
+    ASSERT_EQ(Os::Stub::Console::Test::StaticData::data.lastCalled, Os::Stub::Console::Test::StaticData::CONSTRUCT_FN);
+    console.write(message, 6);
+    ASSERT_EQ(Os::Stub::Console::Test::StaticData::data.lastCalled, Os::Stub::Console::Test::StaticData::WRITE_FN);
+    ASSERT_EQ(Os::Stub::Console::Test::StaticData::data.message, message);
+    ASSERT_EQ(Os::Stub::Console::Test::StaticData::data.size, 6);
+
+}
+
+int main(int argc, char **argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}
diff --git a/Svc/ActiveTextLogger/test/ut/ActiveTextLoggerTester.cpp b/Svc/ActiveTextLogger/test/ut/ActiveTextLoggerTester.cpp
index e01ba068d7..01314bac68 100644
--- a/Svc/ActiveTextLogger/test/ut/ActiveTextLoggerTester.cpp
+++ b/Svc/ActiveTextLogger/test/ut/ActiveTextLoggerTester.cpp
@@ -107,7 +107,7 @@ namespace Svc {
                       "EVENT: (%d) (%d:%d,%d) %s: %s",
                        id,timeTag.getTimeBase(),timeTag.getSeconds(),timeTag.getUSeconds(),severityString,text.toChar());
               ASSERT_EQ(0,strcmp(textStr,buf));
-              (void) Fw::StringUtils::string_copy(oldLine, buf, sizeof(oldLine));
+              (void) Fw::StringUtils::string_copy(oldLine, buf, static_cast<FwSizeType>(sizeof(oldLine)));
           }
       }
       stream1.close();
@@ -210,7 +210,7 @@ namespace Svc {
                       "EVENT: (%d) (%d:%d,%d) %s: %s",
                        id,timeTag.getTimeBase(),timeTag.getSeconds(),timeTag.getUSeconds(),severityString,text.toChar());
               ASSERT_EQ(0,strcmp(textStr,buf));
-              (void) Fw::StringUtils::string_copy(oldLine, buf, sizeof(oldLine));
+              (void) Fw::StringUtils::string_copy(oldLine, buf, static_cast<FwSizeType>(sizeof(oldLine)));
           }
       }
       stream1.close();
diff --git a/Svc/AssertFatalAdapter/test/ut/AssertFatalAdapterTester.cpp b/Svc/AssertFatalAdapter/test/ut/AssertFatalAdapterTester.cpp
index 01b5139374..217546cd13 100644
--- a/Svc/AssertFatalAdapter/test/ut/AssertFatalAdapterTester.cpp
+++ b/Svc/AssertFatalAdapter/test/ut/AssertFatalAdapterTester.cpp
@@ -61,7 +61,7 @@ namespace Svc {
 #else
         fileString = __FILE__;
 #endif
-        (void) Fw::StringUtils::string_copy(file, fileString.toChar(), sizeof(file));
+        (void) Fw::StringUtils::string_copy(file, fileString.toChar(), static_cast<FwSizeType>(sizeof(file)));
 
         // FW_ASSERT_0
 
diff --git a/Svc/Deframer/test/ut-fprime-protocol/DeframerTestMain.cpp b/Svc/Deframer/test/ut-fprime-protocol/DeframerTestMain.cpp
index 94c731f866..da7091a50e 100644
--- a/Svc/Deframer/test/ut-fprime-protocol/DeframerTestMain.cpp
+++ b/Svc/Deframer/test/ut-fprime-protocol/DeframerTestMain.cpp
@@ -6,7 +6,7 @@
 
 #include "Fw/Test/UnitTest.hpp"
 #include "GenerateFrames.hpp"
-#include "Os/Log.hpp"
+#include "Os/Console.hpp"
 #include "STest/Scenario/BoundedScenario.hpp"
 #include "STest/Scenario/RandomScenario.hpp"
 #include "STest/Scenario/Scenario.hpp"
diff --git a/Svc/Framer/test/ut/FramerTestMain.cpp b/Svc/Framer/test/ut/FramerTestMain.cpp
index 8e87e79d1b..b0c0189f9b 100644
--- a/Svc/Framer/test/ut/FramerTestMain.cpp
+++ b/Svc/Framer/test/ut/FramerTestMain.cpp
@@ -3,11 +3,11 @@
 // ----------------------------------------------------------------------
 
 #include "Fw/Test/UnitTest.hpp"
-#include "Os/Log.hpp"
+#include "Os/Console.hpp"
 #include "FramerTester.hpp"
 
 // Enable the console logging provided by Os::Log
-Os::Log logger;
+Os::Console logger;
 
 TEST(Nominal, Com) {
     COMMENT("Send one Fw::Com buffer to the framer (nominal behavior)");