pw_log: Logging module first steps

This starts a logging module facade, with a frontend and a dumb IO based
backend. The initial version is lacking many features, like an API to
control enabling or disabling logs.

Change-Id: I3075aed33d9600a1190ca5237b5f7cfcfdc8f2ed
diff --git a/BUILD.gn b/BUILD.gn
index 44e8667..c3890b2 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -68,6 +68,7 @@
 pw_test_group("pw_module_tests") {
   group_deps = [
     "$dir_pw_base64:tests",
+    "$dir_pw_log:tests",
     "$dir_pw_polyfill:tests",
     "$dir_pw_preprocessor:tests",
     "$dir_pw_protobuf:tests",
diff --git a/docs/BUILD.gn b/docs/BUILD.gn
index 92a2e59..031834e 100644
--- a/docs/BUILD.gn
+++ b/docs/BUILD.gn
@@ -50,6 +50,7 @@
     "$dir_pw_dumb_io:docs",
     "$dir_pw_dumb_io_baremetal_stm32f429:docs",
     "$dir_pw_dumb_io_stdio:docs",
+    "$dir_pw_log:docs",
     "$dir_pw_module:docs",
     "$dir_pw_polyfill:docs",
     "$dir_pw_preprocessor:docs",
diff --git a/modules.gni b/modules.gni
index e2c0293..8ab3503 100644
--- a/modules.gni
+++ b/modules.gni
@@ -28,6 +28,8 @@
 dir_pw_dumb_io_baremetal_stm32f429 =
     "$dir_pigweed/pw_dumb_io_baremetal_stm32f429"
 dir_pw_dumb_io_stdio = "$dir_pigweed/pw_dumb_io_stdio"
+dir_pw_log = "$dir_pigweed/pw_log"
+dir_pw_log_basic = "$dir_pigweed/pw_log_basic"
 dir_pw_module = "$dir_pigweed/pw_module"
 dir_pw_polyfill = "$dir_pigweed/pw_polyfill"
 dir_pw_preprocessor = "$dir_pigweed/pw_preprocessor"
diff --git a/pw_log/BUILD b/pw_log/BUILD
new file mode 100644
index 0000000..6d89404
--- /dev/null
+++ b/pw_log/BUILD
@@ -0,0 +1,34 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+filegroup(
+    name = "pw_log",
+    srcs = [
+        "public/pw_log/log.h",
+        "public/pw_log/levels.h",
+    ],
+)
+
+# TODO: This isn't a real Bazel build yet.
+filegroup(
+    name = "test",
+    srcs = [
+        "basic_log_test.cc",
+        "basic_log_test_plain_c.c",
+    ],
+)
diff --git a/pw_log/BUILD.gn b/pw_log/BUILD.gn
new file mode 100644
index 0000000..40965fb
--- /dev/null
+++ b/pw_log/BUILD.gn
@@ -0,0 +1,64 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import("$dir_pw_build/facade.gni")
+import("$dir_pw_docgen/docs.gni")
+import("$dir_pw_unit_test/test.gni")
+
+config("default_config") {
+  include_dirs = [ "public" ]
+}
+
+source_set("facade") {
+  public_configs = [ ":default_config" ]
+  public = [
+    "public/pw_log/levels.h",
+    "public/pw_log/log.h",
+  ]
+  sources = public
+}
+
+pw_facade("pw_log") {
+  backend = dir_pw_log_backend
+  public_deps = [
+    ":facade",
+  ]
+}
+
+pw_test_group("tests") {
+  tests = []
+  if (dir_pw_log_backend != "") {
+    tests += [ ":basic_log_test" ]
+  }
+}
+
+if (dir_pw_log_backend != "") {
+  pw_test("basic_log_test") {
+    deps = [
+      ":pw_log",
+      dir_pw_log_backend,
+    ]
+
+    sources = [
+      "basic_log_test.cc",
+      "basic_log_test_plain_c.c",
+    ]
+  }
+}
+
+pw_doc_group("docs") {
+  sources = [
+    "docs.rst",
+  ]
+}
diff --git a/pw_log/basic_log_test.cc b/pw_log/basic_log_test.cc
new file mode 100644
index 0000000..c7c2dd7
--- /dev/null
+++ b/pw_log/basic_log_test.cc
@@ -0,0 +1,142 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+// This is mostly a compile test to verify that the log backend is able to
+// compile the constructs  promised by the logging facade; and that when run,
+// there is no crash.
+//
+// TODO(pwbug/88): Add verification of the actually logged statements.
+
+#define PW_LOG_MODULE_NAME "TST"
+
+#include "gtest/gtest.h"
+#include "pw_log/log.h"
+
+// TODO(pwbug/86): Test unsigned integer logging (32 and 64 bit); test pointer
+// logging.
+
+void LoggingFromFunction() { PW_LOG_INFO("From a function!"); }
+
+const int N = 3;
+
+TEST(BasicLog, DebugLevel) {
+  PW_LOG_DEBUG("This log statement should be at DEBUG level; no args");
+  for (int i = 0; i < N; ++i) {
+    PW_LOG_DEBUG("Counting: %d", i);
+  }
+  PW_LOG_DEBUG("Here is a string: %s; with another string %s", "foo", "bar");
+}
+
+TEST(BasicLog, InfoLevel) {
+  PW_LOG_INFO("This log statement should be at INFO level; no args");
+  for (int i = 0; i < N; ++i) {
+    PW_LOG_INFO("Counting: %d", i);
+  }
+  PW_LOG_INFO("Here is a string: %s; with another string %s", "foo", "bar");
+}
+
+TEST(BasicLog, WarnLevel) {
+  PW_LOG_WARN("This log statement should be at WARN level; no args");
+  for (int i = 0; i < N; ++i) {
+    PW_LOG_WARN("Counting: %d", i);
+  }
+  PW_LOG_WARN("Here is a string: %s; with another string %s", "foo", "bar");
+}
+
+TEST(BasicLog, ErrorLevel) {
+  PW_LOG_ERROR("This log statement should be at ERROR level; no args");
+  for (int i = 0; i < N; ++i) {
+    PW_LOG_ERROR("Counting: %d", i);
+  }
+  PW_LOG_ERROR("Here is a string: %s; with another string %s", "foo", "bar");
+}
+
+TEST(BasicLog, CriticalLevel) {
+  PW_LOG_CRITICAL("Critical, emergency log. Device should not reboot");
+}
+
+TEST(BasicLog, ManualLevel) {
+  PW_LOG(PW_LOG_LEVEL_DEBUG, 0, "A manual DEBUG-level message");
+  PW_LOG(PW_LOG_LEVEL_DEBUG, 1, "A manual DEBUG-level message; with a flag");
+
+  PW_LOG(PW_LOG_LEVEL_INFO, 0, "A manual INFO-level message");
+  PW_LOG(PW_LOG_LEVEL_INFO, 1, "A manual INFO-level message; with a flag");
+
+  PW_LOG(PW_LOG_LEVEL_WARN, 0, "A manual WARN-level message");
+  PW_LOG(PW_LOG_LEVEL_WARN, 1, "A manual WARN-level message; with a flag");
+
+  PW_LOG(PW_LOG_LEVEL_ERROR, 0, "A manual ERROR-level message");
+  PW_LOG(PW_LOG_LEVEL_ERROR, 1, "A manual ERROR-level message; with a flag");
+
+  PW_LOG(PW_LOG_LEVEL_CRITICAL, 0, "A manual CRITICAL-level message");
+  PW_LOG(
+      PW_LOG_LEVEL_CRITICAL, 1, "A manual CRITICAL-level message; with a flag");
+}
+
+TEST(BasicLog, FromAFunction) { LoggingFromFunction(); }
+
+TEST(BasicLog, CustomLogLevels) {
+  // Log levels other than the standard ones work; what each backend does is
+  // implementation defined.
+  PW_LOG(0, 0, "Custom log level: 0");
+  PW_LOG(1, 0, "Custom log level: 1");
+  PW_LOG(2, 0, "Custom log level: 2");
+  PW_LOG(3, 0, "Custom log level: 3");
+  PW_LOG(100, 0, "Custom log level: 100");
+}
+
+#define TEST_FAILED_LOG "IF THIS MESSAGE WAS LOGGED, THE TEST FAILED"
+
+TEST(BasicLog, FilteringByLevel) {
+#undef PW_LOG_SKIP_LOGS_WITH_LEVEL_LT
+#define PW_LOG_SKIP_LOGS_WITH_LEVEL_LT PW_LOG_LEVEL_ERROR
+
+  PW_LOG_DEBUG(TEST_FAILED_LOG);
+  PW_LOG_INFO(TEST_FAILED_LOG);
+  PW_LOG_WARN(TEST_FAILED_LOG);
+
+  PW_LOG_ERROR("This log should appear as error status (and that's good)");
+
+#undef PW_LOG_SKIP_LOGS_WITH_LEVEL_LT
+#define PW_LOG_SKIP_LOGS_WITH_LEVEL_LT 0
+}
+
+TEST(BasicLog, FilteringByFlags) {
+#undef PW_LOG_SKIP_LOGS_WITH_FLAGS
+#define PW_LOG_SKIP_LOGS_WITH_FLAGS 1
+
+  // Flag is set so these should all get zapped.
+  PW_LOG(PW_LOG_LEVEL_INFO, 1, TEST_FAILED_LOG);
+  PW_LOG(PW_LOG_LEVEL_ERROR, 1, TEST_FAILED_LOG);
+
+  // However, a different flag bit should still log.
+  PW_LOG(PW_LOG_LEVEL_INFO, 1 << 1, "This flagged log is intended to appear");
+  PW_LOG(PW_LOG_LEVEL_ERROR, 1 << 1, "This flagged log is intended to appear");
+
+#undef PW_LOG_SKIP_LOGS_WITH_FLAGS
+#define PW_LOG_SKIP_LOGS_WITH_FLAGS 0
+}
+
+TEST(BasicLog, ChangingTheModuleName) {
+#undef PW_LOG_MODULE_NAME
+#define PW_LOG_MODULE_NAME "PQR"
+  PW_LOG_INFO("This has a custom module name");
+  PW_LOG_INFO("So does this");
+}
+
+extern "C" {
+void BasicLogTestPlainC();
+};
+
+TEST(BasicLog, FromPlainC) { BasicLogTestPlainC(); }
diff --git a/pw_log/basic_log_test_plain_c.c b/pw_log/basic_log_test_plain_c.c
new file mode 100644
index 0000000..71d93be
--- /dev/null
+++ b/pw_log/basic_log_test_plain_c.c
@@ -0,0 +1,100 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+// This is test verifies that the logging backend:
+//
+// - Compiles as plain C
+// - Runs when compiled as C
+//
+// Unfortunately, since we do not know where the log sink actually goes, the
+// logging functionality itself is not verified beyond compiling and running.
+
+#define PW_LOG_MODULE_NAME "CTS"
+
+#include "pw_log/log.h"
+
+#ifdef __cplusplus
+#error "This file must be compiled as plain C to verify C compilation works."
+#endif  // __cplusplus
+
+void LoggingFromFunctionPlainC() { PW_LOG_INFO("From a function!"); }
+
+void BasicLogTestPlainC() {
+  int n = 3;
+
+  // Debug level
+  PW_LOG_DEBUG("This log statement should be at DEBUG level; no args");
+  for (int i = 0; i < n; ++i) {
+    PW_LOG_DEBUG("Counting: %d", i);
+  }
+  PW_LOG_DEBUG("Here is a string: %s; with another string %s", "foo", "bar");
+
+  // Info level
+  PW_LOG_INFO("This log statement should be at INFO level; no args");
+  for (int i = 0; i < n; ++i) {
+    PW_LOG_INFO("Counting: %d", i);
+  }
+  PW_LOG_INFO("Here is a string: %s; with another string %s", "foo", "bar");
+
+  // Warn level
+  PW_LOG_WARN("This log statement should be at WARN level; no args");
+  for (int i = 0; i < n; ++i) {
+    PW_LOG_WARN("Counting: %d", i);
+  }
+  PW_LOG_WARN("Here is a string: %s; with another string %s", "foo", "bar");
+
+  // Error level.
+  PW_LOG_ERROR("This log statement should be at ERROR level; no args");
+  for (int i = 0; i < n; ++i) {
+    PW_LOG_ERROR("Counting: %d", i);
+  }
+  PW_LOG_ERROR("Here is a string: %s; with another string %s", "foo", "bar");
+
+  // Critical level.
+  PW_LOG_CRITICAL("This log is the last one; device should reboot");
+
+  // Core log macro, with manually specified level and flags.
+  PW_LOG(PW_LOG_LEVEL_DEBUG, 0, "A manual DEBUG-level message");
+  PW_LOG(PW_LOG_LEVEL_DEBUG, 1, "A manual DEBUG-level message; with a flag");
+
+  PW_LOG(PW_LOG_LEVEL_INFO, 0, "A manual INFO-level message");
+  PW_LOG(PW_LOG_LEVEL_INFO, 1, "A manual INFO-level message; with a flag");
+
+  PW_LOG(PW_LOG_LEVEL_WARN, 0, "A manual WARN-level message");
+  PW_LOG(PW_LOG_LEVEL_WARN, 1, "A manual WARN-level message; with a flag");
+
+  PW_LOG(PW_LOG_LEVEL_ERROR, 0, "A manual ERROR-level message");
+  PW_LOG(PW_LOG_LEVEL_ERROR, 1, "A manual ERROR-level message; with a flag");
+
+  PW_LOG(PW_LOG_LEVEL_CRITICAL, 0, "A manual CRITICAL-level message");
+  PW_LOG(
+      PW_LOG_LEVEL_CRITICAL, 1, "A manual CRITICAL-level message; with a flag");
+
+  // Log levels other than the standard ones work; what each backend does is
+  // implementation defined.
+  PW_LOG(0, PW_LOG_NO_FLAGS, "Custom log level: 0");
+  PW_LOG(1, PW_LOG_NO_FLAGS, "Custom log level: 1");
+  PW_LOG(2, PW_LOG_NO_FLAGS, "Custom log level: 2");
+  PW_LOG(3, PW_LOG_NO_FLAGS, "Custom log level: 3");
+  PW_LOG(100, PW_LOG_NO_FLAGS, "Custom log level: 100");
+
+  // Logging from a function.
+  LoggingFromFunctionPlainC();
+
+  // Changing the module name mid-file.
+#undef PW_LOG_MODULE_NAME
+#define PW_LOG_MODULE_NAME "XYZ"
+  PW_LOG_INFO("This has a custom module name");
+  PW_LOG_INFO("So does this");
+}
diff --git a/pw_log/docs.rst b/pw_log/docs.rst
new file mode 100644
index 0000000..0f07dbc
--- /dev/null
+++ b/pw_log/docs.rst
@@ -0,0 +1,106 @@
+.. _chapter-pw-log:
+
+.. default-domain:: cpp
+
+.. highlight:: cpp
+
+------
+pw_log
+------
+Pigweed's logging module provides facilities for applications to log
+information about the execution of their application. The module is split into
+two components:
+
+1. The facade (this module) which is only a macro interface layer
+2. The backend, provided elsewhere, that implements the low level logging
+
+Basic usage example
+-------------------
+
+.. code-block:: cpp
+
+  #define PW_LOG_MODULE_NAME "BLE"
+  #include "pw_log/log.h"
+
+  int main() {
+    PW_LOG_INFO("Booting...");
+    if (BootFailed()) {
+      PW_LOG_CRITICAL("Had trouble booting due to error %d", GetErrorCode());
+      ReportErrorsAndHalt();
+    }
+    PW_LOG_INFO("Successfully booted");
+  }
+
+
+Logging macros
+--------------
+
+.. cpp:function:: PW_LOG(level, flags, fmt, ...)
+
+  This is the primary mechanism for logging.
+
+  *level* - An integer level as defined by ``pw_log/levels.h``.
+
+  *flags* - Arbitrary flags the backend can leverage. The semantics of these
+  flags are not defined in the facade, but are instead meant as a general
+  mechanism for communication bits of information to the logging backend.
+
+  Here are some ideas for what a backend might use flags for:
+
+  - Example: ``HAS_PII`` - A log has personally-identifying data
+  - Example: ``HAS_DII`` - A log has device-identifying data
+  - Example: ``RELIABLE_DELIVERY`` - Ask the backend to ensure the log is
+    delivered; this may entail blocking other logs.
+  - Example: ``BEST_EFFORT`` - Don't deliver this log if it would mean blocking
+    or dropping important-flagged logs
+
+  *fmt* - The message to log, which may contain format specifiers like ``%d``
+  or ``%0.2f``.
+
+  Example:
+
+  .. code-block:: cpp
+
+    PW_LOG(PW_NO_FLAGS, PW_LOG_LEVEL_INFO, "Temperature is %d degrees", temp);
+    PW_LOG(UNRELIABLE_DELIVERY, PW_LOG_LEVEL_ERROR, "It didn't work!"); 
+
+  .. note::
+
+    ``PW_LOG()`` should not be used frequently; typically only when adding
+    flags to a particular message to mark PII or to indicate delivery
+    guarantees.  For most cases, prefer to use the direct ``PW_LOG_DEBUG``
+    style macros, which are often implemented more efficiently in the backend.
+
+
+.. cpp:function:: PW_LOG_DEBUG(fmt, ...)
+.. cpp:function:: PW_LOG_INFO(fmt, ...)
+.. cpp:function:: PW_LOG_WARN(fmt, ...)
+.. cpp:function:: PW_LOG_ERROR(fmt, ...)
+.. cpp:function:: PW_LOG_CRITICAL(fmt, ...)
+
+  Shorthand for `PW_LOG(PW_LOG_NO_FLAGS, <level>, fmt, ...)`.
+
+Logging attributes
+------------------
+
+The logging facade in Pigweed is designed to facilitate the capture of at least
+the following attributes:
+
+- *Level* - The log level; for example, INFO, DEBUG, ERROR, etc. Typically an
+  integer
+- *Flags* - Bitset for e.g. RELIABLE_DELIVERY, or HAS_PII, or BEST_EFFORT
+- *File* - The file where the log was triggered
+- *Line* - The line number in the file where the log line occured
+- *Function* - What function the log comes from. This is expensive in binary
+  size to use!
+- *Module* - The user-defined module name for the log statement; e.g. “BLE” or
+  “BAT”
+- *Message* - The message itself; with % format arguments
+- *Arguments* - The format arguments to message
+- *Thread* - For devices running with an RTOS, capturing the thread is very
+  useful
+- *Others* - Processor security level? Maybe Thread is a good proxy for this
+
+Each backend may decide to capture different attributes to balance the tradeoff
+between call site code size, call site run time, wire format size, logging
+complexity, and more.
diff --git a/pw_log/public/pw_log/levels.h b/pw_log/public/pw_log/levels.h
new file mode 100644
index 0000000..c4670f7
--- /dev/null
+++ b/pw_log/public/pw_log/levels.h
@@ -0,0 +1,25 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+// Standard log levels. These are compatible with the log levels from Python's
+// logging library, but this could be customized if desired by the backend.
+//
+// clang-format off
+#define PW_LOG_LEVEL_DEBUG    10
+#define PW_LOG_LEVEL_INFO     20
+#define PW_LOG_LEVEL_WARN     30
+#define PW_LOG_LEVEL_ERROR    40
+#define PW_LOG_LEVEL_CRITICAL 50
+// clang-format on
diff --git a/pw_log/public/pw_log/log.h b/pw_log/public/pw_log/log.h
new file mode 100644
index 0000000..f54f372
--- /dev/null
+++ b/pw_log/public/pw_log/log.h
@@ -0,0 +1,135 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+//==============================================================================
+//
+// This file describes Pigweed's public user-facing logging API.
+//
+// THIS PUBLIC API IS NOT STABLE OR COMPLETE!
+//
+// Key functionality is still missing:
+//
+// - API for controlling verbosity at compile time
+// - API for controlling verbosity at run time
+// - API for querying if logging is enabled for the given level or flags
+//
+#pragma once
+
+#include "pw_log/levels.h"
+#include "pw_preprocessor/macro_arg_count.h"  // PW_COMMA_ARGS
+
+// log_backend.h must ultimately resolve to a header that implements the macros
+// required by the logging facade, as described below.
+//
+// Inputs: Macros the downstream user provides to control the logging system:
+//
+//   PW_LOG_MODULE_NAME
+//     - The module name the backend should use
+//
+// Outputs: Macros log_backend.h is expected to provide:
+//
+//   PW_LOG(level, flags, fmt, ...)
+//     - Required.
+//       Level - An integer level as defined by pw_log/levels.h
+//       Flags - Arbitrary flags the backend can leverage; user-defined.
+//               Example: HAS_PII - A log has personally-identifying data
+//               Example: HAS_DII - A log has device-identifying data
+//               Example: RELIABLE_DELIVERY - Ask backend to ensure the
+//               log is delivered; this may entail blocking other logs.
+//               Example: BEST_EFFORT - Don't deliver this log if it
+//               would mean blocking or dropping important-flagged logs
+//
+//   PW_LOG_DEBUG(fmt, ...)
+//   PW_LOG_INFO(fmt, ...)
+//   PW_LOG_WARN(fmt, ...)
+//   PW_LOG_ERROR(fmt, ...)
+//   PW_LOG_CRITICAL(fmt, ...)
+//     - Optional. If not defined by the backend, the facade's default
+//       implementation defines these in terms of PW_LOG().
+//
+#include "pw_log_backend/log_backend.h"
+
+// Default: Module name
+#ifndef PW_LOG_MODULE_NAME
+#define PW_LOG_MODULE_NAME ""
+#endif  // PW_LOG_MODULE_NAME
+
+// Default: Flags
+#ifndef PW_LOG_NO_FLAGS
+#define PW_LOG_NO_FLAGS 0
+#endif  // PW_LOG_NO_FLAGS
+
+// Note: The filtering semantics are likely to change.
+
+// For backends that elect to only provide the general PW_LOG() macro and not
+// specialized versions, define the standard PW_LOG_<level>() macros in terms
+// of the general PW_LOG().
+#ifndef PW_LOG_DEBUG
+#define PW_LOG_DEBUG(message, ...) \
+  PW_LOG(                          \
+      PW_LOG_LEVEL_DEBUG, PW_LOG_NO_FLAGS, message PW_COMMA_ARGS(__VA_ARGS__))
+#endif  // PW_LOG_DEBUG
+
+#ifndef PW_LOG_INFO
+#define PW_LOG_INFO(message, ...) \
+  PW_LOG(PW_LOG_LEVEL_INFO, PW_LOG_NO_FLAGS, message PW_COMMA_ARGS(__VA_ARGS__))
+#endif  // PW_LOG_INFO
+
+#ifndef PW_LOG_WARN
+#define PW_LOG_WARN(message, ...) \
+  PW_LOG(PW_LOG_LEVEL_WARN, PW_LOG_NO_FLAGS, message PW_COMMA_ARGS(__VA_ARGS__))
+#endif  // PW_LOG_WARN
+
+#ifndef PW_LOG_ERROR
+#define PW_LOG_ERROR(message, ...) \
+  PW_LOG(                          \
+      PW_LOG_LEVEL_ERROR, PW_LOG_NO_FLAGS, message PW_COMMA_ARGS(__VA_ARGS__))
+#endif  // PW_LOG_ERROR
+
+#ifndef PW_LOG_CRITICAL
+#define PW_LOG_CRITICAL(message, ...) \
+  PW_LOG(PW_LOG_LEVEL_CRITICAL,       \
+         PW_LOG_NO_FLAGS,             \
+         message PW_COMMA_ARGS(__VA_ARGS__))
+#endif  // PW_LOG_CRITICAL
+
+// Define short, usable names if requested.
+// TODO(pwbug/17): Convert this to the config system when available.
+#ifndef PW_LOG_USE_SHORT_NAMES
+#define PW_LOG_USE_SHORT_NAMES 0
+#endif
+#ifndef PW_LOG_USE_ULTRA_SHORT_NAMES
+#define PW_LOG_USE_ULTRA_SHORT_NAMES 0
+#endif  // PW_LOG_USE_SHORT_NAMES
+
+// clang-format off
+#if PW_LOG_USE_SHORT_NAMES
+#define PW_LOG          LOG
+#define PW_LOG_DEBUG    LOG_DEBUG
+#define PW_LOG_INFO     LOG_INFO
+#define PW_LOG_WARN     LOG_WARN
+#define PW_LOG_ERROR    LOG_ERROR
+#define PW_LOG_CRITICAL LOG_CRITICAL
+#endif  // PW_LOG_USE_SHORT_NAMES
+// clang-format on
+
+// clang-format off
+#if PW_LOG_USE_ULTRA_SHORT_NAMES
+#define PW_LOG          LOG
+#define PW_LOG_DEBUG    DBG
+#define PW_LOG_INFO     INF
+#define PW_LOG_WARN     WRN
+#define PW_LOG_ERROR    ERR
+#define PW_LOG_CRITICAL CRT
+#endif  // PW_LOG_USE_ULTRA_SHORT_NAMES
+// clang-format on
diff --git a/pw_log_basic/BUILD b/pw_log_basic/BUILD
new file mode 100644
index 0000000..9430dd5
--- /dev/null
+++ b/pw_log_basic/BUILD
@@ -0,0 +1,26 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])  # Apache License 2.0
+
+filegroup(
+    name = "pw_log_basic",
+    srcs = [
+        "public/pw_log_basic/log_basic.h",
+        "public_overrides/pw_log_backend/log_backend.h",
+        "log_basic.cc",
+    ],
+)
diff --git a/pw_log_basic/BUILD.gn b/pw_log_basic/BUILD.gn
new file mode 100644
index 0000000..97979e2
--- /dev/null
+++ b/pw_log_basic/BUILD.gn
@@ -0,0 +1,51 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+config("default_config") {
+  include_dirs = [ "public" ]
+}
+
+config("backend_config") {
+  include_dirs = [ "public_overrides" ]
+}
+
+if (dir_pw_log_backend == dir_pw_log_basic) {
+  source_set("pw_log_basic") {
+    public_configs = [
+      ":backend_config",
+      ":default_config",
+    ]
+    deps = [
+      ":pw_log_basic_core",
+    ]
+    public = [
+      "public_overrides/pw_log_backend/log_backend.h",
+    ]
+    sources = public
+  }
+}
+
+source_set("pw_log_basic_core") {
+  public_configs = [ ":default_config" ]
+  deps = [
+    "$dir_pw_dumb_io",
+    "$dir_pw_log:facade",
+    "$dir_pw_preprocessor",
+    "$dir_pw_string",
+  ]
+  public = [
+    "public/pw_log_basic/log_basic.h",
+  ]
+  sources = public + [ "log_basic.cc" ]
+}
diff --git a/pw_log_basic/log_basic.cc b/pw_log_basic/log_basic.cc
new file mode 100644
index 0000000..f13980e
--- /dev/null
+++ b/pw_log_basic/log_basic.cc
@@ -0,0 +1,141 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+// This is a very basic direct output log implementation with no buffering.
+
+#include "pw_log_basic/log_basic.h"
+
+#include <cstring>
+
+#include "pw_dumb_io/dumb_io.h"
+#include "pw_log/levels.h"
+#include "pw_string/string_builder.h"
+
+// ANSI color constants to control the terminal. Not Windows compatible.
+// clang-format off
+#define MAGENTA   "\033[35m"
+#define YELLOW    "\033[33m"
+#define RED       "\033[31m"
+#define GREEN     "\033[32m"
+#define BLUE      "\033[96m"
+#define BLACK     "\033[30m"
+#define YELLOW_BG "\033[43m"
+#define WHITE_BG  "\033[47m"
+#define RED_BG    "\033[41m"
+#define BOLD      "\033[1m"
+#define RESET     "\033[0m"
+// clang-format on
+
+// TODO(pwbug/17): Expose these through the config system.
+#define PW_USE_EMOJIS 1
+#define PW_LOG_SHOW_FILENAME 0
+#define PW_LOG_SHOW_FLAG 0
+
+namespace {
+
+const char* LogLevelToLogLevelName(int level) {
+  switch (level) {
+    // clang-format off
+#if PW_USE_EMOJIS
+    case PW_LOG_LEVEL_DEBUG    : return "👾" RESET;
+    case PW_LOG_LEVEL_INFO     : return "ℹ️ " RESET;
+    case PW_LOG_LEVEL_WARN     : return "⚠️ " RESET;
+    case PW_LOG_LEVEL_ERROR    : return "❌" RESET;
+    case PW_LOG_LEVEL_CRITICAL : return "☠️ " RESET;
+    default: return "❔" RESET;
+#else
+    case PW_LOG_LEVEL_DEBUG    : return BLUE     BOLD        "DBG" RESET;
+    case PW_LOG_LEVEL_INFO     : return MAGENTA  BOLD        "INF" RESET;
+    case PW_LOG_LEVEL_WARN     : return YELLOW   BOLD        "WRN" RESET;
+    case PW_LOG_LEVEL_ERROR    : return RED      BOLD        "ERR" RESET;
+    case PW_LOG_LEVEL_CRITICAL : return BLACK    BOLD RED_BG "FTL" RESET;
+    default                    : return GREEN    BOLD        "UNK" RESET;
+#endif
+      // clang-format on
+  }
+}
+
+#if PW_LOG_SHOW_FILENAME
+const char* GetFileBasename(const char* filename) {
+  int length = std::strlen(filename);
+  if (length == 0) {
+    return filename;
+  }
+
+  // Start on the last character.
+  // TODO(pwbug/38): This part of the function doesn't work for Windows paths.
+  const char* basename = filename + std::strlen(filename) - 1;
+  while (basename != filename && *basename != '/') {
+    basename--;
+  }
+  if (*basename == '/') {
+    basename++;
+  }
+  return basename;
+}
+#endif  // PW_LOG_SHOW_FILENAME
+
+}  // namespace
+
+// This is a fully loaded, inefficient-at-the-callsite, log implementation.
+extern "C" void pw_Log(int level,
+                       unsigned int flags,
+                       const char* module_name,
+                       const char* file_name,
+                       int line_number,
+                       const char* function_name,
+                       const char* message,
+                       ...) {
+  PW_UNUSED(function_name);
+  PW_UNUSED(line_number);
+
+  // Accumulate the log message in this buffer, then output it.
+  pw::StringBuffer<150> buffer;
+
+  // Column: Filename
+#if PW_SHOW_FILENAME
+  buffer.Format(" %-30s |", GetFileBasename(file_name));
+#else
+  PW_UNUSED(file_name);
+#endif
+
+  // Column: Module
+  buffer << " " BOLD;
+  buffer.Format("%3s", module_name);
+  buffer << RESET " ";
+
+  // Column: Flag
+#if PW_LOG_SHOW_FLAG
+#if PW_USE_EMOJIS
+  buffer << (flags ? "🚩" : "  ");
+#else
+  buffer << (flags ? "*" : "|");
+#endif  // PW_USE_EMOJIS
+  buffer << " ";
+#else
+  PW_UNUSED(flags);
+#endif  // PW_LOG_SHOW_FLAG
+
+  // Column: Level
+  buffer << LogLevelToLogLevelName(level) << "   ";
+
+  // Column: Message
+  va_list args;
+  va_start(args, message);
+  buffer.Format(message, args);
+  va_end(args);
+
+  // All done; flush the log.
+  pw::dumb_io::WriteLine(buffer.view());
+}
diff --git a/pw_log_basic/public/pw_log_basic/log_basic.h b/pw_log_basic/public/pw_log_basic/log_basic.h
new file mode 100644
index 0000000..b9a9e78
--- /dev/null
+++ b/pw_log_basic/public/pw_log_basic/log_basic.h
@@ -0,0 +1,49 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include "pw_preprocessor/util.h"
+
+PW_EXTERN_C_START
+
+// Log a message with the listed attributes.
+void pw_Log(int level,
+            unsigned int flags,
+            const char* module_name,
+            const char* file_name,
+            int line_number,
+            const char* function_name,
+            const char* message,
+            ...);
+
+PW_EXTERN_C_END
+
+// Log a message with many attributes included.
+//
+// This is the log macro frontend that funnels everything into the C handler
+// above, pw_Log(). It's not efficient at the callsite, since it passes many
+// arguments. Additionally, the use of the __FUNC__ macro adds a static const
+// char[] variable inside functions with a log.
+//
+// TODO(pwbug/87): Reconsider the naming of this module when more is in place.
+#define PW_LOG(level, flags, message, ...)      \
+  do {                                          \
+    pw_Log((level),                             \
+           (flags),                             \
+           PW_LOG_MODULE_NAME,                  \
+           __FILE__,                            \
+           __LINE__,                            \
+           __func__,                            \
+           message PW_COMMA_ARGS(__VA_ARGS__)); \
+  } while (0)
diff --git a/pw_log_basic/public_overrides/pw_log_backend/log_backend.h b/pw_log_basic/public_overrides/pw_log_backend/log_backend.h
new file mode 100644
index 0000000..290d246
--- /dev/null
+++ b/pw_log_basic/public_overrides/pw_log_backend/log_backend.h
@@ -0,0 +1,20 @@
+// Copyright 2020 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+// This override header merely points to the true backend, in this case the
+// basic one. The reason to redirect is to permit the use of multiple backends
+// (though only pw_log/log.h can only point to 1 backend).
+#pragma once
+
+#include "pw_log_basic/log_basic.h"
diff --git a/pw_vars_default.gni b/pw_vars_default.gni
index 071aec1..cd9e2fe 100644
--- a/pw_vars_default.gni
+++ b/pw_vars_default.gni
@@ -103,6 +103,9 @@
 # Backend for the pw_dumb_io module.
 dir_pw_dumb_io_backend = ""
 
