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",