+# Backend for the pw_log module.
+dir_pw_log_backend = ""
+
 ############################## MODULE CONFIGS ##################################
 
 # Module configuration options for pw_boot_armv7m.
diff --git a/targets/host/host_common.gni b/targets/host/host_common.gni
index 543268b..9d9f610 100644
--- a/targets/host/host_common.gni
+++ b/targets/host/host_common.gni
@@ -14,9 +14,12 @@
 
 import("$dir_pigweed/pw_vars_default.gni")
 
-# Configure backend for pw_dumb_io facade.
-dir_pw_dumb_io_backend = "$dir_pw_dumb_io_stdio"
-
 declare_args() {
   pw_build_host_tools = true
 }
+
+# Configure backend for pw_dumb_io facade.
+dir_pw_dumb_io_backend = "$dir_pw_dumb_io_stdio"
+
+# Configure backend for logging facade.
+dir_pw_log_backend = "$dir_pw_log_basic"
diff --git a/targets/stm32f429i-disc1/target_config.gni b/targets/stm32f429i-disc1/target_config.gni
index 0a1b382..276770b 100644
--- a/targets/stm32f429i-disc1/target_config.gni
+++ b/targets/stm32f429i-disc1/target_config.gni
@@ -61,6 +61,7 @@
 dir_pw_boot_backend = dir_pw_boot_armv7m
 dir_pw_cpu_exception_backend = dir_pw_cpu_exception_armv7m
 dir_pw_dumb_io_backend = dir_pw_dumb_io_baremetal_stm32f429
+dir_pw_log_backend = dir_pw_log_basic
 
 pw_boot_armv7m_config.defines += [
   "PW_BOOT_FLASH_BEGIN=0x08000200",